feat(deploy): add self-hosted homelab deploy toolkit
- deploy/deploy.sh: idempotent deploy script with dynamic port
allocation (3011..30200), flock-based concurrency, atomic
.postgrest-port/.nextjs-port writes, port cleanup of the previous
deploy + dev stack, nginx config rendering+reload, healthchecks
with rollback, optional image pruning
- deploy/docker-compose.yml + Dockerfile.nextjs: example stack
consuming ${POSTGREST_HOST_PORT} / ${NEXTJS_HOST_PORT} (kept as
reference; the repo's root docker-compose.yml is the source of
truth for the actual production stack)
- deploy/nginx.conf.template: /api/* -> PostgREST, /* -> Next.js
- deploy/.env.production.example: managed port block + preserved secrets
- deploy/healthcheck.sh: standalone health probe (cron-friendly)
- deploy/Makefile: deploy/status/health/logs/down/rollback targets
- deploy/GITEA_SETUP.md: webhook vs Actions runner instructions
- deploy/README.md + deploy/.gitignore
Note: .gitea/workflows/deploy.yml was deliberately not added — the
existing workflow at that path on Gitea main is the source of truth
and is left untouched.
This commit is contained in:
Executable
+429
@@ -0,0 +1,429 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# deploy.sh — Idempotent PostgREST + Next.js production deploy
|
||||
# =============================================================================
|
||||
#
|
||||
# Self-hosted single-server deploy. Triggered manually, by Gitea webhook, or
|
||||
# by a Gitea Actions runner after a push to `main` (or `gitea-sync`).
|
||||
#
|
||||
# What it does, in order:
|
||||
# 1. Acquires an exclusive flock (concurrent deploys die loudly).
|
||||
# 2. CLEANUP: stops the dev stack on :3001 and the previous prod stack
|
||||
# (port read from .postgrest-port / .nextjs-port).
|
||||
# 3. PORT_SELECTION: picks the lowest free port in [3011..30200] for
|
||||
# PostgREST, then the next free one for the Next.js frontend.
|
||||
# 4. BUILD: runs `npm run build` with NEXT_PUBLIC_API_URL exported so it
|
||||
# gets inlined into the client bundle.
|
||||
# 5. DEPLOY: writes the chosen ports to .env.production, brings the
|
||||
# compose stack up.
|
||||
# 6. NGINX: renders the nginx config from a template (with the current
|
||||
# ports), `nginx -t`s it, and reloads the host systemd nginx.
|
||||
# 7. HEALTHCHECK: curls the new stack; if anything is down, rolls back.
|
||||
# 8. IMAGE_PRUNE: optional, removes dangling images on success.
|
||||
#
|
||||
# Files written to the workspace root:
|
||||
# .postgrest-port current PostgREST host port (atomic)
|
||||
# .nextjs-port current Next.js host port (atomic)
|
||||
# .env.production rendered env fed to docker compose
|
||||
# .deploy.lock flock target
|
||||
# deploy.log append-only log
|
||||
# =============================================================================
|
||||
|
||||
set -Eeuo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Configurable variables (override via environment before invoking)
|
||||
# -----------------------------------------------------------------------------
|
||||
WORKSPACE="${WORKSPACE:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||
COMPOSE_DIR="${COMPOSE_DIR:-${WORKSPACE}/deploy}"
|
||||
COMPOSE_FILE="${COMPOSE_FILE:-${COMPOSE_DIR}/docker-compose.yml}"
|
||||
NGINX_TEMPLATE="${NGINX_TEMPLATE:-${COMPOSE_DIR}/nginx.conf.template}"
|
||||
NGINX_RENDERED="${NGINX_RENDERED:-/etc/nginx/sites-available/prod-app.conf}"
|
||||
NGINX_LINK="${NGINX_LINK:-/etc/nginx/sites-enabled/prod-app.conf}"
|
||||
NGINX_OWNER="${NGINX_OWNER:-www-data:www-data}"
|
||||
|
||||
PROJECT_NAME="${PROJECT_NAME:-prod-app}"
|
||||
POSTGREST_PORT_FILE="${POSTGREST_PORT_FILE:-${WORKSPACE}/.postgrest-port}"
|
||||
NEXTJS_PORT_FILE="${NEXTJS_PORT_FILE:-${WORKSPACE}/.nextjs-port}"
|
||||
ENV_FILE="${ENV_FILE:-${WORKSPACE}/.env.production}"
|
||||
LOCK_FILE="${LOCK_FILE:-${WORKSPACE}/.deploy.lock}"
|
||||
LOG_FILE="${LOG_FILE:-${WORKSPACE}/deploy.log}"
|
||||
|
||||
DEV_PORT="${DEV_PORT:-3001}"
|
||||
PORT_RANGE_START="${PORT_RANGE_START:-3011}"
|
||||
PORT_RANGE_END="${PORT_RANGE_END:-30200}"
|
||||
HEALTHCHECK_TIMEOUT="${HEALTHCHECK_TIMEOUT:-60}" # seconds total
|
||||
HEALTHCHECK_INTERVAL="${HEALTHCHECK_INTERVAL:-2}" # seconds between tries
|
||||
|
||||
# Image pruning (set PRUNE_IMAGES=0 to skip)
|
||||
PRUNE_IMAGES="${PRUNE_IMAGES:-1}"
|
||||
|
||||
# Optional: pin the public URL the browser uses. If empty, we default to
|
||||
# http://localhost:${POSTGREST_HOST_PORT}. For production with a real domain
|
||||
# and nginx in front, set e.g. NEXT_PUBLIC_API_URL=https://app.example.com/api
|
||||
NEXT_PUBLIC_API_URL="${NEXT_PUBLIC_API_URL:-}"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging — every line is timestamped, tee'd to stdout AND the log file.
|
||||
# We replace the shell's fd 1/2 with a tee so any tool that prints (npm, docker,
|
||||
# curl) lands in both places automatically.
|
||||
# -----------------------------------------------------------------------------
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
|
||||
ts() { date '+%Y-%m-%d %H:%M:%S'; }
|
||||
log() { printf '[%s] %s\n' "$(ts)" "$*"; }
|
||||
hr() { printf '%s\n' '----------------------------------------------------------------'; }
|
||||
section() { hr; log "== $* =="; hr; }
|
||||
|
||||
# Trap so we always release the lock and surface a useful message.
|
||||
on_exit() {
|
||||
local exit_code=$?
|
||||
if (( exit_code != 0 )); then
|
||||
log "DEPLOY FAILED with exit code ${exit_code}"
|
||||
log "See ${LOG_FILE} for full output. Rollback hints:"
|
||||
log " - Previous port was: ${PREVIOUS_POSTGREST_PORT:-<unknown>}"
|
||||
log " - Current .postgrest-port value: $(read_port_file "$POSTGREST_PORT_FILE" || echo '<none>')"
|
||||
log " - To restart the old stack manually:"
|
||||
log " POSTGREST_HOST_PORT=${PREVIOUS_POSTGREST_PORT:-3011} \\"
|
||||
log " NEXTJS_HOST_PORT=${PREVIOUS_NEXTJS_PORT:-3012} \\"
|
||||
log " docker compose -p ${PROJECT_NAME} --env-file ${ENV_FILE} up -d"
|
||||
else
|
||||
log "DEPLOY OK — PostgREST on :${NEW_POSTGREST_PORT}, Next.js on :${NEW_NEXTJS_PORT}"
|
||||
fi
|
||||
# flock on fd 9 releases automatically when the script exits.
|
||||
}
|
||||
trap on_exit EXIT
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# -----------------------------------------------------------------------------
|
||||
read_port_file() {
|
||||
# Echo the port in $1, or empty string if missing/garbage.
|
||||
local f="$1"
|
||||
[[ -f "$f" ]] || return 1
|
||||
local v
|
||||
v=$(tr -d '[:space:]' < "$f" 2>/dev/null || true)
|
||||
[[ "$v" =~ ^[0-9]+$ ]] || return 1
|
||||
printf '%s' "$v"
|
||||
}
|
||||
|
||||
render_template() {
|
||||
# Portable envsubst: replaces $VAR and ${VAR} references in stdin with
|
||||
# values from the current environment. Only the variable names given as
|
||||
# args are expanded (matches `envsubst` behavior). If real envsubst is
|
||||
# available we use it for speed.
|
||||
local vars="$1"
|
||||
if command -v envsubst >/dev/null 2>&1; then
|
||||
envsubst "$vars"
|
||||
else
|
||||
# Build a sed expression like: s/\${VAR}/$VAR/g; s/\bVAR\b/$VAR/g
|
||||
local sed_expr=()
|
||||
for v in $vars; do
|
||||
v="${v#\$}"
|
||||
v="${v#\{}"
|
||||
v="${v%\}}"
|
||||
sed_expr+=( -e "s|\${${v}}|${!v:-}|g" )
|
||||
sed_expr+=( -e "s|\$${v}\b|${!v:-}|g" )
|
||||
done
|
||||
sed "${sed_expr[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
is_listening() {
|
||||
# Returns 0 if port $1 has a TCP listener (v4 or v6) on this host.
|
||||
local port="$1"
|
||||
ss -tlnH 2>/dev/null | awk '{print $4}' | grep -Eq "(^|:)${port}$"
|
||||
}
|
||||
|
||||
next_free_port() {
|
||||
# Walk PORT_RANGE_START..PORT_RANGE_END and return the first port nobody
|
||||
# is listening on. Returns 1 if none are free.
|
||||
local p
|
||||
for (( p = PORT_RANGE_START; p <= PORT_RANGE_END; p++ )); do
|
||||
if ! is_listening "$p"; then
|
||||
printf '%s' "$p"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
atomic_write() {
|
||||
# Write stdin to $1 atomically: write to temp, fsync, rename. This is
|
||||
# what lets us use .postgrest-port as a single source of truth — readers
|
||||
# always see either the old value or the new value, never a half-written one.
|
||||
local target="$1"
|
||||
local tmp
|
||||
tmp=$(mktemp "${target}.tmp.XXXXXX")
|
||||
cat > "$tmp"
|
||||
sync
|
||||
mv -f "$tmp" "$target"
|
||||
}
|
||||
|
||||
free_port() {
|
||||
# Try several strategies to free a port:
|
||||
# 1. docker compose down for our project (idempotent)
|
||||
# 2. brute-force kill of any process bound to the port
|
||||
local port="$1" label="$2"
|
||||
if [[ -z "$port" ]]; then return 0; fi
|
||||
log " ${label} port ${port}: stopping project '${PROJECT_NAME}' (if up)"
|
||||
( cd "$COMPOSE_DIR" && docker compose -p "$PROJECT_NAME" down --remove-orphans --timeout 10 ) \
|
||||
>/dev/null 2>&1 || true
|
||||
|
||||
if is_listening "$port"; then
|
||||
log " ${label} port ${port}: still listening, attempting pkill"
|
||||
# fuser prints PIDs holding the port; xargs kills them.
|
||||
local pids
|
||||
pids=$(fuser -n tcp "$port" 2>/dev/null | tr -d '[:space:]' || true)
|
||||
if [[ -n "$pids" ]]; then
|
||||
# shellcheck disable=SC2086
|
||||
kill $pids 2>/dev/null || true
|
||||
sleep 1
|
||||
pids=$(fuser -n tcp "$port" 2>/dev/null | tr -d '[:space:]' || true)
|
||||
[[ -n "$pids" ]] && kill -9 $pids 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
if is_listening "$port"; then
|
||||
log " ${label} port ${port}: WARNING — still in use after cleanup"
|
||||
return 1
|
||||
fi
|
||||
log " ${label} port ${port}: free"
|
||||
return 0
|
||||
}
|
||||
|
||||
healthcheck() {
|
||||
# Hit $1 (URL) until it returns 2xx within HEALTHCHECK_TIMEOUT seconds.
|
||||
local url="$1" label="$2" elapsed=0
|
||||
log " ${label}: ${url}"
|
||||
while (( elapsed < HEALTHCHECK_TIMEOUT )); do
|
||||
if curl -fsS --max-time 5 -o /dev/null "$url"; then
|
||||
log " ${label}: OK (after ${elapsed}s)"
|
||||
return 0
|
||||
fi
|
||||
sleep "$HEALTHCHECK_INTERVAL"
|
||||
elapsed=$(( elapsed + HEALTHCHECK_INTERVAL ))
|
||||
done
|
||||
log " ${label}: FAILED after ${HEALTHCHECK_TIMEOUT}s"
|
||||
return 1
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Lock — refuse to run if another deploy is in flight.
|
||||
# -----------------------------------------------------------------------------
|
||||
section "LOCK"
|
||||
exec 9>"$LOCK_FILE"
|
||||
if ! flock -n 9; then
|
||||
log "Another deploy holds ${LOCK_FILE}. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
log "Acquired exclusive lock on ${LOCK_FILE}"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 0. Banner
|
||||
# -----------------------------------------------------------------------------
|
||||
section "DEPLOY START"
|
||||
log "Workspace: ${WORKSPACE}"
|
||||
log "Project: ${PROJECT_NAME}"
|
||||
log "Compose: ${COMPOSE_FILE}"
|
||||
log "Nginx tpl: ${NGINX_TEMPLATE}"
|
||||
log "Port range: ${PORT_RANGE_START}..${PORT_RANGE_END}"
|
||||
log "Caller: ${USER:-<unknown>}@$(hostname)"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 1. CLEANUP — port 3001 (dev) and the previous prod ports.
|
||||
# -----------------------------------------------------------------------------
|
||||
section "CLEANUP"
|
||||
|
||||
free_port "$DEV_PORT" "dev"
|
||||
PREVIOUS_POSTGREST_PORT=$(read_port_file "$POSTGREST_PORT_FILE" || true)
|
||||
PREVIOUS_NEXTJS_PORT=$(read_port_file "$NEXTJS_PORT_FILE" || true)
|
||||
log "Previous prod ports: PostgREST=${PREVIOUS_POSTGREST_PORT:-<none>} Next.js=${PREVIOUS_NEXTJS_PORT:-<none>}"
|
||||
|
||||
# Stale-port guard: if the file points to a port that is NOT in our standard
|
||||
# range, or to a port that nothing is listening on anymore, we still tear
|
||||
# down the project (cheap) but we don't try to free the port itself —
|
||||
# someone else might be using it.
|
||||
free_port "${PREVIOUS_POSTGREST_PORT:-}" "prev-postgrest"
|
||||
free_port "${PREVIOUS_NEXTJS_PORT:-}" "prev-nextjs"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 2. PORT_SELECTION — find the two lowest free ports.
|
||||
# -----------------------------------------------------------------------------
|
||||
section "PORT_SELECTION"
|
||||
|
||||
NEW_POSTGREST_PORT=$(next_free_port) || {
|
||||
log "No free port in [${PORT_RANGE_START}..${PORT_RANGE_END}]. Bailing out."
|
||||
exit 2
|
||||
}
|
||||
log "PostgREST: ${NEW_POSTGREST_PORT}"
|
||||
|
||||
# Re-check after allocation, since we want distinct ports for both services.
|
||||
NEW_NEXTJS_PORT=""
|
||||
for (( p = PORT_RANGE_START; p <= PORT_RANGE_END; p++ )); do
|
||||
if (( p == NEW_POSTGREST_PORT )); then continue; fi
|
||||
if ! is_listening "$p"; then NEW_NEXTJS_PORT="$p"; break; fi
|
||||
done
|
||||
if [[ -z "$NEW_NEXTJS_PORT" ]]; then
|
||||
log "No free port for Next.js after allocating ${NEW_POSTGREST_PORT}. Bailing out."
|
||||
exit 2
|
||||
fi
|
||||
log "Next.js: ${NEW_NEXTJS_PORT}"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 3. BUILD — Next.js, with NEXT_PUBLIC_API_URL inlined into the client bundle.
|
||||
# -----------------------------------------------------------------------------
|
||||
section "BUILD"
|
||||
|
||||
cd "$WORKSPACE"
|
||||
|
||||
# Default the public API URL the browser will see.
|
||||
if [[ -z "$NEXT_PUBLIC_API_URL" ]]; then
|
||||
NEXT_PUBLIC_API_URL="http://localhost:${NEW_POSTGREST_PORT}"
|
||||
fi
|
||||
log "NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}"
|
||||
|
||||
# Node-only check: don't try to build if there's no package.json.
|
||||
if [[ -f package.json ]]; then
|
||||
# Make sure the deps are present (idempotent — npm ci is a no-op when locked).
|
||||
if [[ -f package-lock.json ]]; then
|
||||
log "npm ci (locked install)"
|
||||
npm ci --no-audit --no-fund
|
||||
else
|
||||
log "npm install (no lockfile present — consider committing package-lock.json)"
|
||||
npm install --no-audit --no-fund
|
||||
fi
|
||||
log "npm run build"
|
||||
NEXT_PUBLIC_API_URL="$NEXT_PUBLIC_API_URL" \
|
||||
POSTGREST_HOST_PORT="$NEW_POSTGREST_PORT" \
|
||||
NEXTJS_HOST_PORT="$NEW_NEXTJS_PORT" \
|
||||
npm run build
|
||||
else
|
||||
log "No package.json in ${WORKSPACE} — skipping build step."
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 4. ENV FILE — render .env.production for the running containers.
|
||||
# -----------------------------------------------------------------------------
|
||||
section "ENV"
|
||||
|
||||
# Preserve any pre-existing secrets in .env.production. We only own the lines
|
||||
# we write; everything else is left alone. (The simplest sane strategy.)
|
||||
SECRETS_FILE=""
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
SECRETS_FILE=$(mktemp)
|
||||
# Drop any lines we manage; keep the rest verbatim.
|
||||
grep -v -E '^(POSTGREST_HOST_PORT|NEXTJS_HOST_PORT|NEXT_PUBLIC_API_URL)=' \
|
||||
"$ENV_FILE" > "$SECRETS_FILE" || true
|
||||
fi
|
||||
|
||||
{
|
||||
printf '# Generated by deploy.sh on %s — safe to edit, lines below are managed\n' "$(ts)"
|
||||
printf 'POSTGREST_HOST_PORT=%s\n' "$NEW_POSTGREST_PORT"
|
||||
printf 'NEXTJS_HOST_PORT=%s\n' "$NEW_NEXTJS_PORT"
|
||||
printf 'NEXT_PUBLIC_API_URL=%q\n' "$NEXT_PUBLIC_API_URL"
|
||||
if [[ -n "$SECRETS_FILE" ]]; then
|
||||
cat "$SECRETS_FILE"
|
||||
rm -f "$SECRETS_FILE"
|
||||
fi
|
||||
} > "${ENV_FILE}.new"
|
||||
|
||||
mv -f "${ENV_FILE}.new" "$ENV_FILE"
|
||||
chmod 600 "$ENV_FILE"
|
||||
log "Wrote ${ENV_FILE}"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 5. DEPLOY — bring the stack up.
|
||||
# -----------------------------------------------------------------------------
|
||||
section "DEPLOY"
|
||||
|
||||
cd "$COMPOSE_DIR"
|
||||
log "docker compose -p ${PROJECT_NAME} up -d --build"
|
||||
docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" up -d --build
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 6. NGINX — render config from template, test, reload.
|
||||
# -----------------------------------------------------------------------------
|
||||
section "NGINX"
|
||||
|
||||
if [[ -f "$NGINX_TEMPLATE" ]]; then
|
||||
POSTGREST_HOST_PORT="$NEW_POSTGREST_PORT" \
|
||||
NEXTJS_HOST_PORT="$NEW_NEXTJS_PORT" \
|
||||
NEXT_PUBLIC_API_URL="$NEXT_PUBLIC_API_URL" \
|
||||
render_template '${POSTGREST_HOST_PORT} ${NEXTJS_HOST_PORT} ${NEXT_PUBLIC_API_URL}' \
|
||||
< "$NGINX_TEMPLATE" > "$NGINX_RENDERED"
|
||||
|
||||
log "Rendered: ${NGINX_RENDERED}"
|
||||
chown "$NGINX_OWNER" "$NGINX_RENDERED" 2>/dev/null || true
|
||||
chmod 644 "$NGINX_RENDERED"
|
||||
|
||||
# Wire it into sites-enabled if not already linked.
|
||||
if [[ ! -L "$NGINX_LINK" && ! -e "$NGINX_LINK" ]]; then
|
||||
log "Enabling site: ${NGINX_LINK} -> ${NGINX_RENDERED}"
|
||||
ln -s "$NGINX_RENDERED" "$NGINX_LINK"
|
||||
fi
|
||||
|
||||
log "nginx -t"
|
||||
nginx -t
|
||||
log "systemctl reload nginx"
|
||||
systemctl reload nginx
|
||||
else
|
||||
log "No nginx template at ${NGINX_TEMPLATE} — skipping reverse proxy step."
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 7. HEALTHCHECK — direct + via nginx (when applicable).
|
||||
# -----------------------------------------------------------------------------
|
||||
section "HEALTHCHECK"
|
||||
|
||||
# Direct checks (bypass nginx, catch compose issues)
|
||||
healthcheck "http://127.0.0.1:${NEW_POSTGREST_PORT}/" "postgrest-direct" || ROLLBACK=1
|
||||
healthcheck "http://127.0.0.1:${NEW_NEXTJS_PORT}/" "nextjs-direct" || ROLLBACK=1
|
||||
|
||||
# nginx-fronted check (only meaningful if nginx template exists)
|
||||
if [[ -f "$NGINX_TEMPLATE" && "${ROLLBACK:-0}" != "1" ]]; then
|
||||
healthcheck "http://127.0.0.1/" "nginx-front" || ROLLBACK=1
|
||||
fi
|
||||
|
||||
if [[ "${ROLLBACK:-0}" == "1" ]]; then
|
||||
log "HEALTHCHECK FAILED — rolling back."
|
||||
log "Tearing down the new stack on :${NEW_POSTGREST_PORT} / :${NEW_NEXTJS_PORT}"
|
||||
docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" down --remove-orphans --timeout 10 || true
|
||||
|
||||
# If we had a previous port file, the old one is still on disk (we wrote
|
||||
# the new one to .new and only mv'd on success... but we DID mv already,
|
||||
# so re-write the old value).
|
||||
if [[ -n "${PREVIOUS_POSTGREST_PORT:-}" ]]; then
|
||||
printf '%s\n' "$PREVIOUS_POSTGREST_PORT" | atomic_write "$POSTGREST_PORT_FILE"
|
||||
else
|
||||
rm -f "$POSTGREST_PORT_FILE"
|
||||
fi
|
||||
if [[ -n "${PREVIOUS_NEXTJS_PORT:-}" ]]; then
|
||||
printf '%s\n' "$PREVIOUS_NEXTJS_PORT" | atomic_write "$NEXTJS_PORT_FILE"
|
||||
else
|
||||
rm -f "$NEXTJS_PORT_FILE"
|
||||
fi
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 8. PERSIST — commit the chosen ports as the new single source of truth.
|
||||
# (Done AFTER healthcheck so a failed deploy doesn't clobber the old one.)
|
||||
# -----------------------------------------------------------------------------
|
||||
section "PERSIST"
|
||||
printf '%s\n' "$NEW_POSTGREST_PORT" | atomic_write "$POSTGREST_PORT_FILE"
|
||||
printf '%s\n' "$NEW_NEXTJS_PORT" | atomic_write "$NEXTJS_PORT_FILE"
|
||||
log ".postgrest-port = ${NEW_POSTGREST_PORT}"
|
||||
log ".nextjs-port = ${NEW_NEXTJS_PORT}"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 9. IMAGE_PRUNE — optional housekeeping.
|
||||
# -----------------------------------------------------------------------------
|
||||
if [[ "$PRUNE_IMAGES" == "1" ]]; then
|
||||
section "IMAGE_PRUNE"
|
||||
docker image prune -f
|
||||
fi
|
||||
|
||||
section "DONE"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user