From c926361ccc5549abfd46967453d0473a32e85a08 Mon Sep 17 00:00:00 2001 From: c4ch3c4d3 <23181631+c4ch3c4d3@users.noreply.github.com> Date: Tue, 6 Oct 2026 10:43:46 -0600 Subject: [PATCH] Add enterprise mode + provider policy enforcement to bootstrap script - Install /etc/openchamber/policy.json (enterpriseMode, organization, pinned relayUrl); sudo with OPENCHAMBER_ENTERPRISE_MODE=1 env fallback; --no-enterprise opt-out - Inject OpenCode provider policies into the synced config: deny all providers, allow those derived from the config's own providers block, BASELINE_ALLOWED_PROVIDERS (openai via codex-auth plugin), and repeatable --allow-provider extras (e.g. constellation) - Extend verification: policy file state, pinned relay, effective provider allow-list - Restructure into sourceable functions with a main guard so the policy and merge logic is testable --- openchamber-boostrap.sh | 210 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 201 insertions(+), 9 deletions(-) diff --git a/openchamber-boostrap.sh b/openchamber-boostrap.sh index 76f42d5..0da1f41 100644 --- a/openchamber-boostrap.sh +++ b/openchamber-boostrap.sh @@ -1,3 +1,4 @@ +#!/usr/bin/env bash # openchamber-prereqs.sh — bootstrap a Linux host for opencode + openchamber # # Intent: run this once on a fresh Linux host, then connect from the @@ -5,7 +6,8 @@ # and inherits the pinned environment. No UI password is used. # # Run as the user who will own the install. Idempotent: safe to re-run, -# every step checks before acting. No root required. +# every step checks before acting. No root required (enterprise policy +# file install uses sudo when available; see step 6). # # What it does: # 1. Installs bun (if missing) @@ -14,22 +16,43 @@ # non-interactive SSH sessions (OpenChamber's exec path) inherit them # 4. Installs opencode (if missing) # 5. Installs/updates @openchamber/web to latest via bun (every run) -# 6. Syncs ~/.config/opencode/opencode.json from CONFIG_URL -# 7. Verifies: versions + relay health +# 6. Installs the enterprise policy file (/etc/openchamber/policy.json): +# enterprise mode + organization + pinned relay. Without root it +# falls back to OPENCHAMBER_ENTERPRISE_MODE=1 in the rc files +# (weaker lock — the user can edit those). Skip with --no-enterprise. +# 7. Syncs ~/.config/opencode/opencode.json from CONFIG_URL and injects +# OpenCode provider policies: deny every provider, then allow the +# ones derived from the config's own providers, the baseline list +# (BASELINE_ALLOWED_PROVIDERS, for plugin-registered providers like +# openai), and any --allow-provider extras. +# 8. Verifies: versions + relay health + policy state # # Flags: --relay-url wss://host/ws (override the default relay endpoint) # --invite [port] print an openchamber:// invite link (+QR) # for importing this host into the desktop # app; starts the instance on # (default 3000) if none is running +# --allow-provider allow an extra model provider in the +# OpenCode policy (repeatable), e.g. +# --allow-provider constellation +# --no-enterprise do not install the enterprise policy +# file / env pin set -euo pipefail RELAY_URL="wss://code.zuccaro.me/ws" CONFIG_URL="https://git.zuccaro.me/bzuccaro/opencode-public/raw/branch/main/opencode.json" +ORGANIZATION="Zuccaro" +POLICY_FILE="/etc/openchamber/policy.json" +# Providers allowed in addition to those defined in the synced config. +# "openai" comes from the opencode-openai-codex-auth plugin, which never +# appears in the config's providers block. +BASELINE_ALLOWED_PROVIDERS="openai" INVITE=0 INVITE_PORT=3000 +NO_ENTERPRISE=0 +ALLOW_PROVIDERS_STR="" while [ $# -gt 0 ]; do case "$1" in --relay-url) RELAY_URL="$2"; shift 2 ;; @@ -37,7 +60,12 @@ while [ $# -gt 0 ]; do INVITE=1 if [ $# -ge 2 ] && [ "${2#--}" = "$2" ]; then INVITE_PORT="$2"; shift; fi shift ;; - *) echo "unknown arg: $1 (usage: $0 [--relay-url wss://host/ws] [--invite [port]])" >&2; exit 1 ;; + --allow-provider) + [ $# -ge 2 ] || die "--allow-provider needs a provider id" + ALLOW_PROVIDERS_STR="$ALLOW_PROVIDERS_STR $2" + shift 2 ;; + --no-enterprise) NO_ENTERPRISE=1; shift ;; + *) echo "unknown arg: $1 (usage: $0 [--relay-url wss://host/ws] [--invite [port]] [--allow-provider ] [--no-enterprise])" >&2; exit 1 ;; esac done @@ -46,6 +74,133 @@ ok() { printf '\033[1;32m[ ok ]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; } die() { printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2; exit 1; } +# ---- helpers ---------------------------------------------------------------- + +# policy_json — emit the enterprise policy file content on stdout +policy_json() { + printf '{\n "enterpriseMode": true,\n "organization": "%s",\n "relayUrl": "%s"\n}\n' \ + "$ORGANIZATION" "$RELAY_URL" +} + +# write_policy_file — idempotently install the enterprise policy file +# at , which must already be writable. Reports what it did on stdout. +write_policy_file() { + local f="$1" tmp + tmp="$(mktemp)" + policy_json > "$tmp" + if [ -f "$f" ] && cmp -s "$tmp" "$f"; then + rm -f "$tmp" + ok "$(basename "$f"): already enforced" + return 0 + fi + if [ -f "$f" ]; then + warn "$(basename "$f"): differs from enforced policy — overwriting" + fi + mv "$tmp" "$f" + ok "$(basename "$f"): installed" +} + +# install_enterprise_policy — put the policy at /etc/openchamber/policy.json +# (sudo when available); without root, pin OPENCHAMBER_ENTERPRISE_MODE=1 in +# the rc files instead and say so loudly. +install_enterprise_policy() { + local dir sudo="" + dir="$(dirname "$POLICY_FILE")" + if [ -d "$dir" ] && [ -w "$dir" ]; then + write_policy_file "$POLICY_FILE" + return 0 + fi + if sudo -n true 2>/dev/null; then + sudo="sudo -n" + elif [ -t 0 ]; then + sudo="sudo" + fi + if [ -n "$sudo" ] && $sudo mkdir -p "$dir" 2>/dev/null && $sudo tee "$POLICY_FILE" >/dev/null <<< "$(policy_json)" 2>/dev/null; then + ok "enterprise policy installed at $POLICY_FILE" + return 0 + fi + warn "no root: cannot write $POLICY_FILE — falling back to env-var mode" + warn "env-var mode is a weaker lock (the user can edit their own rc files)" + pin_enterprise_env +} + +# pin_enterprise_env — pin OPENCHAMBER_ENTERPRISE_MODE=1 like the relay var +pin_enterprise_env() { + local line='export OPENCHAMBER_ENTERPRISE_MODE=1' + if grep -q "OPENCHAMBER_ENTERPRISE_MODE" ~/.bashrc 2>/dev/null; then + ok ".bashrc: enterprise mode already pinned" + else + pin_top ~/.bashrc "$line" + fi + pin_append ~/.zshenv "$line" + pin_append ~/.zshrc "$line" + pin_append ~/.profile "$line" +} + +# merge_policies [extra provider ids...] — inject the OpenCode +# provider policy into a config: deny every provider, then allow those +# derived from the config's own providers block, the baseline list, and any +# extras (deduped, in that order). Uses python3, falls back to bun. +merge_policies() { + local in="$1" out="$2" tmp + local extras="${*:3}" + tmp="$(mktemp)" + if command -v python3 >/dev/null 2>&1; then + python3 - "$in" "$tmp" "$BASELINE_ALLOWED_PROVIDERS" "$extras" <<'PY' +import json, sys +src, dst, baseline, extras = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] +cfg = json.load(open(src)) +derived = list((cfg.get("providers") or {}).keys()) +rules = [{"action": "provider.use", "resource": "*", "effect": "deny"}] +seen = [] +for p in derived + [b.strip() for b in baseline.split(",") if b.strip()] + extras.split(): + if p and p not in seen: + seen.append(p) + rules.append({"action": "provider.use", "resource": p, "effect": "allow"}) +cfg.setdefault("experimental", {})["policies"] = rules +with open(dst, "w") as f: + json.dump(cfg, f, indent=2) + f.write("\n") +PY + elif command -v bun >/dev/null 2>&1; then + IN="$in" OUT="$tmp" BASELINE="$BASELINE_ALLOWED_PROVIDERS" EXTRAS="$extras" bun -e ' +const fs = require("fs"); +const cfg = JSON.parse(fs.readFileSync(process.env.IN, "utf8")); +const derived = Object.keys(cfg.providers || {}); +const rules = [{ action: "provider.use", resource: "*", effect: "deny" }]; +const seen = []; +for (const p of [...derived, + ...process.env.BASELINE.split(",").map(s => s.trim()).filter(Boolean), + ...process.env.EXTRAS.split(/\s+/).filter(Boolean)]) { + if (p && !seen.includes(p)) { seen.push(p); rules.push({ action: "provider.use", resource: p, effect: "allow" }); } +} +cfg.experimental = cfg.experimental || {}; +cfg.experimental.policies = rules; +fs.writeFileSync(process.env.OUT, JSON.stringify(cfg, null, 2) + "\n"); +' + else + die "need python3 or bun to inject provider policies" + fi + mv "$tmp" "$out" +} + +# allowed_providers_from — print the allow-listed provider ids +# from a config's experimental.policies as a comma-separated list, or "?" if +# the file can't be read/parsed. +allowed_providers_from() { + python3 -c ' +import json,sys +try: + cfg=json.load(open(sys.argv[1])) + print(",".join(r["resource"] for r in cfg.get("experimental",{}).get("policies",[]) if r.get("effect")=="allow")) +except Exception: + print("?") +' "$1" 2>/dev/null +} + +# ---- main ------------------------------------------------------------------- + +main() { info "host: $(uname -srm)" [ "$(uname -s)" = "Linux" ] || die "this script targets Linux hosts" @@ -121,11 +276,20 @@ info "installing/updating @openchamber/web to latest..." bun install -g @openchamber/web@latest ok "openchamber version: $(openchamber --version 2>/dev/null || echo '?')" -# ---- 6. opencode config sync ------------------------------------------------ +# ---- 6. enterprise policy -------------------------------------------------- +if [ "$NO_ENTERPRISE" = "1" ]; then + info "skipping enterprise policy (--no-enterprise)" +else + install_enterprise_policy +fi + +# ---- 7. opencode config sync + provider policies --------------------------- CFG_DIR="$HOME/.config/opencode" CFG_FILE="$CFG_DIR/opencode.json" TMP_CFG="$(mktemp)" curl -fsSL --max-time 60 "$CONFIG_URL" -o "$TMP_CFG" || die "could not download config from $CONFIG_URL" +info "injecting provider policies (deny all; allow derived + baseline:${ALLOW_PROVIDERS_STR:+ extras:$ALLOW_PROVIDERS_STR})" +merge_policies "$TMP_CFG" "$TMP_CFG" if [ -f "$CFG_FILE" ] && cmp -s "$TMP_CFG" "$CFG_FILE"; then ok "opencode config already up to date" rm -f "$TMP_CFG" @@ -137,7 +301,7 @@ else info "existing config backed up to $(basename "$BAK")" fi mv "$TMP_CFG" "$CFG_FILE" - ok "opencode config installed from CONFIG_URL" + ok "opencode config installed from CONFIG_URL (with provider policies)" fi if command -v python3 >/dev/null 2>&1; then python3 -m json.tool "$CFG_FILE" >/dev/null && ok "config is valid JSON" @@ -145,7 +309,7 @@ elif command -v bun >/dev/null 2>&1; then bun -e "JSON.parse(require('fs').readFileSync('$CFG_FILE','utf8'))" && ok "config is valid JSON" fi -# ---- 7. verify -------------------------------------------------------------- +# ---- 8. verify -------------------------------------------------------------- echo info "==== verification ====" echo "bun: $(bun --version 2>/dev/null || echo MISSING)" @@ -153,6 +317,28 @@ echo "node shim: $(env node -e 'console.log("exec-ok")' 2>/dev/null || echo BRO echo "opencode: $(opencode --version 2>/dev/null || echo MISSING)" echo "openchamber:$(openchamber --version 2>/dev/null || echo MISSING)" +if [ -f "$POLICY_FILE" ]; then + if command -v python3 >/dev/null 2>&1; then + if python3 -m json.tool "$POLICY_FILE" >/dev/null 2>&1; then + ok "enterprise policy: $POLICY_FILE (valid JSON, relay pinned: $(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("relayUrl","?"))' "$POLICY_FILE" 2>/dev/null || echo '?'))" + else + warn "enterprise policy: $POLICY_FILE is not valid JSON — OpenChamber stays in enterprise mode but pins nothing" + fi + else + ok "enterprise policy: $POLICY_FILE present" + fi +elif grep -q "OPENCHAMBER_ENTERPRISE_MODE" ~/.bashrc 2>/dev/null; then + warn "enterprise mode via env var only (weaker lock) — no policy file at $POLICY_FILE" +else + warn "enterprise mode not configured (skipped with --no-enterprise?)" +fi + +if command -v python3 >/dev/null 2>&1; then + echo "allowed providers: $(allowed_providers_from "$CFG_FILE")" +else + warn "cannot read back provider allow-list (no python3)" +fi + HEALTH_URL="$(printf '%s' "$RELAY_URL" | sed 's|^wss://|https://|; s|/ws$|/health|')" if curl -fsS --max-time 15 "$HEALTH_URL" >/dev/null 2>&1; then ok "relay reachable: $HEALTH_URL" @@ -160,7 +346,7 @@ else warn "relay health check failed: $HEALTH_URL (may not be deployed yet)" fi -# ---- 8. invite link (optional) ---------------------------------------------- +# ---- 9. invite link (optional) ---------------------------------------------- if [ "$INVITE" = "1" ]; then info "generating invite link (relay transport embedded)..." if openchamber connect-url --relay -p "$INVITE_PORT" --qr; then @@ -171,4 +357,10 @@ if [ "$INVITE" = "1" ]; then fi echo -ok "prerequisites complete" \ No newline at end of file +ok "prerequisites complete" +} + +# Sourceable for testing: only run when executed directly. +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + main "$@" +fi