@edgehero/pi-dispatch 1.10.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +303 -150
- package/README.md +52 -0
- package/deploy/com.pi-dispatch.worker.plist +10 -4
- package/deploy/docker-compose.yml +49 -16
- package/deploy/egress-proxy.conf +32 -2
- package/deploy/nssm-install.cmd +12 -6
- package/deploy/pi-dispatch-egress-out.network +10 -0
- package/deploy/pi-dispatch-egress-proxy.container +50 -0
- package/deploy/pi-dispatch-netns-keeper.container +80 -0
- package/deploy/pi-dispatch-netns-keeper.network +18 -0
- package/deploy/pi-dispatch-valkey.container +51 -0
- package/deploy/pi-dispatch-valkey.network +16 -0
- package/deploy/receiver.service +6 -0
- package/deploy/worker-env-wrapper.cmd +12 -1
- package/deploy/worker-env-wrapper.sh +63 -37
- package/deploy/worker.service +18 -8
- package/package.json +15 -5
- package/src/azure-host.mjs +19 -0
- package/src/azure-identity.mjs +18 -2
- package/src/backend-conformance.mjs +71 -18
- package/src/backend-local.mjs +637 -21
- package/src/backend-podman.mjs +1168 -0
- package/src/backend-registry.mjs +86 -3
- package/src/backends.mjs +489 -37
- package/src/branch.mjs +7 -2
- package/src/cancel-cli.mjs +174 -0
- package/src/cancel-state.mjs +125 -0
- package/src/cli.mjs +188 -90
- package/src/config.mjs +503 -43
- package/src/connection.mjs +374 -8
- package/src/container-spec.mjs +102 -7
- package/src/daemon-facts.mjs +167 -0
- package/src/deployment-venue.mjs +158 -0
- package/src/docker-run.mjs +146 -15
- package/src/doctor.mjs +4756 -394
- package/src/egress-conf-copy.mjs +166 -0
- package/src/egress-proxy-state.mjs +151 -0
- package/src/egress.mjs +456 -25
- package/src/entry.mjs +27 -0
- package/src/env-allowlist.mjs +245 -40
- package/src/env-file.mjs +1869 -33
- package/src/exit-code.mjs +15 -0
- package/src/flow-gate.mjs +5 -3
- package/src/forgejo-host.mjs +19 -0
- package/src/forgejo-identity.mjs +21 -2
- package/src/get-token.mjs +67 -18
- package/src/git-dirty.mjs +9 -1
- package/src/git-hardening.mjs +33 -0
- package/src/github-app-setup.mjs +29 -12
- package/src/github-prompt.mjs +4 -1
- package/src/gitlab-host.mjs +19 -0
- package/src/gitlab-identity.mjs +19 -2
- package/src/host-pi.mjs +19 -3
- package/src/host-registry.mjs +29 -2
- package/src/identity.mjs +29 -4
- package/src/image-preflight.mjs +46 -11
- package/src/image-ref.mjs +21 -0
- package/src/index.mjs +363 -13
- package/src/init.mjs +197 -38
- package/src/job-user.mjs +252 -0
- package/src/json-duplicates.mjs +204 -0
- package/src/live-probes.mjs +1020 -0
- package/src/materialize.mjs +4 -11
- package/src/netns-keeper.mjs +264 -0
- package/src/on-failure.mjs +119 -0
- package/src/outbox.mjs +7 -0
- package/src/packages.mjs +2 -2
- package/src/podman-stack.mjs +1304 -0
- package/src/prepare-github.mjs +6 -6
- package/src/prepare-local.mjs +51 -17
- package/src/prepare.mjs +27 -6
- package/src/pricing.mjs +9 -5
- package/src/processor.mjs +506 -26
- package/src/provider-key.mjs +66 -0
- package/src/provider-steering.mjs +185 -0
- package/src/queue.mjs +35 -8
- package/src/redact.mjs +84 -0
- package/src/reserved-env.mjs +7 -3
- package/src/retention-sweep.mjs +178 -0
- package/src/run-container.mjs +181 -14
- package/src/run-history.mjs +105 -16
- package/src/runtime-observations.mjs +1152 -0
- package/src/runtime-settings.mjs +13 -8
- package/src/sandbox-cli.mjs +100 -95
- package/src/sandbox-store.mjs +612 -45
- package/src/sandbox.mjs +1459 -37
- package/src/schedules.mjs +16 -3
- package/src/secret-profiles.mjs +2 -1
- package/src/secrets.mjs +24 -6
- package/src/service-env.mjs +247 -0
- package/src/service.mjs +618 -28
- package/src/session-store.mjs +678 -53
- package/src/start.mjs +1348 -326
- package/src/subscriptions.mjs +7 -3
- package/src/transient.mjs +240 -0
- package/src/triggers-file.mjs +71 -15
- package/src/triggers.mjs +179 -19
- package/src/up.mjs +1399 -85
- package/src/valkey-auth.mjs +529 -0
- package/src/valkey-endpoint.mjs +367 -0
- package/src/watch-closer.mjs +158 -0
package/.env.example
CHANGED
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
# Copy to .env and fill in. Never commit a real .env (it is gitignored).
|
|
2
|
+
#
|
|
3
|
+
# A value in 'single quotes' is quoted because it NEEDS to be: `pi-dispatch up` writes bare values and quotes
|
|
4
|
+
# only what it must. Do not tidy the quotes away. On the POSIX wrapper deployments this file is sourced by a
|
|
5
|
+
# shell (`set -a; . ./.env`), where an unquoted value with a space in it is a command-prefix assignment: the
|
|
6
|
+
# key ends up UNSET and the rest of the line is EXECUTED (measured in sh, bash and zsh). systemd's
|
|
7
|
+
# EnvironmentFile= honours single quotes too.
|
|
8
|
+
# The exception is Windows: deploy/worker-env-wrapper.cmd keeps surrounding quotes as PART of the value,
|
|
9
|
+
# which is why `up` never writes a quoted value there and why those paths are written bare.
|
|
10
|
+
# On Windows, also start each line with the key exactly as spelled here: no indent, no blank before the =,
|
|
11
|
+
# and the same case (cmd's `set` ignores case, so a later `webhook_secret=` replaces WEBHOOK_SECRET). Keep
|
|
12
|
+
# " % ! ^, characters outside ASCII and a leading = out of values there: the wrapper reads the file with
|
|
13
|
+
# `for /f` and cannot be shown to carry them. `up` and the setup wizard refuse to edit such a line and name it.
|
|
14
|
+
#
|
|
15
|
+
# VALUES ARE LITERAL UNDER systemd, and `$` is where this file's four consumers stop agreeing.
|
|
16
|
+
# `PI_JOBS_DIR=$HOME/jobs` is read as the literal characters `$HOME/jobs` by systemd's EnvironmentFile= and
|
|
17
|
+
# by the Windows cmd wrapper, and as your home directory by a sourcing shell and by compose's env_file
|
|
18
|
+
# (measured: systemd 252, compose v2.31.0, sh/bash/zsh/dash). Nothing refuses it, so one file puts the jobs
|
|
19
|
+
# in two different places depending on which deployment reads it. Write the path out in full, not $HOME.
|
|
2
20
|
|
|
3
21
|
# --- Provider credential ---
|
|
4
|
-
# pi supports ~
|
|
22
|
+
# pi supports ~40 providers; set the key for the one you use, under the variable name pi expects.
|
|
5
23
|
# The worker forwards ONLY the configured provider's key into the job container -- nothing else.
|
|
6
|
-
# Anthropic: ANTHROPIC_API_KEY (
|
|
24
|
+
# Anthropic: ANTHROPIC_API_KEY (pi reads ANTHROPIC_AUTH_TOKEN, a bearer token, and ANTHROPIC_OAUTH_TOKEN
|
|
25
|
+
# BEFORE it, so leave both unset: either one set silently wins over the API key)
|
|
7
26
|
# OpenAI: OPENAI_API_KEY Google: GEMINI_API_KEY Groq: GROQ_API_KEY ... etc.
|
|
8
27
|
# You can LEAVE THIS BLANK if you are already logged into pi: when the env has no key, the worker reads the
|
|
9
28
|
# API key from ~/.pi/agent/auth.json (host-side) and env-injects it -- on by default, nothing to set.
|
|
10
29
|
# API-key logins only; an OAuth/subscription login is refused (it expires; use an API key for a service).
|
|
11
30
|
ANTHROPIC_API_KEY=
|
|
12
|
-
#
|
|
31
|
+
# uncomment to force env-only (fail loudly on a missing env key instead of using your pi login)
|
|
32
|
+
# PI_AUTH_FROM_PI=0
|
|
13
33
|
|
|
14
34
|
# --- Which provider/model to run by default (override per job with --provider / --model) ---
|
|
15
35
|
PI_PROVIDER=anthropic
|
|
@@ -17,107 +37,188 @@ PI_PROVIDER=anthropic
|
|
|
17
37
|
PI_MODEL=claude-sonnet-4-5-20250929
|
|
18
38
|
|
|
19
39
|
# --- Spend + concurrency guards (money bounds; all have conservative defaults) ---
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
40
|
+
# per-job turn cap -- pi has none of its own, so the harness imposes one
|
|
41
|
+
PI_MAX_TURNS=30
|
|
42
|
+
# max job containers started per day (mandatory window)
|
|
43
|
+
PI_DAILY_CAP=25
|
|
44
|
+
# optional weekly ceiling on container starts; unset = weekly window disabled
|
|
45
|
+
# PI_WEEKLY_CAP=100
|
|
46
|
+
# optional monthly ceiling on container starts; unset = monthly window disabled
|
|
47
|
+
# PI_MONTHLY_CAP=400
|
|
48
|
+
# optional soft-hold band (1-99): once any window hits this % of its cap, new starts
|
|
49
|
+
# pause (in-flight jobs finish) and the panel meter turns amber; unset = disabled
|
|
50
|
+
# PI_SOFT_HOLD_PCT=80
|
|
51
|
+
# optional per-job token budget: the runner aborts the agent once cumulative usage exceeds it.
|
|
52
|
+
# LAGGING (spends before it can see the total) -- a single-job runaway backstop, not before-the-spend;
|
|
53
|
+
# PI_MAX_TURNS stays the proactive lever. Unset = per-job token budget disabled (usage is still recorded).
|
|
54
|
+
# PI_MAX_TOKENS=
|
|
55
|
+
# optional daily token cap: once a day's recorded spend reaches it, the NEXT job is refused pre-container.
|
|
56
|
+
# Check-AFTER by nature (token cost is only known post-run), unlike PI_DAILY_CAP; unset = disabled
|
|
57
|
+
# PI_DAILY_TOKEN_CAP=
|
|
58
|
+
# how many jobs run in parallel
|
|
59
|
+
PI_CONCURRENCY=3
|
|
32
60
|
|
|
33
61
|
# --- Infrastructure ---
|
|
34
62
|
VALKEY_URL=redis://127.0.0.1:6379
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
#
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
#
|
|
63
|
+
# The Valkey's password, per deployment: `pi-dispatch init` writes a new one here (and creates this file readable by you
|
|
64
|
+
# alone), and `up` / `service install` add one to a deployment that has none and restart its Valkey with it (the queue is
|
|
65
|
+
# kept). Every client sends it: the worker, the receiver, the CLI, the panel and doctor. Without it any local account can
|
|
66
|
+
# read, enqueue or delete this deployment's jobs. Hex or base64url only (A-Z a-z 0-9 - _), at least 16 characters.
|
|
67
|
+
# A key of its own, never inside VALKEY_URL, which doctor prints and the panel's pointer file stores; a VALKEY_URL with
|
|
68
|
+
# its own password (a managed Valkey) is left as it is, and that password wins. With PI_VALKEY_SHARED=1 below, set it to
|
|
69
|
+
# the password of the account whose Valkey it is. docker compose reads it with --env-file .env (docs/podman.md, docs/github.md).
|
|
70
|
+
# VALKEY_PASSWORD=
|
|
71
|
+
# docker compose only: the port deploy/docker-compose.yml publishes its Valkey on, 127.0.0.1:<this>. Default 6379. Keep it
|
|
72
|
+
# equal to VALKEY_URL's port: `pi-dispatch up` and /dispatch setup write it here when that is not 6379 (never over a value
|
|
73
|
+
# already set), and doctor flags one that disagrees. Not read by the worker, the receiver or the CLI, which dial VALKEY_URL.
|
|
74
|
+
# PI_VALKEY_PORT=
|
|
75
|
+
# podman venue: the worker (at every start), `service install`, `up` and doctor take a Valkey that answers VALKEY_URL only when THIS account
|
|
76
|
+
# (or one of its containers) holds it. One held by another account, by root (docker-proxy, a rootful container) or by a
|
|
77
|
+
# system service is refused, naming its owner: give this account its own port instead (VALKEY_URL=redis://127.0.0.1:6380).
|
|
78
|
+
# Set this to 1 only when that Valkey is shared ON PURPOSE: every account using it can read and drain the others' jobs
|
|
79
|
+
# Read from this file ONLY: exporting it in a shell does nothing, and the commands say so
|
|
80
|
+
# PI_VALKEY_SHARED=1
|
|
81
|
+
# what this machine calls itself. Default: your hostname, lowercased and reduced to [A-Za-z0-9._-]
|
|
82
|
+
# Lands on every worker log line and in every run record, and identifies this host to the others when you run more than one
|
|
83
|
+
# Set it if your hostname is something you would rather not have in your own run history (a laptop often carries a person's name)
|
|
84
|
+
# Refused at boot if it is not [A-Za-z0-9._-], does not start with a letter or digit, is over 64 characters, or ends in .json or .log
|
|
85
|
+
# SETTING IT TURNS ON MULTI-HOST ROUTING (docs/multi-host.md): work only this machine can do (a cron folder, a chained child,
|
|
86
|
+
# a manual run) is enqueued to this host's own queue instead of the shared one, and the wait-check and scoped-concurrency
|
|
87
|
+
# ceilings become fleet-wide instead of per process. Leave it unset on a single-machine deployment and nothing changes
|
|
88
|
+
# PI_WORKER_NAME=
|
|
89
|
+
# the DEFAULT job image. Any trigger may name its own with "image" in triggers.json (docs/job-image.md)
|
|
90
|
+
# Jobs run with --pull=never: pull or BUILD every image you name -- the worker never fetches one at job time, and doctor checks presence
|
|
91
|
+
# docker pull ghcr.io/edgehero/pi-job:latest && docker tag ghcr.io/edgehero/pi-job:latest pi-job:latest (or build image/Dockerfile)
|
|
92
|
+
# On rootful Podman, run these and every docker command through a docker context pointed at podman.sock, with the real docker CLI (docs/podman.md)
|
|
93
|
+
PI_JOB_IMAGE=pi-job:latest
|
|
94
|
+
# where per-job /job inputs live (default: <OS temp dir>/pi-dispatch-<your uid>/jobs, one per account, created 0700)
|
|
95
|
+
# Another account's directory there, or one you set that another account owns, stops the worker at boot (exit 2), and doctor names it
|
|
96
|
+
# Pin it (and PI_SANDBOX_DIR) yourself if the worker runs as a different account than your /dispatch panel or `pi-dispatch sandbox`: the default is per account
|
|
97
|
+
# PI_JOBS_DIR=
|
|
98
|
+
# where per-job status records (and optional raw logs) land (default: ~/.pi-dispatch/logs)
|
|
99
|
+
# DURABLE by default, outside any repo and outside the OS temp dir, so a reboot keeps your run history. `pi-dispatch up` writes the resolved default here explicitly
|
|
100
|
+
# Unless YOUR shell already sets PI_LOGS_DIR to a relative path or one inside the deployment folder, which `up` refuses to persist and says so
|
|
101
|
+
# It pins whatever the account default RESOLVES to, and refuses only a value YOUR shell exported that is relative or inside this folder
|
|
102
|
+
# That refusal exists because the retention sweep below deletes every .log and .json past its window, and a deployment folder holds triggers.json
|
|
103
|
+
# Pin it yourself if the worker runs as a different account than your /dispatch panel: the default is per USER, and the two must agree or the panel shows an empty history
|
|
104
|
+
# PI_LOGS_DIR=
|
|
105
|
+
# default 0; set 1 to ALSO write raw container output to logs/<jobId>.log -- PII-bearing (issue/comment text), host-only (never mounted into the container), off by default (opt-in)
|
|
106
|
+
# PI_CAPTURE_JOB_LOGS=
|
|
107
|
+
# default 30; prunes logs older than N days at boot AND on the retention timer below; 0 = keep forever
|
|
108
|
+
# PI_LOG_RETENTION_DAYS=
|
|
109
|
+
# default 24; how often the three retention sweeps (logs, sandboxes, sessions) re-run while the worker is up
|
|
110
|
+
# The sandbox one covers two things since issue #337: the retained directories, and the pi-sandbox-<id>-net network of a run whose directory is already gone
|
|
111
|
+
# 0 = BOOT-ONLY, which is what every version before this one did: a worker that never restarts never re-sweeps, so a configured window described nothing
|
|
112
|
+
# At 0 that network waits for the boot AFTER the one that removes the directory, so on a worker that never restarts `docker network rm` stays the only route
|
|
113
|
+
# The window is a FLOOR, not a ceiling: a file dies on the first sweep AFTER its window closes, so the real ceiling is its window plus this interval (up to 48h for a sandbox at both defaults)
|
|
114
|
+
# Refused above 168 (one week): setInterval clamps a longer delay to 1ms, and a sweep slower than a week is 0 with extra steps
|
|
115
|
+
# PI_SWEEP_INTERVAL_HOURS=
|
|
116
|
+
# where a finished run's directory is kept so you can re-open it (default: <PI_JOBS_DIR>/sandboxes, mode 0700)
|
|
117
|
+
# `pi-dispatch sandbox <jobId>` starts a fresh container from the same image with the same workspace and NO credentials (docs/sandbox.md)
|
|
118
|
+
# A retained directory holds the run's clone plus its prompt.md/event.json -- so issue text. Host-only; never mounted into a job
|
|
119
|
+
# PI_SANDBOX_DIR=
|
|
120
|
+
# default 24. NOTE: 0 means OFF here -- nothing is retained and cleanup deletes as it always did
|
|
121
|
+
# This is the OPPOSITE of PI_LOG_RETENTION_DAYS/PI_SESSIONS_TTL_DAYS, where 0 means keep forever
|
|
122
|
+
# There is deliberately no keep-forever value: one repo clone per run with no ceiling is a disk bomb. Use --pin for the one run worth keeping
|
|
123
|
+
# PI_SANDBOX_RETENTION_HOURS=
|
|
124
|
+
# default 7; `pi-dispatch sandbox <jobId> --pin` extends THAT run to now + this many days. Still swept afterwards
|
|
125
|
+
# PI_SANDBOX_PIN_DAYS=
|
|
126
|
+
# default 30; bash's own TMOUT inside a sandbox, so a forgotten shell closes itself; 0 = no idle logout
|
|
127
|
+
# Honest gap: TMOUT does not tick while a foreground command runs, so a sandbox left serving an app stays up (`pi-dispatch sandbox --list` finds it)
|
|
128
|
+
# PI_SANDBOX_IDLE_MINUTES=
|
|
129
|
+
# NO DEFAULT, on purpose. Where persisted agent transcripts live, so a trigger with "resume": true can continue the session that opened the PR (docs/sessions.md)
|
|
130
|
+
# Unset = the feature is unavailable and an armed trigger refuses PRE-SPEND rather than running unpersisted and looking like it worked
|
|
131
|
+
# A transcript is the most PII-bearing thing this system stores -- issue text, file contents, tool output, the agent's own reasoning. Mode 0700, host-only, OUTSIDE any git repo
|
|
132
|
+
# Deliberately not defaulted AT ALL, unlike PI_LOGS_DIR: unset means the feature is unavailable and an armed trigger refuses pre-spend rather than running unpersisted
|
|
133
|
+
# PI_SESSIONS_DIR=
|
|
134
|
+
# default 14; a transcript older than this is not resumed AND is swept at boot and on the retention timer; 0 = keep forever
|
|
135
|
+
# Enforced at OPEN as well as at boot: a stale transcript is a live input to a future job, not debris
|
|
136
|
+
# PI_SESSIONS_TTL_DAYS=
|
|
137
|
+
# default 8388608 (8 MiB); a transcript larger than this is not resumed; 0 = no cap
|
|
138
|
+
# Not disk hygiene -- an oversized transcript is a prefill nobody sized PI_MAX_TOKENS for
|
|
139
|
+
# PI_SESSION_MAX_BYTES=
|
|
140
|
+
# unset/0 = no bound. How old the CONVERSATION may be, read from the session header's own timestamp
|
|
141
|
+
# A DIFFERENT CLOCK from PI_SESSIONS_TTL_DAYS, not a finer setting of it: that one reads mtime, which every COMPLETED run refreshes,
|
|
142
|
+
# so a lineage that keeps finishing work never expires however old its first turn is. This one measures from the first turn
|
|
143
|
+
# A header with no readable timestamp is refused rather than assumed young (reason: conversation-too-old)
|
|
144
|
+
# PI_SESSION_MAX_AGE_DAYS=
|
|
145
|
+
# unset/0 = no bound. How many times in a row one key may be resumed before the next job starts fresh
|
|
146
|
+
# The bound a long lineage actually needs: age and size grow slowly, a chain grows once per run
|
|
147
|
+
# The count is kept whether or not the bound is set, so setting it later takes effect on the next job rather than N jobs later
|
|
148
|
+
# PI_SESSION_MAX_RESUME_CHAIN=
|
|
149
|
+
# unset = no bound; 1-100. Refuse a resume when the saved session's context is already this full, e.g. 80
|
|
150
|
+
# A SAFETY bound before an economic one: past pi's compaction threshold a resumed job replays a model-written summary of the transcript,
|
|
151
|
+
# written while that model was reading attacker-authored text (specs/open-questions.md, OQ-003). This ceiling is the host's own, and pi's threshold stays pi's
|
|
152
|
+
# The measurement comes from the job image's runner, so it is inert until you are running an image that reports it and each key has completed one run since
|
|
153
|
+
# PI_SESSION_MAX_CONTEXT_PCT=
|
|
154
|
+
# unset = a run.resume job REFUSES to mint under GITHUB_AUTH_SOURCE=gh, pre-spend
|
|
155
|
+
# That source is your whole gh login: full-scope and non-expiring, and a transcript is a FILE -- any command that echoed an auth header persists it
|
|
156
|
+
# Prefer GITHUB_AUTH_SOURCE=app or a short-expiry fine-grained PAT. Set exactly 1 to accept the trade explicitly (SECURITY.md, docs/sessions.md)
|
|
157
|
+
# PI_SESSIONS_ALLOW_GH_SOURCE=
|
|
158
|
+
# ABSOLUTE path to the unified triggers.json, read by BOTH worker and receiver (a relative path resolves against the service's WorkingDirectory).
|
|
159
|
+
# Unset = cron disabled for the worker; the receiver falls back to ./triggers.json in the folder it starts from (what `pi-dispatch init` scaffolds)
|
|
160
|
+
# and refuses to start when neither exists (it holds the label/comment/pull_request trigger config)
|
|
161
|
+
# PI_TRIGGERS_FILE=
|
|
162
|
+
# The two keys below are the only ones where uncommenting WITHOUT filling in a path is worse than leaving
|
|
163
|
+
# them alone: the worker keeps an empty value and refuses to start, rather than treating it as unset.
|
|
164
|
+
# `pi-dispatch up` fills them in for you; by hand, write the path or leave the line commented.
|
|
165
|
+
# ABSOLUTE path to pause-windows.json — "quiet hours" per folder/repo (pause runs between certain times/days/dates, auto-resume). Unset = feature off. See docs/pause-windows.md
|
|
166
|
+
# `pi-dispatch up` sets this to the pause-windows.json in the folder it runs in, which is the one `init` scaffolds and the one the panel defaults to, so all three agree
|
|
167
|
+
# PI_PAUSE_WINDOWS_FILE=
|
|
168
|
+
# ABSOLUTE path to scoped-limits.json — per repo/folder job-count caps (day/week/month, refused pre-spend as scope-cap) and max concurrent jobs per scope (excess deferred, never dropped).
|
|
169
|
+
# Unset = no scoped caps or concurrency; the one-job-per-folder mutex for local jobs is always on and needs no file. See docs/scoped-limits.md
|
|
170
|
+
# `pi-dispatch up` sets this to the scoped-limits.json in the folder it runs in, the same way and for the same reason as PI_PAUSE_WINDOWS_FILE above
|
|
171
|
+
# PI_SCOPED_LIMITS_FILE=
|
|
172
|
+
# path to subscriptions.json — operator-declared subscription plan prices (the admin defaults to ./subscriptions.json in its working directory). Read by the ADMIN EXTENSION only, never at job time.
|
|
173
|
+
# Subscription-backed providers bill 0 per run (their rate tables are all zeros), so this file is where the real price lives — cost analytics only; it changes no routing, auth, or job behavior
|
|
174
|
+
# PI_SUBSCRIPTIONS_FILE=
|
|
175
|
+
# ABSOLUTE path to the runtime settings overlay (default: ~/.pi-dispatch/settings.json); edited by the admin extension, read by the worker per job
|
|
176
|
+
# DURABLE by default, beside the run history. Losing it does not stop jobs, it WIDENS them: a missing file reads as an empty overlay and every panel-set cap falls back to this file's values
|
|
177
|
+
# Same per-user caveat as PI_LOGS_DIR, and the same fix: `pi-dispatch up` writes the resolved account default here explicitly, so the worker and the panel cannot drift apart silently
|
|
178
|
+
# The setup wizard's deployment pointer does NOT carry this key (it carries the triggers, pause-windows, scoped-limits and subscriptions paths), so an explicit line here is what keeps the two in step
|
|
179
|
+
# PI_SETTINGS_FILE=
|
|
180
|
+
# ABSOLUTE path to the deployment pointer the /dispatch panel reads to find a deployment built elsewhere
|
|
181
|
+
# (default: <your pi agent dir>/pi-dispatch-deployment.json). Read by the ADMIN EXTENSION only; the worker and receiver never look at it.
|
|
182
|
+
# Your own environment still wins key by key, so this points the panel at a deployment, it does not override one
|
|
183
|
+
# PI_DISPATCH_DEPLOYMENT_FILE=
|
|
184
|
+
# where `/dispatch insights` writes its HTML artifact (default: <OS temp dir>/pi-dispatch-<your uid>/graph). Admin extension only. See docs/insights.md
|
|
185
|
+
# PI_GRAPH_DIR=
|
|
93
186
|
|
|
94
187
|
# --- Reuse your existing pi setup in every job (see docs/global-pi-overlay.md) ---
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
#
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
#
|
|
109
|
-
|
|
188
|
+
# dir with your host pi setup (models.json/skills/APPEND_SYSTEM.md), mounted /opt/pi-global:ro into every job, layered UNDER each repo's .pi/. Unset = off. Stage it with: pi-dispatch import-pi
|
|
189
|
+
# PI_GLOBAL_PI_DIR=
|
|
190
|
+
# the overlay's extensions LOAD by default (staging them with import-pi, which prints each one, is the vetting step). Set exactly 0 to keep them staged but dormant.
|
|
191
|
+
# Unset, empty and the legacy 1 all mean LOAD. ANY other value refuses to boot -- a typo must never silently leave code running against adversarial input with open egress.
|
|
192
|
+
# This knob covers the OVERLAY only. A serviced repo's own /workspace/.pi/extensions load regardless (they are default-branch, merge-gated) -- see SECURITY.md.
|
|
193
|
+
# PI_GLOBAL_ALLOW_EXTENSIONS=
|
|
194
|
+
# where YOUR pi setup lives on this host (default: ~/.pi/agent). `pi-dispatch import-pi` reads its models.json,
|
|
195
|
+
# skills and APPEND_SYSTEM.md from here, and the panel looks here for the deployment pointer.
|
|
196
|
+
# It is also read AT JOB TIME: with no provider key in the environment the worker reads this directory's auth.json
|
|
197
|
+
# for one (on by default; PI_AUTH_FROM_PI=0 turns it off), so pointing this at the wrong place makes every job
|
|
198
|
+
# refuse pre-spend with no credential for the provider. It is the SOURCE that gets staged, never the thing mounted:
|
|
199
|
+
# PI_GLOBAL_PI_DIR above is what a job actually sees. Set it if your pi lives somewhere other than your home directory
|
|
200
|
+
# PI_CODING_AGENT_DIR=
|
|
201
|
+
# path to pi-packages.json (default: ./pi-packages.json; --packages-file wins). Read ONLY by `pi-dispatch import-pi --with-packages`, never at job time.
|
|
202
|
+
# Staged packages/ rides INSIDE PI_GLOBAL_PI_DIR -- no separate mount, no separate env dir -- and loads for EVERY job once staged; decline it PER TRIGGER with "packages": false in triggers.json, NOT by an env flag.
|
|
203
|
+
# Versions must be EXACT (no ^ ~ * or latest); staging uses --ignore-scripts, so a package needing a build step is staged INCOMPLETE and import-pi warns.
|
|
204
|
+
# PI_PACKAGES_FILE=
|
|
205
|
+
# comma-separated extra env var NAMES to forward into the container (e.g. a CUSTOM provider's key). Explicit allowlist, not a pass-through.
|
|
206
|
+
# GITHUB_TOKEN/GH_TOKEN are refused here -- the worker mints per-job tokens
|
|
207
|
+
# PI_FORWARD_ENV=
|
|
110
208
|
|
|
111
209
|
# --- Per-trigger vault secrets (docs/secrets.md, issue #225) ---
|
|
112
210
|
# A trigger may name references ("secrets": { "STRIPE_KEY": "op://ci/stripe/api-key" }) and the profile that
|
|
113
211
|
# resolves them. The worker runs YOUR script once per reference, on the HOST, before the container starts.
|
|
114
212
|
# The job receives values; it never holds your manager's credential and cannot enumerate your vault.
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
213
|
+
# name:/absolute/path pairs, comma separated (each entry splits on its FIRST colon, so a Windows C:\ path parses).
|
|
214
|
+
# A resolver is one line: `exec op read --no-newline "$1"`, `exec pass show "$1"`, `exec vault kv get -field=... "$1"`.
|
|
215
|
+
# Exit 2 if the reference is wrong, exit 1 if you could not reach your manager (that one retries). Unset = feature off.
|
|
216
|
+
# PI_SECRET_PROFILES=
|
|
217
|
+
# OS-path-delimited (; on Windows, : elsewhere) directories a PANEL-declared resolver may live in.
|
|
218
|
+
# Default empty = fail-closed: `/dispatch secrets add` can declare nothing, and only PI_SECRET_PROFILES above is honoured.
|
|
219
|
+
# PI_SECRET_RESOLVER_ROOTS=
|
|
220
|
+
# default 10000, per reference. Sits before a paid container and is multiplied by the reference count, so tighter than doctor's 30s.
|
|
221
|
+
# PI_SECRET_RESOLVE_TIMEOUT_MS=
|
|
121
222
|
|
|
122
223
|
# --- Holding a job until something else happens (docs/wait-for.md, issue #230) ---
|
|
123
224
|
# A trigger may carry "waitFor": [{ "after": "2026-09-01T09:00:00Z" }, { "profile": "jira" }]. The job is
|
|
@@ -125,45 +226,82 @@ PI_JOB_IMAGE=pi-job:latest # the DEFAULT job image. Any trigger may nam
|
|
|
125
226
|
# survives a restart, and runs exactly once when every condition clears. An `after` is answered from the
|
|
126
227
|
# clock and costs nothing. A `profile` names one of YOUR scripts, which the worker runs on the HOST with the
|
|
127
228
|
# job's id-only target as its first argument.
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
229
|
+
# name:/absolute/path pairs, comma separated (each entry splits on its FIRST colon, so a Windows C:\ path parses). Unset = feature off.
|
|
230
|
+
# Map the codes yourself, because a bare pipeline cannot: `s=$(jira issue view "$1" --plain) || exit 1` then
|
|
231
|
+
# `case "$s" in *"Status: Done"*) exit 0;; *) exit 3;; esac`. `grep -q` alone exits 1 for "no match", which is a counted FAULT, not "not yet".
|
|
232
|
+
# Exit 0 to go, 3 for not yet, 2 if it will NEVER clear (terminal), 1 if you could not tell (held, and counted).
|
|
233
|
+
# PI_WAIT_PROFILES=
|
|
234
|
+
# default 10000, per check. Sits before a paid container and holds a concurrency slot while it runs.
|
|
235
|
+
# PI_WAIT_CHECK_TIMEOUT_MS=
|
|
236
|
+
# default 60000, floored at 30000: a positive value BELOW the floor is raised to it, while 0, a negative, a fraction or junk still refuses at boot.
|
|
237
|
+
# The base cadence; it backs off toward 15 minutes, or toward YOUR value if you set a longer one.
|
|
238
|
+
# PI_WAIT_INTERVAL_MS=
|
|
239
|
+
# default 1. How many checks may run at once. Will be held below PI_CONCURRENCY at the gate, so a check can never take the last slot from a paid job.
|
|
240
|
+
# PI_WAIT_CHECK_SLOTS=
|
|
241
|
+
# default 86400000 (24h). A profile hold terminates here with a named reason: a dependency, unlike a pause window, does not end on its own.
|
|
242
|
+
# PI_WAIT_MAX_MS=
|
|
243
|
+
# default 2592000000 (30d). The separate, larger ceiling on an `after` instant, which polls nothing while it waits.
|
|
244
|
+
# PI_WAIT_AFTER_MAX_MS=
|
|
245
|
+
# default 96, per job. Nothing in the spend caps sees a check, so this is the bound that does.
|
|
246
|
+
# PI_WAIT_MAX_CHECKS=
|
|
247
|
+
# default 5 consecutive "could not tell" answers. What makes a broken check loud in minutes instead of silent for a day.
|
|
248
|
+
# PI_WAIT_MAX_FAULTS=
|
|
249
|
+
|
|
250
|
+
# --- Telling you when a paid job dies (docs/notifications.md, issue #288) ---
|
|
251
|
+
# Every free refusal already comments on its issue. This is the other half: when a job that SPENT money
|
|
252
|
+
# ends wrong (the 30-minute kill, an in-container budget stop, a final infrastructure failure), the worker
|
|
253
|
+
# runs YOUR one command with id-only arguments: <jobId> <outcome> <reason> <host>. Never a title, a body,
|
|
254
|
+
# or any payload text. You wire ntfy, Slack or mail yourself in one line; this project ships no transport.
|
|
255
|
+
# absolute path to ONE executable. Exec'd directly (an argv array, never a shell), fire and forget,
|
|
256
|
+
# at most once per job, and a hook fault can never change a job's outcome. Its exit code is logged
|
|
257
|
+
# (`on_failure`) and otherwise unread. Unset = off, byte-identically.
|
|
258
|
+
# PI_ON_FAILURE=
|
|
259
|
+
# default 10000. SIGTERM at the deadline, SIGKILL 2s later. Bounds a leaked child, not a job: the
|
|
260
|
+
# hook runs after the outcome is decided and holds nothing.
|
|
261
|
+
# PI_ON_FAILURE_TIMEOUT_MS=
|
|
140
262
|
|
|
141
|
-
|
|
263
|
+
# tear down a scheduler after N consecutive stalls (money backstop)
|
|
264
|
+
PI_SCHEDULER_STALL_MAX=2
|
|
142
265
|
|
|
143
266
|
# --- Container backend: where a job's container is built, and what that place guarantees (docs/backends.md) ---
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
267
|
+
# which backends this deployment blesses, comma separated (default: local, the docker daemon on this host).
|
|
268
|
+
# podman is this worker account's own rootless Podman: jobs run as the worker's uid with --userns=keep-id, and a rootful or remote Podman is refused there (docs/podman.md).
|
|
269
|
+
# A trigger's run.backend selects among these; one that names none runs on the first. local is not required: a list without it never asks this host's docker CLI anything at boot. An unknown name refuses to boot.
|
|
270
|
+
# With podman listed, `pi-dispatch up` and `pi-dispatch service install` start Valkey and the egress proxy as Quadlet units in this account's user manager (docs/podman.md). service install reads this key from THIS file, never from your shell.
|
|
271
|
+
# PI_BACKENDS=
|
|
272
|
+
# the minimum every backend above must declare, as property=word pairs: e.g. egress=enforced,nonRoot=asserted
|
|
273
|
+
# Words are enforced (this worker builds it and a test reads it back), asserted (something outside the worker provides it, unverifiable from here) or absent.
|
|
274
|
+
# A floor naming a switched-off control refuses to boot: asking for egress=enforced while PI_EGRESS=0 is a bound you would believe in and not have.
|
|
275
|
+
# credentialTransit=enforced also refuses to boot, and refuses each job, while the docker CLI resolves an endpoint off this host (DOCKER_HOST, a docker context) or determinately fails to say which (a CLI that timed out is retried instead).
|
|
276
|
+
# isolation=enforced and mountSet=enforced refuse the same way while the daemon is not observed applying a container's bounds or the runtime adds mounts of its own: always for isolation on Podman through its Docker API, and for mountSet there without an empty /etc/containers/mounts.conf (docs/podman.md).
|
|
277
|
+
# On the podman backend the same three words are observed from `podman info` and the account's own files instead: the cgroup controllers delegated, an empty mounts.conf (the user's ~/.config/containers/mounts.conf wins over /etc's), and a service that is not remote.
|
|
278
|
+
# `pi-dispatch doctor` prints enforced quietly, asserted as a warning naming who asserts it, and absent as a failure.
|
|
279
|
+
# Env-only, deliberately: a bound that can be widened from the surface it bounds is not a bound.
|
|
280
|
+
# PI_BACKEND_FLOOR=
|
|
151
281
|
|
|
152
282
|
# --- Egress policy: what a job container may reach on the network (docs/egress.md) ---
|
|
153
|
-
# ON by default. Every job runs on its own --internal Docker network
|
|
154
|
-
#
|
|
155
|
-
# START THE PROXY: docker compose -f deploy/docker-compose.yml --profile egress up -d
|
|
283
|
+
# ON by default. Every job runs on its own --internal Docker network whose only other member is an allowlist
|
|
284
|
+
# proxy, with no route off this host, and a job whose policy cannot serve it is refused BEFORE it spends a slot.
|
|
285
|
+
# START THE PROXY: docker compose --env-file .env -f deploy/docker-compose.yml --profile egress up -d
|
|
286
|
+
# (on the rootless podman backend, `pi-dispatch up` or `pi-dispatch service install` starts it as a Quadlet unit instead)
|
|
156
287
|
# Until you do, every job is refused pre-spend (loud, free, and naming that command). The hosts live in
|
|
157
288
|
# egress-allowlist.conf next to this file (`pi-dispatch init` writes it, and never overwrites it).
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
289
|
+
# exactly 0 (off) or 1/unset (on). Any other value refuses to boot -- a typo must never leave you believing you have a policy you do not.
|
|
290
|
+
# PI_EGRESS=0
|
|
291
|
+
# the proxy container the per-job network is built around (default: pi-dispatch-egress-proxy)
|
|
292
|
+
# While the policy is armed, PI_FORWARD_ENV must not carry HTTPS_PROXY/HTTP_PROXY/NO_PROXY/NODE_USE_ENV_PROXY: the policy sets them itself, and a forwarded value would redirect every job while looking like the control working.
|
|
293
|
+
# PI_EGRESS_PROXY=
|
|
161
294
|
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
295
|
+
# OS-path-delimited allowlist (; on Windows, : elsewhere) of folders the model-callable dispatch_run may target; default empty = fail-closed (dispatch_run refuses every folder until you opt in)
|
|
296
|
+
# PI_DISPATCH_RUN_ROOTS=
|
|
297
|
+
# per-hour cap on model-invoked dispatch_run enqueues; 0 disables the tool
|
|
298
|
+
# PI_DISPATCH_RUN_PER_HOUR=3
|
|
299
|
+
# set 1 to render the admin extension's views with plain ASCII instead of box-drawing/sparkline-ramp glyphs (for glyph-width-hostile terminals); read at extension load
|
|
300
|
+
# PI_DISPATCH_ASCII=
|
|
301
|
+
# max follow-up chain depth from a job's /outbox; 0 = chaining kill-switch
|
|
302
|
+
# PI_CHAIN_DEPTH_MAX=1
|
|
303
|
+
# max request-<n>.json collected per completed job
|
|
304
|
+
# PI_CHAIN_MAX_PER_JOB=2
|
|
167
305
|
|
|
168
306
|
# --- GitHub trigger (receiver + worker auth) ---
|
|
169
307
|
# Webhook receiver. Required only when your triggers name github (or GITHUB_AUTH_SOURCE is set): every forge arm is
|
|
@@ -171,17 +309,27 @@ PI_SCHEDULER_STALL_MAX=2 # tear down a scheduler after N consecutive
|
|
|
171
309
|
WEBHOOK_SECRET=
|
|
172
310
|
RECEIVER_PORT=3000
|
|
173
311
|
RECEIVER_BIND=0.0.0.0
|
|
312
|
+
# how long a BOOTING receiver (serve and poll alike, every configured forge) keeps retrying an
|
|
313
|
+
# identity lookup that failed TRANSIENTLY -- a 502, a refused connection, the forge restarting --
|
|
314
|
+
# before exiting 1 for the supervisor to start another window. Default 600, floored at 60: a
|
|
315
|
+
# positive value below the floor is raised to it, while 0, a negative, a fraction or junk refuses
|
|
316
|
+
# at boot. Retries run IN-PROCESS (5s between attempts, backing off to 30s), so the bound is the
|
|
317
|
+
# same under systemd, launchd and nssm -- and the floor keeps systemd's StartLimitBurst crash-loop
|
|
318
|
+
# bound meaningful: a retrying boot exits at most once per window, never five times in a minute.
|
|
319
|
+
# A determinate refusal (a bad token, an untrusted CA) still exits 2 immediately, never retried.
|
|
320
|
+
# RECEIVER_IDENTITY_RETRY_SECONDS=
|
|
174
321
|
# Worker GitHub auth: source is gh | pat | app (default gh)
|
|
175
322
|
# gh = your full login scopes reach token-carrying jobs (doctor warns and names them); pat/app = narrower
|
|
176
323
|
GITHUB_AUTH_SOURCE=gh
|
|
177
324
|
# For GITHUB_AUTH_SOURCE=pat: a repo-scoped, short-expiry fine-grained PAT
|
|
178
325
|
GITHUB_PAT=
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
326
|
+
# which variable above actually holds the PAT. Default GITHUB_PAT; set this only if your
|
|
327
|
+
# secrets manager insists on its own name and you would rather not copy the value to a second key.
|
|
328
|
+
# The NAME is not checked against anything: whatever you put here is read verbatim, so a typo
|
|
329
|
+
# reads an empty variable and the worker refuses at boot naming the name you chose. Pointing it
|
|
330
|
+
# at a variable that holds something else (GITHUB_APP_PRIVATE_KEY, say) is the mistake worth
|
|
331
|
+
# knowing about, because nothing stops it and the PAT path would then send that value to GitHub.
|
|
332
|
+
# GITHUB_PAT_VAR=
|
|
185
333
|
# For GITHUB_AUTH_SOURCE=app (optional; required for multi-tenant)
|
|
186
334
|
# `pi-dispatch setup github` fills all three in one browser click (App Manifest flow) and writes the PEM 0600
|
|
187
335
|
GITHUB_APP_ID=
|
|
@@ -198,16 +346,18 @@ GITHUB_APP_PRIVATE_KEY=
|
|
|
198
346
|
# same queue as the webhook path; about a minute of latency instead of a second. Nothing here is read by
|
|
199
347
|
# `pi-dispatch-receiver serve`, and no WEBHOOK_SECRET is needed to poll: there is no inbound delivery to
|
|
200
348
|
# verify, because the poller originates every request itself. See docs/polling.md.
|
|
201
|
-
#
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
#
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
349
|
+
# WHICH repos to watch: comma-separated owner/name (e.g. acme/web,acme/api). Duplicates are dropped.
|
|
350
|
+
# Each entry must be exactly owner/name -- one slash, no spaces -- or the receiver refuses at boot naming the bad entry.
|
|
351
|
+
# Leave it UNSET only with GITHUB_AUTH_SOURCE=app: the poller then lists the App installation's own repos and
|
|
352
|
+
# re-lists every tenth cycle, so installing the App on a new repo starts polling it without an edit here.
|
|
353
|
+
# Unset under any other auth source is a boot refusal, deliberately: a PAT names no repo set, and a poller
|
|
354
|
+
# watching nothing looks exactly like a poller that is working.
|
|
355
|
+
# POLL_REPOS=
|
|
356
|
+
# seconds between cycles. Default 60, floored at 30: a positive value BELOW the floor is raised to it,
|
|
357
|
+
# while 0, a negative, a fraction or junk still refuses at boot.
|
|
358
|
+
# GitHub asks pollers to respect its own x-poll-interval hint, which is honored as a MINIMUM when it arrives,
|
|
359
|
+
# so a busy hour slows the loop down rather than the loop hammering the API. A typo'd 1 must not turn this into a hammer.
|
|
360
|
+
# POLL_INTERVAL_SECONDS=
|
|
211
361
|
|
|
212
362
|
# --- GitLab trigger (receiver + worker auth) ---
|
|
213
363
|
# Optional. Set these only to service GitLab projects; leaving GITLAB_TOKEN unset means no /gitlab
|
|
@@ -217,13 +367,14 @@ GITHUB_APP_PRIVATE_KEY=
|
|
|
217
367
|
# scope that can post a note -- GitLab offers no contents-vs-issues split -- so scope it to one project
|
|
218
368
|
# and rotate it (CONST-TOKEN-SCOPED-PER-JOB). A GROUP token reaches every project in the group.
|
|
219
369
|
GITLAB_TOKEN=
|
|
220
|
-
#
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
370
|
+
# accepts exactly one value, "pat", which is also the default, so there is nothing to set here.
|
|
371
|
+
# It exists to REFUSE the wrong assumption rather than to offer a choice: GITHUB_AUTH_SOURCE has
|
|
372
|
+
# three sources, and an operator who reasons by symmetry and writes app here gets a sentence at
|
|
373
|
+
# boot saying GitLab has no App equivalent, instead of a knob that is silently ignored.
|
|
374
|
+
# The refusal needs GITLAB_TOKEN to be set: with no token there is no GitLab to configure, the
|
|
375
|
+
# whole block is skipped, and a stray app here really is ignored. Same for the two below.
|
|
376
|
+
# FORGEJO_AUTH_SOURCE and AZURE_AUTH_SOURCE are the same variable for the same reason.
|
|
377
|
+
# GITLAB_AUTH_SOURCE=
|
|
227
378
|
# Your instance root. Only for self-hosted GitLab.
|
|
228
379
|
GITLAB_URL=https://gitlab.com
|
|
229
380
|
# How the receiver verifies a delivery. REQUIRED once any GITLAB_* variable is set, and deliberately not
|
|
@@ -245,7 +396,8 @@ FORGEJO_URL=
|
|
|
245
396
|
# expire: there is no App or installation token, so rotation is the whole mitigation
|
|
246
397
|
# (CONST-TOKEN-SCOPED-PER-JOB).
|
|
247
398
|
FORGEJO_TOKEN=
|
|
248
|
-
#
|
|
399
|
+
# only "pat", the default. See GITLAB_AUTH_SOURCE above for why it exists at all.
|
|
400
|
+
# FORGEJO_AUTH_SOURCE=
|
|
249
401
|
# The harness account's NUMERIC id. Required when the token above is repository-scoped, because such a
|
|
250
402
|
# token may not carry read:user and therefore cannot call GET /user. The receiver refuses to boot without an
|
|
251
403
|
# identity from one source or the other: the bot-loop guard compares against it, and an unresolved identity
|
|
@@ -264,7 +416,8 @@ AZURE_ORG_URL=
|
|
|
264
416
|
# permissions in Project Settings -- not from the token's scopes. It also needs vso.graph, to resolve the
|
|
265
417
|
# actor's project membership before a job may be enqueued.
|
|
266
418
|
AZURE_TOKEN=
|
|
267
|
-
#
|
|
419
|
+
# only "pat", the default. See GITLAB_AUTH_SOURCE above for why it exists at all.
|
|
420
|
+
# AZURE_AUTH_SOURCE=
|
|
268
421
|
# REQUIRED once any AZURE_* variable is set, and deliberately not defaulted: both modes are shared-secret
|
|
269
422
|
# compares that cover no bytes, so which header carries the secret must be a choice somebody made.
|
|
270
423
|
# basic -- Authorization: Basic <base64>, the credential you set on the subscription
|