deploy: Docker compose + entrypoint (pull-loop autosync), Nix revert script, .env (gitignored) for Gitea deploy token

This commit is contained in:
sam
2026-09-09 07:21:44 +10:00
parent 31e9a708cc
commit 9ca027c86f
16 changed files with 514 additions and 0 deletions

29
deploy/deploy-apply.sh Normal file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Apply Kontra Nix additions on .13 and rebuild. Run as: sudo bash deploy-apply.sh --from /var/www/kontra_day/deploy
set -euo pipefail
DEPLOY_DIR="${1:-/var/www/kontra_day/deploy}"
CFG=/etc/nixos/configuration.nix
BAK=$CFG.bak-kontra-$(date +%Y%m%d-%H%M%S)
echo "== backup configuration.nix -> $BAK"
cp -a "$CFG" "$BAK"
echo "== patch configuration.nix"
python3 "$DEPLOY_DIR/patch-nix.py" "$CFG"
echo "== Nix syntax check"
if ! nix-instantiate --parse "$CFG" >/dev/null; then
echo "!! Nix syntax FAILED — restoring backup"
cp -a "$BAK" "$CFG"
exit 1
fi
echo "OK"
echo "== nixos-rebuild switch (may take minutes)"
nixos-rebuild switch --show-trace 2>&1 | tail -40
echo "== status"
systemctl --no-pager --full status kontra.service 2>/dev/null | head -12
systemctl --no-pager list-timers kontra-pull.timer 2>/dev/null | head -4
echo "ALL DONE"

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Kontra → Docker on .13. Runs as sam (docker group). Requires root once for the Nix revert.
# Usage: bash docker-kontra-up.sh [--revert-first]
set -euo pipefail
cd /home/sam/Docker/Containers/kontra
if [ "${1:-}" = "--revert-first" ]; then
echo "== REVERTING Nix systemd additions (needs sudo) =="
sudo bash /var/www/kontra_day/deploy/revert-kontra-nix.sh
echo " (ignore home-manager errors — not Kontra's concern; Kontra units are gone)"
fi
echo "== build + start kontra container =="
docker compose up -d --build 2>&1 | tail -25
echo "== wait for boot =="
sleep 4
echo "== container status =="
docker ps --format "{{.Names}} | {{.Status}} | {{.Ports}}" | grep kontra || true
echo "== direct test =="
curl -s -o /dev/null -w "localhost:8600 -> HTTP %{http_code}\n" http://127.0.0.1:8600/ || echo "not up yet (check: docker logs kontra)"
echo "== logs tail =="
docker logs --tail 15 kontra 2>&1 | tail -15
echo "DONE"

27
deploy/docker/Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
# Kontra — GOTH news-opinion site. Self-contained container.
# Build context = /home/sam/Docker/Containers/kontra (has src/{go.mod,go.sum,src/...})
FROM golang:1.26-bookworm AS build
# templ CLI for codegen (generates *_templ.go before build)
RUN go install github.com/a-h/templ/cmd/templ@v0.3.1020
ENV PATH="/root/go/bin:${PATH}"
WORKDIR /src
COPY src/go.mod src/go.sum ./
RUN go mod download
COPY src/src/ ./src/
WORKDIR /src/src
RUN templ generate
RUN go build -trimpath -ldflags="-s -w" -o /kontra-bin .
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates git openssh-client bash \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /kontra-bin /usr/local/bin/kontra-bin
# entrypoint (pull loop + webserver) from runtime mount
COPY entrypoint.sh /usr/local/bin/kontra-entrypoint
RUN chmod +x /usr/local/bin/kontra-entrypoint
EXPOSE 8600
ENTRYPOINT ["/usr/local/bin/kontra-entrypoint", "kontra-bin", "8600"]

View File

@@ -0,0 +1,18 @@
services:
kontra:
build:
context: .
dockerfile: Dockerfile
container_name: kontra
restart: unless-stopped
ports:
- "8600:8600"
volumes:
- /var/www/kontra_day/content:/var/www/kontra_day/content
- /home/sam/.ssh/id_ed25519:/root/.ssh/id_ed25519:ro
environment:
- PORT=8600
- KONTRA_CONTENT=/var/www/kontra_day/content
- KONTRA_MEDIA=/media
extra_hosts:
- "gitea.lab.audasmedia.com.au:192.168.20.35"

