config: load public cc-ci runtime environment
This commit is contained in:
@@ -1114,19 +1114,24 @@ def markdown(rep: dict) -> str:
|
||||
|
||||
|
||||
def _gitea_auth(url: str) -> dict:
|
||||
"""Basic auth for the private mirror, from /srv/cc-ci/.testenv.
|
||||
"""Basic auth for the private mirror, using public config plus .testenv.
|
||||
|
||||
Sent as a HEADER, never embedded in the URL: in-URL credentials leak into shell history, process
|
||||
lists and error messages, and urllib mis-parses a password containing a colon."""
|
||||
host = re.sub(r"^https?://", "", url).split("/")[0]
|
||||
env = {}
|
||||
try:
|
||||
for ln in open(os.environ.get("CCCI_TESTENV", "/srv/cc-ci/.testenv")):
|
||||
for path in (
|
||||
"/srv/cc-ci/cc-ci/.env.public",
|
||||
os.environ.get("CCCI_TESTENV", "/srv/cc-ci/.testenv"),
|
||||
):
|
||||
try:
|
||||
lines = open(path)
|
||||
except OSError:
|
||||
continue
|
||||
for ln in lines:
|
||||
if "=" in ln and not ln.strip().startswith("#"):
|
||||
k, v = ln.strip().split("=", 1)
|
||||
env[k] = v.strip().strip("\"'")
|
||||
except OSError:
|
||||
return {}
|
||||
if host != env.get("GITEA_URL", "git.autonomic.zone"):
|
||||
return {}
|
||||
u, pw = env.get("GITEA_USERNAME"), env.get("GITEA_PASSWORD")
|
||||
|
||||
@@ -34,7 +34,7 @@ supports_resume = false
|
||||
prompt_delivery = "ping" # send after the TUI connects
|
||||
connect_delay = 12
|
||||
submit_key = "C-m"
|
||||
preamble = "set -a; . /srv/cc-ci/.testenv; set +a"
|
||||
preamble = "set -a; . /srv/cc-ci/cc-ci-plan/load-env.sh; set +a"
|
||||
stall_idle = 900
|
||||
active_re = "esc interrupt|thinking|inferring|running tool|tool call|preparing patch|reading|searching"
|
||||
limit_re = "spend limit|usage limit|limit reached|reached your .*limit|out of (credits|tokens)"
|
||||
|
||||
@@ -112,7 +112,7 @@ def start(mode="resume"):
|
||||
if not Path(OPENCODE_BIN).exists():
|
||||
die(f"opencode not found at {OPENCODE_BIN}")
|
||||
cmd = (
|
||||
f"set -a; . /srv/cc-ci/.testenv; set +a; "
|
||||
f"set -a; . /srv/cc-ci/cc-ci-plan/load-env.sh; set +a; "
|
||||
f"NO_COLOR=1 {OPENCODE_BIN} attach {OPENCODE_SERVER} --dir {WORKDIR}"
|
||||
)
|
||||
log(f"starting {SESSION} (backend=opencode, model={LOOP_MODEL or 'default'})")
|
||||
|
||||
@@ -154,7 +154,7 @@ def start(mode="resume"):
|
||||
f"OPENCODE_CONFIG_CONTENT={shlex.quote(json.dumps({'model': LOOP_MODEL}))} "
|
||||
)
|
||||
cmd = (
|
||||
f"set -a; . /srv/cc-ci/.testenv; set +a; "
|
||||
f"set -a; . /srv/cc-ci/cc-ci-plan/load-env.sh; set +a; "
|
||||
f"{model_env}NO_COLOR=1 {OPENCODE_BIN} attach {OPENCODE_SERVER} --dir {shlex.quote(WORKDIR)}"
|
||||
)
|
||||
log(f"starting {SESSION} (backend=opencode, model={LOOP_MODEL or 'default'})")
|
||||
|
||||
@@ -109,7 +109,7 @@ def start(mode, date):
|
||||
cwd = "/srv/cc-ci-orch/cc-ci"
|
||||
share_flag = "--share" if OPENCODE_SHARE else ""
|
||||
cmd = (
|
||||
f"set -a; . /srv/cc-ci/.testenv; set +a; "
|
||||
f"set -a; . /srv/cc-ci/cc-ci-plan/load-env.sh; set +a; "
|
||||
f"{OPENCODE_BIN} run {model_flag} {share_flag} --attach '{OPENCODE_SERVER}' "
|
||||
f"--title '{SESSION}' --dir {cwd} \"$(cat '{kf}')\""
|
||||
)
|
||||
|
||||
@@ -107,7 +107,7 @@ def spawn_supervisor(sid, reason):
|
||||
kf = Path(LOG_DIR) / f".kickoff-{SUP_SESSION}.txt"
|
||||
kf.write_text(build_kickoff(sid, reason))
|
||||
share = "--share" if OPENCODE_SHARE else ""
|
||||
cmd = (f"set -a; . {WORKDIR}/.testenv; set +a; {OPENCODE_BIN} run --model '{MODEL}' {share} "
|
||||
cmd = (f"set -a; . /srv/cc-ci/cc-ci-plan/load-env.sh; set +a; {OPENCODE_BIN} run --model '{MODEL}' {share} "
|
||||
f"--attach '{OPENCODE_SERVER}' --title '{SUP_SESSION}' --dir {WORKDIR} \"$(cat '{kf}')\"")
|
||||
_sh(["tmux", "new-session", "-d", "-s", SUP_SESSION, "-c", WORKDIR, cmd])
|
||||
_sh(["tmux", "pipe-pane", "-o", "-t", SUP_SESSION, f"cat >> '{LOG_DIR}/{SUP_SESSION}.log'"])
|
||||
|
||||
@@ -216,7 +216,7 @@ def start(mode="use-or-create"):
|
||||
# so they must come AFTER `run` (a global `opencode --model X run` is ignored).
|
||||
share_flag = "--share" if OPENCODE_SHARE else ""
|
||||
cmd = (
|
||||
f"set -a; . /srv/cc-ci/.testenv; set +a; "
|
||||
f"set -a; . /srv/cc-ci/cc-ci-plan/load-env.sh; set +a; "
|
||||
f"{OPENCODE_BIN} run {model_flag} {share_flag} --attach '{OPENCODE_SERVER}' "
|
||||
f"--title '{SESSION}' \"$(cat '{kf}')\""
|
||||
)
|
||||
@@ -521,7 +521,7 @@ def resume(reason="manual"):
|
||||
"write+push the weekly summary, then `python3 /srv/cc-ci/cc-ci-plan/launch-report.py fresh`, print "
|
||||
"'" + DONE_MARKER + "', and go idle.")
|
||||
share = "--share" if OPENCODE_SHARE else ""
|
||||
cmd = (f"set -a; . /srv/cc-ci/.testenv; set +a; {OPENCODE_BIN} run -s {sid} --continue "
|
||||
cmd = (f"set -a; . /srv/cc-ci/cc-ci-plan/load-env.sh; set +a; {OPENCODE_BIN} run -s {sid} --continue "
|
||||
f"--model '{MODEL}' {share} --attach '{OPENCODE_SERVER}' --dir '{WORKDIR}' \"$(cat '{kf}')\"")
|
||||
subprocess.run(["tmux", "new-session", "-d", "-s", SESSION, "-c", WORKDIR, cmd])
|
||||
subprocess.run(["tmux", "pipe-pane", "-o", "-t", SESSION, f"cat >> '{LOG_FILE}'"])
|
||||
|
||||
@@ -323,7 +323,7 @@ def start_agent(role, session, workdir):
|
||||
# absolute repo paths for builder/adversary work.
|
||||
session_cwd = "/srv/cc-ci-orch/cc-ci"
|
||||
cmd = (
|
||||
f"set -a; . /srv/cc-ci/.testenv; set +a; "
|
||||
f"set -a; . /srv/cc-ci/cc-ci-plan/load-env.sh; set +a; "
|
||||
f"NO_COLOR=1 {OPENCODE_BIN} attach {OPENCODE_SERVER} --dir {session_cwd}"
|
||||
)
|
||||
log(f"starting {session} (backend=opencode, phase={pid}, model={model or 'default'})")
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Load cc-ci runtime configuration. Source this file; do not execute it.
|
||||
#
|
||||
# Callers enable `set -a` so both files become exported environment variables.
|
||||
# Public configuration is loaded first, then the secret file. This preserves a
|
||||
# local secret override only where an emergency migration explicitly needs it.
|
||||
|
||||
if [ -r /srv/cc-ci/cc-ci/.env.public ]; then
|
||||
. /srv/cc-ci/cc-ci/.env.public
|
||||
fi
|
||||
|
||||
if [ ! -r /srv/cc-ci/.testenv ]; then
|
||||
echo "cc-ci secret environment is missing: /srv/cc-ci/.testenv" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
. /srv/cc-ci/.testenv
|
||||
@@ -71,7 +71,7 @@ log "writing report $REPORT ..."
|
||||
if [ -f "$SUMMARY" ]; then cat "$SUMMARY"; else echo "(no dated summary at $SUMMARY — /upgrade-all may have stalled on the usage limit or errored; check the cc-ci-upgrader session + $LOG)"; fi
|
||||
echo
|
||||
echo "## Open PRs per recipe (post-run)"
|
||||
set -a; . /srv/cc-ci/.testenv 2>/dev/null; set +a
|
||||
set -a; . /srv/cc-ci/cc-ci-plan/load-env.sh; set +a
|
||||
G="https://${GITEA_USERNAME}:${GITEA_PASSWORD}@${GITEA_URL}/api/v1"
|
||||
for r in $(curl -fsS "$G/orgs/recipe-maintainers/repos?limit=100" 2>/dev/null | python3 -c "import sys,json;print('\n'.join(sorted(x['name'] for x in json.load(sys.stdin))))" 2>/dev/null); do
|
||||
case "$r" in cc-ci|cc-ci-orchestrator|cc-ci-secrets|archived-*) continue;; esac
|
||||
|
||||
+3
-2
@@ -83,9 +83,10 @@ repo, a commit, a log, or the dashboard** (§9) — reference locations only.
|
||||
| What | Where | How to use |
|
||||
|---|---|---|
|
||||
| **cc-ci SSH (root)** | private key `~/.ssh/cc-ci-root-ed25519`; `Host cc-ci` in `~/.ssh/config` (HostName `100.90.116.4`, no ProxyCommand) | Just run `ssh cc-ci` (logs in as **root**). The orchestrator VM is a direct tailnet peer — direct route, no proxy. Pubkey already in cc-ci's `/root/.ssh/authorized_keys`. |
|
||||
| **Gitea bot account** | `/srv/cc-ci/.testenv` → `GITEA_USERNAME` (`autonomic-bot`), `GITEA_PASSWORD`, `GITEA_URL` (`git.autonomic.zone`) | Basic-auth to the Gitea API, or mint a scoped token: `POST https://$GITEA_URL/api/v1/users/$GITEA_USERNAME/tokens`. Used to push the `cc-ci` project repo, read recipe repos, comment on PRs, and poll for `!testme` (read-level; the bot does not register webhooks). |
|
||||
| **Gitea bot account** | `/srv/cc-ci/cc-ci/.env.public` → `GITEA_USERNAME` (`autonomic-bot`); `/srv/cc-ci/.testenv` → `GITEA_PASSWORD`; `GITEA_URL` defaults to `git.autonomic.zone` | Basic-auth to the Gitea API, or mint a scoped token: `POST https://$GITEA_URL/api/v1/users/$GITEA_USERNAME/tokens`. Used to push the `cc-ci` project repo, read recipe repos, comment on PRs, and poll for `!testme` (read-level; the bot does not register webhooks). |
|
||||
|
||||
Load them in a shell with: `set -a; . /srv/cc-ci/.testenv; set +a` (don't echo the values).
|
||||
Load runtime configuration in a shell with: `set -a; . /srv/cc-ci/cc-ci-plan/load-env.sh; set +a`
|
||||
(don't echo secret values). The helper loads tracked public configuration first and then `.testenv`.
|
||||
|
||||
### The Tailscale connection (how `ssh cc-ci` works)
|
||||
|
||||
|
||||
@@ -40,19 +40,22 @@ from datetime import datetime, timezone
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
USED_RECIPES = os.path.join(HERE, "used-recipes.md")
|
||||
TESTENV = os.environ.get("CCCI_TESTENV", "/srv/cc-ci/.testenv")
|
||||
PUBLIC_ENV = "/srv/cc-ci/cc-ci/.env.public"
|
||||
NS = "recipe-maintainers"
|
||||
|
||||
|
||||
def _env() -> dict:
|
||||
e = {}
|
||||
try:
|
||||
for ln in open(TESTENV):
|
||||
for path in (PUBLIC_ENV, TESTENV):
|
||||
try:
|
||||
lines = open(path)
|
||||
except OSError:
|
||||
continue
|
||||
for ln in lines:
|
||||
ln = ln.strip()
|
||||
if "=" in ln and not ln.startswith("#"):
|
||||
k, v = ln.split("=", 1)
|
||||
e[k] = v.strip().strip('"').strip("'")
|
||||
except OSError:
|
||||
pass
|
||||
return e
|
||||
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ from datetime import datetime, timezone
|
||||
|
||||
LOGDIR = "/srv/cc-ci/.cc-ci-logs"
|
||||
TESTENV = "/srv/cc-ci/.testenv"
|
||||
PUBLIC_ENV = "/srv/cc-ci/cc-ci/.env.public"
|
||||
INFRA = {"cc-ci", "cc-ci-orchestrator", "cc-ci-secrets"}
|
||||
HOST_REPORTS = "/var/lib/cc-ci-reports"
|
||||
# Both kinds live in ONE archive, distinguished by a suffix on a common title.
|
||||
@@ -56,14 +57,16 @@ KINDS = {"week": ("The Recipe Report", "Week of {d} — full"),
|
||||
|
||||
def _env():
|
||||
e = {}
|
||||
try:
|
||||
for ln in open(TESTENV):
|
||||
for path in (PUBLIC_ENV, TESTENV):
|
||||
try:
|
||||
lines = open(path)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
for ln in lines:
|
||||
ln = ln.strip()
|
||||
if "=" in ln and not ln.startswith("#"):
|
||||
k, v = ln.split("=", 1)
|
||||
e[k] = v.strip().strip('"').strip("'")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return e
|
||||
|
||||
|
||||
|
||||
@@ -30,12 +30,11 @@ set -o errexit -o nounset -o pipefail
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ORCH="$(dirname "$HERE")"
|
||||
SSH="${SSH:-cc-ci}"
|
||||
TESTENV="${TESTENV:-/srv/cc-ci/.testenv}"
|
||||
RECONCILE="${RECONCILE:-$ORCH/.claude/skills/recipe-upgrade/open-recipe-pr.sh}"
|
||||
USED_RECIPES="${USED_RECIPES:-$HERE/used-recipes.md}"
|
||||
|
||||
[ -f "$RECONCILE" ] || { echo "ERROR: reconcile helper not found: $RECONCILE" >&2; exit 1; }
|
||||
set -a; . "$TESTENV"; set +a
|
||||
set -a; . /srv/cc-ci/cc-ci-plan/load-env.sh; set +a
|
||||
: "${GITEA_USERNAME:?}"; : "${GITEA_PASSWORD:?}"; : "${GITEA_URL:=git.autonomic.zone}"
|
||||
|
||||
if [ "${1:-}" = "--all" ]; then
|
||||
|
||||
@@ -131,7 +131,8 @@ SSHCFG
|
||||
|
||||
# opencode web server — one shared instance; agent sessions attach to it for web visibility.
|
||||
# Serves the web UI at http://oc.commoninternet.net (via nginx below, tailscale-only).
|
||||
# Provider creds are read from /srv/cc-ci/.testenv at startup.
|
||||
# Public provider configuration is tracked in the cc-ci repo; credentials
|
||||
# remain in /srv/cc-ci/.testenv.
|
||||
systemd.services.opencode-web = {
|
||||
description = "opencode web server for cc-ci agents";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
@@ -141,7 +142,10 @@ SSHCFG
|
||||
Type = "simple";
|
||||
User = "loops"; Group = "users";
|
||||
WorkingDirectory = "/srv/cc-ci-orch/cc-ci";
|
||||
EnvironmentFile = "/srv/cc-ci/.testenv";
|
||||
EnvironmentFile = [
|
||||
"-/srv/cc-ci/cc-ci/.env.public"
|
||||
"/srv/cc-ci/.testenv"
|
||||
];
|
||||
ExecStartPre = "${pkgs.coreutils}/bin/rm -rf /tmp/opencode";
|
||||
ExecStart = "/home/loops/.local/bin/opencode serve --hostname 127.0.0.1 --port 4096";
|
||||
Restart = "on-failure";
|
||||
|
||||
Reference in New Issue
Block a user