From d46b0664d36c26a529ba1b2e9536dced82174a40 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 3 Jan 2026 20:00:52 +0100 Subject: [PATCH] init --- .dockerignore | 5 ++ .gitea/workflows/build.yml | 67 +++++++++++++++ Dockerfile | 26 ++++++ README.md | 172 +++++++++++++++++++++++++++++++++++++ docker-compose.yml | 21 +++++ entrypoint.sh | 47 ++++++++++ 6 files changed, 338 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/build.yml create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0064d2d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.gitignore +README.md +LICENSE +*.md diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..eb8a9a4 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,67 @@ +name: Build Container +run-name: ${{ gitea.actor }} is pushing +on: [push] + +env: + REGISTRY: gitea.haschek.at + IMAGE_NAME: ${{ gitea.repository }} + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - run: echo "🎉 Building ${{ gitea.repository }} because of a ${{ gitea.event_name }} event." + + - name: Checkout + uses: actions/checkout@v2 + + - name: Normalize image name to lowercase + run: | + echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> "$GITHUB_ENV" + env: + IMAGE_NAME: ${{ env.IMAGE_NAME }} + + - name: Prepare + id: prep + run: | + DOCKER_IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}" + VERSION=latest + SHORTREF=${GITHUB_SHA::8} + + TAGS="${DOCKER_IMAGE}:latest" + + # Set output parameters. + echo ::set-output name=tags::${TAGS} + echo ::set-output name=docker_image::${DOCKER_IMAGE} + + - name: Set up QEMU + uses: docker/setup-qemu-action@master + with: + platforms: all + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ gitea.actor }} + password: ${{ secrets.BUILD_TOKEN}} + + - name: Build + uses: docker/build-push-action@v3 + env: + ACTIONS_RUNTIME_TOKEN: '' + with: + builder: ${{ steps.buildx.outputs.name }} + context: . + file: Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.prep.outputs.tags }} + cache-from: | + type=registry,ref=registry.haschek.at/${{ env.IMAGE_NAME_LC }}:buildcache + cache-to: | + type=registry,ref=registry.haschek.at/${{ env.IMAGE_NAME_LC }}:buildcache,mode=max \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5aaa1e9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM python:3.12-slim + +LABEL maintainer="Your Name " +LABEL description="Certbot with dns-standalone plugin for wildcard certificate generation" + +# Install certbot and the dns-standalone plugin +RUN pip install --no-cache-dir certbot certbot-dns-standalone + +# Create directories for Let's Encrypt data +RUN mkdir -p /etc/letsencrypt /var/lib/letsencrypt /var/log/letsencrypt + +# Copy the entrypoint script +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Expose DNS port (53) for both TCP and UDP +EXPOSE 53/tcp +EXPOSE 53/udp + +# Expose HTTP port for potential HTTP challenges +EXPOSE 80 + +# Volume for certificate storage +VOLUME ["/etc/letsencrypt", "/var/lib/letsencrypt"] + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..fecad17 --- /dev/null +++ b/README.md @@ -0,0 +1,172 @@ +# DNS Wildcard Certificate Generator + +A Docker container for easily obtaining wildcard SSL certificates from Let's Encrypt using the `certbot-dns-standalone` plugin. + +## How It Works + +This uses the `dns-standalone` authenticator which runs its own DNS server to respond to ACME DNS-01 challenges. You need to configure your DNS to delegate `_acme-challenge` queries to this container. + +## Prerequisites + +1. A server with port 53 (DNS) available +2. DNS configuration to route challenge queries to your server (see DNS Setup below) + +## DNS Setup + +### Option 1: Direct NS Record + +Point `_acme-challenge` records to your certbot server using CNAME and NS records: + +```dns +; For acme.example.com as your certbot endpoint +acme IN NS ns.acme.example.com. +ns.acme IN A 1.2.3.4 + +; For each domain you want certificates for +_acme-challenge.example.com IN CNAME example.com.acme.example.com. +``` + +Where `1.2.3.4` is the IP of the server running this container. + +### Option 2: DNS Proxy/Forwarding + +If you already run a DNS server, configure it to forward `_acme-challenge` queries to the container. + +## Usage + +### Quick Start + +```bash +docker run -it --rm \ + -v "/etc/letsencrypt:/etc/letsencrypt" \ + -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ + -p 53:53/tcp -p 53:53/udp \ + -e EMAIL="youremail@example.com" \ + -e DOMAINS="-d example.com -d *.example.com" \ + dns-wildcard-cert +``` + +### Build Locally + +```bash +docker build -t dns-wildcard-cert . +``` + +### Environment Variables + +| Variable | Required | Default | Description | +|----------|----------|---------|-------------| +| `EMAIL` | Yes | - | Email for Let's Encrypt registration | +| `DOMAINS` | Yes | - | Domain flags (e.g., `-d example.com -d *.example.com`) | +| `DNS_ADDRESS` | No | `0.0.0.0` | IPv4 address to bind DNS server | +| `DNS_IPV6_ADDRESS` | No | `::` | IPv6 address to bind DNS server | +| `DNS_PORT` | No | `53` | Port for DNS server | +| `STAGING` | No | `false` | Use Let's Encrypt staging server (for testing) | +| `DRY_RUN` | No | `false` | Perform a dry run without saving certificates | + +### Examples + +**Test with staging server first:** +```bash +docker run -it --rm \ + -v "/etc/letsencrypt:/etc/letsencrypt" \ + -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ + -p 53:53/tcp -p 53:53/udp \ + -e EMAIL="youremail@example.com" \ + -e DOMAINS="-d example.com -d *.example.com" \ + -e STAGING="true" \ + dns-wildcard-cert +``` + +**Dry run (no certificates saved):** +```bash +docker run -it --rm \ + -p 53:53/tcp -p 53:53/udp \ + -e EMAIL="youremail@example.com" \ + -e DOMAINS="-d example.com -d *.example.com" \ + -e DRY_RUN="true" \ + dns-wildcard-cert +``` + +**Bind to specific IP:** +```bash +docker run -it --rm \ + -v "/etc/letsencrypt:/etc/letsencrypt" \ + -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ + -p 1.2.3.4:53:53/tcp -p 1.2.3.4:53:53/udp \ + -e EMAIL="youremail@example.com" \ + -e DOMAINS="-d example.com -d *.example.com" \ + -e DNS_ADDRESS="0.0.0.0" \ + dns-wildcard-cert +``` + +**Use non-standard port (with DNS forwarding):** +```bash +docker run -it --rm \ + -v "/etc/letsencrypt:/etc/letsencrypt" \ + -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ + -p 5555:5555/tcp -p 5555:5555/udp \ + -e EMAIL="youremail@example.com" \ + -e DOMAINS="-d example.com -d *.example.com" \ + -e DNS_PORT="5555" \ + dns-wildcard-cert +``` + +## Certificate Renewal + +For renewal, you can run the same container periodically or use certbot's renew command: + +```bash +docker run -it --rm \ + -v "/etc/letsencrypt:/etc/letsencrypt" \ + -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ + -p 53:53/tcp -p 53:53/udp \ + --entrypoint certbot \ + dns-wildcard-cert renew +``` + +## Certificate Location + +Certificates are stored in the `/etc/letsencrypt` volume: + +- Certificate: `/etc/letsencrypt/live//fullchain.pem` +- Private Key: `/etc/letsencrypt/live//privkey.pem` + +## Docker Compose + +```yaml +version: '3.8' + +services: + certbot: + build: . + ports: + - "53:53/tcp" + - "53:53/udp" + environment: + - EMAIL=youremail@example.com + - DOMAINS=-d example.com -d *.example.com + - STAGING=false + volumes: + - letsencrypt:/etc/letsencrypt + - letsencrypt-lib:/var/lib/letsencrypt + +volumes: + letsencrypt: + letsencrypt-lib: +``` + +## Parameter Changes + +**Note:** The old certbot-dns-standalone parameter format has changed: + +| Old Format | New Format | +|------------|------------| +| `--authenticator certbot-dns-standalone:dns-standalone` | `--authenticator dns-standalone` | +| `--certbot-dns-standalone:dns-standalone-address=` | `--dns-standalone-address=` | +| `--certbot-dns-standalone:dns-standalone-ipv6-address=` | `--dns-standalone-ipv6-address=` | +| `--certbot-dns-standalone:dns-standalone-port=` | `--dns-standalone-port=` | + +## License + +MIT diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..676af95 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3.8' + +services: + certbot: + build: . + ports: + - "53:53/tcp" + - "53:53/udp" + environment: + - EMAIL=youremail@example.com + - DOMAINS=-d example.com -d *.example.com + # Set to true for testing + - STAGING=false + - DRY_RUN=false + volumes: + - letsencrypt:/etc/letsencrypt + - letsencrypt-lib:/var/lib/letsencrypt + +volumes: + letsencrypt: + letsencrypt-lib: diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..89452c3 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -e + +# Default values +EMAIL="${EMAIL:-}" +DOMAINS="${DOMAINS:-}" +DNS_ADDRESS="${DNS_ADDRESS:-0.0.0.0}" +DNS_IPV6_ADDRESS="${DNS_IPV6_ADDRESS:-::}" +DNS_PORT="${DNS_PORT:-53}" +STAGING="${STAGING:-false}" +DRY_RUN="${DRY_RUN:-false}" + +# Validate required environment variables +if [ -z "$EMAIL" ]; then + echo "ERROR: EMAIL environment variable is required" + echo "Usage: docker run -e EMAIL=you@example.com -e DOMAINS='-d example.com -d *.example.com' ..." + exit 1 +fi + +if [ -z "$DOMAINS" ]; then + echo "ERROR: DOMAINS environment variable is required" + echo "Usage: docker run -e EMAIL=you@example.com -e DOMAINS='-d example.com -d *.example.com' ..." + exit 1 +fi + +# Build certbot command +CMD="certbot --non-interactive --agree-tos --email ${EMAIL} certonly" +CMD="${CMD} --authenticator dns-standalone" +CMD="${CMD} --dns-standalone-address=${DNS_ADDRESS}" +CMD="${CMD} --dns-standalone-ipv6-address=${DNS_IPV6_ADDRESS}" +CMD="${CMD} --dns-standalone-port=${DNS_PORT}" + +# Add staging flag if requested (useful for testing) +if [ "$STAGING" = "true" ]; then + CMD="${CMD} --staging" +fi + +# Add dry-run flag if requested +if [ "$DRY_RUN" = "true" ]; then + CMD="${CMD} --dry-run" +fi + +# Add domains +CMD="${CMD} ${DOMAINS}" + +echo "Running: ${CMD}" +exec ${CMD}