#!/bin/sh # ───────────────────────────────────────────────────────────────────────────── # FrontierScore Worker — one-command installer & self-updater # # curl -fsSL https://get.frontierscore.ai | sh # # Re-run any time to update: it refreshes itself, pulls the latest worker image, # and restarts — your enrolled identity is kept. It also schedules a daily # auto-update so you never have to think about it. # # Non-interactive (CI / automation): pass values as env vars, e.g. # curl -fsSL https://get.frontierscore.ai | \ # GHCR_USER=acct GHCR_TOKEN=ghp_xxx ENROLL_TOKEN=fsenr_xxx \ # WORKER_REGION=us-east ANTHROPIC_API_KEY=sk-ant-xxx sh # ───────────────────────────────────────────────────────────────────────────── set -eu SELF_VERSION="2026-06-26.3" SELF_URL="https://get.frontierscore.ai/install.sh" IMAGE="ghcr.io/digital-one-consulting/frontierscore-worker:stable" CONF_DIR="${FSX_HOME:-${HOME:-/root}/.frontierscore}" ENV_FILE="$CONF_DIR/worker.env" CONTAINER="frontierscore-worker" VOLUME="frontierscore-worker-state" CONTROL_URL_DEFAULT="https://control.frontierscore.ai" INGEST_URL_DEFAULT="https://ingest.frontierscore.ai" # Registry access is BAKED IN (read-only, shared across customers, revocable) — the # token below is substituted into the SERVED script by nginx; the repo keeps only a # placeholder. So partners don't handle registry creds at all. Env vars still override. : "${GHCR_USER:=frontierscore-main-server}" : "${GHCR_TOKEN:=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}" say(){ printf '\033[1;36m==>\033[0m %s\n' "$*"; } ok(){ printf ' \033[1;32mok\033[0m %s\n' "$*"; } die(){ printf '\033[1;31mERROR:\033[0m %s\n' "$*" >&2; exit 1; } UPDATE_MODE="${FSX_UPDATE:-}" # ── Self-update: re-exec the freshest copy of this script ──────────────────── if [ -z "${FSX_NO_SELFUPDATE:-}" ] && command -v curl >/dev/null 2>&1; then latest="$(curl -fsSL "$SELF_URL" 2>/dev/null || true)" lver="$(printf '%s\n' "$latest" | sed -n 's/^SELF_VERSION="\(.*\)"$/\1/p' | head -n1)" if [ -n "$lver" ] && [ "$lver" != "$SELF_VERSION" ]; then say "updating installer $SELF_VERSION -> $lver" tmp="$(mktemp)"; printf '%s' "$latest" >"$tmp" FSX_NO_SELFUPDATE=1 exec sh "$tmp" fi fi # ── 1. Docker ──────────────────────────────────────────────────────────────── if ! command -v docker >/dev/null 2>&1; then if [ -r /etc/debian_version ] && [ "$(id -u)" = 0 ]; then say "Installing Docker"; curl -fsSL https://get.docker.com | sh else die "Docker is required — install it (https://docs.docker.com/engine/install/) and re-run." fi fi docker info >/dev/null 2>&1 || die "Docker is not running / not accessible (try sudo, or add your user to the 'docker' group)." # ── 2. Config — prompt (terminal) or env, persisted for re-runs ────────────── mkdir -p "$CONF_DIR"; chmod 700 "$CONF_DIR" # shellcheck disable=SC1090 [ -f "$ENV_FILE" ] && . "$ENV_FILE" 2>/dev/null || true ask(){ # ask VAR "prompt" [silent] — required value _v="$1"; _q="$2"; _s="${3:-}"; eval "_cur=\${$_v:-}" [ -n "$_cur" ] && return 0 [ -e /dev/tty ] || die "missing $_v (no terminal — pass it as an env var)" if [ "$_s" = silent ]; then printf '%s: ' "$_q" >/dev/tty; stty -echo /dev/null || true read -r _val /dev/null || true; printf '\n' >/dev/tty else printf '%s: ' "$_q" >/dev/tty; read -r _val /dev/tty while :; do printf 'Provider [anthropic / openai / google / xai / deepseek]: ' >/dev/tty; read -r _p /dev/tty; continue; fi;; *) printf ' Unknown provider — pick one of the five.\n' >/dev/tty; continue;; esac printf ' %s key: ' "$_p" >/dev/tty; stty -echo /dev/null || true read -r _val /dev/null || true; printf '\n' >/dev/tty [ -n "$_val" ] && eval "$_k=\$_val" has_key || continue printf 'Add another provider key? [y/N]: ' >/dev/tty; read -r _more "$ENV_FILE" chmod 600 "$ENV_FILE" # ── 4. Registry login with the baked (or overridden) read-only creds ────────── case "${GHCR_TOKEN:-}" in *@@*|"") : ;; # placeholder not substituted / empty → rely on any existing docker login *) say "Authenticating to ghcr.io" printf '%s' "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USER" --password-stdin >/dev/null \ && ok "registry login ok" || die "ghcr.io login failed — please contact FrontierScore (registry access may have changed)." ;; esac # ── 5. Pull + 6. (re)start the hardened, outbound-only container ────────────── say "Pulling the latest worker image" docker pull "$IMAGE" >/dev/null || die "could not pull the image — registry login or network issue." ok "image up to date" say "Starting the worker" docker rm -f "$CONTAINER" >/dev/null 2>&1 || true docker run -d --name "$CONTAINER" --restart unless-stopped \ --env-file "$ENV_FILE" -v "$VOLUME":/state \ --cap-drop ALL --security-opt no-new-privileges:true \ --memory 512m --cpus 0.5 \ --log-driver json-file --log-opt max-size=10m --log-opt max-file=5 \ "$IMAGE" >/dev/null ok "worker running" docker image prune -f >/dev/null 2>&1 || true # ── 7. Schedule a daily auto-update (idempotent) ───────────────────────────── if [ -z "$UPDATE_MODE" ] && command -v crontab >/dev/null 2>&1; then line="0 4 * * * curl -fsSL $SELF_URL | FSX_UPDATE=1 FSX_HOME=$CONF_DIR sh >/dev/null 2>&1" if ( crontab -l 2>/dev/null | grep -v 'get.frontierscore.ai'; echo "$line" ) | crontab - 2>/dev/null; then ok "auto-update scheduled (daily 04:00)" fi fi # ── Done ───────────────────────────────────────────────────────────────────── if [ -z "$UPDATE_MODE" ]; then say "Done — your worker is running and will keep itself up to date." sleep 3 docker logs --tail 4 "$CONTAINER" 2>&1 | sed 's/^/ /' || true cat <