View File

@@ -0,0 +1,55 @@
#!/bin/sh
# Kontra container entrypoint.
# - pulls content from Gitea every 30s
# - runs the webserver; restarts on content change (binary loads content at boot)
set -eu
BIN="${1:-kontra-bin}"
PORT="${2:-8600}"
CONTENT="/var/www/kontra_day/content"
GIT=/usr/bin/git
# Gitea SSH key for sam (mounted into the container)
export GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new -i /root/.ssh/id_ed25519"
# git refuses repos owned by another uid (host sam=1000); trust the mounted repo.
"$GIT" config --global --add safe.directory "$CONTENT" >/dev/null 2>&1 || true
"$GIT" config --global user.email "kontra-bot@localhost" >/dev/null 2>&1 || true
"$GIT" config --global user.name "kontra-bot" >/dev/null 2>&1 || true
# Initial clone if the mounted dir is empty (fresh deployment).
if [ ! -d "$CONTENT/.git" ]; then
echo "cloning kontra-content..."
"$GIT" clone git@gitea.lab.audasmedia.com.au:2222/sam/kontra-content.git "$CONTENT"
fi
"$GIT" -C "$CONTENT" rev-parse --is-inside-work-tree >/dev/null 2>&1 || true
start_server() {
# exec: replaces THIS shell, so $! is the real binary PID; kill hits it.
KONTRA_CONTENT="$CONTENT" KONTRA_MEDIA="/media" PORT="$PORT" exec "$BIN"
}
# First start.
start_server &
SERVER_PID=$!
# Pull every 30s; restart server when HEAD actually changes.
PREV=$("$GIT" -C "$CONTENT" rev-parse HEAD 2>/dev/null || echo "")
while true; do
sleep 30
if "$GIT" -C "$CONTENT" pull --ff-only origin main >/tmp/pull.log 2>&1; then
CUR=$("$GIT" -C "$CONTENT" rev-parse HEAD)
if [ -n "$PREV" ] && [ -n "$CUR" ] && [ "$PREV" != "$CUR" ]; then
echo "$(date +%T) content changed ($PREV -> $CUR); restarting server"
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
sleep 1
start_server &
SERVER_PID=$!
fi
PREV=$CUR
else
echo "pull failed:"; cat /tmp/pull.log
fi
done

14
deploy/fix-pull-path.sh Normal file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Fix kontra-pull ExecStart: /bin/bash -> /run/current-system/sw/bin/bash on .13
set -euo pipefail
CFG=/etc/nixos/configuration.nix
BAK=$CFG.bak-kontra-fix-$(date +%Y%m%d-%H%M%S)
cp -a "$CFG" "$BAK"
sed -i 's|/bin/bash /var/www/kontra_day/bin/kontra-pull.sh|/run/current-system/sw/bin/bash /var/www/kontra_day/bin/kontra-pull.sh|' "$CFG"
echo "patched. verifying:"
grep -n "kontra-pull.sh" "$CFG"
nix-instantiate --parse "$CFG" >/dev/null && echo "Nix syntax OK"
echo "== nixos-rebuild switch =="
nixos-rebuild switch --show-trace 2>&1 | tail -25
echo "== status =="
systemctl --no-pager status kontra-pull.service 2>/dev/null | head -8 || true

