vibed-infra 0.4.0 → 0.6.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/README.md +37 -60
- package/install-auto-update.sh +17 -1
- package/install.sh +145 -13
- package/lib/__pycache__/load_config.cpython-312.pyc +0 -0
- package/lib/__pycache__/product_config.cpython-312.pyc +0 -0
- package/lib/env.sh +29 -0
- package/lib/generate.py +34 -3
- package/lib/host_gateway.sh +115 -0
- package/lib/package.py +48 -2
- package/lib/persistlog/__init__.py +123 -0
- package/lib/persistlog/__pycache__/__init__.cpython-312.pyc +0 -0
- package/lib/persistlog/ship.py +69 -0
- package/lib/product_config.py +5 -3
- package/lib/update_queue.sh +53 -0
- package/package.json +1 -1
- package/schema/packageconfig.md +23 -39
- package/skills/dns-configure/SKILL.md +16 -0
- package/skills/infra-update-agent/SKILL.md +40 -0
- package/skills/persist-logs/SKILL.md +64 -0
- package/skills/system-gateway/SKILL.md +30 -27
- package/templates/generic/DNS-SKILL.md.tmpl +40 -0
- package/templates/generic/start-api.sh +8 -0
- package/templates/generic/update-api.sh +6 -5
- package/templates/generic/update-gateway.sh +6 -5
- package/templates/generic/update-nodes.sh +5 -4
- package/templates/generic/update-ui.sh +6 -5
- package/templates/host-gateway/.env.example +15 -0
- package/templates/host-gateway/gateway/conf.d/00-default.conf +16 -0
- package/templates/host-gateway/gateway/nginx.conf +25 -0
- package/templates/host-gateway/reload-gateway.sh +7 -0
- package/templates/host-gateway/start-gateway.sh +108 -0
- package/templates/host-gateway/update-gateway.sh +41 -0
- package/templates/persist-logs/install-persist-logs.sh +38 -0
- package/templates/update-agent/agent.sh +87 -0
- package/templates/update-agent/enqueue.sh +54 -0
- package/templates/update-agent/install-agent.sh +60 -0
- package/templates/update-agent/webhook_server.py +132 -0
package/README.md
CHANGED
|
@@ -1,98 +1,75 @@
|
|
|
1
1
|
# vibed-infra
|
|
2
2
|
|
|
3
|
-
Product-agnostic VPS
|
|
3
|
+
Product-agnostic VPS packager: **`package.sh`** builds committed **`dist/`** for wget install. One **host gateway** serves many apps; a **serial update agent** pulls images without overload; **persist-logs** enable event-sourced recovery; **`dist/DNS-SKILL.md`** configures DNS via an AU browser agent.
|
|
4
4
|
|
|
5
5
|
Published on npm as **`vibed-infra`**.
|
|
6
6
|
|
|
7
7
|
## Product maintainer flow
|
|
8
8
|
|
|
9
|
-
Author **`templates/`** in your product repo:
|
|
10
|
-
|
|
11
9
|
| File | Purpose |
|
|
12
10
|
|------|---------|
|
|
13
|
-
| `vibed-infra-config.yml` |
|
|
14
|
-
| `api-config.yaml` |
|
|
15
|
-
| `ui-config.yaml` | UI image, port |
|
|
16
|
-
| `nodes-config.yaml` | Worker image, opaque `config:` |
|
|
17
|
-
|
|
18
|
-
Then package and commit **`dist/`**:
|
|
11
|
+
| `vibed-infra-config.yml` | Name, templates, `network.edge` (default `vps-edge`), `gateway.sites[]`, auto-update |
|
|
12
|
+
| `api-config.yaml` / `ui-config.yaml` / `nodes-config.yaml` | Images + opaque config |
|
|
19
13
|
|
|
20
14
|
```bash
|
|
21
|
-
./package.sh
|
|
15
|
+
./package.sh
|
|
22
16
|
git add dist && git commit && git push
|
|
17
|
+
# Copy dist/DNS-SKILL.md into AU agent browser extension; give it the VPS IP
|
|
23
18
|
```
|
|
24
19
|
|
|
25
|
-
|
|
20
|
+
## Operator flow (VPS)
|
|
26
21
|
|
|
27
22
|
```bash
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Example product: [`examples/vps-hello`](examples/vps-hello) (`app/`, `build-images.sh`, four YAMLs, committed `dist/`).
|
|
32
|
-
|
|
33
|
-
## Operator flow (wget on VPS)
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
wget -qO- https://raw.githubusercontent.com/ORG/REPO/main/dist/install-api.sh | bash
|
|
37
|
-
wget -qO- .../dist/install-nodes.sh | bash
|
|
23
|
+
wget -qO- .../dist/install-api.sh | bash
|
|
38
24
|
wget -qO- .../dist/install-ui.sh | bash
|
|
39
|
-
wget -qO- .../dist/install-
|
|
40
|
-
|
|
25
|
+
wget -qO- .../dist/install-nodes.sh | bash
|
|
26
|
+
wget -qO- .../dist/install-gateway.sh | bash # bootstraps ~/services/gateway once + apps/{name}/
|
|
41
27
|
```
|
|
42
28
|
|
|
43
29
|
| Profile | Role |
|
|
44
30
|
|---------|------|
|
|
45
|
-
| `api` |
|
|
46
|
-
| `
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
| `api` / `ui` / `nodes` | Join shared `vps-edge` |
|
|
32
|
+
| `gateway` | Host nginx + this app’s `sites.conf` under `apps/` |
|
|
33
|
+
|
|
34
|
+
Multi-app: further products’ `install-gateway.sh` only add `apps/{other}/sites.conf` and reload — no second 80/443 bind.
|
|
35
|
+
|
|
36
|
+
## Machine services (installed once)
|
|
37
|
+
|
|
38
|
+
| Path | Purpose |
|
|
39
|
+
|------|---------|
|
|
40
|
+
| `~/services/gateway` | Host nginx (`GATEWAY_HOME`) |
|
|
41
|
+
| `~/services/vibed-infra/update-agent` | Serial pull queue + optional GHCR webhook |
|
|
42
|
+
| `~/services/vibed-infra/persist-logs` | Per-app WALs + optional R2/S3 ship |
|
|
43
|
+
|
|
44
|
+
Auto-update cron **enqueues** work; the agent processes one job at a time. Updates prune dangling images (`DOCKER_AUTO_PRUNE=1`).
|
|
49
45
|
|
|
50
46
|
## Layout
|
|
51
47
|
|
|
52
48
|
| Path | Purpose |
|
|
53
49
|
|------|---------|
|
|
54
|
-
| `package.sh` / `lib/package.py` | Build
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `examples/vps-hello/` | Full example with dist e2e tests |
|
|
61
|
-
| `skills/` | Cursor skills |
|
|
62
|
-
| `github/workflows/` | Reusable GHCR build workflow |
|
|
50
|
+
| `package.sh` / `lib/package.py` | Build `dist/` |
|
|
51
|
+
| `templates/host-gateway/` | Shared gateway skeleton |
|
|
52
|
+
| `templates/update-agent/` | Queue agent + webhook |
|
|
53
|
+
| `templates/persist-logs/` | Shipper install |
|
|
54
|
+
| `lib/persistlog/` | Python append / seal / replay |
|
|
55
|
+
| `skills/` | system-gateway, infra-update-agent, persist-logs, dns-configure |
|
|
63
56
|
|
|
64
57
|
## Environment
|
|
65
58
|
|
|
66
59
|
| Variable | Meaning |
|
|
67
60
|
|----------|---------|
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `INSTALL_DIR` | Target directory (default `.`) |
|
|
61
|
+
| `GATEWAY_HOME` | Host gateway dir (default `~/services/gateway`) |
|
|
62
|
+
| `VIBED_HOME` | Machine vibed root (default `~/services/vibed-infra`) |
|
|
63
|
+
| `PERSIST_LOG_DIR` | Per-service event log dir |
|
|
64
|
+
| `DOCKER_AUTO_PRUNE` | Prune dangling images after update (default on) |
|
|
73
65
|
|
|
74
66
|
## Tests
|
|
75
67
|
|
|
76
68
|
```bash
|
|
77
|
-
npm test
|
|
78
|
-
npm run test:dist
|
|
79
|
-
./examples/vps-hello/test-dist.sh --profile api # single profile
|
|
69
|
+
npm test
|
|
70
|
+
npm run test:dist
|
|
80
71
|
```
|
|
81
72
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
## Publishing (npm)
|
|
85
|
-
|
|
86
|
-
Every **merge to `main`** runs [Publish npm](.github/workflows/publish.yml):
|
|
87
|
-
|
|
88
|
-
1. Bumps **minor** (`0.1.0` → `0.2.0`)
|
|
89
|
-
2. Commits `chore: release vibed-infra v…`, tags `v…`, pushes
|
|
90
|
-
3. Runs `npm publish` (needs repo secret `NPM_TOKEN`)
|
|
91
|
-
|
|
92
|
-
**Major versions** — set `version` in `package.json` yourself (e.g. `1.0.0`) and include **`[major]`** in the commit message. That publishes the pinned version with **no** automatic minor bump.
|
|
93
|
-
|
|
94
|
-
Set repository secret **`NPM_TOKEN`** before the first merge to `main`.
|
|
95
|
-
|
|
96
|
-
Ordinary PRs only run CI (`npm test` + dist e2e); they do **not** publish.
|
|
73
|
+
## Publishing
|
|
97
74
|
|
|
98
|
-
|
|
75
|
+
Merge to `main` runs Publish npm (minor bump). Set `NPM_TOKEN`. Ordinary PRs run CI only.
|
package/install-auto-update.sh
CHANGED
|
@@ -88,7 +88,23 @@ build_cron_minutes() {
|
|
|
88
88
|
}
|
|
89
89
|
CRON_MINUTES="$(build_cron_minutes "${CRON_OFFSET:-0}" "${INTERVAL_MIN:-30}")"
|
|
90
90
|
CRON_SCHED="${CRON_MINUTES} * * * *"
|
|
91
|
-
|
|
91
|
+
# Prefer shared update-agent queue when available; else direct update script.
|
|
92
|
+
AGENT_ENQUEUE=""
|
|
93
|
+
for cand in \
|
|
94
|
+
"${VIBED_HOME:-}/update-agent/enqueue.sh" \
|
|
95
|
+
"${HOME:-}/services/vibed-infra/update-agent/enqueue.sh" \
|
|
96
|
+
"/var/lib/vibed/update-agent/enqueue.sh"
|
|
97
|
+
do
|
|
98
|
+
if [[ -n "$cand" && -x "$cand" ]]; then
|
|
99
|
+
AGENT_ENQUEUE="$cand"
|
|
100
|
+
break
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
if [[ -n "$AGENT_ENQUEUE" ]]; then
|
|
104
|
+
CRON_CMD="cd ${SCRIPT_DIR} && UPDATE_REASON=cron /bin/bash ${AGENT_ENQUEUE} ${SCRIPT_DIR} >/dev/null 2>&1"
|
|
105
|
+
else
|
|
106
|
+
CRON_CMD="cd ${SCRIPT_DIR} && /bin/bash ${SCRIPT_DIR}/${UPDATE_SCRIPT} >/dev/null 2>&1"
|
|
107
|
+
fi
|
|
92
108
|
dir_slug="$(basename "$SCRIPT_DIR" | tr -c 'A-Za-z0-9._-' '_')"
|
|
93
109
|
CRON_D_FILE="/etc/cron.d/infra-${ROLE}-${dir_slug}"
|
|
94
110
|
|
package/install.sh
CHANGED
|
@@ -57,7 +57,7 @@ LOCAL_PACKAGER=0
|
|
|
57
57
|
if [[ "$PACKAGER_RAW" =~ ^/ ]] && [[ -d "$PACKAGER_RAW/lib" ]]; then
|
|
58
58
|
LOCAL_PACKAGER=1
|
|
59
59
|
fi
|
|
60
|
-
for lib in fetch.sh env.sh prompt.sh tls.sh load_config.py generate.py; do
|
|
60
|
+
for lib in fetch.sh env.sh prompt.sh tls.sh load_config.py generate.py host_gateway.sh; do
|
|
61
61
|
if [[ "$LOCAL_PACKAGER" == "1" ]]; then
|
|
62
62
|
cp -f "${PACKAGER_RAW}/lib/${lib}" "${INFRA_LIB}/${lib}"
|
|
63
63
|
else
|
|
@@ -79,6 +79,8 @@ source "${INFRA_LIB}/fetch.sh"
|
|
|
79
79
|
source "${INFRA_LIB}/prompt.sh"
|
|
80
80
|
# shellcheck source=/dev/null
|
|
81
81
|
source "${INFRA_LIB}/tls.sh"
|
|
82
|
+
# shellcheck source=/dev/null
|
|
83
|
+
source "${INFRA_LIB}/host_gateway.sh"
|
|
82
84
|
|
|
83
85
|
cd "$DEST"
|
|
84
86
|
|
|
@@ -203,15 +205,85 @@ for e in extras:
|
|
|
203
205
|
done
|
|
204
206
|
|
|
205
207
|
if [[ "$ROLE" == "gateway" ]]; then
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
208
|
+
PRODUCT_NAME="$(
|
|
209
|
+
python3 -c "
|
|
210
|
+
import sys
|
|
211
|
+
sys.path.insert(0, '${INFRA_LIB}')
|
|
212
|
+
from load_config import load_packageconfig
|
|
213
|
+
from pathlib import Path
|
|
214
|
+
c = load_packageconfig(Path('${PC_LOCAL}'))
|
|
215
|
+
print(c.get('name') or 'app')
|
|
216
|
+
"
|
|
217
|
+
)"
|
|
218
|
+
GATEWAY_HOME="${GATEWAY_HOME:-$(vibed_gateway_home)}"
|
|
219
|
+
export GATEWAY_HOME
|
|
220
|
+
mkdir -p "$DEST/gateway/conf.d" "$DEST/lib"
|
|
221
|
+
|
|
222
|
+
# Phase 1: bootstrap shared host gateway once
|
|
223
|
+
vibed_bootstrap_host_gateway "$PACKAGER_RAW"
|
|
224
|
+
if [[ "$LOCAL_PACKAGER" == "1" ]]; then
|
|
225
|
+
cp -f "${PACKAGER_RAW}/lib/env.sh" "${GATEWAY_HOME}/lib-env.sh"
|
|
226
|
+
cp -f "${PACKAGER_RAW}/lib/host_gateway.sh" "${GATEWAY_HOME}/lib/host_gateway.sh" 2>/dev/null || \
|
|
227
|
+
mkdir -p "${GATEWAY_HOME}/lib" && cp -f "${PACKAGER_RAW}/lib/host_gateway.sh" "${GATEWAY_HOME}/lib/host_gateway.sh"
|
|
214
228
|
fi
|
|
229
|
+
|
|
230
|
+
# Phase 2: app extension — sites.conf under host apps/{name}/
|
|
231
|
+
SITES_TMP="$DEST/gateway/sites.conf"
|
|
232
|
+
python3 "${INFRA_LIB}/generate.py" "$PC_LOCAL" --profile "$PROFILE" --mode app -o "$SITES_TMP"
|
|
233
|
+
META_TMP="$DEST/gateway/meta.json"
|
|
234
|
+
python3 -c "
|
|
235
|
+
import json, sys
|
|
236
|
+
from pathlib import Path
|
|
237
|
+
sys.path.insert(0, '${INFRA_LIB}')
|
|
238
|
+
from load_config import load_packageconfig, get_profile
|
|
239
|
+
c = load_packageconfig(Path('${PC_LOCAL}'))
|
|
240
|
+
p = get_profile(c, '${PROFILE}')
|
|
241
|
+
meta = {
|
|
242
|
+
'name': c.get('name'),
|
|
243
|
+
'installDir': '${DEST}',
|
|
244
|
+
'sites': p.get('sites') or [],
|
|
245
|
+
'images': c.get('images') or {},
|
|
246
|
+
}
|
|
247
|
+
Path('${META_TMP}').write_text(json.dumps(meta, indent=2) + '\n')
|
|
248
|
+
"
|
|
249
|
+
vibed_install_app_sites "$PRODUCT_NAME" "$SITES_TMP" "$META_TMP"
|
|
250
|
+
|
|
251
|
+
# Product install dir keeps thin wrappers that point at host gateway
|
|
252
|
+
infra_write_if_missing "${PRODUCT_RAW}/gateway/nginx.conf" "$DEST/gateway/nginx.conf" 0
|
|
253
|
+
cat >"$DEST/start-gateway.sh" <<EOF
|
|
254
|
+
#!/usr/bin/env bash
|
|
255
|
+
set -euo pipefail
|
|
256
|
+
export GATEWAY_HOME="${GATEWAY_HOME}"
|
|
257
|
+
# shellcheck source=/dev/null
|
|
258
|
+
source "\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)/.infra-lib/host_gateway.sh" 2>/dev/null \\
|
|
259
|
+
|| source "${GATEWAY_HOME}/lib/host_gateway.sh"
|
|
260
|
+
vibed_reload_or_start_gateway
|
|
261
|
+
EOF
|
|
262
|
+
cat >"$DEST/update-gateway.sh" <<EOF
|
|
263
|
+
#!/usr/bin/env bash
|
|
264
|
+
set -euo pipefail
|
|
265
|
+
SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
266
|
+
cd "\$SCRIPT_DIR"
|
|
267
|
+
# shellcheck source=lib-env.sh
|
|
268
|
+
[[ -f lib-env.sh ]] && source lib-env.sh && load_dotenv .env
|
|
269
|
+
export GATEWAY_HOME="${GATEWAY_HOME}"
|
|
270
|
+
# Regenerate this app's sites and reload host (image update is host's update-gateway.sh)
|
|
271
|
+
if [[ -f .packageconfig.yaml ]]; then
|
|
272
|
+
python3 "\${SCRIPT_DIR}/.infra-lib/generate.py" .packageconfig.yaml --profile gateway --mode app \\
|
|
273
|
+
-o "\${GATEWAY_HOME}/apps/${PRODUCT_NAME}/sites.conf" 2>/dev/null \\
|
|
274
|
+
|| python3 "\${GATEWAY_HOME}/../vibed-infra/lib/generate.py" .packageconfig.yaml --profile gateway --mode app \\
|
|
275
|
+
-o "\${GATEWAY_HOME}/apps/${PRODUCT_NAME}/sites.conf"
|
|
276
|
+
fi
|
|
277
|
+
# shellcheck source=/dev/null
|
|
278
|
+
source "\${SCRIPT_DIR}/.infra-lib/host_gateway.sh" 2>/dev/null || source "${GATEWAY_HOME}/lib/host_gateway.sh"
|
|
279
|
+
vibed_reload_or_start_gateway
|
|
280
|
+
EOF
|
|
281
|
+
chmod +x "$DEST/start-gateway.sh" "$DEST/update-gateway.sh"
|
|
282
|
+
# generate.py / host_gateway.sh already live in .infra-lib from bootstrap
|
|
283
|
+
cp -f "${INFRA_LIB}/host_gateway.sh" "$DEST/.infra-lib/host_gateway.sh" 2>/dev/null || true
|
|
284
|
+
|
|
285
|
+
# Ensure host network name is in product .env example path after copy
|
|
286
|
+
echo "GATEWAY_HOME=${GATEWAY_HOME}" >>"$DEST/.env.example" 2>/dev/null || true
|
|
215
287
|
fi
|
|
216
288
|
|
|
217
289
|
if [[ ! -f "$DEST/.env" ]]; then
|
|
@@ -221,6 +293,22 @@ else
|
|
|
221
293
|
echo "exists: $DEST/.env"
|
|
222
294
|
fi
|
|
223
295
|
|
|
296
|
+
# Align product DOCKER_NETWORK with host edge when gateway/host exists
|
|
297
|
+
if vibed_host_gateway_ready 2>/dev/null; then
|
|
298
|
+
HG="$(vibed_gateway_home)"
|
|
299
|
+
if [[ -f "$HG/.env" ]]; then
|
|
300
|
+
HOST_NET="$(grep -E '^DOCKER_NETWORK=' "$HG/.env" | head -1 | cut -d= -f2- || true)"
|
|
301
|
+
if [[ -n "$HOST_NET" && -f "$DEST/.env" ]]; then
|
|
302
|
+
if grep -q '^DOCKER_NETWORK=' "$DEST/.env"; then
|
|
303
|
+
sed -i "s|^DOCKER_NETWORK=.*|DOCKER_NETWORK=${HOST_NET}|" "$DEST/.env"
|
|
304
|
+
else
|
|
305
|
+
echo "DOCKER_NETWORK=${HOST_NET}" >>"$DEST/.env"
|
|
306
|
+
fi
|
|
307
|
+
echo "joined host edge network: $HOST_NET"
|
|
308
|
+
fi
|
|
309
|
+
fi
|
|
310
|
+
fi
|
|
311
|
+
|
|
224
312
|
cat >"$DEST/.infra-profile" <<EOF
|
|
225
313
|
PROFILE=${PROFILE}
|
|
226
314
|
ROLE=${ROLE}
|
|
@@ -229,12 +317,26 @@ UPDATE_SCRIPT=${UPDATE_SCRIPT}
|
|
|
229
317
|
EOF
|
|
230
318
|
|
|
231
319
|
chmod +x "$DEST/${START_SCRIPT}" 2>/dev/null || true
|
|
320
|
+
|
|
321
|
+
# Ensure update-agent + persist-logs on first install (idempotent)
|
|
322
|
+
if [[ -f "${PACKAGER_RAW}/templates/update-agent/install-agent.sh" ]]; then
|
|
323
|
+
PACKAGER_RAW="$PACKAGER_RAW" VIBED_HOME="${VIBED_HOME:-}" bash "${PACKAGER_RAW}/templates/update-agent/install-agent.sh" || true
|
|
324
|
+
fi
|
|
325
|
+
if [[ -f "${PACKAGER_RAW}/templates/persist-logs/install-persist-logs.sh" ]]; then
|
|
326
|
+
PACKAGER_RAW="$PACKAGER_RAW" VIBED_HOME="${VIBED_HOME:-}" bash "${PACKAGER_RAW}/templates/persist-logs/install-persist-logs.sh" || true
|
|
327
|
+
fi
|
|
328
|
+
|
|
232
329
|
INFRA_PROFILE="$PROFILE" ./install-auto-update.sh || true
|
|
233
330
|
|
|
234
331
|
if [[ "$ROLE" == "gateway" ]]; then
|
|
235
332
|
# shellcheck source=/dev/null
|
|
236
333
|
source "$DEST/lib-env.sh"
|
|
237
334
|
load_dotenv "$DEST/.env"
|
|
335
|
+
HG="$(vibed_gateway_home)"
|
|
336
|
+
# Prefer host certs
|
|
337
|
+
if [[ -f "$HG/.env" ]]; then
|
|
338
|
+
load_dotenv "$HG/.env"
|
|
339
|
+
fi
|
|
238
340
|
CERT_DIR="$(python3 -c "
|
|
239
341
|
import sys
|
|
240
342
|
sys.path.insert(0, '${INFRA_LIB}')
|
|
@@ -243,13 +345,37 @@ from pathlib import Path
|
|
|
243
345
|
p = get_profile(load_packageconfig(Path('${PC_LOCAL}')), '${PROFILE}')
|
|
244
346
|
sites = p.get('sites') or []
|
|
245
347
|
if not sites:
|
|
246
|
-
print('/
|
|
348
|
+
print('${HG}/certs')
|
|
247
349
|
else:
|
|
248
350
|
s = sites[0]
|
|
249
|
-
print(s.get('tlsCertDir') or ('/
|
|
351
|
+
print(s.get('tlsCertDir') or ('${HG}/certs'))
|
|
250
352
|
")"
|
|
251
353
|
TLS_FULLCHAIN="${TLS_FULLCHAIN:-${CERT_DIR}/fullchain.pem}"
|
|
252
354
|
TLS_PRIVKEY="${TLS_PRIVKEY:-${CERT_DIR}/privkey.pem}"
|
|
355
|
+
# Prefer writable host certs when packaged tlsCertDir is not writable (e.g. /etc/letsencrypt)
|
|
356
|
+
if [[ ! -f "$TLS_FULLCHAIN" ]]; then
|
|
357
|
+
HOST_CERT_DIR="${HG}/certs"
|
|
358
|
+
mkdir -p "$HOST_CERT_DIR" 2>/dev/null || true
|
|
359
|
+
if [[ -x "${PRODUCT_RAW}/gen-dev-certs.sh" ]]; then
|
|
360
|
+
APP_HOST="$(python3 -c "
|
|
361
|
+
import sys
|
|
362
|
+
sys.path.insert(0, '${INFRA_LIB}')
|
|
363
|
+
from load_config import load_packageconfig, get_profile
|
|
364
|
+
from pathlib import Path
|
|
365
|
+
p = get_profile(load_packageconfig(Path('${PC_LOCAL}')), '${PROFILE}')
|
|
366
|
+
sites = p.get('sites') or []
|
|
367
|
+
print(sites[0]['host'] if sites else 'localhost')
|
|
368
|
+
")"
|
|
369
|
+
if APP_HOST="$APP_HOST" bash "${PRODUCT_RAW}/gen-dev-certs.sh" "$HOST_CERT_DIR" 2>/dev/null; then
|
|
370
|
+
TLS_FULLCHAIN="${HOST_CERT_DIR}/fullchain.pem"
|
|
371
|
+
TLS_PRIVKEY="${HOST_CERT_DIR}/privkey.pem"
|
|
372
|
+
fi
|
|
373
|
+
fi
|
|
374
|
+
fi
|
|
375
|
+
if [[ -f "$HG/.env" ]]; then
|
|
376
|
+
sed -i "s|^TLS_FULLCHAIN=.*|TLS_FULLCHAIN=${TLS_FULLCHAIN}|" "$HG/.env" 2>/dev/null || true
|
|
377
|
+
sed -i "s|^TLS_PRIVKEY=.*|TLS_PRIVKEY=${TLS_PRIVKEY}|" "$HG/.env" 2>/dev/null || true
|
|
378
|
+
fi
|
|
253
379
|
mapfile -t DOMAIN_ARR < <(python3 -c "
|
|
254
380
|
import sys
|
|
255
381
|
sys.path.insert(0, '${INFRA_LIB}')
|
|
@@ -261,8 +387,14 @@ for s in p.get('sites') or []:
|
|
|
261
387
|
for a in s.get('aliases') or []:
|
|
262
388
|
print(a)
|
|
263
389
|
")
|
|
264
|
-
infra_tls_offer_interactive "$TLS_FULLCHAIN" "$TLS_PRIVKEY" "${DOMAIN_ARR[@]}"
|
|
390
|
+
infra_tls_offer_interactive "$TLS_FULLCHAIN" "$TLS_PRIVKEY" "${DOMAIN_ARR[@]}" || true
|
|
265
391
|
fi
|
|
266
392
|
|
|
267
393
|
echo ""
|
|
268
|
-
|
|
394
|
+
if [[ "$ROLE" == "gateway" ]]; then
|
|
395
|
+
echo "Install complete. Host gateway: $(vibed_gateway_home)"
|
|
396
|
+
echo "App sites: $(vibed_gateway_home)/apps/${PRODUCT_NAME:-app}/sites.conf"
|
|
397
|
+
echo "Start: cd $DEST && ./start-gateway.sh"
|
|
398
|
+
else
|
|
399
|
+
echo "Install complete. Start: cd $DEST && ./${START_SCRIPT}"
|
|
400
|
+
fi
|
|
Binary file
|
|
Binary file
|
package/lib/env.sh
CHANGED
|
@@ -222,3 +222,32 @@ chown_data_dir() {
|
|
|
222
222
|
fi
|
|
223
223
|
chmod 755 "$dir" 2>/dev/null || true
|
|
224
224
|
}
|
|
225
|
+
|
|
226
|
+
# Default on during auto-update. Set DOCKER_AUTO_PRUNE=0 to disable.
|
|
227
|
+
docker_auto_prune_on() {
|
|
228
|
+
if [[ -n "${DOCKER_AUTO_PRUNE+x}" ]]; then
|
|
229
|
+
env_flag_on DOCKER_AUTO_PRUNE
|
|
230
|
+
return
|
|
231
|
+
fi
|
|
232
|
+
return 0
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# Drop dangling images left after retagged pulls (e.g. :main). Optionally also
|
|
236
|
+
# remove unused tagged images older than DOCKER_PRUNE_UNTIL when DOCKER_PRUNE_UNUSED=1.
|
|
237
|
+
prune_docker_images() {
|
|
238
|
+
local log_dir="${1:-.}"
|
|
239
|
+
local role_label="${2:-infra}"
|
|
240
|
+
docker_auto_prune_on || return 0
|
|
241
|
+
if ! command -v docker >/dev/null 2>&1; then
|
|
242
|
+
return 0
|
|
243
|
+
fi
|
|
244
|
+
local summary
|
|
245
|
+
summary="$(docker image prune -f 2>/dev/null | awk '/Total reclaimed space/{print; found=1} END{if(!found) print "done"}' || echo done)"
|
|
246
|
+
log_update "$log_dir" "${role_label}: prune dangling images — ${summary}"
|
|
247
|
+
if env_flag_on DOCKER_PRUNE_UNUSED; then
|
|
248
|
+
local until="${DOCKER_PRUNE_UNTIL:-72h}"
|
|
249
|
+
summary="$(docker image prune -af --filter "until=${until}" 2>/dev/null \
|
|
250
|
+
| awk '/Total reclaimed space/{print; found=1} END{if(!found) print "done"}' || echo done)"
|
|
251
|
+
log_update "$log_dir" "${role_label}: prune unused images (until=${until}) — ${summary}"
|
|
252
|
+
fi
|
|
253
|
+
}
|
package/lib/generate.py
CHANGED
|
@@ -22,6 +22,8 @@ def render_site(site: dict) -> str:
|
|
|
22
22
|
create = site.get("createPath") or ""
|
|
23
23
|
be_var = backend.replace("-", "_")
|
|
24
24
|
ui_var = ui.replace("-", "_")
|
|
25
|
+
cert = site.get("sslCertificate") or "/etc/nginx/certs/fullchain.pem"
|
|
26
|
+
key = site.get("sslCertificateKey") or "/etc/nginx/certs/privkey.pem"
|
|
25
27
|
proxy_headers = f""" proxy_pass http://${be_var}_upstream;
|
|
26
28
|
proxy_set_header Host $host;
|
|
27
29
|
proxy_set_header X-Real-IP $remote_addr;
|
|
@@ -42,8 +44,8 @@ server {{
|
|
|
42
44
|
http2 on;
|
|
43
45
|
server_name {server_names};
|
|
44
46
|
|
|
45
|
-
ssl_certificate
|
|
46
|
-
ssl_certificate_key
|
|
47
|
+
ssl_certificate {cert};
|
|
48
|
+
ssl_certificate_key {key};
|
|
47
49
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
48
50
|
ssl_prefer_server_ciphers on;
|
|
49
51
|
|
|
@@ -78,7 +80,23 @@ server {{
|
|
|
78
80
|
"""
|
|
79
81
|
|
|
80
82
|
|
|
83
|
+
def render_app_sites(prof: dict) -> str:
|
|
84
|
+
"""Per-app sites.conf (no ACME block — host 00-default.conf owns that)."""
|
|
85
|
+
sites = prof.get("sites") or []
|
|
86
|
+
parts = [
|
|
87
|
+
"# Generated by lib/generate.py — app sites for host gateway apps/{name}/sites.conf",
|
|
88
|
+
"",
|
|
89
|
+
]
|
|
90
|
+
for site in sites:
|
|
91
|
+
parts.append(render_site(site))
|
|
92
|
+
parts.append("")
|
|
93
|
+
return "\n".join(parts)
|
|
94
|
+
|
|
95
|
+
|
|
81
96
|
def render_gateway(conf: dict, profile: str, prof: dict, output_name: str = "domains.conf") -> str:
|
|
97
|
+
"""Full standalone conf (legacy) or app-only when output is sites.conf."""
|
|
98
|
+
if Path(output_name).name == "sites.conf":
|
|
99
|
+
return render_app_sites(prof)
|
|
82
100
|
sites = prof.get("sites") or []
|
|
83
101
|
parts = [
|
|
84
102
|
"# Generated by lib/generate.py — edit packageconfig sites[] and re-install",
|
|
@@ -106,10 +124,23 @@ def main() -> int:
|
|
|
106
124
|
ap.add_argument("packageconfig")
|
|
107
125
|
ap.add_argument("--profile", default="gateway")
|
|
108
126
|
ap.add_argument("-o", "--output", required=True)
|
|
127
|
+
ap.add_argument(
|
|
128
|
+
"--mode",
|
|
129
|
+
choices=("full", "app"),
|
|
130
|
+
default=None,
|
|
131
|
+
help="full=standalone domains.conf; app=sites.conf for host gateway",
|
|
132
|
+
)
|
|
109
133
|
args = ap.parse_args()
|
|
110
134
|
conf = load_packageconfig(Path(args.packageconfig))
|
|
111
135
|
prof = get_profile(conf, args.profile)
|
|
112
|
-
|
|
136
|
+
out_name = Path(args.output).name
|
|
137
|
+
mode = args.mode
|
|
138
|
+
if mode is None:
|
|
139
|
+
mode = "app" if out_name == "sites.conf" else "full"
|
|
140
|
+
if mode == "app":
|
|
141
|
+
out = render_app_sites(prof)
|
|
142
|
+
else:
|
|
143
|
+
out = render_gateway(conf, args.profile, prof, out_name)
|
|
113
144
|
Path(args.output).write_text(out, encoding="utf-8")
|
|
114
145
|
print(f"wrote {args.output}")
|
|
115
146
|
return 0
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Host / shared gateway helpers. Sourced by install.sh and start scripts.
|
|
3
|
+
# shellcheck shell=bash
|
|
4
|
+
|
|
5
|
+
vibed_gateway_home() {
|
|
6
|
+
if [[ -n "${GATEWAY_HOME:-}" ]]; then
|
|
7
|
+
echo "$GATEWAY_HOME"
|
|
8
|
+
return 0
|
|
9
|
+
fi
|
|
10
|
+
if [[ -n "${HOME:-}" ]]; then
|
|
11
|
+
echo "${HOME}/services/gateway"
|
|
12
|
+
return 0
|
|
13
|
+
fi
|
|
14
|
+
echo "/var/lib/vibed/gateway"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
vibed_home() {
|
|
18
|
+
if [[ -n "${VIBED_HOME:-}" ]]; then
|
|
19
|
+
echo "$VIBED_HOME"
|
|
20
|
+
return 0
|
|
21
|
+
fi
|
|
22
|
+
if [[ -n "${HOME:-}" ]]; then
|
|
23
|
+
echo "${HOME}/services/vibed-infra"
|
|
24
|
+
return 0
|
|
25
|
+
fi
|
|
26
|
+
echo "/var/lib/vibed"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
vibed_host_gateway_ready() {
|
|
30
|
+
local home
|
|
31
|
+
home="$(vibed_gateway_home)"
|
|
32
|
+
[[ -f "${home}/.vibed-host-gateway" ]]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
vibed_bootstrap_host_gateway() {
|
|
36
|
+
local packager="${1:?packager root or URL}"
|
|
37
|
+
local home
|
|
38
|
+
home="$(vibed_gateway_home)"
|
|
39
|
+
mkdir -p "$home/gateway/conf.d" "$home/apps" "$home/certs" "$home/certbot-www"
|
|
40
|
+
|
|
41
|
+
if [[ -f "${home}/.vibed-host-gateway" ]]; then
|
|
42
|
+
echo "host gateway already present: $home"
|
|
43
|
+
return 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
echo "bootstrapping host gateway at $home"
|
|
47
|
+
|
|
48
|
+
_hg_fetch() {
|
|
49
|
+
local rel="$1" dest="$2"
|
|
50
|
+
mkdir -p "$(dirname "$dest")"
|
|
51
|
+
if [[ "$packager" =~ ^/ ]]; then
|
|
52
|
+
cp -f "${packager}/${rel}" "$dest"
|
|
53
|
+
else
|
|
54
|
+
if command -v curl >/dev/null 2>&1; then
|
|
55
|
+
curl -fsSL "${packager}/${rel}" -o "$dest"
|
|
56
|
+
else
|
|
57
|
+
wget -qO "$dest" "${packager}/${rel}"
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_hg_fetch "templates/host-gateway/gateway/nginx.conf" "$home/gateway/nginx.conf"
|
|
63
|
+
_hg_fetch "templates/host-gateway/gateway/conf.d/00-default.conf" "$home/gateway/conf.d/00-default.conf"
|
|
64
|
+
_hg_fetch "templates/host-gateway/.env.example" "$home/.env.example"
|
|
65
|
+
_hg_fetch "templates/host-gateway/start-gateway.sh" "$home/start-gateway.sh"
|
|
66
|
+
_hg_fetch "templates/host-gateway/reload-gateway.sh" "$home/reload-gateway.sh"
|
|
67
|
+
_hg_fetch "templates/host-gateway/update-gateway.sh" "$home/update-gateway.sh"
|
|
68
|
+
chmod +x "$home/start-gateway.sh" "$home/reload-gateway.sh" "$home/update-gateway.sh"
|
|
69
|
+
|
|
70
|
+
if [[ ! -f "$home/.env" ]]; then
|
|
71
|
+
cp "$home/.env.example" "$home/.env"
|
|
72
|
+
fi
|
|
73
|
+
if [[ -f "${packager}/lib/env.sh" ]] || [[ "$packager" =~ ^/ ]]; then
|
|
74
|
+
if [[ "$packager" =~ ^/ ]]; then
|
|
75
|
+
cp -f "${packager}/lib/env.sh" "$home/lib-env.sh"
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
echo "1" >"${home}/.vibed-host-gateway"
|
|
80
|
+
echo "bootstrapped host gateway: $home"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
vibed_install_app_sites() {
|
|
84
|
+
local app_name="$1"
|
|
85
|
+
local sites_conf_src="$2"
|
|
86
|
+
local meta_json="${3:-}"
|
|
87
|
+
local home
|
|
88
|
+
home="$(vibed_gateway_home)"
|
|
89
|
+
local app_dir="${home}/apps/${app_name}"
|
|
90
|
+
mkdir -p "$app_dir"
|
|
91
|
+
cp -f "$sites_conf_src" "${app_dir}/sites.conf"
|
|
92
|
+
if [[ -n "$meta_json" && -f "$meta_json" ]]; then
|
|
93
|
+
cp -f "$meta_json" "${app_dir}/meta.json"
|
|
94
|
+
fi
|
|
95
|
+
echo "installed app sites: ${app_dir}/sites.conf"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
vibed_reload_or_start_gateway() {
|
|
99
|
+
local home
|
|
100
|
+
home="$(vibed_gateway_home)"
|
|
101
|
+
# shellcheck source=/dev/null
|
|
102
|
+
[[ -f "$home/lib-env.sh" ]] && source "$home/lib-env.sh"
|
|
103
|
+
# shellcheck disable=SC1091
|
|
104
|
+
[[ -f "$home/.env" ]] && load_dotenv "$home/.env" 2>/dev/null || true
|
|
105
|
+
local name="${GATEWAY_NAME:-vps-gateway}"
|
|
106
|
+
if docker inspect "$name" >/dev/null 2>&1; then
|
|
107
|
+
if [[ -x "$home/reload-gateway.sh" ]]; then
|
|
108
|
+
(cd "$home" && ./reload-gateway.sh)
|
|
109
|
+
else
|
|
110
|
+
(cd "$home" && ./start-gateway.sh)
|
|
111
|
+
fi
|
|
112
|
+
else
|
|
113
|
+
(cd "$home" && ./start-gateway.sh)
|
|
114
|
+
fi
|
|
115
|
+
}
|