Skip to content

Commit

Permalink
Merge pull request #17 from cromefire/docker-build
Browse files Browse the repository at this point in the history
Add docker build to workflows
  • Loading branch information
cromefire authored Oct 22, 2023
2 parents 024d7ce + 5a7169f commit af06407
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 33 deletions.
128 changes: 128 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Docker

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

on:
push:
branches: [ "main" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "main" ]

env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}

jobs:
build:
strategy:
fail-fast: false
matrix:
platform:
- docker: linux/amd64
go: amd64
dockerfile: Dockerfile
- docker: linux/arm/v6
go: arm
qemu: arm
dockerfile: alpine.Dockerfile
- docker: linux/arm/v7
go: arm
qemu: arm
dockerfile: Dockerfile
- docker: linux/arm64
go: arm64
qemu: arm64
dockerfile: Dockerfile
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up QEMU
uses: docker/setup-qemu-action@v1
if: matrix.platform.qemu
with:
platforms: ${{ matrix.platform.qemu }}

# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/[email protected]
with:
cosign-release: 'v2.2.0'

# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/[email protected]

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/[email protected]
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/[email protected]
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/[email protected]
with:
context: .
file: ${{ matrix.platform.dockerfile }}
build-args: GOARCH=${{ matrix.platform.go }}
platforms: ${{ matrix.platform.docker }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ FROM golang:1.21-alpine as server_build

WORKDIR /appbuild

# Add build deps
RUN apk add --update gcc g++ git
ARG GOARCH

COPY go.mod go.sum /appbuild/

Expand Down
76 changes: 45 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
This project has some simple goals:

- Offer a slim service without any additional service requirements
- Allow for two different combined strategies: Polling (through FRITZ!Box SOAP-API) and Pushing (FRITZ!Box Custom-DynDns setting).
- Allow for two different combined strategies: Polling (through FRITZ!Box SOAP-API) and Pushing (FRITZ!Box Custom-DynDns
setting).
- Allow multiple domains to be updated with new A (IPv4) and AAAA (IPv6) records
- Push those IP changes directly to CloudFlare DNS
- Deploy in docker compose
Expand Down Expand Up @@ -39,23 +40,24 @@ You can use this strategy if you have:

In your `.env` file or your system environment variables you can be configured:

| Variable name | Description |
| --- | --- |
| DYNDNS_SERVER_BIND | required, network interface to bind to, i.e. `:8080` |
| DYNDNS_SERVER_USERNAME | optional, username for the DynDNS service |
| DYNDNS_SERVER_PASSWORD | optional, password for the DynDNS service |
| Variable name | Description |
|------------------------|------------------------------------------------------|
| DYNDNS_SERVER_BIND | required, network interface to bind to, i.e. `:8080` |
| DYNDNS_SERVER_USERNAME | optional, username for the DynDNS service |
| DYNDNS_SERVER_PASSWORD | optional, password for the DynDNS service |

Now configure the FRITZ!Box router to push IP changes towards this service. Log into the admin panel and go to
`Internet > Shares > DynDNS tab` and setup a `Custom` provider:

| Property | Description / Value |
| --- | --- |
| Update-URL | http://[server-ip]/ip?v4=\<ipaddr\>&v6=\<ip6addr\>&prefix=\<ip6lanprefix\> |
| Domain | Enter at least one domain name so the router can probe if the update was successfully |
| Username | Enter '_' if `DYNDNS_SERVER_USERNAME` env is unset |
| Password | Enter '_' if `DYNDNS_SERVER_PASSWORD` env is unset |
| Property | Description / Value |
|------------|---------------------------------------------------------------------------------------|
| Update-URL | http://[server-ip]/ip?v4=\<ipaddr\>&v6=\<ip6addr\>&prefix=\<ip6lanprefix\> |
| Domain | Enter at least one domain name so the router can probe if the update was successfully |
| Username | Enter '_' if `DYNDNS_SERVER_USERNAME` env is unset |
| Password | Enter '_' if `DYNDNS_SERVER_PASSWORD` env is unset |

If you specified credentials you need to append them as additional GET parameters into the Update-URL like `&username=<user>&password=<pass>`.
If you specified credentials you need to append them as additional GET parameters into the Update-URL
like `&username=<user>&password=<pass>`.

### FRITZ!Box polling

Expand All @@ -67,27 +69,31 @@ You can use this strategy if you have:

In your `.env` file or your system environment variables you can be configured:

| Variable name | Description |
| --- | --- |
| FRITZBOX_ENDPOINT_URL | optional, how can we reach the router, i.e. `http://fritz.box:49000`, the port should be 49000 anyway. |
| FRITZBOX_ENDPOINT_TIMEOUT | optional, a duration we give the router to respond, i.e. `10s`. |
| FRITZBOX_ENDPOINT_INTERVAL | optional, a duration how often we want to poll the WAN IPs from the router, i.e. `120s` |
| Variable name | Description |
|----------------------------|--------------------------------------------------------------------------------------------------------|
| FRITZBOX_ENDPOINT_URL | optional, how can we reach the router, i.e. `http://fritz.box:49000`, the port should be 49000 anyway. |
| FRITZBOX_ENDPOINT_TIMEOUT | optional, a duration we give the router to respond, i.e. `10s`. |
| FRITZBOX_ENDPOINT_INTERVAL | optional, a duration how often we want to poll the WAN IPs from the router, i.e. `120s` |

You can try the endpoint URL in the browser to make sure you have the correct port, you should receive an `404 ERR_NOT_FOUND`.
You can try the endpoint URL in the browser to make sure you have the correct port, you should receive
an `404 ERR_NOT_FOUND`.

## Cloudflare setup

To get your API Token do the following: Login to the cloudflare dashboard, go to `My Profile > API Tokens > Create Token > Edit zone DNS`, give to token some good name (e.g. "DDNS"), add all zones that the DDNS should be used for, click `Continue to summary` and `Create token`. Be sure to copy the token and add it to the config, you won't be able to see it again.
To get your API Token do the following: Login to the cloudflare dashboard, go
to `My Profile > API Tokens > Create Token > Edit zone DNS`, give to token some good name (e.g. "DDNS"), add all zones
that the DDNS should be used for, click `Continue to summary` and `Create token`. Be sure to copy the token and add it
to the config, you won't be able to see it again.

In your `.env` file or your system environment variables you can be configured:

| Variable name | Description |
| --- | --- |
| CLOUDFLARE_API_TOKEN | required, your Cloudflare API Token |
| Variable name | Description |
|-----------------------|-------------------------------------------------------------------|
| CLOUDFLARE_API_TOKEN | required, your Cloudflare API Token |
| CLOUDFLARE_ZONES_IPV4 | comma-separated list of domains to update with new IPv4 addresses |
| CLOUDFLARE_ZONES_IPV6 | comma-separated list of domains to update with new IPv6 addresses |
| CLOUDFLARE_API_EMAIL | deprecated, your Cloudflare account email |
| CLOUDFLARE_API_KEY | deprecated, your Cloudflare Global API key |
| CLOUDFLARE_API_EMAIL | deprecated, your Cloudflare account email |
| CLOUDFLARE_API_KEY | deprecated, your Cloudflare Global API key |

This service allows to update multiple records, an advanced example would be:

Expand All @@ -105,12 +111,16 @@ IPv6 port-forwarding works differently and so if you want to use it you have to

Warning: `FRITZBOX_ENDPOINT_URL` has to be set for this to work.

To access a device via IPv6 you need to add it's global IPv6 address to cloudflare, for this to be calculated you need to find out the local part of it's IP.
You can find out the local part of a device's IP, by going to the device's settings and looking at the `IPv6 Interface-ID`.
It should look something like this: `::1234:5678:90ab:cdef`
To access a device via IPv6 you need to add it's global IPv6 address to cloudflare, for this to be calculated you need
to find out the local part of it's IP.
You can find out the local part of a device's IP, by going to the device's settings and looking at
the `IPv6 Interface-ID`.
It should look something like this: `::1234:5678:90ab:cdef`.
Sometimes the FritzBox seems to use a subnet, so you might need to add change it from something
like `::1234:5678:90ab:cdef` to `::1:1234:5678:90ab:cdef`

| Variable name | Description |
| --- | --- |
| Variable name | Description |
|---------------------------|-------------------------------------------------|
| DEVICE_LOCAL_ADDRESS_IPV6 | required, enter the local part of the device IP |

## Docker compose setup
Expand All @@ -136,10 +146,14 @@ services:
- CLOUDFLARE_ZONES_IPV6=test.example.com
```

Now we could configure the FRITZ!Box to `http://[docker-host-ip]:49000/ip?v4=<ipaddr>&v6=<ip6addr>&prefix=<ip6lanprefix>` and it should trigger the update process.
Now we could configure the FRITZ!Box
to `http://[docker-host-ip]:49000/ip?v4=<ipaddr>&v6=<ip6addr>&prefix=<ip6lanprefix>` and it should trigger the update
process.

## Docker build

_Note: A pre-built docker image is also available on this docker repository._

More raw approach would be to build and run it yourself:

```
Expand Down
33 changes: 33 additions & 0 deletions alpine.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
FROM golang:1.21-alpine as server_build

WORKDIR /appbuild

ARG GOARCH

COPY go.mod go.sum /appbuild/

COPY ./ /appbuild

RUN CGO_ENABLED=0 GOOS=linux go build -o fritzbox-cloudflare-dyndns

# Build deployable server
FROM alpine:3

ENV FRITZBOX_ENDPOINT_URL=${FRITZBOX_ENDPOINT_URL:-http://fritz.box:49000} \
FRITZBOX_ENDPOINT_TIMEOUT=${FRITZBOX_ENDPOINT_TIMEOUT:-30s} \
DYNDNS_SERVER_BIND=${DYNDNS_SERVER_BIND:-:8080} \
DYNDNS_SERVER_USERNAME=${DYNDNS_SERVER_USERNAME} \
DYNDNS_SERVER_PASSWORD=${DYNDNS_SERVER_PASSWORD} \
CLOUDFLARE_API_EMAIL="" \
CLOUDFLARE_API_KEY="" \
CLOUDFLARE_ZONES_IPV4="" \
CLOUDFLARE_ZONES_IPV6="" \
CLOUDFLARE_LOCAL_ADDRESS_IPV6=""

WORKDIR /app

COPY --from=server_build /appbuild/fritzbox-cloudflare-dyndns /app/fritzbox-cloudflare-dyndns

EXPOSE 8080

CMD ["./fritzbox-cloudflare-dyndns"]

0 comments on commit af06407

Please sign in to comment.