Compare commits
5
Commits
b0771234da
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
278bd3fbde | ||
|
|
0f3fb06377 | ||
|
|
c3954d1533 | ||
|
|
778341b7a5 | ||
|
|
c926361ccc |
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
# OpenChamber automatic config backups
|
||||||
|
*.openchamber.backup
|
||||||
+61
-71
@@ -1,91 +1,81 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"providers": {
|
||||||
"plugin": [
|
|
||||||
"opencode-openai-codex-auth",
|
|
||||||
"superpowers@git+https://github.com/obra/superpowers.git"
|
|
||||||
],
|
|
||||||
"provider": {
|
|
||||||
"zuccaro": {
|
"zuccaro": {
|
||||||
"models": {
|
"name": "Zuccaro AI (OUI)",
|
||||||
"bifrost.Blackwell-Auto": {
|
"baseUrl": "https://ai.zuccaro.me/api",
|
||||||
"cost": {"cache_read": 0.03, "cache_write": 0, "input": 0.15, "output": 0.5},
|
"api": "openai-completions",
|
||||||
"interleaved": {"field": "reasoning_content"},
|
"models": [
|
||||||
"limit": {"context": 409600, "output": 131072},
|
{
|
||||||
"modalities": {"input": ["text", "image"], "output": ["text"]},
|
"id": "bifrost.Blackwell-Auto",
|
||||||
"name": "Blackwell Auto",
|
"name": "Blackwell Auto",
|
||||||
"reasoning": true,
|
"reasoning": true,
|
||||||
"variants": {
|
"input": ["text", "image"],
|
||||||
"high": {"reasoningEffort": "high"},
|
"cost": { "input": 0.15, "output": 0.5, "cacheRead": 0.03, "cacheWrite": 0 },
|
||||||
"low": {"reasoningEffort": "low"},
|
"contextWindow": 524288,
|
||||||
"max": {"reasoningEffort": "max"},
|
"maxTokens": 131072,
|
||||||
"medium": {"reasoningEffort": "medium"},
|
"thinkingLevelMap": {
|
||||||
"no-thinking": {"reasoningEffort": "none"},
|
"off": "none",
|
||||||
"xhigh": {"reasoningEffort": "xhigh"}
|
"minimal": "minimal",
|
||||||
}
|
"low": "low",
|
||||||
"bifrost.DeepSeek-V4-Flash-Vision-Exp": {
|
"medium": "medium",
|
||||||
"interleaved": {"field": "reasoning_content"},
|
"high": "high",
|
||||||
"limit": {"context": 524288, "output": 384000},
|
"xhigh": "xhigh",
|
||||||
"modalities": {"input": ["text", "image"], "output": ["text"]},
|
"max": "max"
|
||||||
"name": "DeepSeek V4 Flash Vision Exp",
|
|
||||||
"reasoning": true,
|
|
||||||
"variants": {
|
|
||||||
"high": {"reasoningEffort": "high"},
|
|
||||||
"low": {"reasoningEffort": "low"},
|
|
||||||
"max": {"reasoningEffort": "max"},
|
|
||||||
"no-thinking": {"body": {"thinking": {"type": "disabled"}}}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bifrost.GLM-5.3-Flash-QAD3500-MXFP8": {
|
{
|
||||||
"cost": {"cache_read": 0.03, "cache_write": 0, "input": 0.15, "output": 0.5},
|
"id": "bifrost.GLM-5.3-Flash-NVFP4",
|
||||||
"interleaved": {"field": "reasoning_content"},
|
|
||||||
"limit": {"context": 409600, "output": 131072},
|
|
||||||
"modalities": {"input": ["text", "image"], "output": ["text"]},
|
|
||||||
"name": "GLM 5.3 Flash NVFP4",
|
"name": "GLM 5.3 Flash NVFP4",
|
||||||
"reasoning": true,
|
"reasoning": true,
|
||||||
"variants": {
|
"input": ["text", "image"],
|
||||||
"high": {"reasoningEffort": "high"},
|
"cost": { "input": 0.15, "output": 0.5, "cacheRead": 0.03, "cacheWrite": 0 },
|
||||||
"low": {"reasoningEffort": "low"},
|
"contextWindow": 524288,
|
||||||
"max": {"reasoningEffort": "max"}
|
"maxTokens": 131072,
|
||||||
|
"thinkingLevelMap": {
|
||||||
|
"off": null,
|
||||||
|
"minimal": null,
|
||||||
|
"low": "low",
|
||||||
|
"medium": null,
|
||||||
|
"high": "high",
|
||||||
|
"xhigh": null,
|
||||||
|
"max": "max"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bifrost.Qwen3.8-27B-Abliterated": {
|
{
|
||||||
"interleaved": {"field": "reasoning_content"},
|
"id": "bifrost.Qwen3.8-27B-Abliterated",
|
||||||
"limit": {"context": 217088, "output": 32768},
|
|
||||||
"modalities": {"input": ["text", "image"], "output": ["text"]},
|
|
||||||
"name": "Qwen3.8 27B Abliterated",
|
"name": "Qwen3.8 27B Abliterated",
|
||||||
"reasoning": true,
|
"reasoning": true,
|
||||||
"variants": {
|
"input": ["text", "image"],
|
||||||
"low": {"reasoningEffort": "low"},
|
"contextWindow": 217088,
|
||||||
"medium": {"reasoningEffort": "medium"},
|
"maxTokens": 32768,
|
||||||
"no-thinking": {"body": {"chat_template_kwargs": {"enable_thinking": false}}},
|
"thinkingLevelMap": {
|
||||||
"xhigh": {"reasoningEffort": "xhigh"}
|
"off": "none",
|
||||||
|
"minimal": null,
|
||||||
|
"low": "low",
|
||||||
|
"medium": "medium",
|
||||||
|
"high": null,
|
||||||
|
"xhigh": "xhigh",
|
||||||
|
"max": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bifrost.Qwen3.8-27B-NVFP4": {
|
{
|
||||||
"interleaved": {"field": "reasoning_content"},
|
"id": "bifrost.Qwen3.8-27B-NVFP4",
|
||||||
"limit": {"context": 217088, "output": 32768},
|
|
||||||
"modalities": {"input": ["text", "image"], "output": ["text"]},
|
|
||||||
"name": "Qwen3.8 27B NVFP4",
|
"name": "Qwen3.8 27B NVFP4",
|
||||||
"reasoning": true,
|
"reasoning": true,
|
||||||
"variants": {
|
"input": ["text", "image"],
|
||||||
"low": {"reasoningEffort": "low"},
|
"contextWindow": 217088,
|
||||||
"medium": {"reasoningEffort": "medium"},
|
"maxTokens": 32768,
|
||||||
"no-thinking": {"body": {"chat_template_kwargs": {"enable_thinking": false}}},
|
"thinkingLevelMap": {
|
||||||
"xhigh": {"reasoningEffort": "xhigh"}
|
"off": "none",
|
||||||
|
"minimal": null,
|
||||||
|
"low": "low",
|
||||||
|
"medium": "medium",
|
||||||
|
"high": null,
|
||||||
|
"xhigh": "xhigh",
|
||||||
|
"max": null
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Qwen3-4B-Instruct-2507-GGUF:Q4_K_XL": {
|
|
||||||
"limit": {"context": 32768, "output": 32768},
|
|
||||||
"modalities": {"input": ["text"], "output": ["text"]},
|
|
||||||
"name": "Qwen3 4B",
|
|
||||||
"reasoning": false
|
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
"name": "Zuccaro AI (OUI)",
|
|
||||||
"npm": "@ai-sdk/openai-compatible",
|
|
||||||
"options": {"baseURL": "https://ai.zuccaro.me/api"}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"model": "zuccaro/bifrost.Blackwell-Auto",
|
|
||||||
"small_model": "zuccaro/bifrost.Qwen3.8-27B-NVFP4"
|
|
||||||
}
|
}
|
||||||
+291
-19
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
# openchamber-prereqs.sh — bootstrap a Linux host for opencode + openchamber
|
# openchamber-prereqs.sh — bootstrap a Linux host for opencode + openchamber
|
||||||
#
|
#
|
||||||
# Intent: run this once on a fresh Linux host, then connect from the
|
# 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.
|
# and inherits the pinned environment. No UI password is used.
|
||||||
#
|
#
|
||||||
# Run as the user who will own the install. Idempotent: safe to re-run,
|
# 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:
|
# What it does:
|
||||||
# 1. Installs bun (if missing)
|
# 1. Installs bun (if missing)
|
||||||
@@ -14,39 +16,272 @@
|
|||||||
# non-interactive SSH sessions (OpenChamber's exec path) inherit them
|
# non-interactive SSH sessions (OpenChamber's exec path) inherit them
|
||||||
# 4. Installs opencode (if missing)
|
# 4. Installs opencode (if missing)
|
||||||
# 5. Installs/updates @openchamber/web to latest via bun (every run)
|
# 5. Installs/updates @openchamber/web to latest via bun (every run)
|
||||||
# 6. Syncs ~/.config/opencode/opencode.json from CONFIG_URL
|
# 6. Installs the enterprise policy file (/etc/openchamber/policy.json):
|
||||||
# 7. Verifies: versions + relay health
|
# enterprise mode + organization + pinned relay. Pass
|
||||||
|
# --enterprise <file> to install your own policy (e.g.
|
||||||
|
# openchamber-policy.json from this repo); otherwise built-in
|
||||||
|
# defaults are used. --relay-url overrides the file's relayUrl.
|
||||||
|
# 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)
|
# Flags: --relay-url wss://host/ws (override the default relay endpoint)
|
||||||
# --invite [port] print an openchamber:// invite link (+QR)
|
# --invite [port] print an openchamber:// invite link (+QR)
|
||||||
# for importing this host into the desktop
|
# for importing this host into the desktop
|
||||||
# app; starts the instance on <port>
|
# app; starts the instance on <port>
|
||||||
# (default 3000) if none is running
|
# (default 3000) if none is running
|
||||||
|
# --allow-provider <id> allow an extra model provider in the
|
||||||
|
# OpenCode policy (repeatable), e.g.
|
||||||
|
# --allow-provider constellation
|
||||||
|
# --enterprise <policy.json> install this file as the OpenChamber
|
||||||
|
# enterprise policy (e.g. the
|
||||||
|
# openchamber-policy.json in this repo);
|
||||||
|
# without it, built-in defaults apply
|
||||||
|
# --no-enterprise do not install the enterprise policy
|
||||||
|
# file / env pin
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
RELAY_URL="wss://code.zuccaro.me/ws"
|
RELAY_URL="wss://code.zuccaro.me/ws"
|
||||||
CONFIG_URL="https://git.zuccaro.me/bzuccaro/opencode-public/raw/branch/main/opencode.json"
|
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=0
|
||||||
INVITE_PORT=3000
|
INVITE_PORT=3000
|
||||||
while [ $# -gt 0 ]; do
|
NO_ENTERPRISE=0
|
||||||
case "$1" in
|
ALLOW_PROVIDERS_STR=""
|
||||||
--relay-url) RELAY_URL="$2"; shift 2 ;;
|
POLICY_SOURCE=""
|
||||||
--invite)
|
|
||||||
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 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
info() { printf '\033[1;34m[info]\033[0m %s\n' "$*"; }
|
info() { printf '\033[1;34m[info]\033[0m %s\n' "$*"; }
|
||||||
ok() { printf '\033[1;32m[ ok ]\033[0m %s\n' "$*"; }
|
ok() { printf '\033[1;32m[ ok ]\033[0m %s\n' "$*"; }
|
||||||
warn() { printf '\033[1;33m[warn]\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; }
|
die() { printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
info "host: $(uname -srm)"
|
# parse_args "$@" — set INVITE/INVITE_PORT/NO_ENTERPRISE/ALLOW_PROVIDERS_STR/
|
||||||
|
# POLICY_SOURCE/RELAY_URL from the command line.
|
||||||
|
parse_args() {
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--relay-url) [ $# -ge 2 ] || die "--relay-url needs a value"; RELAY_URL="$2"; shift 2 ;;
|
||||||
|
--invite)
|
||||||
|
INVITE=1
|
||||||
|
if [ $# -ge 2 ] && [ "${2#--}" = "$2" ]; then INVITE_PORT="$2"; shift; fi
|
||||||
|
shift ;;
|
||||||
|
--allow-provider)
|
||||||
|
[ $# -ge 2 ] || die "--allow-provider needs a provider id"
|
||||||
|
ALLOW_PROVIDERS_STR="$ALLOW_PROVIDERS_STR $2"
|
||||||
|
shift 2 ;;
|
||||||
|
--enterprise)
|
||||||
|
[ $# -ge 2 ] || die "--enterprise needs a policy file path"
|
||||||
|
[ -r "$2" ] || die "enterprise policy file not found: $2"
|
||||||
|
POLICY_SOURCE="$2"
|
||||||
|
shift 2 ;;
|
||||||
|
--no-enterprise) NO_ENTERPRISE=1; shift ;;
|
||||||
|
*) echo "unknown arg: $1 (usage: $0 [--relay-url wss://host/ws] [--invite [port]] [--allow-provider <id>] [--enterprise <policy.json>] [--no-enterprise])" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- helpers ----------------------------------------------------------------
|
||||||
|
|
||||||
|
# policy_json — emit the enterprise policy file content on stdout.
|
||||||
|
# With POLICY_SOURCE set: the file is the source of truth, except --relay-url
|
||||||
|
# overrides its relayUrl key. Without it: built-in defaults.
|
||||||
|
policy_json() {
|
||||||
|
if [ -z "$POLICY_SOURCE" ]; then
|
||||||
|
printf '{\n "enterpriseMode": true,\n "organization": "%s",\n "relayUrl": "%s"\n}\n' \
|
||||||
|
"$ORGANIZATION" "$RELAY_URL"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local tmp
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
python3 - "$POLICY_SOURCE" "$tmp" "$RELAY_URL" <<'PY' || { rm -f "$tmp"; die "invalid JSON in enterprise policy file: $POLICY_SOURCE"; }
|
||||||
|
import json, sys
|
||||||
|
src, dst, relay = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
|
p = json.load(open(src))
|
||||||
|
if relay and p.get("relayUrl") != relay:
|
||||||
|
p["relayUrl"] = relay
|
||||||
|
with open(dst, "w") as f:
|
||||||
|
json.dump(p, f, indent=2)
|
||||||
|
f.write("\n")
|
||||||
|
PY
|
||||||
|
elif command -v bun >/dev/null 2>&1; then
|
||||||
|
IN="$POLICY_SOURCE" OUT="$tmp" RELAY="$RELAY_URL" bun -e '
|
||||||
|
const fs = require("fs");
|
||||||
|
const p = JSON.parse(fs.readFileSync(process.env.IN, "utf8"));
|
||||||
|
if (process.env.RELAY && p.relayUrl !== process.env.RELAY) p.relayUrl = process.env.RELAY;
|
||||||
|
fs.writeFileSync(process.env.OUT, JSON.stringify(p, null, 2) + "\n");
|
||||||
|
' || { rm -f "$tmp"; die "invalid JSON in enterprise policy file: $POLICY_SOURCE"; }
|
||||||
|
else
|
||||||
|
die "need python3 or bun to read the enterprise policy file"
|
||||||
|
fi
|
||||||
|
cat "$tmp"
|
||||||
|
rm -f "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
# write_policy_file <path> — idempotently install the enterprise policy file
|
||||||
|
# at <path>, 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"
|
||||||
|
chmod 644 "$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="" tmp current
|
||||||
|
dir="$(dirname "$POLICY_FILE")"
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
policy_json > "$tmp"
|
||||||
|
|
||||||
|
# Already correct? Then we're done regardless of root.
|
||||||
|
if [ -r "$POLICY_FILE" ]; then
|
||||||
|
current="$(cat "$POLICY_FILE" 2>/dev/null || true)"
|
||||||
|
if [ "$current" = "$(cat "$tmp")" ]; then
|
||||||
|
rm -f "$tmp"
|
||||||
|
ok "$(basename "$POLICY_FILE"): already enforced"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$dir" ] && [ -w "$dir" ]; then
|
||||||
|
write_policy_file "$POLICY_FILE"
|
||||||
|
rm -f "$tmp"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if sudo -n true 2>/dev/null; then
|
||||||
|
sudo="sudo -n"
|
||||||
|
elif [ -t 0 ]; then
|
||||||
|
sudo="sudo"
|
||||||
|
fi
|
||||||
|
if [ -n "$sudo" ]; then
|
||||||
|
if [ -f "$POLICY_FILE" ]; then
|
||||||
|
warn "$(basename "$POLICY_FILE"): differs from enforced policy — overwriting"
|
||||||
|
fi
|
||||||
|
if $sudo mkdir -p "$dir" 2>/dev/null && $sudo tee "$POLICY_FILE" >/dev/null < "$tmp" && $sudo chmod 644 "$POLICY_FILE" 2>/dev/null; then
|
||||||
|
rm -f "$tmp"
|
||||||
|
ok "enterprise policy installed at $POLICY_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm -f "$tmp"
|
||||||
|
if [ -f "$POLICY_FILE" ]; then
|
||||||
|
warn "no root: $POLICY_FILE exists but differs from enforced policy and could not be repaired"
|
||||||
|
return 1
|
||||||
|
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 <in> <out> [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 status=0
|
||||||
|
local extras="${*:3}"
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
python3 - "$in" "$tmp" "$BASELINE_ALLOWED_PROVIDERS" "$extras" <<'PY' || status=1
|
||||||
|
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");
|
||||||
|
' || status=1
|
||||||
|
else
|
||||||
|
die "need python3 or bun to inject provider policies"
|
||||||
|
fi
|
||||||
|
if [ "$status" -ne 0 ]; then
|
||||||
|
rm -f "$tmp"
|
||||||
|
die "could not inject provider policies into $in (bad JSON or interpreter failure) — existing config left untouched"
|
||||||
|
fi
|
||||||
|
mv "$tmp" "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
# allowed_providers_from <config.json> — 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() {
|
||||||
|
parse_args "$@"
|
||||||
|
|
||||||
|
info "host: $(uname -srm)"
|
||||||
[ "$(uname -s)" = "Linux" ] || die "this script targets Linux hosts"
|
[ "$(uname -s)" = "Linux" ] || die "this script targets Linux hosts"
|
||||||
|
|
||||||
# ---- preflight -------------------------------------------------------------
|
# ---- preflight -------------------------------------------------------------
|
||||||
@@ -121,11 +356,20 @@ info "installing/updating @openchamber/web to latest..."
|
|||||||
bun install -g @openchamber/web@latest
|
bun install -g @openchamber/web@latest
|
||||||
ok "openchamber version: $(openchamber --version 2>/dev/null || echo '?')"
|
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_DIR="$HOME/.config/opencode"
|
||||||
CFG_FILE="$CFG_DIR/opencode.json"
|
CFG_FILE="$CFG_DIR/opencode.json"
|
||||||
TMP_CFG="$(mktemp)"
|
TMP_CFG="$(mktemp)"
|
||||||
curl -fsSL --max-time 60 "$CONFIG_URL" -o "$TMP_CFG" || die "could not download config from $CONFIG_URL"
|
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
|
if [ -f "$CFG_FILE" ] && cmp -s "$TMP_CFG" "$CFG_FILE"; then
|
||||||
ok "opencode config already up to date"
|
ok "opencode config already up to date"
|
||||||
rm -f "$TMP_CFG"
|
rm -f "$TMP_CFG"
|
||||||
@@ -137,7 +381,7 @@ else
|
|||||||
info "existing config backed up to $(basename "$BAK")"
|
info "existing config backed up to $(basename "$BAK")"
|
||||||
fi
|
fi
|
||||||
mv "$TMP_CFG" "$CFG_FILE"
|
mv "$TMP_CFG" "$CFG_FILE"
|
||||||
ok "opencode config installed from CONFIG_URL"
|
ok "opencode config installed from CONFIG_URL (with provider policies)"
|
||||||
fi
|
fi
|
||||||
if command -v python3 >/dev/null 2>&1; then
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
python3 -m json.tool "$CFG_FILE" >/dev/null && ok "config is valid JSON"
|
python3 -m json.tool "$CFG_FILE" >/dev/null && ok "config is valid JSON"
|
||||||
@@ -145,7 +389,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"
|
bun -e "JSON.parse(require('fs').readFileSync('$CFG_FILE','utf8'))" && ok "config is valid JSON"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---- 7. verify --------------------------------------------------------------
|
# ---- 8. verify --------------------------------------------------------------
|
||||||
echo
|
echo
|
||||||
info "==== verification ===="
|
info "==== verification ===="
|
||||||
echo "bun: $(bun --version 2>/dev/null || echo MISSING)"
|
echo "bun: $(bun --version 2>/dev/null || echo MISSING)"
|
||||||
@@ -153,6 +397,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 "opencode: $(opencode --version 2>/dev/null || echo MISSING)"
|
||||||
echo "openchamber:$(openchamber --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|')"
|
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
|
if curl -fsS --max-time 15 "$HEALTH_URL" >/dev/null 2>&1; then
|
||||||
ok "relay reachable: $HEALTH_URL"
|
ok "relay reachable: $HEALTH_URL"
|
||||||
@@ -160,7 +426,7 @@ else
|
|||||||
warn "relay health check failed: $HEALTH_URL (may not be deployed yet)"
|
warn "relay health check failed: $HEALTH_URL (may not be deployed yet)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---- 8. invite link (optional) ----------------------------------------------
|
# ---- 9. invite link (optional) ----------------------------------------------
|
||||||
if [ "$INVITE" = "1" ]; then
|
if [ "$INVITE" = "1" ]; then
|
||||||
info "generating invite link (relay transport embedded)..."
|
info "generating invite link (relay transport embedded)..."
|
||||||
if openchamber connect-url --relay -p "$INVITE_PORT" --qr; then
|
if openchamber connect-url --relay -p "$INVITE_PORT" --qr; then
|
||||||
@@ -172,3 +438,9 @@ fi
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
ok "prerequisites complete"
|
ok "prerequisites complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sourceable for testing: only run when executed directly.
|
||||||
|
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"enterpriseMode": true,
|
||||||
|
"organization": "Zuccaro",
|
||||||
|
"relayUrl": "wss://code.zuccaro.me/ws"
|
||||||
|
}
|
||||||
Reference in new issue
Block a user