From 4c98caabc8a698428a1852148859f596dd09b9e7 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 25 Apr 2026 18:17:01 +0200 Subject: [PATCH] ci(deploy): add deploy-placeholder workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Manual-trigger workflow that substitutes PUBLIC_SALES_EMAIL into placeholder/index.html, rsyncs placeholder/ to the Hetzner docroot over SSH:222, then smoke-tests the live origin for the sentinel string, mailto link, and logo URL. Shares the deploy-production concurrency group with deploy.yml so the two workflows can never race on the same docroot. Recovery is the regular deploy.yml — no separate un-placeholder workflow. --- .gitea/workflows/deploy-placeholder.yml | 99 +++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .gitea/workflows/deploy-placeholder.yml diff --git a/.gitea/workflows/deploy-placeholder.yml b/.gitea/workflows/deploy-placeholder.yml new file mode 100644 index 0000000..312fb5f --- /dev/null +++ b/.gitea/workflows/deploy-placeholder.yml @@ -0,0 +1,99 @@ +# ----------------------------------------------------------------------------- +# cameleer-website — Deploy under-construction placeholder +# +# MANUAL TRIGGER ONLY. Replaces the live cameleer.io docroot with a static +# "back shortly" page. Recovery: trigger Actions → deploy → Run workflow on +# the desired main commit. +# +# Shares the deploy-production concurrency group with deploy.yml so the two +# workflows queue rather than race on the same docroot. +# +# This workflow does NOT run npm/astro. The placeholder is hand-authored +# static HTML in placeholder/, deliberately decoupled from the main build so +# it can ship even when the main build is broken (which is the worst case in +# which a placeholder is needed). +# +# Required secrets (repo settings → Actions → Secrets): +# SFTP_HOST, SFTP_USER, SFTP_PATH, SFTP_KEY, SFTP_KNOWN_HOSTS +# PUBLIC_SALES_EMAIL +# ----------------------------------------------------------------------------- + +name: deploy-placeholder + +on: + workflow_dispatch: + +concurrency: + group: deploy-production + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + + - name: Substitute sales email into placeholder + env: + PUBLIC_SALES_EMAIL: ${{ secrets.PUBLIC_SALES_EMAIL }} + run: | + set -e + : "${PUBLIC_SALES_EMAIL:?PUBLIC_SALES_EMAIL secret must be set}" + sed -i "s|__SALES_EMAIL__|${PUBLIC_SALES_EMAIL}|g" placeholder/index.html + if grep -q '__SALES_EMAIL__' placeholder/index.html; then + echo "Token __SALES_EMAIL__ still present after substitution — refusing to ship." + exit 1 + fi + + - name: Configure SSH + env: + SFTP_KEY: ${{ secrets.SFTP_KEY }} + SFTP_KNOWN_HOSTS: ${{ secrets.SFTP_KNOWN_HOSTS }} + run: | + set -e + : "${SFTP_KEY:?SFTP_KEY secret must be set}" + : "${SFTP_KNOWN_HOSTS:?SFTP_KNOWN_HOSTS secret must be set}" + mkdir -p ~/.ssh + printf '%s\n' "$SFTP_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + printf '%s\n' "$SFTP_KNOWN_HOSTS" > ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts + if ! command -v rsync >/dev/null 2>&1 || ! command -v ssh >/dev/null 2>&1; then + if command -v sudo >/dev/null 2>&1; then SUDO=sudo; else SUDO=; fi + $SUDO apt-get update -qq + $SUDO apt-get install -y --no-install-recommends rsync openssh-client + fi + + - name: Deploy via rsync + env: + SFTP_USER: ${{ secrets.SFTP_USER }} + SFTP_HOST: ${{ secrets.SFTP_HOST }} + SFTP_PATH: ${{ secrets.SFTP_PATH }} + run: | + : "${SFTP_USER:?SFTP_USER secret must be set}" + : "${SFTP_HOST:?SFTP_HOST secret must be set}" + : "${SFTP_PATH:?SFTP_PATH secret must be set}" + # Hetzner Webhosting splits SSH into two ports: + # port 22 — SFTP only, no remote command exec + # port 222 — full SSH with shell exec (rsync needs this) + # `--rsync-path=/usr/bin/rsync` tells the local rsync where to find + # the remote binary on Hetzner's locked-down PATH. + # `BatchMode=yes` disables interactive prompts. + rsync -avz --delete --rsync-path=/usr/bin/rsync \ + -e "ssh -p 222 -i $HOME/.ssh/id_ed25519 -o BatchMode=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$HOME/.ssh/known_hosts" \ + placeholder/ "$SFTP_USER@$SFTP_HOST:$SFTP_PATH/" + + - name: Post-deploy smoke test + run: | + set -e + echo "Confirming the placeholder is live on www.cameleer.io..." + BODY=$(curl -sf https://www.cameleer.io/) + echo "$BODY" | grep -qF 'Routes are remapping' \ + || { echo "Sentinel string missing — placeholder did not land."; exit 1; } + echo "$BODY" | grep -qF 'mailto:' \ + || { echo "mailto: link missing — sales email substitution may have failed."; exit 1; } + curl -sfI https://www.cameleer.io/cameleer-logo.png > /dev/null \ + || { echo "cameleer-logo.png not reachable on the live origin."; exit 1; } + echo "Placeholder is live."