53
deploy/fix-pull2.sh Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Fix kontra-pull.sh: use runuser (not su) + point service at sam-writable deploy path.
set -euo pipefail
CFG=/etc/nixos/configuration.nix
BAK=$CFG.bak-kontra-fix2-$(date +%Y%m%d-%H%M%S)
cp -a "$CFG" "$BAK"
echo "backup: $BAK"
# Replace the ExecStart path in the Nix config (pull service -> deploy/ copy)
python3 - "$CFG" <<'PYEOF'
import sys
p = sys.argv[1]
src = open(p).read()
old = 'ExecStart = "/run/current-system/sw/bin/bash /var/www/kontra_day/bin/kontra-pull.sh";'
new = 'ExecStart = "/run/current-system/sw/bin/bash /var/www/kontra_day/deploy/kontra-pull.sh";'
if old in src:
src = src.replace(old, new)
else:
print("WARN: old ExecStart not found; check manually")
open(p,'w').write(src)
PYEOF
grep -n "kontra-pull.sh" "$CFG"
# Install fixed script into deploy/ (sam-writable)
cat > /var/www/kontra_day/deploy/kontra-pull.sh <<'SH'
#!/bin/sh
set -e
CONTENT=/var/www/kontra_day/content
GIT=/run/current-system/sw/bin/git
RUNUSER=/run/current-system/sw/bin/runuser
BEFORE=$("$RUNUSER" -u sam -- "$GIT" -C "$CONTENT" rev-parse HEAD)
"$RUNUSER" -u sam -- "$GIT" -C "$CONTENT" pull --ff-only origin main >/tmp/kontra-pull.log 2>&1 || { echo "pull failed:"; cat /tmp/kontra-pull.log; exit 1; }
AFTER=$("$RUNUSER" -u sam -- "$GIT" -C "$CONTENT" rev-parse HEAD)
if [ -n "$BEFORE" ] && [ -n "$AFTER" ] && [ "$BEFORE" != "$AFTER" ]; then
echo "content changed: $BEFORE -> $AFTER; restarting kontra"
/run/current-system/sw/bin/systemctl restart kontra.service
fi
exit 0
SH
chmod 0755 /var/www/kontra_day/deploy/kontra-pull.sh
# syntax check
nix-instantiate --parse "$CFG" >/dev/null && echo "Nix syntax OK" || { echo "SYNTAX FAIL"; cp -a "$BAK" "$CFG"; exit 1; }
echo "== nixos-rebuild switch =="
nixos-rebuild switch --show-trace 2>&1 | tail -20
echo "== force a run + status =="
systemctl start kontra-pull.service 2>&1 | head -2
sleep 3
systemctl --no-pager status kontra-pull.service 2>/dev/null | head -8
echo "== content head =="
cd /var/www/kontra_day/content && git log --oneline -1
echo "DONE"

30
deploy/install-kontra.sh Normal file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Install Kontra deploy on .13 — run as root (sudo bash install.sh).
# Copies: service units, pull script; enables service + timer.
set -euo pipefail
DIR=/var/www/kontra_day
BIN=$DIR/bin
echo "== placing pull script =="
install -m 0755 -o root -g root deploy/kontra-pull.sh $BIN/kontra-pull.sh
echo "== installing systemd units =="
install -m 0644 deploy/kontra.service /etc/systemd/system/kontra.service
install -m 0644 deploy/kontra-pull.service /etc/systemd/system/kontra-pull.service
install -m 0644 deploy/kontra-pull.timer /etc/systemd/system/kontra-pull.timer
echo "== daemon-reload =="
systemctl daemon-reload
echo "== enable+start kontra =="
systemctl enable --now kontra.service || true
systemctl start kontra.service || true
echo "== enable+start pull timer =="
systemctl enable --now kontra-pull.timer || true
echo "== status =="
systemctl --no-pager --full status kontra.service | head -15
systemctl --no-pager list-timers kontra-pull.timer | head -5
echo "DONE"

View File

@@ -0,0 +1,8 @@
[Unit]
Description=Kontra deploy pull (root; pulls as sam, restarts site if changed)
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/bash /var/www/kontra_day/bin/kontra-pull.sh

19
deploy/kontra-pull.sh Normal file
View File

