[pending-judge] feat(DC-164): operator env overrides in start.sh + lifetime-license wiring (rev2)
Operator requirement (Sami Ahmed, in-thread 2026-10-08): premium becomes a creator-only LIFETIME license on his host, surviving BOTH failure domains (license server down; machine/container rebuild). Design: a safe, ENFORCED mechanism to turn on the pre-existing ALLOW_LIFETIME_LICENSE gate (license-manager, DC-85) on exactly one host — the flag lives OUTSIDE the repo in a root-owned file, so shipping start.sh to customers cannot hand them a lifetime bypass. - dashcaddy-api/scripts/operator-flags.sh: pure translator from /etc/dashcaddy/operator.env to docker -e flag material. It ENFORCES the operator-file security contract: regular file (not symlink), owner uid matches the reading identity (root => uid 0), mode grants neither group nor other access and no setuid/sticky bits. Violation => exit 3 + FATAL stderr and start.sh forwards NOTHING (fail closed). Never sources or executes file content: keys validated against [A-Za-z_][A-Za-z0-9_]*=, values pass through verbatim; missing file => silent exit 0 (normal customer case). - start.sh: single helper execution, explicit exit-code inspection, flags expanded into docker run via the set-u-safe array construct. Without the file, docker args are byte-identical to before. Boot log prints KEY names only, never values. - __tests__/scripts/operator-flags.test.js: 14 EXECUTED tests (8 behavior incl. an injection probe asserting command-substitution values stay inert text, + 6 security-contract: 0644/04600/symlink/foreign-owner/directory all => exit 3; root-0600 => emit). Offline-mode resilience (ops-verified on DNS2, unchanged in code): with no LICENSE_SERVER_URL the activation verifies by HMAC against the bind-mounted .license-secret and persists in the mounted credential store; the lifetime code (minted offline, keygen-verified LIFETIME, durationDays 0) re-activates on rebuild. His premium is live: tier premium, lifetime true. No runtime logic touched: license-manager, routes, app.js, Dockerfile unchanged; 76+21 existing license tests stay green.
This commit is contained in:
3 files changed
+285
No files matched your search
@@ -33,6 +33,31 @@ DNS_PRIMARY="100.121.150.22" # Technitium (Tailscale IP) — resolves *.sami
|
||||
# is correct.
|
||||
DNS_FALLBACK="100.71.97.12" # DNS1 Technitium secondary — serves *.sami + recurses
|
||||
|
||||
# --- DC-164 rev2: operator env overrides --------------------------------------
|
||||
# /etc/dashcaddy/operator.env is forwarded into the container as -e flags via
|
||||
# scripts/operator-flags.sh. The helper ENFORCES a security contract (regular
|
||||
# file, uid 0 owner, mode 0600-style, no symlink): violation => exit 3, NO
|
||||
# flags forwarded (fail closed; stderr explains). The canonical use:
|
||||
# ALLOW_LIFETIME_LICENSE=true on the CREATOR's host only — the lifetime
|
||||
# code tier is rejected everywhere else (license-manager keeps that gate).
|
||||
# Missing file / no overrides = normal customer case (zero flags added).
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
OPERATOR_ENV_FILE="${OPERATOR_ENV_FILE:-/etc/dashcaddy/operator.env}"
|
||||
OPERATOR_FLAGS=()
|
||||
OP_HELPER="$SCRIPT_DIR/dashcaddy-api/scripts/operator-flags.sh"
|
||||
if [ -f "$OP_HELPER" ] && [ -e "$OPERATOR_ENV_FILE" ]; then
|
||||
# single execution; stdout captured, rc inspected explicitly
|
||||
OP_RAW="$(bash "$OP_HELPER" "$OPERATOR_ENV_FILE")" || OP_RC=$?; OP_RC=${OP_RC:-0}
|
||||
if [ "$OP_RC" -eq 3 ]; then
|
||||
echo "[start.sh] WARNING: operator env file rejected by contract check — forwarding NO overrides" >&2
|
||||
elif [ -n "$OP_RAW" ]; then
|
||||
while IFS= read -r kv; do
|
||||
[ -n "$kv" ] && OPERATOR_FLAGS+=(-e "$kv")
|
||||
done <<< "$OP_RAW"
|
||||
echo "[start.sh] operator.env: $(( ${#OPERATOR_FLAGS[@]} / 2 )) items forwarded (keys only: $(printf '%s\n' "${OPERATOR_FLAGS[@]}" | grep -v '^-e$' | cut -d= -f1 | paste -sd, -))"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- One-time migration from Docker image layer to bind mount --------------
|
||||
# DC-039 follow-up. Before v1.14.10, certain modules (audit-logger, license-
|
||||
# keygen, credential-manager) defaulted their files to /app/src/* via
|
||||
@@ -220,4 +245,5 @@ docker run -d --restart unless-stopped --name ${CONTAINER_NAME} \
|
||||
-e SHIPDECK_BRIDGE_URL=http://172.17.0.1:8977 \
|
||||
-e SHIPDECK_BRIDGE_TOKEN_FILE=/app/data/shipdeck-bridge-token \
|
||||
-e CA_CERT_PATH=/etc/ssl/sami-ca/root.crt \
|
||||
${OPERATOR_FLAGS[@]+"${OPERATOR_FLAGS[@]}"} \
|
||||
${IMAGE}
|
||||
Reference in new issue
Block a user