From cd6111872d67c79a45d59cc5825eb3460789e924 Mon Sep 17 00:00:00 2001 From: Basil Bruhn Date: Mon, 21 Sep 2026 10:19:22 +0200 Subject: [PATCH 1/4] CI for webhosting Signed-off-by: Basil Bruhn --- .gitea/workflows/webhosting.yaml | 41 ++++++++++++++++++++++++++++++++ README.md | 17 +++++++++++++ get-cert.sh | 14 +++++++++++ 3 files changed, 72 insertions(+) create mode 100644 .gitea/workflows/webhosting.yaml create mode 100644 get-cert.sh diff --git a/.gitea/workflows/webhosting.yaml b/.gitea/workflows/webhosting.yaml new file mode 100644 index 0000000..4715a40 --- /dev/null +++ b/.gitea/workflows/webhosting.yaml @@ -0,0 +1,41 @@ +name: WebHosting + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + validate: + runs-on: webhosting + steps: + - uses: https://github.com/actions/checkout@v4 + with: + persist-credentials: false + - name: Lint and check Compose syntax + run: | + set -e + yamllint -d '{extends: relaxed, rules: {line-length: disable}}' docker-compose.yaml stacks .gitea + shellcheck get-cert.sh + docker compose config --quiet + + deploy: + needs: validate + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: webhosting + steps: + - name: Deploy + run: | + set -eo pipefail + cd /opt/WebHosting + git pull --ff-only origin main + domains=$(awk '$1 == "ssl_certificate" && $2 ~ /^\/etc\/nginx\/certs\/[^\/]+\.crt;$/ {sub(/^\/etc\/nginx\/certs\//, "", $2); sub(/\.crt;$/, "", $2); print $2}' conf.d/*.conf | sort -u) + for domain in $domains; do + if [ ! -s "/etc/pki/tls/certs/$domain.crt" ] || [ ! -s "/etc/pki/tls/private/$domain.key" ]; then + bash get-cert.sh "$domain" + fi + done + docker compose up -d + docker compose exec -T nginx nginx -t + docker compose exec -T nginx nginx -s reload diff --git a/README.md b/README.md index 6438d43..3aabad3 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,20 @@ Shared web applications behind Nginx/Apache, deployed in `/opt/WebHosting`. Puppet must provision all required env files before Compose validation or deployment. Use `config --quiet` to avoid printing secrets. After an update, check the application; if its container IP changed, validate and reload Nginx using the host's deployment procedure. + +Gitea Actions runs linting and Compose syntax checks for PRs. After a merge to +`main`, it pulls the repo, obtains missing certificates, runs Compose, and +tests/reloads Nginx. Failed commands stop the job; there is no automatic rollback. + +For new sites, use `/etc/nginx/certs/.crt` and +`/etc/nginx/private/.key` in the Nginx config. Deployment calls +`get-cert.sh ` if either file is missing, using the existing host ACME +config and creating the certificate/key symlinks. The script does not reload Nginx; +CI does that at the end. Existing certificates keep their normal renewal schedule. + +Register the runner as `webhosting:host` with capacity 1 and a workspace separate +from `/opt/WebHosting`. It needs Git, Node.js 20+, yamllint, ShellCheck, Docker +Compose, Certbot, and access to Docker, the env files, and certificate directories. +Require the reported `validate` check +in Gitea's branch protection for `main`. Changes to the file-mounted `nginx.conf` +require recreating Nginx to ensure it reads the new file. diff --git a/get-cert.sh b/get-cert.sh new file mode 100644 index 0000000..27e6a2b --- /dev/null +++ b/get-cert.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +domain=${1:?Usage: bash get-cert.sh DOMAIN} + +certbot certonly \ + -d "$domain" \ + --cert-name "$domain" \ + --config /etc/letsencrypt/cli.ini \ + --keep-until-expiring \ + --no-directory-hooks + +ln -sf "/etc/letsencrypt/live/$domain/fullchain.pem" "/etc/pki/tls/certs/$domain.crt" +ln -sf "/etc/letsencrypt/live/$domain/privkey.pem" "/etc/pki/tls/private/$domain.key" From 08f7695a4685945f6c905b1ab76e53b76a5b46f8 Mon Sep 17 00:00:00 2001 From: Basil Bruhn Date: Mon, 21 Sep 2026 10:21:11 +0200 Subject: [PATCH 2/4] action Signed-off-by: Basil Bruhn --- .gitea/workflows/webhosting.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/webhosting.yaml b/.gitea/workflows/webhosting.yaml index 4715a40..4edfcd1 100644 --- a/.gitea/workflows/webhosting.yaml +++ b/.gitea/workflows/webhosting.yaml @@ -10,7 +10,7 @@ jobs: validate: runs-on: webhosting steps: - - uses: https://github.com/actions/checkout@v4 + - uses: actions/checkout@v4 with: persist-credentials: false - name: Lint and check Compose syntax From 0f8306a8af188c35996ec3e5f02167943a4715ee Mon Sep 17 00:00:00 2001 From: Basil Bruhn Date: Mon, 21 Sep 2026 13:57:02 +0200 Subject: [PATCH 3/4] update ci and readme Signed-off-by: Basil Bruhn --- .gitea/workflows/webhosting.yaml | 12 +++++++++--- README.md | 16 +++++----------- get-cert.sh | 14 -------------- 3 files changed, 14 insertions(+), 28 deletions(-) delete mode 100644 get-cert.sh diff --git a/.gitea/workflows/webhosting.yaml b/.gitea/workflows/webhosting.yaml index 4edfcd1..f96ba70 100644 --- a/.gitea/workflows/webhosting.yaml +++ b/.gitea/workflows/webhosting.yaml @@ -17,7 +17,6 @@ jobs: run: | set -e yamllint -d '{extends: relaxed, rules: {line-length: disable}}' docker-compose.yaml stacks .gitea - shellcheck get-cert.sh docker compose config --quiet deploy: @@ -32,9 +31,16 @@ jobs: git pull --ff-only origin main domains=$(awk '$1 == "ssl_certificate" && $2 ~ /^\/etc\/nginx\/certs\/[^\/]+\.crt;$/ {sub(/^\/etc\/nginx\/certs\//, "", $2); sub(/\.crt;$/, "", $2); print $2}' conf.d/*.conf | sort -u) for domain in $domains; do - if [ ! -s "/etc/pki/tls/certs/$domain.crt" ] || [ ! -s "/etc/pki/tls/private/$domain.key" ]; then - bash get-cert.sh "$domain" + if [ ! -s "/etc/letsencrypt/live/$domain/fullchain.pem" ] || [ ! -s "/etc/letsencrypt/live/$domain/privkey.pem" ]; then + certbot certonly \ + -d "$domain" \ + --cert-name "$domain" \ + --config /etc/letsencrypt/cli.ini \ + --keep-until-expiring \ + --no-directory-hooks fi + ln -sf "/etc/letsencrypt/live/$domain/fullchain.pem" "/etc/pki/tls/certs/$domain.crt" + ln -sf "/etc/letsencrypt/live/$domain/privkey.pem" "/etc/pki/tls/private/$domain.key" done docker compose up -d docker compose exec -T nginx nginx -t diff --git a/README.md b/README.md index 3aabad3..017bf78 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,8 @@ Gitea Actions runs linting and Compose syntax checks for PRs. After a merge to tests/reloads Nginx. Failed commands stop the job; there is no automatic rollback. For new sites, use `/etc/nginx/certs/.crt` and -`/etc/nginx/private/.key` in the Nginx config. Deployment calls -`get-cert.sh ` if either file is missing, using the existing host ACME -config and creating the certificate/key symlinks. The script does not reload Nginx; -CI does that at the end. Existing certificates keep their normal renewal schedule. - -Register the runner as `webhosting:host` with capacity 1 and a workspace separate -from `/opt/WebHosting`. It needs Git, Node.js 20+, yamllint, ShellCheck, Docker -Compose, Certbot, and access to Docker, the env files, and certificate directories. -Require the reported `validate` check -in Gitea's branch protection for `main`. Changes to the file-mounted `nginx.conf` -require recreating Nginx to ensure it reads the new file. +`/etc/nginx/private/.key` in the Nginx config. CI checks the host's +`/etc/letsencrypt/live//` for `fullchain.pem` and `privkey.pem`, requesting +a certificate if either is missing. It uses the existing host ACME config and +ensures symlinks under `/etc/pki/tls/`, mounted at those Nginx paths in the container. +It reloads Nginx at the end. Existing certificates keep their normal renewal schedule. diff --git a/get-cert.sh b/get-cert.sh deleted file mode 100644 index 27e6a2b..0000000 --- a/get-cert.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -e - -domain=${1:?Usage: bash get-cert.sh DOMAIN} - -certbot certonly \ - -d "$domain" \ - --cert-name "$domain" \ - --config /etc/letsencrypt/cli.ini \ - --keep-until-expiring \ - --no-directory-hooks - -ln -sf "/etc/letsencrypt/live/$domain/fullchain.pem" "/etc/pki/tls/certs/$domain.crt" -ln -sf "/etc/letsencrypt/live/$domain/privkey.pem" "/etc/pki/tls/private/$domain.key" From a2b6974e761a97947367be6f51bdb2efbdb0e670 Mon Sep 17 00:00:00 2001 From: bruhn_b Date: Tue, 22 Sep 2026 10:32:23 +0200 Subject: [PATCH 4/4] Add manual trigger --- .gitea/workflows/webhosting.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitea/workflows/webhosting.yaml b/.gitea/workflows/webhosting.yaml index f96ba70..2ad20a0 100644 --- a/.gitea/workflows/webhosting.yaml +++ b/.gitea/workflows/webhosting.yaml @@ -5,6 +5,8 @@ on: branches: [main] push: branches: [main] + + workflow_dispatch: jobs: validate: