ework-aio 0.2.5 → 0.2.7
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/package.json +3 -1
- package/scripts/e2e-install.sh +213 -0
- package/src/cli.ts +1 -1
- package/src/commands/install.ts +19 -0
- package/src/config.ts +26 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ework-aio",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "All-in-one installer for ework (issue tracker) + ework-daemon (AI bridge) + opencode-ework (plugin). One command: npm i -g ework-aio && ework-aio install.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
"start": "bun bin/ework-aio.js",
|
|
42
42
|
"check": "tsc --noEmit",
|
|
43
43
|
"test": "bun test",
|
|
44
|
+
"test:e2e": "bash scripts/e2e-install.sh",
|
|
45
|
+
"test:regression": "bash scripts/regression.sh",
|
|
44
46
|
"postinstall": "echo '\\n ework-aio installed. Run \\033[1mework-aio install\\033[0m to set up services.\\n Help: ework-aio --help\\n'"
|
|
45
47
|
},
|
|
46
48
|
"dependencies": {
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# End-to-end install test.
|
|
3
|
+
#
|
|
4
|
+
# Spawns a clean debian container (via Dockerfile.regression) and runs the
|
|
5
|
+
# FULL `ework-aio install` flow — not a hand-rolled .env like the older
|
|
6
|
+
# regression.sh. This is the test that would have caught every bug we
|
|
7
|
+
# shipped between v0.2.0 and v0.2.6:
|
|
8
|
+
#
|
|
9
|
+
# - v0.2.4 plugin key fix → caught by issue-create → daemon receives webhook
|
|
10
|
+
# - v0.2.5 daemon binary fix → caught by /api/status probe (was printing help)
|
|
11
|
+
# - v0.2.6 webhook secret → caught by "no invalid signature in daemon.log"
|
|
12
|
+
# - v0.2.6 access log → caught by "no EACCES in web.log"
|
|
13
|
+
#
|
|
14
|
+
# Usage: ./scripts/e2e-install.sh [container-runtime] [npm-tag]
|
|
15
|
+
# container-runtime: docker (default) | podman
|
|
16
|
+
# npm-tag: latest (default) | 0.2.6 | file:.. (for local builds)
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
RUNTIME="${1:-docker}"
|
|
21
|
+
NPM_TAG="${2:-latest}"
|
|
22
|
+
IMAGE="${IMAGE:-ework-aio:e2e}"
|
|
23
|
+
|
|
24
|
+
c_grn=$'\033[32m'; c_red=$'\033[31m'; c_ylw=$'\033[33m'; c_rst=$'\033[0m'
|
|
25
|
+
pass() { printf '%sPASS%s %s\n' "$c_grn" "$c_rst" "$*"; }
|
|
26
|
+
fail() { printf '%sFAIL%s %s\n' "$c_red" "$c_rst" "$*"; exit 1; }
|
|
27
|
+
info() { printf '%s…%s %s\n' "$c_ylw" "$c_rst" "$*" >&2; }
|
|
28
|
+
|
|
29
|
+
if ! "$RUNTIME" image inspect "$IMAGE" >/dev/null 2>&1; then
|
|
30
|
+
info "Building $IMAGE"
|
|
31
|
+
# Build context = repo root (this script's parent dir + ..). Lets us invoke
|
|
32
|
+
# the script from anywhere (npm run test:e2e, CI, etc.) without cwd issues.
|
|
33
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
34
|
+
"$RUNTIME" build --network=host -f "$REPO_ROOT/Dockerfile.regression" \
|
|
35
|
+
-t "$IMAGE" "$REPO_ROOT"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
info "Using image: $IMAGE (testing npm tag: $NPM_TAG)"
|
|
39
|
+
|
|
40
|
+
# Unique ports so we don't collide with a host-side ework stack when run with
|
|
41
|
+
# --network host. Override via env if needed.
|
|
42
|
+
WORK_PORT="${WORK_PORT:-14002}"
|
|
43
|
+
DAEMON_PORT="${DAEMON_PORT:-14101}"
|
|
44
|
+
|
|
45
|
+
# opencode binary is host-specific (path may differ across machines). Mount
|
|
46
|
+
# readonly so the container preflight sees it on PATH. Override via env.
|
|
47
|
+
OPENCODE_HOST_BIN="${OPENCODE_HOST_BIN:-/home/dog/.local/bin/opencode}"
|
|
48
|
+
|
|
49
|
+
"$RUNTIME" run --rm -i --network host \
|
|
50
|
+
-v "$OPENCODE_HOST_BIN:/usr/local/bin/opencode:ro" \
|
|
51
|
+
-e WORK_PORT="$WORK_PORT" \
|
|
52
|
+
-e DAEMON_PORT="$DAEMON_PORT" \
|
|
53
|
+
-e NPM_TAG="$NPM_TAG" \
|
|
54
|
+
-e HTTP_PROXY="${HTTP_PROXY:-}" \
|
|
55
|
+
-e HTTPS_PROXY="${HTTPS_PROXY:-}" \
|
|
56
|
+
-e http_proxy="${HTTP_PROXY:-}" \
|
|
57
|
+
-e https_proxy="${HTTPS_PROXY:-}" \
|
|
58
|
+
-e NO_PROXY="127.0.0.1,localhost" \
|
|
59
|
+
"$IMAGE" bash -euo pipefail <<'EOSCRIPT'
|
|
60
|
+
set -euo pipefail
|
|
61
|
+
|
|
62
|
+
c_grn=$'\033[32m'; c_red=$'\033[31m'; c_ylw=$'\033[33m'; c_rst=$'\033[0m'
|
|
63
|
+
pass() { printf '%sPASS%s %s\n' "$c_grn" "$c_rst" "$*"; }
|
|
64
|
+
fail() { printf '%sFAIL%s %s\n' "$c_red" "$c_rst" "$*"; exit 1; }
|
|
65
|
+
info() { printf '%s…%s %s\n' "$c_ylw" "$c_rst" "$*" >&2; }
|
|
66
|
+
|
|
67
|
+
WORK_PORT="${WORK_PORT:-14002}"
|
|
68
|
+
DAEMON_PORT="${DAEMON_PORT:-14101}"
|
|
69
|
+
NPM_TAG="${NPM_TAG:-latest}"
|
|
70
|
+
DATA_DIR=/tmp/aio-e2e
|
|
71
|
+
|
|
72
|
+
info "clean state"
|
|
73
|
+
rm -rf "$DATA_DIR"
|
|
74
|
+
mkdir -p "$DATA_DIR"
|
|
75
|
+
|
|
76
|
+
info "opencode binary on PATH"
|
|
77
|
+
opencode --version | head -1
|
|
78
|
+
|
|
79
|
+
info "npm install -g ework-web@${NPM_TAG} ework-daemon@latest ework-aio@${NPM_TAG}"
|
|
80
|
+
# Note: ework-web and ework-daemon aren't version-locked to ework-aio. We
|
|
81
|
+
# always install latest of the services and only parameterize ework-aio so
|
|
82
|
+
# we can E2E-test a PR branch against published services.
|
|
83
|
+
for pkg in ework-web ework-daemon opencode-ework; do
|
|
84
|
+
npm install -g "$pkg@latest" 2>&1 | tail -2
|
|
85
|
+
done
|
|
86
|
+
npm install -g "ework-aio@${NPM_TAG}" 2>&1 | tail -2
|
|
87
|
+
|
|
88
|
+
info "v0.2.5 regression: all 4 bins on PATH"
|
|
89
|
+
for b in ework-aio ework-web ework-daemon ework-daemon-server; do
|
|
90
|
+
command -v "$b" >/dev/null 2>&1 || fail "$b not on PATH"
|
|
91
|
+
pass "$b -> $(command -v "$b")"
|
|
92
|
+
done
|
|
93
|
+
|
|
94
|
+
info "run ework-aio install (the real installer — no hand-rolled .env)"
|
|
95
|
+
# --allow-root: container runs as root; refusing would test an unrealistic
|
|
96
|
+
# path. The installer's data still lands in /tmp/aio-e2e via --data-dir.
|
|
97
|
+
ework-aio install \
|
|
98
|
+
--allow-root \
|
|
99
|
+
--data-dir "$DATA_DIR" \
|
|
100
|
+
--port "$WORK_PORT" \
|
|
101
|
+
--daemon-port "$DAEMON_PORT" \
|
|
102
|
+
--bot-name e2e-bot \
|
|
103
|
+
--yes 2>&1 | tee /tmp/install.log
|
|
104
|
+
|
|
105
|
+
pass "install exited 0"
|
|
106
|
+
|
|
107
|
+
info "v0.2.6 regression: webhook secrets match across web and daemon .env"
|
|
108
|
+
WEB_SEC=$(grep ^WORK_DAEMON_WEBHOOK_SECRET= "$DATA_DIR/ework-web/.env" | cut -d= -f2-)
|
|
109
|
+
DAE_SEC=$(grep ^GITEA_WEBHOOK_SECRET= "$DATA_DIR/ework-daemon/.env" | cut -d= -f2-)
|
|
110
|
+
[[ -n "$WEB_SEC" ]] || fail "WORK_DAEMON_WEBHOOK_SECRET empty in web .env"
|
|
111
|
+
[[ -n "$DAE_SEC" ]] || fail "GITEA_WEBHOOK_SECRET empty in daemon .env"
|
|
112
|
+
[[ "$WEB_SEC" == "$DAE_SEC" ]] \
|
|
113
|
+
|| fail "webhook secrets differ (web=$WEB_SEC daemon=$DAE_SEC)"
|
|
114
|
+
pass "secrets match (${#WEB_SEC} chars)"
|
|
115
|
+
|
|
116
|
+
info "v0.2.6 regression: WORK_ACCESS_LOG set, not defaulting to /tmp/ework-access.log"
|
|
117
|
+
ACCESS_LOG_VAL=$(grep ^WORK_ACCESS_LOG= "$DATA_DIR/ework-web/.env" | cut -d= -f2-)
|
|
118
|
+
[[ -n "$ACCESS_LOG_VAL" ]] || fail "WORK_ACCESS_LOG missing from web .env"
|
|
119
|
+
[[ "$ACCESS_LOG_VAL" != "/tmp/ework-access.log" ]] \
|
|
120
|
+
|| fail "WORK_ACCESS_LOG still defaults to /tmp/ework-access.log"
|
|
121
|
+
pass "WORK_ACCESS_LOG=$ACCESS_LOG_VAL"
|
|
122
|
+
|
|
123
|
+
info "wait for ework-web /login"
|
|
124
|
+
for i in $(seq 1 60); do
|
|
125
|
+
if curl -sf -o /dev/null "http://127.0.0.1:$WORK_PORT/login"; then
|
|
126
|
+
pass "/login responds (after ${i} half-seconds)"
|
|
127
|
+
break
|
|
128
|
+
fi
|
|
129
|
+
sleep 0.5
|
|
130
|
+
[[ $i -eq 60 ]] && { tail -30 "$DATA_DIR/run/web.log" 2>/dev/null; fail "ework-web did not come up"; }
|
|
131
|
+
done
|
|
132
|
+
|
|
133
|
+
info "wait for daemon /api/status"
|
|
134
|
+
for i in $(seq 1 60); do
|
|
135
|
+
if curl -sf -o /dev/null "http://127.0.0.1:$DAEMON_PORT/api/status"; then
|
|
136
|
+
pass "/api/status responds (after ${i} half-seconds)"
|
|
137
|
+
break
|
|
138
|
+
fi
|
|
139
|
+
sleep 0.5
|
|
140
|
+
[[ $i -eq 60 ]] && { tail -30 "$DATA_DIR/run/daemon.log" 2>/dev/null; fail "daemon did not come up"; }
|
|
141
|
+
done
|
|
142
|
+
|
|
143
|
+
info "v0.2.6 regression: no EACCES in web.log"
|
|
144
|
+
sleep 1
|
|
145
|
+
if grep -q "EACCES" "$DATA_DIR/run/web.log" 2>/dev/null; then
|
|
146
|
+
fail "EACCES in web.log:"
|
|
147
|
+
grep EACCES "$DATA_DIR/run/web.log" | head -3
|
|
148
|
+
fi
|
|
149
|
+
pass "no EACCES in web.log"
|
|
150
|
+
|
|
151
|
+
info "v0.2.6 regression: no invalid signature in daemon.log (pre-issue-create)"
|
|
152
|
+
if grep -q "invalid signature" "$DATA_DIR/run/daemon.log" 2>/dev/null; then
|
|
153
|
+
fail "invalid signature BEFORE issue create — autoWire webhook must have fired"
|
|
154
|
+
grep "invalid signature" "$DATA_DIR/run/daemon.log" | head -3
|
|
155
|
+
fi
|
|
156
|
+
pass "no invalid signature yet"
|
|
157
|
+
|
|
158
|
+
info "build admin auth cookie"
|
|
159
|
+
WORK_TOKEN=$(grep ^WORK_TOKEN= "$DATA_DIR/ework-web/.env" | cut -d= -f2-)
|
|
160
|
+
WORK_COOKIE_SECRET=$(grep ^WORK_COOKIE_SECRET= "$DATA_DIR/ework-web/.env" | cut -d= -f2-)
|
|
161
|
+
COOKIE_SIG=$(printf '%s' "$WORK_TOKEN" \
|
|
162
|
+
| openssl dgst -sha256 -hmac "$WORK_COOKIE_SECRET" -binary \
|
|
163
|
+
| base64 | tr '+/' '-_' | tr -d '=')
|
|
164
|
+
AUTH_COOKIE="ework_auth=${WORK_TOKEN}.${COOKIE_SIG}"
|
|
165
|
+
|
|
166
|
+
info "create test project (triggers autoWire webhook registration)"
|
|
167
|
+
PROJ_CODE=$(curl -sS -o /dev/null -w '%{http_code}' -X POST \
|
|
168
|
+
"http://127.0.0.1:$WORK_PORT/projects" \
|
|
169
|
+
-H "Cookie: $AUTH_COOKIE" \
|
|
170
|
+
--data-urlencode 'owner=e2e' \
|
|
171
|
+
--data-urlencode 'name=test')
|
|
172
|
+
[[ "$PROJ_CODE" == "303" || "$PROJ_CODE" == "302" ]] \
|
|
173
|
+
|| fail "project create failed: HTTP $PROJ_CODE"
|
|
174
|
+
pass "project create -> $PROJ_CODE"
|
|
175
|
+
|
|
176
|
+
info "create issue (triggers webhook POST to daemon)"
|
|
177
|
+
ISSUE_HEAD=$(curl -sS -i -X POST \
|
|
178
|
+
"http://127.0.0.1:$WORK_PORT/e2e/test/issues" \
|
|
179
|
+
-H "Cookie: $AUTH_COOKIE" \
|
|
180
|
+
--data-urlencode 'title=e2e test' \
|
|
181
|
+
--data-urlencode 'body=trigger webhook')
|
|
182
|
+
ISSUE_STATUS=$(printf '%s\n' "$ISSUE_HEAD" | head -1 | awk '{print $2}')
|
|
183
|
+
[[ "$ISSUE_STATUS" == "303" ]] \
|
|
184
|
+
|| fail "issue create did not return 303: $ISSUE_STATUS"
|
|
185
|
+
pass "issue create -> 303"
|
|
186
|
+
|
|
187
|
+
info "wait for webhook delivery + daemon processing"
|
|
188
|
+
sleep 5
|
|
189
|
+
|
|
190
|
+
info "v0.2.6 regression: webhook was accepted (no invalid signature after issue create)"
|
|
191
|
+
if grep -q "invalid signature" "$DATA_DIR/run/daemon.log" 2>/dev/null; then
|
|
192
|
+
fail "invalid signature AFTER issue create — webhook secret mismatch"
|
|
193
|
+
grep "invalid signature" "$DATA_DIR/run/daemon.log" | head -5
|
|
194
|
+
fi
|
|
195
|
+
pass "no invalid signature after issue create"
|
|
196
|
+
|
|
197
|
+
info "daemon received the issue event"
|
|
198
|
+
ACTIVE=$(curl -sS "http://127.0.0.1:$DAEMON_PORT/api/status" | jq -r '.issues // 0')
|
|
199
|
+
[[ "$ACTIVE" -ge 1 ]] \
|
|
200
|
+
|| fail "daemon did not register the issue (issues=$ACTIVE)"
|
|
201
|
+
pass "daemon registered issue (issues=$ACTIVE)"
|
|
202
|
+
|
|
203
|
+
echo
|
|
204
|
+
echo "===== E2E PASSED ====="
|
|
205
|
+
echo " ework-aio version: $(ework-aio --version 2>/dev/null || echo unknown)"
|
|
206
|
+
echo " data dir: $DATA_DIR"
|
|
207
|
+
echo " web: http://127.0.0.1:$WORK_PORT/login"
|
|
208
|
+
echo " daemon: http://127.0.0.1:$DAEMON_PORT/api/status"
|
|
209
|
+
echo " logs: $DATA_DIR/run/{web,daemon}.log"
|
|
210
|
+
EOSCRIPT
|
|
211
|
+
|
|
212
|
+
echo
|
|
213
|
+
echo "${c_grn}E2E COMPLETE${c_rst}"
|
package/src/cli.ts
CHANGED
package/src/commands/install.ts
CHANGED
|
@@ -357,6 +357,25 @@ export async function runInstall(
|
|
|
357
357
|
await patchEnvKey(paths.daemonEnvFile, "GITEA_TOKEN", botToken);
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
// 10.5 Reconcile webhook secret across web and daemon .env files.
|
|
361
|
+
// Pre-v0.2.6 installs generated two independent hex(20) values for
|
|
362
|
+
// WORK_DAEMON_WEBHOOK_SECRET (web) and GITEA_WEBHOOK_SECRET (daemon),
|
|
363
|
+
// so signature verification always failed. Forward-fill won't fix this
|
|
364
|
+
// because both keys already exist. We treat web as the source of truth
|
|
365
|
+
// (ework-web's DB has webhook rows signed with whatever
|
|
366
|
+
// WORK_DAEMON_WEBHOOK_SECRET was at creation time) and overwrite daemon.
|
|
367
|
+
{
|
|
368
|
+
const webSecret = await readEnvKey(paths.webEnvFile, "WORK_DAEMON_WEBHOOK_SECRET");
|
|
369
|
+
const daemonSecret = await readEnvKey(paths.daemonEnvFile, "GITEA_WEBHOOK_SECRET");
|
|
370
|
+
if (webSecret && daemonSecret && webSecret !== daemonSecret) {
|
|
371
|
+
await patchEnvKey(paths.daemonEnvFile, "GITEA_WEBHOOK_SECRET", webSecret);
|
|
372
|
+
logger.warn(`webhook secret mismatch — overwrote daemon GITEA_WEBHOOK_SECRET to match web`);
|
|
373
|
+
} else if (webSecret && !daemonSecret) {
|
|
374
|
+
await patchEnvKey(paths.daemonEnvFile, "GITEA_WEBHOOK_SECRET", webSecret);
|
|
375
|
+
logger.ok(`propagated WORK_DAEMON_WEBHOOK_SECRET → GITEA_WEBHOOK_SECRET`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
360
379
|
// 11. (Systemd only) Write + install daemon unit.
|
|
361
380
|
if (opts.useSystemd && paths.daemonUnitFile && systemdOk && !opts.noStart) {
|
|
362
381
|
const userInfo = os.userInfo();
|
package/src/config.ts
CHANGED
|
@@ -38,6 +38,26 @@ export interface EnvKeySpec {
|
|
|
38
38
|
|
|
39
39
|
const hex = (bytes: number): string => randomBytes(bytes).toString("hex");
|
|
40
40
|
|
|
41
|
+
// ework-web signs outgoing webhook POSTs with WORK_DAEMON_WEBHOOK_SECRET
|
|
42
|
+
// and ework-daemon verifies them with GITEA_WEBHOOK_SECRET. They MUST be
|
|
43
|
+
// the same value — pre-v0.2.6 each key had an independent hex(20) generator,
|
|
44
|
+
// so the two secrets never matched and every webhook was rejected with
|
|
45
|
+
// "invalid signature" on the daemon side.
|
|
46
|
+
//
|
|
47
|
+
// The closure-memoized `sharedWebhookSecret` returns the same value across
|
|
48
|
+
// both calls within a single install run, so first install is correct.
|
|
49
|
+
// Reconciliation for already-broken installs lives in install.ts (it reads
|
|
50
|
+
// both .env files and, if they differ, overwrites the daemon side from the
|
|
51
|
+
// web side — web is the source of truth because ework-web's DB has webhooks
|
|
52
|
+
// signed with whatever WORK_DAEMON_WEBHOOK_SECRET was at creation time).
|
|
53
|
+
let _sharedWebhookSecret: string | null = null;
|
|
54
|
+
function sharedWebhookSecret(): string {
|
|
55
|
+
if (_sharedWebhookSecret === null) {
|
|
56
|
+
_sharedWebhookSecret = hex(20);
|
|
57
|
+
}
|
|
58
|
+
return _sharedWebhookSecret;
|
|
59
|
+
}
|
|
60
|
+
|
|
41
61
|
export const WEB_ENV_KEYS: readonly EnvKeySpec[] = [
|
|
42
62
|
{ envVar: "WORK_PORT", file: "web", generate: (c) => String(c.workPort) },
|
|
43
63
|
{ envVar: "WORK_HOST", file: "web", generate: () => "127.0.0.1" },
|
|
@@ -48,9 +68,13 @@ export const WEB_ENV_KEYS: readonly EnvKeySpec[] = [
|
|
|
48
68
|
{ envVar: "WORK_DB_PATH", file: "web", generate: (c) => c.paths.webDbPath },
|
|
49
69
|
{ envVar: "WORK_ATTACHMENT_ROOT", file: "web", generate: (c) => c.paths.webAttachmentRoot },
|
|
50
70
|
{ envVar: "WORK_FILE_ROOTS", file: "web", generate: (c) => `/tmp,${c.paths.dataDir}` },
|
|
71
|
+
// Without this, ework-web falls back to /tmp/ework-access.log which is
|
|
72
|
+
// owned by whichever user touched it first. On shared boxes the runtime
|
|
73
|
+
// user can't append → "EACCES: permission denied" on every request.
|
|
74
|
+
{ envVar: "WORK_ACCESS_LOG", file: "web", generate: (c) => `${c.paths.runDir}/web-access.log` },
|
|
51
75
|
{ envVar: "WORK_DAEMON_BOT_LOGIN", file: "web", generate: (c) => c.botName },
|
|
52
76
|
{ envVar: "WORK_DAEMON_WEBHOOK_URL", file: "web", generate: (c) => `http://127.0.0.1:${c.daemonPort}` },
|
|
53
|
-
{ envVar: "WORK_DAEMON_WEBHOOK_SECRET", file: "web", secret: true, generate:
|
|
77
|
+
{ envVar: "WORK_DAEMON_WEBHOOK_SECRET", file: "web", secret: true, generate: sharedWebhookSecret },
|
|
54
78
|
] as const;
|
|
55
79
|
|
|
56
80
|
export const DAEMON_ENV_KEYS: readonly EnvKeySpec[] = [
|
|
@@ -62,7 +86,7 @@ export const DAEMON_ENV_KEYS: readonly EnvKeySpec[] = [
|
|
|
62
86
|
// These tokens come from the bot bootstrap flow — empty placeholder here,
|
|
63
87
|
// filled in by write_daemon_env after PAT is minted.
|
|
64
88
|
{ envVar: "GITEA_TOKEN", file: "daemon", secret: true, generate: () => "" },
|
|
65
|
-
{ envVar: "GITEA_WEBHOOK_SECRET", file: "daemon", secret: true, generate:
|
|
89
|
+
{ envVar: "GITEA_WEBHOOK_SECRET", file: "daemon", secret: true, generate: sharedWebhookSecret },
|
|
66
90
|
{ envVar: "BOT_USERNAME", file: "daemon", generate: (c) => c.botName },
|
|
67
91
|
{ envVar: "BOT_TOKEN", file: "daemon", secret: true, generate: () => "" },
|
|
68
92
|
{ envVar: "OPENCODE_BINARY", file: "daemon", generate: (c) => c.opencodeBin },
|