@@ -0,0 +1,19 @@
#!/bin/sh
# Kontra content deploy: pull from Gitea; restart site if content changed.
# Runs as ROOT (systemd oneshot). Git runs as 'sam' (uses sam's Gitea SSH key)
# via runuser (NixOS-native drop-privilege); restart needs root.
set -e
CONTENT=/var/www/kontra_day/content
GIT=/run/current-system/sw/bin/git
RUNUSER=/run/current-system/sw/bin/runuser
BEFORE=$("$RUNUSER" -u sam -- "$GIT" -C "$CONTENT" rev-parse HEAD)
"$RUNUSER" -u sam -- "$GIT" -C "$CONTENT" pull --ff-only origin main >/tmp/kontra-pull.log 2>&1 \
|| { echo "pull failed:"; cat /tmp/kontra-pull.log; exit 1; }
AFTER=$("$RUNUSER" -u sam -- "$GIT" -C "$CONTENT" rev-parse HEAD)
if [ -n "$BEFORE" ] && [ -n "$AFTER" ] && [ "$BEFORE" != "$AFTER" ]; then
echo "content changed: $BEFORE -> $AFTER; restarting kontra"
"/run/current-system/sw/bin/systemctl" restart kontra.service
fi
exit 0

10
deploy/kontra-pull.timer Normal file
View File

@@ -0,0 +1,10 @@
[Unit]
Description=Kontra content pull timer (every 30s)
[Timer]
OnBootSec=30s
OnUnitActiveSec=30s
AccuracySec=2s
[Install]
WantedBy=timers.target

20
deploy/kontra.service Normal file
View File

@@ -0,0 +1,20 @@
[Unit]
Description=Kontra — GOTH news-opinion site
After=network.target
[Service]
Type=simple
User=sam
Group=users
WorkingDirectory=/var/www/kontra_day
Environment=KONTRA_CONTENT=/var/www/kontra_day/content
Environment=KONTRA_MEDIA=/media
Environment=PORT=8600
# Binds :8600 (localhost-adjacent; .13 Caddy vhost reverse_proxies to it).
ExecStart=/var/www/kontra_day/bin/kontra-bin
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env bash
# Apply Kontra additions to /etc/nixos/configuration.nix on .13, then rebuild.
# Run as: sudo bash patch-kontra-nix.sh
set -euo pipefail
CFG=/etc/nixos/configuration.nix
BAK=$CFG.bak-kontra-$(date +%Y%m%d-%H%M%S)
cp -a "$CFG" "$BAK"
echo "backup: $BAK"
python3 - "$CFG" <<'PYEOF'
import sys, io
path = sys.argv[1]
src = open(path).read()
# --- 1) Caddy extraConfig: append reverse_proxy block before the closing `'';`
caddy_add = '''
http://kontra.lab.audasmedia.com.au:8000 {
reverse_proxy localhost:8600
}
http://admin.kontra.lab.audasmedia.com.au:8000 {
reverse_proxy localhost:8600
}
http://kontra.home.lab:8000 {
reverse_proxy localhost:8600
}
'''
# The extraConfig string ends with '\n '';' (line 77). Insert before that.
needle = "\n '';"
i = src.rindex(needle, 0, src.index("services.caddy"))
src = src[:i] + caddy_add + needle + src[i+len(needle):]
# --- 2) systemd services + timer: insert after the services.caddy attr close (line 79)
services_add = '''
# --- KONTRA (GOTH news-opinion site) ---
systemd.services.kontra = {
description = "Kontra GOTH news-opinion site";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = "sam";
Group = "users";
WorkingDirectory = "/var/www/kontra_day";
Environment = [ "KONTRA_CONTENT=/var/www/kontra_day/content" "KONTRA_MEDIA=/media" "PORT=8600" ];
ExecStart = "/var/www/kontra_day/bin/kontra-bin";
Restart = "on-failure";
RestartSec = "3";
NoNewPrivileges = true;
};
};
systemd.services.kontra-pull = {
description = "Pull Kontra content from Gitea (every 30s timer)";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "/bin/bash /var/www/kontra_day/bin/kontra-pull.sh";
};
};
systemd.timers.kontra-pull = {
description = "Kontra content pull timer (30s)";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "30s";
OnUnitActiveSec = "30s";
AccuracySec = "2s";
};
};
'''
# Insert after the services.caddy closing "};" (line 79). Find "};" following services.caddy.
anchor = "services.caddy"
idx = src.index(anchor)
close = src.find("};", idx)
close2 = src.find("};", close + 2) # services attr closes with one more "};"? see structure
# The caddy attr is " services.caddy = {\n ... \n '';\n};" -- one close. Insert after it.
insert_at = close + 2
src = src[:insert_at] + "\n" + services_add + src[insert_at:]
open(path, "w").write(src)
print("patched configuration.nix")
PY
echo "== sanity: syntax check =="
nix-instantiate --parse "$CFG" >/dev/null && echo "Nix syntax OK" || { echo "NIX SYNTAX FAILED — restoring backup"; cp -a "$BAK" "$CFG"; exit 1; }
echo "== nixos-rebuild switch (this may take a while) =="
nixos-rebuild switch --show-trace 2>&1 | tail -30
echo "DONE"

