feat(lanes): per-lane database, Redis, and .env isolation (A1+A2)

Gives each lane its own slot-derived runtime (ports, detached process
lifecycle, profile-driven hooks) and its own database/Redis logical
index/.env file, so two lanes running the same repo's stack at once no
longer share state. Machine-level DB/Redis credentials live at
~/.ccam/secrets.env (mode 0600, never returned by any route); a hook's
output is redacted of that password (raw and URL-encoded forms) before
it reaches a log file or the lane_hook_output websocket broadcast.
Wired into provision/up/reset/remove; reset accepts --keep-db to skip
the drop/recreate/migrate/reseed block entirely.
This commit is contained in:
2026-08-04 10:03:40 +07:00
parent d71086f677
commit 9d145865dd
19 changed files with 3265 additions and 12 deletions
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# db-create.sh Create this lane's Postgres database inside a docker-compose
# service, via `createdb`. Ported verbatim from Shipyard's own lane-bootstrap
# / lane-up / lane-reset scripts, so a Postgres-via-compose repo gets parity
# by copying this template into its own .ccam/profile/hooks/.
#
# Called as: db-create.sh <db-name> (also available as $DB_NAME)
# Requires, declared in the repo's own profile.env:
# COMPOSE_FILE path to the docker-compose file (e.g. "$LANE_DIR/docker-compose.yml")
# DB_SERVICE the compose service name running Postgres
# Provided by CCAM's hook environment (server/lib/secrets.js):
# PG_USER (PG_PASS is deliberately not exported — trust auth inside the
# compose network needs no password for this call)
# ─────────────────────────────────────────────────────────────────────────────
# @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
set -euo pipefail
NAME="${1:-$DB_NAME}"
[ -n "$NAME" ] || { echo "db-create: no database name given" >&2; exit 1; }
if docker compose -f "$COMPOSE_FILE" exec -T "$DB_SERVICE" \
psql -U "$PG_USER" -tAc "SELECT 1 FROM pg_database WHERE datname='$NAME'" | grep -q 1; then
echo "db-create: $NAME already exists"
exit 0
fi
docker compose -f "$COMPOSE_FILE" exec -T "$DB_SERVICE" createdb -U "$PG_USER" "$NAME"
echo "db-create: created $NAME"
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# db-drop.sh Drop this lane's Postgres database inside a docker-compose
# service, via `dropdb --if-exists`. Ported verbatim from Shipyard's own
# lane-reset / lane-remove scripts. CCAM only ever calls this with a name it
# derived itself (server/lib/lane-services.js:dropDatabase), so this script
# never needs to re-validate its argument.
#
# Called as: db-drop.sh <db-name> (also available as $DB_NAME)
# Requires/provides: same as db-create.sh in this same directory.
# ─────────────────────────────────────────────────────────────────────────────
# @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
set -euo pipefail
NAME="${1:-$DB_NAME}"
[ -n "$NAME" ] || { echo "db-drop: no database name given" >&2; exit 1; }
docker compose -f "$COMPOSE_FILE" exec -T "$DB_SERVICE" dropdb --if-exists -U "$PG_USER" "$NAME"
echo "db-drop: dropped $NAME (if it existed)"
@@ -0,0 +1,23 @@
# postgres-compose profile template — data-isolation additions (A2).
# Copy the declarations you need into your repo's own .ccam/profile/profile.env
# alongside db-create.sh / db-drop.sh from this template's hooks/ directory.
DB_PREFIX="myapp_l" # lane in slot 3 -> myapp_l3 ; empty = no per-lane DB
DB_KIND="postgres" # informational
DB_URL_SCHEME="postgresql" # DATABASE_URL scheme
REDIS=1 # 1 = allocate a logical Redis index = slot
# (16 logical DBs by default, 0-15 — keep
# LANE_MAX_SLOTS <= 15 if you turn this on)
ENV_FILES="backend/.env" # file(s) to seed, relative to the lane
ENV_SOURCE="backend/.env" # source path in the source repo
ENV_REWRITE="DATABASE_URL REDIS_URL UPLOAD_DIR" # keys CCAM overwrites per lane
ENV_PRESERVE="JWT_SECRET" # keys kept from the lane's OWN file on --force
UPLOAD_SUBDIR="backend/data/uploads" # exported as UPLOAD_DIR
# Read by db-create.sh / db-drop.sh, NOT by CCAM itself:
COMPOSE_FILE="docker-compose.yml" # relative to the lane's working copy
DB_SERVICE="postgres" # the compose service name running Postgres
# Machine-level credentials (PG_HOST/PORT/USER/PASS, REDIS_HOST/PORT) come
# from ~/.ccam/secrets.env, never from here — see server/lib/secrets.js.