The previous packager (scripts/packageDectRelayAgent.js) shipped a
source-only bundle and expected the DC host to build the image with
`docker compose up --build`. That fails hard in corporate DCs with
TLS-intercepted egress: Alpine's apk fetch of dl-cdn.alpinelinux.org
can't verify the intercepted certificate ("apk: TLS: server
certificate not trusted"), and npm install would fail the same way
if apk had succeeded.
New approach: build the image ONCE on the dev machine (where TLS
works), save it as a gzipped tarball, and ship a ZIP whose install
step is `docker load` + `docker compose up -d`. Zero network calls
inside the DC container, ever.
Bundling (dev-machine):
- dect-relay-agent/bundle.sh: build → docker save → gzip → zip.
Auto-derives version from package.json, records git sha + dirty
flag + build date into image labels. Cross-arch friendly
(--platform=linux/amd64 by default; --platform linux/arm64 for
ARM DCs). Output: dect-relay-agent-bundle-<YYYYMMDD-HHMMSS>.zip
at repo root (typically 40-60MB).
- dect-relay-agent/Dockerfile: multi-stage node:20-alpine build.
No apk add. No runtime npm install. Non-root `node` user (uid
1000). Node handles SIGTERM natively via index.js handlers, so
no tini/dumb-init needed. Designed to build from the REPO ROOT
(not the agent folder) because the agent imports shared modules
from ../integrations/cisco-dect and ../utils.
- dect-relay-agent/Dockerfile.dockerignore: per-Dockerfile ignore
(BuildKit ≥ 23.0) with a whitelist that keeps the build context
to ~50KB. Older Docker daemons fall through to the repo-root
.dockerignore, which already excludes secrets — nothing sensitive
can leak either way.
- package.json: `npm run package:relay` now invokes bundle.sh.
Runtime (DC-host):
- dect-relay-agent/docker-compose.yml: pins IMAGE_TAG from .env
(install.sh writes it there — never falls back to :latest), reads
the rest of the config via env_file, restart: unless-stopped,
host networking (needed to reach 10.x/8 without userland proxy
translation, and the agent doesn't listen on anything). Hardened:
read_only: true rootfs with a 16MB /tmp tmpfs, cap_drop: ALL,
no-new-privileges, log rotation at 10MB × 5 files.
- dect-relay-agent/install.sh: preflight (docker + compose present,
daemon reachable, bundle files intact), docker load, pin loaded
tag into .env, validate .env has the three required values not
still set to placeholder strings, docker compose up -d, tail last
40 log lines. Idempotent — safe to re-run on upgrades.
Cleanup:
- scripts/packageDectRelayAgent.js: deleted (superseded).
- .gitignore: drops the scripts/* + !packageDectRelayAgent.js dance
since we no longer need to whitelist that one file; add pattern
for the datestamped bundle zips + staging dirs at repo root.
- dect-relay-agent/README.md: replaces the deploy section with the
new dev-machine-build → DC-host-load workflow, plus a
troubleshooting section keyed on the exact error messages seen
during the failed in-DC build (TLS cert not trusted, docker perm
denied, DIGEST_401).
Verified: all 113 existing tests still pass. Docker build itself
requires a Docker daemon (dev machine) so can't be exercised in
this sandbox — the bash scripts pass `bash -n` syntax checks.
130 lines
6 KiB
Bash
Executable file
130 lines
6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# ────────────────────────────────────────────────────────────────────
|
|
# DECT relay agent — data-center install / upgrade helper.
|
|
#
|
|
# Run this inside the unzipped bundle directory on the DC host. It:
|
|
# 1. Sanity-checks that Docker + Compose are installed.
|
|
# 2. Loads the shipped image tarball into the local Docker daemon.
|
|
# 3. Pins IMAGE_TAG in .env to whatever tag was baked into the
|
|
# tarball (so compose can never fall back to a stale local
|
|
# cache without you noticing).
|
|
# 4. Verifies .env exists and has the required keys populated.
|
|
# 5. Runs `docker compose up -d` and tails the last 40 lines.
|
|
#
|
|
# Safe to run repeatedly — it's a straightforward upgrade too:
|
|
# unzip -o new-bundle.zip -d dect-relay-agent-bundle
|
|
# cd dect-relay-agent-bundle
|
|
# ./install.sh
|
|
# ────────────────────────────────────────────────────────────────────
|
|
set -euo pipefail
|
|
|
|
# Colors, only if stdout is a TTY. Corporate SSH sessions often are;
|
|
# CI / pipe-to-file are not.
|
|
if [[ -t 1 ]]; then
|
|
BOLD=$'\033[1m'; DIM=$'\033[2m'; RED=$'\033[31m'; GREEN=$'\033[32m'
|
|
YELLOW=$'\033[33m'; RESET=$'\033[0m'
|
|
else
|
|
BOLD=''; DIM=''; RED=''; GREEN=''; YELLOW=''; RESET=''
|
|
fi
|
|
|
|
log() { echo "${BOLD}[install]${RESET} $*"; }
|
|
die() { echo "${RED}[install] ERROR:${RESET} $*" >&2; exit 1; }
|
|
|
|
# Ensure we're running from the bundle dir (compose file must be here).
|
|
cd "$(dirname "$0")"
|
|
|
|
# ─── 1. Preflight ──────────────────────────────────────────────────
|
|
log "Preflight checks"
|
|
command -v docker >/dev/null 2>&1 || die "docker is not installed or not on PATH"
|
|
|
|
# Compose v2 is `docker compose` (space); v1 is `docker-compose` (dash).
|
|
# Prefer v2. bail if neither is available.
|
|
if docker compose version >/dev/null 2>&1; then
|
|
COMPOSE="docker compose"
|
|
elif command -v docker-compose >/dev/null 2>&1; then
|
|
COMPOSE="docker-compose"
|
|
echo "${YELLOW}[install] Using legacy docker-compose v1. Consider upgrading to Compose v2.${RESET}"
|
|
else
|
|
die "docker compose (v2) not found and docker-compose (v1) not on PATH"
|
|
fi
|
|
|
|
if ! docker info >/dev/null 2>&1; then
|
|
die "docker daemon is not reachable. Are you in the 'docker' group, or should you re-run with sudo?"
|
|
fi
|
|
|
|
[[ -f image.tar.gz ]] || die "image.tar.gz not found in $(pwd) — is the bundle complete?"
|
|
[[ -f docker-compose.yml ]] || die "docker-compose.yml not found — is the bundle complete?"
|
|
|
|
# ─── 2. Load image ─────────────────────────────────────────────────
|
|
log "Loading Docker image from image.tar.gz (this is the only step that touches the docker daemon's image store)"
|
|
# `docker load` prints "Loaded image: <tag>" for each tag in the archive.
|
|
# We tee to stderr so the operator sees it, and grep the tag out for
|
|
# use in the .env pin step below.
|
|
LOAD_OUTPUT="$(gunzip -c image.tar.gz | docker load)"
|
|
echo "$LOAD_OUTPUT"
|
|
LOADED_TAG="$(echo "$LOAD_OUTPUT" | awk -F': ' '/Loaded image/ {print $2; exit}')"
|
|
[[ -n "$LOADED_TAG" ]] || die "docker load did not report a loaded image tag"
|
|
log "Loaded image: ${GREEN}${LOADED_TAG}${RESET}"
|
|
|
|
# ─── 3. .env setup ─────────────────────────────────────────────────
|
|
if [[ ! -f .env ]]; then
|
|
cp .env.example .env
|
|
echo "${YELLOW}[install] Created .env from .env.example. Edit it now with real values, then re-run this script.${RESET}"
|
|
echo " Required: DECT_RELAY_BOT_URL, DECT_RELAY_AGENT_TOKEN, DECT_ADMIN_PASSWORD"
|
|
exit 2
|
|
fi
|
|
|
|
# Pin IMAGE_TAG in .env to the tag we just loaded. Idempotent —
|
|
# rewrites the line each run so upgrades to a new tarball tag Just
|
|
# Work without operator intervention.
|
|
if grep -q '^IMAGE_TAG=' .env; then
|
|
# Portable in-place sed (works on both GNU sed and BSD sed on macOS).
|
|
# The `.bak` tempfile is removed at end.
|
|
sed -i.bak "s|^IMAGE_TAG=.*|IMAGE_TAG=${LOADED_TAG}|" .env
|
|
rm -f .env.bak
|
|
else
|
|
printf '\n# Pinned automatically by install.sh on %s\nIMAGE_TAG=%s\n' \
|
|
"$(date -u +%FT%TZ)" "$LOADED_TAG" >> .env
|
|
fi
|
|
log "Pinned IMAGE_TAG=${LOADED_TAG} in .env"
|
|
|
|
# Validate the operator has actually filled in the required values —
|
|
# .env.example ships with placeholders that would blow up at runtime
|
|
# with a less friendly error.
|
|
MISSING=()
|
|
required_var() {
|
|
local key="$1" val
|
|
val="$(grep -E "^${key}=" .env | tail -1 | cut -d= -f2-)"
|
|
# Strip surrounding quotes and whitespace so both bare and quoted
|
|
# values validate the same.
|
|
val="${val#\"}"; val="${val%\"}"
|
|
val="${val#\'}"; val="${val%\'}"
|
|
val="${val## }"; val="${val%% }"
|
|
if [[ -z "$val" ]] || [[ "$val" == "replace-with-shared-secret" ]] \
|
|
|| [[ "$val" == "replace-with-dect-serviceability-password" ]] \
|
|
|| [[ "$val" == "wss://your-bot-host.example.com/dect-relay/ws" ]]; then
|
|
MISSING+=("$key")
|
|
fi
|
|
}
|
|
required_var DECT_RELAY_BOT_URL
|
|
required_var DECT_RELAY_AGENT_TOKEN
|
|
required_var DECT_ADMIN_PASSWORD
|
|
|
|
if [[ ${#MISSING[@]} -gt 0 ]]; then
|
|
echo "${RED}[install] .env is missing required values or still has placeholder text:${RESET}"
|
|
for k in "${MISSING[@]}"; do echo " - $k"; done
|
|
echo " Edit .env and re-run this script."
|
|
exit 2
|
|
fi
|
|
|
|
# ─── 4. Compose up ─────────────────────────────────────────────────
|
|
log "Starting container via ${COMPOSE} up -d"
|
|
$COMPOSE up -d
|
|
|
|
log "Container started. Recent logs:"
|
|
sleep 2
|
|
$COMPOSE logs --tail=40 dect-relay-agent || true
|
|
|
|
echo
|
|
log "${GREEN}Done.${RESET} Follow live logs with: ${DIM}${COMPOSE} logs -f dect-relay-agent${RESET}"
|
|
log "Stop the agent with: ${DIM}${COMPOSE} down${RESET}"
|