Some checks failed
deploy / Build prerendered web (push) Successful in 7m14s
deploy / Deploy web to oolon (push) Successful in 18s
deploy / Build api + worker (static musl) (push) Successful in 5m41s
deploy / Deploy moments-worker to frootmig (push) Successful in 19s
deploy / Deploy moments-api to nikola (push) Successful in 24s
refresh / Rebuild prerendered web (push) Successful in 7m15s
refresh / Deploy refreshed web to oolon (push) Failing after 26s
oolon's TCP 443 belongs to the stream SNI router, which ssl_prereads the
handshake and forwards to the local https tier on 127.0.0.1:14443 with
PROXY protocol. site.conf.tmpl predates that and still bound 443 itself,
so every deploy and every daily refresh rsynced a vhost that collides
with the router.
Nothing in the pipeline caught it. `nginx -t` only detects duplicate
listeners within a context, not across http{} and stream{}, and
`systemctl reload` merely sends SIGHUP, so it exits 0 while nginx logs
"bind() to 0.0.0.0:443 failed (98: Address already in use) ... still
could not bind()", aborts the reconfiguration and keeps its old cycle.
The deploy went green while oolon's running config was frozen. It stayed
frozen for a day, stranding every cert the step@ timers renewed on disk
until eleven internal vhosts were serving expired certs. A cold start
would have failed outright, taking the whole public edge down.
Template the listen line from WEB_LISTEN (manifest web.config.listen for
script/deploy.sh, which renders the same template), and assert that the
reload landed by requiring a fresh worker generation, dumping the nginx
error log when it did not.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182wzZE8DguMPWhxD21gfP2
117 lines
4.6 KiB
YAML
117 lines
4.6 KiB
YAML
name: refresh
|
|
|
|
# Daily re-bake of the prerendered site. The crawler-visible HTML is a static
|
|
# snapshot taken at build time; this job rebuilds it from the current gist (CV)
|
|
# and activity API and redeploys *only* the web tier — so edits propagate to
|
|
# what crawlers and AI screeners see without a code push and without bouncing
|
|
# the api/worker. Humans already get live data via post-hydration refetch.
|
|
#
|
|
# Uses the same gitea_ci + scoped-sudo path as deploy.yml's deploy-web job;
|
|
# see asset/sudoers.d/web-host.conf and script/infra-setup.sh.
|
|
|
|
on:
|
|
schedule:
|
|
# 04:17 UTC daily — off-peak, arbitrary minute to avoid the top-of-hour herd.
|
|
- cron: '17 4 * * *'
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: deploy # share the deploy lock so a refresh never races a push deploy
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
WEB_HOST: oolon.kosherinata.internal
|
|
SERVER_NAME: rob.tn
|
|
WEB_ROOT: /var/www/rob.tn
|
|
API_PORT: "42424"
|
|
API_UPSTREAM_SCHEME: http
|
|
API_UPSTREAM_ADDR: nikola.kosherinata.internal:42424
|
|
# Internal mesh API base (the runner can't reach the public rob.tn); see
|
|
# deploy.yml. The api serves its routes under /v1.
|
|
VITE_API_BASE: http://nikola.kosherinata.internal:42424/v1
|
|
DEPLOY_KEY: |
|
|
${{ secrets.RSYNC_SSH_KEY }}
|
|
|
|
jobs:
|
|
build-web:
|
|
name: Rebuild prerendered web
|
|
runs-on: fedora-44 # image bakes in node + pnpm; that's all we need here
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
# Install without the pnpm 10 build-script gate, then rebuild the native
|
|
# deps vite needs (see deploy.yml build-web for the rationale).
|
|
- name: Build web (vite client + prerender)
|
|
working-directory: ui
|
|
run: |
|
|
pnpm install --frozen-lockfile --ignore-scripts
|
|
pnpm rebuild @swc/core esbuild
|
|
pnpm run build
|
|
- uses: actions/upload-artifact@v3
|
|
with: { name: web-dist, path: ui/dist, retention-days: 1 }
|
|
|
|
deploy-web:
|
|
name: Deploy refreshed web to oolon
|
|
needs: build-web
|
|
runs-on: fedora-44
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/download-artifact@v3
|
|
with: { name: web-dist, path: dist }
|
|
|
|
- name: SSH init
|
|
run: |
|
|
mkdir -p ~/.ssh
|
|
echo "${DEPLOY_KEY}" > ~/.ssh/id_ed25519
|
|
chmod 600 ~/.ssh/id_ed25519
|
|
ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \
|
|
gitea_ci@"${WEB_HOST}" 'hostname -f'
|
|
|
|
- name: Render nginx vhost
|
|
run: |
|
|
mkdir -p rendered
|
|
python3 - <<'PY'
|
|
import os
|
|
t = open("asset/nginx/site.conf.tmpl").read()
|
|
for k in ("SERVER_NAME", "API_UPSTREAM_SCHEME", "API_UPSTREAM_ADDR"):
|
|
t = t.replace("{{%s}}" % k, os.environ[k])
|
|
t = t.replace("{{DOCROOT}}", os.environ["WEB_ROOT"])
|
|
open("rendered/site.conf", "w").write(t)
|
|
PY
|
|
|
|
- name: Sync static site (prerendered)
|
|
run: |
|
|
ssh gitea_ci@"${WEB_HOST}" 'sudo /usr/bin/install -d -m 0755 '"${WEB_ROOT}"
|
|
rsync -az --delete --mkpath --rsync-path='sudo rsync' \
|
|
--chown=root:root --chmod=D755,F644 \
|
|
dist/ gitea_ci@"${WEB_HOST}":"${WEB_ROOT}/"
|
|
ssh gitea_ci@"${WEB_HOST}" 'sudo /usr/sbin/restorecon -R '"${WEB_ROOT}"
|
|
|
|
- name: Sync nginx vhost + reload
|
|
run: |
|
|
rsync -az --mkpath --rsync-path='sudo rsync' --chown=root:root --chmod=0644 \
|
|
rendered/site.conf \
|
|
gitea_ci@"${WEB_HOST}":/etc/nginx/conf.d/"${SERVER_NAME}".conf
|
|
ssh gitea_ci@"${WEB_HOST}" '
|
|
set -euo pipefail
|
|
sudo /usr/sbin/setsebool -P httpd_can_network_connect on
|
|
if ! sudo /usr/sbin/semanage port -l | grep -E "^http_port_t" | grep -qw '"${API_PORT}"'; then
|
|
sudo /usr/sbin/semanage port -a -t http_port_t -p tcp '"${API_PORT}"'
|
|
fi
|
|
sudo /usr/sbin/restorecon -R /etc/nginx/conf.d/'"${SERVER_NAME}"'.conf
|
|
sudo /usr/sbin/nginx -t
|
|
# Verify the reload actually landed — see deploy.yml for why neither
|
|
# `nginx -t` nor `systemctl reload`s exit code is enough.
|
|
master=$(cat /run/nginx.pid)
|
|
before=$(pgrep -P "$master" | sort | tr "\n" " ")
|
|
sudo /usr/bin/systemctl reload nginx
|
|
for _ in $(seq 1 10); do
|
|
sleep 1
|
|
after=$(pgrep -P "$master" | sort | tr "\n" " ")
|
|
[ "$before" = "$after" ] || break
|
|
done
|
|
if [ "$before" = "$after" ]; then
|
|
echo "nginx reload did not take effect: worker generation unchanged" >&2
|
|
sudo /usr/bin/tail -n 50 /var/log/nginx/error.log >&2 || true
|
|
exit 1
|
|
fi'
|