73
deploy/patch-nix.py Normal file
View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Apply Kontra additions to a copy of configuration.nix. Test on /tmp, then run on .13."""
import sys
path = sys.argv[1]
src = open(path).read()
# --- 1) Caddy extraConfig: append reverse_proxy block before closing ''; ---
caddy_add = """
http://kontra.lab.audasmedia.com.au:8000 {
reverse_proxy localhost:8600
}
http://admin.kontra.lab.audasmedia.com.au:8000 {
reverse_proxy localhost:8600
}
http://kontra.home.lab:8000 {
reverse_proxy localhost:8600
}
"""
needle = "\n'';"
idx_caddy = src.index("services.caddy")
i = src.index(needle, idx_caddy)
src = src[:i] + caddy_add + needle + src[i + len(needle):]
# --- Step 2) systemd service + timer after services.caddy closes ---
services_add = """
# --- KONTRA (GOTH news-opinion site) ---
systemd.services.kontra = {
description = "Kontra GOTH news-opinion site";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = "sam";
Group = "users";
WorkingDirectory = "/var/www/kontra_day";
Environment = [ "KONTRA_CONTENT=/var/www/kontra_day/content" "KONTRA_MEDIA=/media" "PORT=8600" ];
ExecStart = "/var/www/kontra_day/bin/kontra-bin";
Restart = "on-failure";
RestartSec = "3";
NoNewPrivileges = true;
};
};
systemd.services.kontra-pull = {
description = "Pull Kontra content from Gitea (30s timer)";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "/run/current-system/sw/bin/bash /var/www/kontra_day/deploy/kontra-pull.sh";
};
};
systemd.timers.kontra-pull = {
description = "Kontra content pull timer (30s)";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "30s";
OnUnitActiveSec = "30s";
AccuracySec = "2s";
};
};
"""
anchor = "services.caddy"
idx = src.index(anchor)
close = src.find("};", idx) # closes services.caddy attr
insert_at = close + 2
src = src[:insert_at] + "\n" + services_add + src[insert_at:]
open(path, "w").write(src)
print("patched:", path)

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Revert ALL Kontra additions from /etc/nixos/configuration.nix on .13.
# Restores the untouched pre-Kontra backup, then rebuilds (removes systemd kontra + pull + caddy vhosts).
# Run as: sudo bash revert-kontra-nix.sh
set -euo pipefail
CFG=/etc/nixos/configuration.nix
PRE=/etc/nixos/configuration.nix.bak-kontra-20260908-195305 # first backup = pre-Kontra (no additions)
if [ ! -f "$PRE" ]; then
echo "!! pre-Kontra backup not found at $PRE — aborting (nothing reverted)."
exit 1
fi
# Safety: confirm the CURRENT file actually contains Kontra markers before overwriting
if ! grep -q "KONTRA" "$CFG"; then
echo "!! Current configuration.nix has no Kontra markers. Leaving as-is."
exit 0
fi
echo "== restoring pre-Kontra configuration.nix"
cp -a "$PRE" "$CFG"
echo " restored. Kontra markers now: $(grep -c CONTRA "$CFG" || true)"
echo "== Nix syntax check"
nix-instantiate --parse "$CFG" >/dev/null && echo "OK" || { echo "SYNTAX FAIL — no change made"; exit 1; }
echo "== nixos-rebuild switch (removes kontra service + timer + caddy vhosts)"
nixos-rebuild switch --show-trace 2>&1 | tail -20
echo "== confirm removal"
systemctl --no-pager list-units --all 2>/dev/null | grep -i kontra || echo " no kontra units (clean)"
echo "DONE — Kontra Nix additions reverted."