feat(deploy): bin/deploy-to-nas.sh voor één-commando redeploy

Lost terugkerende pijn op: na cross-build op Mac vergeet je makkelijk
de .env mee te nemen of vanuit de juiste directory te starten, met
"FAIL: ... is not set" als gevolg in pre-flight.

Script doet in volgorde:
1. docker buildx build --platform linux/amd64 --load
2. docker save | gzip → scrum4me-agent-runner-amd64.tar.gz
3. scp tarball + compose + (eerste keer) .env naar NAS
4. ssh: docker load + sanity-check op .env + compose up --force-recreate
5. ssh: docker compose logs -f (Ctrl-C om te stoppen)

Bestaande NAS-.env wordt niet overschreven. Eerste deploy patcht de
NAS-paden via sed. Sanity-check faalt expliciet als anthropic-,
SCRUM4ME_- of DATABASE_URL-vars ontbreken — ipv stille pre-flight-fail.

Config via .env.deploy (zit in .gitignore). Voor eerste deploy en
volledige procedure: README "Deploy — cross-build" sectie.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Madhura68 2026-05-06 01:30:22 +02:00
parent 70cfe0374e
commit 4b2241235e
4 changed files with 157 additions and 0 deletions

16
.env.deploy.example Normal file
View file

@ -0,0 +1,16 @@
# .env.deploy — config voor bin/deploy-to-nas.sh
# Kopieer naar .env.deploy en pas aan; staat in .gitignore.
# SSH-target voor de NAS. Moet werkend zijn met SSH-key (geen password).
NAS_HOST=admin@nas.local
# Pad op de NAS waar tarball + compose + .env terechtkomen.
# Default: /share/Agent/scrum4me-agent-runner
# NAS_REMOTE_DIR=/share/Agent/scrum4me-agent-runner
# Build-args (overrides). Standaard:
# MCP_GIT_REF=main — pin een commit in productie indien gewenst
# CLAUDE_CODE_VERSION=latest — pin een claude-code release indien gewenst
# AGENT_UID=1000, AGENT_GID=1000
# MCP_GIT_REF=main
# CLAUDE_CODE_VERSION=latest

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
# Secrets
.env
.env.deploy
*.env.local
# Local dev overrides (niet committen, per ontwikkelaar)

View file

@ -117,6 +117,34 @@ docker compose logs -f
> mapt deze stack standaard `18080:8080`. Override via
> `AGENT_HEALTH_PORT_HOST` in `.env` als je een andere host-poort wilt.
## Snelle redeploy — `bin/deploy-to-nas.sh`
Voor een **bestaande deploy** die je opnieuw wil bouwen + deployen
(bijvoorbeeld na een merge in `scrum4me-mcp` of een aanpassing aan
`CLAUDE.md`):
```bash
# Eenmalig: NAS-target instellen
cp .env.deploy.example .env.deploy
vi .env.deploy # zet NAS_HOST=admin@<nas>
# Daarna: één commando voor de hele cyclus
bin/deploy-to-nas.sh
```
Het script doet:
1. `docker buildx build --platform linux/amd64 --load`
2. `docker save | gzip → scrum4me-agent-runner-amd64.tar.gz`
3. `scp` van tarball + `docker-compose.yml` + (eerste keer) `.env` naar NAS
4. `ssh` op NAS: `docker load` + sanity-check op `.env` + `docker compose up -d --force-recreate`
5. `docker compose logs -f` — lokaal-volgbaar terwijl pre-flight + eerste batch starten
`.env` op de NAS wordt **niet** overschreven als 'ie er al staat. Bij een
verse NAS-installatie wordt 'ie wél geüpload + ge-sed't (NAS_BASE,
AGENT_UID, etc.). Voor de **eerste deploy** of een schoon volume zie de
volledige procedure hieronder.
## Deploy — cross-build vanaf Mac (Apple Silicon → amd64-NAS)
Alternatief voor de in-place build hierboven. Bouw de image op je Mac voor

112
bin/deploy-to-nas.sh Executable file
View file

