mirror of
https://scm.tikali.ai/tikali/applications/monky/monky-deployd.git
synced 2026-09-18 05:36:15 +00:00
6336012b74
The GitLab project is private (its parent groups are private, so it cannot be made public): the v0.1.1 one-liner answered 401 anonymously. install.sh gains --token / MONKY_DEPLOYD_TOKEN and sends `DEPLOY-TOKEN: <token>` (a GitLab deploy token, scope read_package_registry only, revocable) on every registry download, the script itself included; the token goes through a 0600 curl -K file (never the command line, the log or an xtrace). The grant is taken via --bootstrap-file when the script is piped (stdin IS the script). Ansible: monky_deployd_download_token (vaulted) -> DEPLOY-TOKEN header, no_log. Docs explain why, the token's scope and the --source gitea alternative (split-horizon Gitea, cbs/iac#102). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
263 lines
13 KiB
Bash
Executable File
263 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# monky-deployd installer — Ubuntu 26.04 (verified target).
|
|
#
|
|
# T=<read-only GitLab deploy token, scope read_package_registry> # from the install kit / OpenBao
|
|
# curl -sSf -H "DEPLOY-TOKEN: $T" \
|
|
# https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd/0.1.2/install.sh \
|
|
# | sudo bash -s -- --env env-qa-02 --site cbs --token "$T" --bootstrap-file bootstrap.jwt \
|
|
# [--transport sdk|proxy|system] [--version 0.1.2] [--enrol-jwt /path/monky-host.env-qa-02.jwt] \
|
|
# [--laptop] [--source gitlab|gitea]
|
|
#
|
|
# --token / MONKY_DEPLOYD_TOKEN: the GitLab project is PRIVATE (its parent groups are private, so it
|
|
# cannot be made public); every download from the generic package registry — this script included —
|
|
# sends `DEPLOY-TOKEN: <token>`. The token is read-only (read_package_registry), revocable, and is
|
|
# never printed by this script. Not needed with --source gitea.
|
|
# --bootstrap-file (or stdin when the script runs from a FILE): the ONE-TIME bootstrap deploy grant
|
|
# from the install kit (GET /v1/backends/{id}/agent/install). The enrolment JWT is read from
|
|
# --enrol-jwt or /etc/monky-deployd/enrol.jwt and is only needed when the host identity is not
|
|
# enrolled yet.
|
|
#
|
|
# What it does (idempotent):
|
|
# 1. apt: ziti-edge-tunnel (OpenZiti `jammy` suite) if absent, docker-compose-plugin, acl
|
|
# 2. downloads the pinned monky-deployd_<ver>_amd64.deb + .sha256 from the scm.tikali.ai package registry
|
|
# (DEPLOY-TOKEN header; --source gitea: the Gitea release, off-estate), verifies, installs
|
|
# 3. enrols /opt/openziti/etc/identities/monky-host.<env>.json if absent (ziti-edge-tunnel enroll),
|
|
# chown ziti:ziti 0600, switches ziti-edge-tunnel.service to `run-host` via a drop-in
|
|
# 4. writes /etc/monky-deployd/config.yaml, ACLs so user monky-deployd can read the identity,
|
|
# the bootstrap grant 0600, (proxy transport: proxy.env + monky-deployd-proxy.service)
|
|
# 5. enables monky-deployd.timer, runs one tick, deletes the enrol JWT, prints the checklist
|
|
set -euo pipefail
|
|
umask 077
|
|
|
|
DEFAULT_VERSION="0.1.2"
|
|
# Download source. PRIMARY is the GitLab project's generic package registry on scm.tikali.ai: inside
|
|
# the estate gitea.cbs.tikali.net is split-horizon to jump1's RED EIP (10.10.0.175), which has no HTTP
|
|
# ingress, so backend boxes cannot reach the Gitea mirror (cbs/iac#102); scm.tikali.ai they can. The
|
|
# project is PRIVATE (parent groups are private), so the registry needs the read-only deploy token
|
|
# (--token / MONKY_DEPLOYD_TOKEN, sent as the DEPLOY-TOKEN header). `--source gitea` keeps the Gitea
|
|
# release as the off-estate alternative (no token). --base-url / MONKY_DEPLOYD_BASE_URL override the
|
|
# base for whichever layout is selected.
|
|
GITLAB_BASE_URL="https://scm.tikali.ai/api/v4/projects/69/packages/generic/monky-deployd"
|
|
GITEA_BASE_URL="https://gitea.cbs.tikali.net/mdella/monky-deployd"
|
|
SOURCE="${MONKY_DEPLOYD_SOURCE:-gitlab}"
|
|
BASE_URL="${MONKY_DEPLOYD_BASE_URL:-}"
|
|
TOKEN="${MONKY_DEPLOYD_TOKEN:-}"
|
|
OPENZITI_SUITE="${OPENZITI_SUITE:-jammy}"
|
|
IDENTITY_DIR="/opt/openziti/etc/identities"
|
|
ETC="/etc/monky-deployd"
|
|
ENV_ID="" SITE="" TRANSPORT="sdk" VERSION="$DEFAULT_VERSION" ENROL_JWT="" BOOTSTRAP_FILE="" NO_RUN="" FORCE_CONFIG="" BAO_CA="" LAPTOP="false"
|
|
|
|
usage() { if [ -f "$0" ]; then sed -n '2,28p' "$0"; else echo "monky-deployd install.sh — see README.md (Install)"; fi; exit "${1:-0}"; }
|
|
die() { echo "install.sh: $*" >&2; exit 1; }
|
|
log() { echo "==> $*"; }
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--env) ENV_ID="$2"; shift 2 ;;
|
|
--site) SITE="$2"; shift 2 ;;
|
|
--transport) TRANSPORT="$2"; shift 2 ;;
|
|
--version) VERSION="$2"; shift 2 ;;
|
|
--enrol-jwt) ENROL_JWT="$2"; shift 2 ;;
|
|
--bootstrap-file) BOOTSTRAP_FILE="$2"; shift 2 ;;
|
|
--bao-ca) BAO_CA="$2"; shift 2 ;;
|
|
--base-url) BASE_URL="$2"; shift 2 ;;
|
|
--source) SOURCE="$2"; shift 2 ;;
|
|
--token) TOKEN="$2"; shift 2 ;;
|
|
--laptop) LAPTOP="true"; shift ;;
|
|
--no-run) NO_RUN=1; shift ;;
|
|
--force-config) FORCE_CONFIG=1; shift ;;
|
|
-h|--help) usage 0 ;;
|
|
*) die "unknown argument $1 (see --help)" ;;
|
|
esac
|
|
done
|
|
|
|
[ "$(id -u)" = 0 ] || die "run as root (sudo)"
|
|
[ -n "$ENV_ID" ] || die "--env is required"
|
|
[ -n "$SITE" ] || die "--site is required"
|
|
[[ "$ENV_ID" =~ ^env-(dev|qa|stage|prod)-[0-9]{2,3}$|^(dev-env-2|prod-cedar)$ ]] || die "env id $ENV_ID is not env-<tier>-<nn>"
|
|
SITE="${SITE,,}"
|
|
[[ "$SITE" =~ ^(cbs|pdx)$ ]] || die "site must be cbs|pdx"
|
|
[[ "$TRANSPORT" =~ ^(sdk|proxy|system)$ ]] || die "transport must be sdk|proxy|system"
|
|
[[ "$SOURCE" =~ ^(gitlab|gitea)$ ]] || die "source must be gitlab|gitea"
|
|
# never let the token leak through xtrace / the environment of children
|
|
{ set +x; } 2>/dev/null
|
|
export -n MONKY_DEPLOYD_TOKEN 2>/dev/null || true
|
|
IDENTITY="$IDENTITY_DIR/monky-host.$ENV_ID.json"
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
|
|
if [ -r /etc/os-release ]; then
|
|
. /etc/os-release
|
|
if [ "${ID:-}" != "ubuntu" ] || [ "${VERSION_ID:-}" != "26.04" ]; then
|
|
echo "WARNING: verified on Ubuntu 26.04; this is ${PRETTY_NAME:-unknown}. Continuing." >&2
|
|
fi
|
|
fi
|
|
|
|
# --- 1. packages -------------------------------------------------------------------------------------
|
|
apt_updated=""
|
|
apt_update_once() { [ -n "$apt_updated" ] || { apt-get update -qq; apt_updated=1; }; }
|
|
need_pkgs=(acl curl ca-certificates gnupg)
|
|
if ! command -v ziti-edge-tunnel >/dev/null 2>&1; then
|
|
log "adding the OpenZiti apt repository ($OPENZITI_SUITE suite)"
|
|
install -d -m 0755 /usr/share/keyrings
|
|
curl -fsSL https://get.openziti.io/tun/package-repos.gpg | gpg --dearmor -o /usr/share/keyrings/openziti.gpg
|
|
chmod 0644 /usr/share/keyrings/openziti.gpg
|
|
echo "deb [signed-by=/usr/share/keyrings/openziti.gpg] https://packages.openziti.org/zitipax-openziti-deb-stable $OPENZITI_SUITE main" \
|
|
> /etc/apt/sources.list.d/openziti.list
|
|
need_pkgs+=(ziti-edge-tunnel)
|
|
fi
|
|
if [ "$TRANSPORT" = "proxy" ] && ! command -v ziti >/dev/null 2>&1; then
|
|
need_pkgs+=(openziti) # the `ziti` CLI (ziti tunnel proxy) from the same repo
|
|
fi
|
|
command -v docker >/dev/null 2>&1 || die "docker is not installed; install Docker Engine first (https://docs.docker.com/engine/install/ubuntu/)"
|
|
docker compose version >/dev/null 2>&1 || need_pkgs+=(docker-compose-plugin)
|
|
python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 12) else 1)' 2>/dev/null || die "python3 >= 3.12 is required"
|
|
if [ "${#need_pkgs[@]}" -gt 0 ]; then
|
|
log "apt install: ${need_pkgs[*]}"
|
|
apt_update_once
|
|
apt-get install -y -qq --no-install-recommends "${need_pkgs[@]}"
|
|
fi
|
|
|
|
# --- 2. the pinned .deb -----------------------------------------------------------------------------
|
|
installed="$(dpkg-query -W -f='${Version}' monky-deployd 2>/dev/null || true)"
|
|
if [ "$installed" = "$VERSION" ]; then
|
|
log "monky-deployd $VERSION already installed"
|
|
else
|
|
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
|
|
deb="monky-deployd_${VERSION}_amd64.deb"
|
|
# the deploy token travels in a 0600 curl config file (-K), never on the command line, so neither
|
|
# `ps` nor an xtrace shows it; the file dies with $tmp
|
|
curlrc="$tmp/curlrc"; : > "$curlrc"; chmod 0600 "$curlrc"
|
|
if [ "$SOURCE" = gitlab ]; then
|
|
url="${BASE_URL:-$GITLAB_BASE_URL}/${VERSION}"
|
|
if [ -n "$TOKEN" ]; then
|
|
printf 'header = "DEPLOY-TOKEN: %s"\n' "$TOKEN" > "$curlrc"
|
|
else
|
|
echo "WARNING: no --token/MONKY_DEPLOYD_TOKEN: the scm.tikali.ai project is private, the download will 401 (use --source gitea off-estate)" >&2
|
|
fi
|
|
else
|
|
url="${BASE_URL:-$GITEA_BASE_URL}/releases/download/v${VERSION}"
|
|
fi
|
|
log "downloading $deb from $url${TOKEN:+ (DEPLOY-TOKEN)}"
|
|
curl -fsSL -K "$curlrc" -o "$tmp/$deb" "$url/$deb" || die "download of $deb failed (private registry: is the deploy token set and valid?)"
|
|
curl -fsSL -K "$curlrc" -o "$tmp/$deb.sha256" "$url/$deb.sha256" || die "download of $deb.sha256 failed"
|
|
rm -f "$curlrc"
|
|
(cd "$tmp" && sha256sum -c "$deb.sha256") || die "sha256 mismatch on $deb"
|
|
apt_update_once || true
|
|
apt-get install -y -qq "$tmp/$deb"
|
|
fi
|
|
command -v monky-deployd >/dev/null || die "monky-deployd not on PATH after install"
|
|
|
|
# --- 3. host identity -------------------------------------------------------------------------------
|
|
install -d -m 0750 "$IDENTITY_DIR"
|
|
[ -n "$ENROL_JWT" ] || { [ -f "$ETC/enrol.jwt" ] && ENROL_JWT="$ETC/enrol.jwt"; } || true
|
|
if [ -s "$IDENTITY" ]; then
|
|
log "host identity present: $IDENTITY"
|
|
else
|
|
[ -n "$ENROL_JWT" ] && [ -s "$ENROL_JWT" ] || die "no identity at $IDENTITY and no enrol JWT (--enrol-jwt or $ETC/enrol.jwt)"
|
|
log "enrolling monky-host.$ENV_ID"
|
|
ziti-edge-tunnel enroll -j "$ENROL_JWT" -i "$IDENTITY"
|
|
fi
|
|
getent passwd ziti >/dev/null && chown ziti:ziti "$IDENTITY" || true
|
|
chmod 0600 "$IDENTITY"
|
|
# ziti-edge-tunnel as a HOST (bind side, no tun/DNS) — the agent dials with the SDK or the proxy
|
|
install -d /etc/systemd/system/ziti-edge-tunnel.service.d
|
|
cat > /etc/systemd/system/ziti-edge-tunnel.service.d/run-host.conf <<'DROPIN'
|
|
# monky-deployd: run-host mode (no tun, no DNS); identities from the standard directory
|
|
[Service]
|
|
ExecStart=
|
|
ExecStart=/opt/openziti/bin/ziti-edge-tunnel run-host --identity-dir=/opt/openziti/etc/identities
|
|
DROPIN
|
|
systemctl daemon-reload
|
|
systemctl enable --now ziti-edge-tunnel.service
|
|
systemctl restart ziti-edge-tunnel.service || true
|
|
|
|
# --- 4. config, ACLs, bootstrap grant -----------------------------------------------------------------
|
|
install -d -m 0750 -o root -g monky-deployd "$ETC"
|
|
install -d -m 0700 -o monky-deployd -g monky-deployd /var/lib/monky-deployd
|
|
setfacl -m u:monky-deployd:r "$IDENTITY"
|
|
setfacl -m u:monky-deployd:rx "$IDENTITY_DIR"
|
|
setfacl -m u:monky-deployd:x /opt/openziti/etc 2>/dev/null || true
|
|
if [ -n "$BAO_CA" ]; then
|
|
install -m 0644 "$BAO_CA" "$ETC/openbao-ca.pem"
|
|
fi
|
|
ca_line="ca_bundle: $ETC/openbao-ca.pem"
|
|
[ -s "$ETC/openbao-ca.pem" ] || { ca_line="ca_bundle: none # TODO: install the openbao-ca certificate (see docs/OPERATIONS.md)"; echo "WARNING: $ETC/openbao-ca.pem missing; TLS to OpenBao will use the system store" >&2; }
|
|
if [ -s "$ETC/config.yaml" ] && [ -z "$FORCE_CONFIG" ]; then
|
|
log "keeping existing $ETC/config.yaml (use --force-config to rewrite)"
|
|
else
|
|
cat > "$ETC/config.yaml" <<CFG
|
|
# written by install.sh $(date -u +%FT%TZ) — monky-deployd $VERSION
|
|
env_id: $ENV_ID
|
|
site: $SITE
|
|
transport: $TRANSPORT
|
|
identity: $IDENTITY
|
|
tenancy:
|
|
service: monky.tenancy.deploy
|
|
host: monky.tenancy.deploy
|
|
port: 8081
|
|
scheme: http
|
|
proxy_addr: 127.0.0.1:18443
|
|
bao:
|
|
service: openbao
|
|
addr: https://bao.cbs.tikali.net:8200
|
|
proxy_addr: 127.0.0.1:18200
|
|
$ca_line
|
|
mount: jwt-tenancy
|
|
role: see-env
|
|
kv_mount: monky
|
|
state_dir: /var/lib/monky-deployd
|
|
deploy_dir: /var/lib/monky-deployd/$ENV_ID
|
|
bootstrap_path: $ETC/bootstrap.jwt
|
|
interval_s: 60
|
|
volumes_on_absent: keep
|
|
laptop_mode: $LAPTOP
|
|
CFG
|
|
chmod 0640 "$ETC/config.yaml"; chgrp monky-deployd "$ETC/config.yaml"
|
|
fi
|
|
if [ "$TRANSPORT" = "proxy" ]; then
|
|
printf 'ZITI_IDENTITY=%s\n' "$IDENTITY" > "$ETC/proxy.env"; chmod 0644 "$ETC/proxy.env"
|
|
systemctl enable --now monky-deployd-proxy.service
|
|
fi
|
|
# bootstrap grant: --bootstrap-file, or stdin — but ONLY when this script runs from a file: with
|
|
# `curl … | bash -s --` stdin IS the script, so reading it here would swallow the rest of it
|
|
grant=""
|
|
if [ -n "$BOOTSTRAP_FILE" ]; then grant="$(tr -d '[:space:]' < "$BOOTSTRAP_FILE")"
|
|
elif [ ! -t 0 ] && [ -f "$0" ]; then grant="$(tr -d '[:space:]' </dev/stdin || true)"; fi
|
|
if [ -n "$grant" ]; then
|
|
printf '%s\n' "$grant" > "$ETC/bootstrap.jwt"
|
|
chown monky-deployd:monky-deployd "$ETC/bootstrap.jwt"; chmod 0600 "$ETC/bootstrap.jwt"
|
|
log "bootstrap grant staged at $ETC/bootstrap.jwt (consumed on first tick)"
|
|
elif [ -s /var/lib/monky-deployd/bao.token ]; then
|
|
log "no bootstrap grant given; existing bao.token kept"
|
|
else
|
|
echo "WARNING: no bootstrap grant on stdin and no bao.token: the agent cannot check in until you provide one" >&2
|
|
fi
|
|
unset grant TOKEN
|
|
|
|
# --- 5. timer + first tick ----------------------------------------------------------------------------------
|
|
systemctl daemon-reload
|
|
systemctl enable --now monky-deployd.timer
|
|
if [ -z "$NO_RUN" ]; then
|
|
log "first tick"
|
|
systemctl start monky-deployd.service || true
|
|
fi
|
|
[ -n "$ENROL_JWT" ] && rm -f "$ENROL_JWT" || true
|
|
rm -f "$ETC/enrol.jwt"
|
|
|
|
cat <<CHECK
|
|
|
|
monky-deployd $VERSION installed for $ENV_ID ($SITE, transport $TRANSPORT). Verification checklist (Ubuntu 26.04):
|
|
python3 --version # >= 3.12
|
|
docker compose version # compose plugin present
|
|
systemctl is-active ziti-edge-tunnel # run-host mode (drop-in run-host.conf)
|
|
$( [ "$TRANSPORT" = proxy ] && echo "systemctl is-active monky-deployd-proxy # ziti tunnel proxy on 18443/18200" || echo "ziti tunnel proxy --help >/dev/null # fallback transport available" )
|
|
/opt/monky-deployd/venv/bin/python -c 'import openziti' # SDK import (transport sdk)
|
|
monky-deployd status # token present, applied == desired
|
|
journalctl -u monky-deployd -n 50 # 'checkin:' and 'applied' lines
|
|
df -h \$(docker info -f '{{.DockerRootDir}}') # free space >= bundle need x 1.5 + headroom
|
|
docker compose -p monky-$ENV_ID ps # healthy
|
|
# from another mesh member: curl monky.percept.$ENV_ID:47283/health -> 200
|
|
CHECK
|
|
CHECK_STATUS="$(monky-deployd status 2>&1 || true)"
|
|
echo "$CHECK_STATUS" | sed 's/^/ | /'
|