@ -0,0 +1,112 @@
#!/usr/bin/env bash
# deploy-to-nas.sh — Mac → NAS cross-build deploy in één commando.
#
# Voert in volgorde uit:
# 1. docker buildx build (linux/amd64, --load)
# 2. docker save | gzip → scrum4me-agent-runner-amd64.tar.gz
# 3. scp tarball + docker-compose.yml + (eerste keer) .env naar NAS
# 4. ssh: docker load + sed-patch .env paths (eerste keer) + compose up --force-recreate
# 5. ssh: docker compose logs -f voor verificatie
#
# Gebruik:
# bin/deploy-to-nas.sh # gebruikt env vars uit .env.deploy
# NAS_HOST=admin@nas bin/deploy-to-nas.sh
#
# Vereisten:
# - docker buildx geïnstalleerd
# - .env aanwezig in repo-root (deze wordt naar NAS gestuurd op eerste run)
# - .env.deploy met NAS_HOST + NAS_REMOTE_DIR (optioneel — anders prompted)
# - SSH-key access naar de NAS (geen password-prompts)
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
# ----- config -----------------------------------------------------------
if [[ -f .env.deploy ]]; then
# shellcheck disable=SC1091
source .env.deploy
fi
: "${NAS_HOST:?NAS_HOST not set — bv. admin@nas.local. Zet 'm in .env.deploy of als env-var.}"
: "${NAS_REMOTE_DIR:=/share/Agent/scrum4me-agent-runner}"
: "${IMAGE_TAG:=scrum4me-agent-runner:local}"
: "${TARBALL:=scrum4me-agent-runner-amd64.tar.gz}"
: "${MCP_GIT_REF:=main}"
: "${CLAUDE_CODE_VERSION:=latest}"
: "${AGENT_UID:=1000}"
: "${AGENT_GID:=1000}"
log() { echo "[deploy-to-nas] $*"; }
# ----- pre-flight -------------------------------------------------------
if [[ ! -f .env ]]; then
log "FAIL: .env ontbreekt in repo-root. Maak 'm aan via: cp .env.example .env"
exit 1
fi
# ----- 1. buildx --------------------------------------------------------
log "1/5 docker buildx build (linux/amd64, MCP_GIT_REF=$MCP_GIT_REF)"
docker buildx build \
--platform linux/amd64 \
--build-arg "MCP_GIT_REF=${MCP_GIT_REF}" \
--build-arg "CLAUDE_CODE_VERSION=${CLAUDE_CODE_VERSION}" \
--build-arg "AGENT_UID=${AGENT_UID}" \
--build-arg "AGENT_GID=${AGENT_GID}" \
-t "$IMAGE_TAG" \
--load \
.
# ----- 2. tarball -------------------------------------------------------
log "2/5 docker save | gzip → $TARBALL"
docker save "$IMAGE_TAG" | gzip > "$TARBALL"
ls -lh "$TARBALL"
# ----- 3. scp -----------------------------------------------------------
log "3/5 scp tarball + compose naar $NAS_HOST:$NAS_REMOTE_DIR"
ssh "$NAS_HOST" "mkdir -p '$NAS_REMOTE_DIR'"
# Check of er al een .env op de NAS staat. Zo niet: stuur de Mac-versie en
# patch hem. Zo wel: laat 'm met rust (kan NAS-specifiek aangepast zijn).
if ssh "$NAS_HOST" "test -f '$NAS_REMOTE_DIR/.env'" 2>/dev/null; then
log " (.env bestaat al op NAS — niet overschreven)"
else
log " geen .env op NAS, kopiëren + patchen"
scp .env "$NAS_HOST:$NAS_REMOTE_DIR/.env"
ssh "$NAS_HOST" "
cd '$NAS_REMOTE_DIR'
chmod 600 .env
sed -i \\
-e 's|^NAS_BASE=.*|NAS_BASE=/share/Agent|' \\
-e 's|^AGENT_BASE=.*|AGENT_BASE=/share/Agent|' \\
-e 's|^AGENT_PLATFORM=.*|AGENT_PLATFORM=linux/amd64|' \\
-e 's|^AGENT_UID=.*|AGENT_UID=${AGENT_UID}|' \\
-e 's|^AGENT_GID=.*|AGENT_GID=${AGENT_GID}|' \\
-e 's|^AGENT_HEALTH_PORT_HOST=.*|AGENT_HEALTH_PORT_HOST=18080|' \\
.env
"
fi
scp "$TARBALL" docker-compose.yml package.json README.md \
"$NAS_HOST:$NAS_REMOTE_DIR/"
# ----- 4. load + restart ------------------------------------------------
log "4/5 docker load + compose up --force-recreate op NAS"
ssh "$NAS_HOST" "
set -eu
source /etc/profile
cd '$NAS_REMOTE_DIR'
# Sanity: env-vars die check-tokens.sh nodig heeft
grep -qE '^(CLAUDE_CODE_OAUTH_TOKEN|ANTHROPIC_API_KEY)=' .env || { echo 'FAIL: anthropic credential ontbreekt in .env'; exit 1; }
grep -qE '^SCRUM4ME_TOKEN=' .env || { echo 'FAIL: SCRUM4ME_TOKEN ontbreekt in .env'; exit 1; }
grep -qE '^DATABASE_URL=' .env || { echo 'FAIL: DATABASE_URL ontbreekt in .env'; exit 1; }
gunzip -c '$TARBALL' | docker load
docker compose up -d --force-recreate
"
# ----- 5. tail logs ------------------------------------------------------
log "5/5 docker compose logs (Ctrl-C om te stoppen)"
ssh "$NAS_HOST" "cd '$NAS_REMOTE_DIR' && docker compose logs -f --tail=50"