vibed-infra 0.5.0 → 0.7.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 +42 -61
- package/github/__pycache__/notify-vps-pull.cpython-312.pyc +0 -0
- package/github/notify-vps-pull.py +194 -0
- package/github/workflows/docker-build-reusable.yml +32 -1
- package/install-auto-update.sh +17 -1
- package/install.sh +257 -27
- package/lib/__pycache__/generate.cpython-312.pyc +0 -0
- package/lib/__pycache__/github_oidc.cpython-312.pyc +0 -0
- package/lib/__pycache__/load_config.cpython-312.pyc +0 -0
- package/lib/__pycache__/product_config.cpython-312.pyc +0 -0
- package/lib/__pycache__/webhook.cpython-312.pyc +0 -0
- package/lib/generate.py +42 -3
- package/lib/github_oidc.py +224 -0
- package/lib/host_gateway.sh +117 -0
- package/lib/package.py +134 -12
- 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 +33 -16
- package/lib/update_queue.sh +57 -0
- package/lib/webhook.py +90 -0
- package/package.json +4 -2
- package/schema/packageconfig.md +30 -48
- package/skills/dns-configure/SKILL.md +18 -0
- package/skills/infra-cicd/SKILL.md +14 -7
- package/skills/infra-packager/SKILL.md +9 -7
- package/skills/infra-update-agent/SKILL.md +64 -0
- package/skills/persist-logs/SKILL.md +64 -0
- package/skills/system-gateway/SKILL.md +43 -23
- package/templates/generic/DNS-SKILL.md.tmpl +35 -0
- package/templates/generic/start-api.sh +8 -0
- package/templates/host-gateway/.env.example +19 -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/setup-tls.sh +249 -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/__pycache__/webhook_server.cpython-312.pyc +0 -0
- package/templates/update-agent/agent.sh +87 -0
- package/templates/update-agent/enqueue.sh +54 -0
- package/templates/update-agent/install-agent.sh +70 -0
- package/templates/update-agent/webhook_server.py +208 -0
package/README.md
CHANGED
|
@@ -1,100 +1,81 @@
|
|
|
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`, `gateway.publicIp` / `tlsEmail` / `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 (domains + publicIp already filled when configured)
|
|
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
|
+
# 1) DNS — paste dist/DNS-SKILL.md into AU browser agent
|
|
24
|
+
# 2) App roles
|
|
25
|
+
wget -qO- .../dist/install-api.sh | bash
|
|
38
26
|
wget -qO- .../dist/install-ui.sh | bash
|
|
27
|
+
wget -qO- .../dist/install-nodes.sh | bash
|
|
28
|
+
# 3) Host gateway + TLS (setup-tls.sh; re-run with --force if host/IP changes)
|
|
39
29
|
wget -qO- .../dist/install-gateway.sh | bash
|
|
40
|
-
# edit each install dir .env, then ./start-*.sh
|
|
41
30
|
```
|
|
42
31
|
|
|
43
32
|
| Profile | Role |
|
|
44
33
|
|---------|------|
|
|
45
|
-
| `api` |
|
|
46
|
-
| `
|
|
47
|
-
|
|
48
|
-
|
|
34
|
+
| `api` / `ui` / `nodes` | Join shared `vps-edge` |
|
|
35
|
+
| `gateway` | Host nginx + `apps/{name}/sites.conf` + HTTPS via `~/services/gateway/setup-tls.sh` |
|
|
36
|
+
|
|
37
|
+
Multi-app: further products’ `install-gateway.sh` only add `apps/{other}/sites.conf`, refresh TLS SANs, and reload — no second 80/443 bind.
|
|
38
|
+
|
|
39
|
+
## Machine services (installed once)
|
|
40
|
+
|
|
41
|
+
| Path | Purpose |
|
|
42
|
+
|------|---------|
|
|
43
|
+
| `~/services/gateway` | Host nginx (`GATEWAY_HOME`) |
|
|
44
|
+
| `~/services/vibed-infra/update-agent` | Serial pull queue + optional GHCR webhook |
|
|
45
|
+
| `~/services/vibed-infra/persist-logs` | Per-app WALs + optional R2/S3 ship |
|
|
46
|
+
|
|
47
|
+
Auto-update cron **enqueues** work; the agent processes one job at a time. After a GHCR push, the reusable image workflow mints a GitHub Actions OIDC JWT and notifies `https://{domain}/_vibed/hooks/ghcr` (fallback `http://{publicIp}/…`) so the pull does not wait for cron. Updates prune dangling images (`DOCKER_AUTO_PRUNE=1`).
|
|
49
48
|
|
|
50
49
|
## Layout
|
|
51
50
|
|
|
52
51
|
| Path | Purpose |
|
|
53
52
|
|------|---------|
|
|
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 |
|
|
53
|
+
| `package.sh` / `lib/package.py` | Build `dist/` |
|
|
54
|
+
| `templates/host-gateway/` | Shared gateway skeleton |
|
|
55
|
+
| `templates/update-agent/` | Queue agent + webhook |
|
|
56
|
+
| `templates/persist-logs/` | Shipper install |
|
|
57
|
+
| `lib/persistlog/` | Python append / seal / replay |
|
|
58
|
+
| `skills/` | system-gateway, infra-update-agent, persist-logs, dns-configure |
|
|
63
59
|
|
|
64
60
|
## Environment
|
|
65
61
|
|
|
66
62
|
| Variable | Meaning |
|
|
67
63
|
|----------|---------|
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
|
|
74
|
-
Auto-update cron runs profile `update-*.sh`. Each update **prunes dangling Docker images** by default (`DOCKER_AUTO_PRUNE=1`). Set `DOCKER_PRUNE_UNUSED=1` to also drop unused tagged images older than `DOCKER_PRUNE_UNTIL` (default `72h`).
|
|
64
|
+
| `GATEWAY_HOME` | Host gateway dir (default `~/services/gateway`) |
|
|
65
|
+
| `VIBED_HOME` | Machine vibed root (default `~/services/vibed-infra`) |
|
|
66
|
+
| `GATEWAY_PUBLIC_IP` | VPS IPv4 (from `gateway.publicIp`) |
|
|
67
|
+
| `TLS_EMAIL` / `TLS_MODE` | Let’s Encrypt email; `lab` or `letsencrypt` |
|
|
68
|
+
| `PERSIST_LOG_DIR` | Per-service event log dir |
|
|
69
|
+
| `DOCKER_AUTO_PRUNE` | Prune dangling images after update (default on) |
|
|
75
70
|
|
|
76
71
|
## Tests
|
|
77
72
|
|
|
78
73
|
```bash
|
|
79
|
-
npm test
|
|
80
|
-
npm run test:dist
|
|
81
|
-
|
|
74
|
+
npm test
|
|
75
|
+
npm run test:dist
|
|
76
|
+
npm run test:e2e-multi # two apps, localhost wget|bash, shared host gateway
|
|
82
77
|
```
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
## Publishing (npm)
|
|
87
|
-
|
|
88
|
-
Every **merge to `main`** runs [Publish npm](.github/workflows/publish.yml):
|
|
89
|
-
|
|
90
|
-
1. Bumps **minor** (`0.1.0` → `0.2.0`)
|
|
91
|
-
2. Commits `chore: release vibed-infra v…`, tags `v…`, pushes
|
|
92
|
-
3. Runs `npm publish` (needs repo secret `NPM_TOKEN`)
|
|
93
|
-
|
|
94
|
-
**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.
|
|
95
|
-
|
|
96
|
-
Set repository secret **`NPM_TOKEN`** before the first merge to `main`.
|
|
97
|
-
|
|
98
|
-
Ordinary PRs only run CI (`npm test` + dist e2e); they do **not** publish.
|
|
79
|
+
## Publishing
|
|
99
80
|
|
|
100
|
-
|
|
81
|
+
Merge to `main` runs Publish npm (minor bump). Set `NPM_TOKEN`. Ordinary PRs run CI only.
|
|
Binary file
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Notify a vibed-infra VPS to pull a GHCR image.
|
|
3
|
+
|
|
4
|
+
Prefers GitHub Actions OIDC (no shared secret). Falls back to compiled
|
|
5
|
+
config token / VIBED_WEBHOOK_SECRET for local curl.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import urllib.error
|
|
14
|
+
import urllib.parse
|
|
15
|
+
import urllib.request
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
# Keep in sync with lib/webhook.py
|
|
19
|
+
HOOK_PATH = "/_vibed/hooks/ghcr"
|
|
20
|
+
TOKEN_VERSION = "vibed-webhook|v1|"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _derive_token(name: str, public_ip: str, host: str) -> str:
|
|
24
|
+
import hashlib
|
|
25
|
+
|
|
26
|
+
raw = f"{TOKEN_VERSION}{name}|{public_ip}|{host}"
|
|
27
|
+
return hashlib.sha256(raw.encode()).hexdigest()[:32]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _strip(s: str) -> str:
|
|
31
|
+
s = s.strip()
|
|
32
|
+
if (s.startswith('"') and s.endswith('"')) or (s.startswith("'") and s.endswith("'")):
|
|
33
|
+
return s[1:-1]
|
|
34
|
+
return s
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _load_webhook(path: Path) -> dict[str, str]:
|
|
38
|
+
text = path.read_text(encoding="utf-8")
|
|
39
|
+
url = fallback = token = name = public_ip = host = ""
|
|
40
|
+
in_webhook = False
|
|
41
|
+
in_sites = False
|
|
42
|
+
for raw in text.splitlines():
|
|
43
|
+
line = raw.rstrip()
|
|
44
|
+
stripped = line.lstrip()
|
|
45
|
+
if stripped.startswith("webhook:"):
|
|
46
|
+
in_webhook = True
|
|
47
|
+
in_sites = False
|
|
48
|
+
continue
|
|
49
|
+
if in_webhook:
|
|
50
|
+
if stripped and not line.startswith(" ") and not line.startswith("\t"):
|
|
51
|
+
in_webhook = False
|
|
52
|
+
elif stripped.startswith("url:"):
|
|
53
|
+
url = _strip(stripped.split(":", 1)[1])
|
|
54
|
+
elif stripped.startswith("fallbackUrl:"):
|
|
55
|
+
fallback = _strip(stripped.split(":", 1)[1])
|
|
56
|
+
elif stripped.startswith("token:"):
|
|
57
|
+
token = _strip(stripped.split(":", 1)[1])
|
|
58
|
+
if stripped.startswith("name:") and not name:
|
|
59
|
+
name = _strip(stripped.split(":", 1)[1])
|
|
60
|
+
if stripped.startswith("publicIp:"):
|
|
61
|
+
public_ip = _strip(stripped.split(":", 1)[1])
|
|
62
|
+
if stripped.startswith("sites:"):
|
|
63
|
+
in_sites = True
|
|
64
|
+
continue
|
|
65
|
+
if in_sites and stripped.startswith("- host:"):
|
|
66
|
+
host = _strip(stripped.split(":", 1)[1])
|
|
67
|
+
in_sites = False
|
|
68
|
+
elif in_sites and stripped.startswith("host:"):
|
|
69
|
+
host = _strip(stripped.split(":", 1)[1])
|
|
70
|
+
in_sites = False
|
|
71
|
+
if not token and name and (public_ip or host):
|
|
72
|
+
token = _derive_token(name, public_ip, host)
|
|
73
|
+
if not url and host:
|
|
74
|
+
url = f"https://{host}{HOOK_PATH}"
|
|
75
|
+
if not fallback and public_ip:
|
|
76
|
+
fallback = f"http://{public_ip}{HOOK_PATH}"
|
|
77
|
+
if not url:
|
|
78
|
+
url = fallback
|
|
79
|
+
return {"url": url, "fallbackUrl": fallback, "token": token}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _package_from_image(image: str) -> str:
|
|
83
|
+
img = image.split("@", 1)[0]
|
|
84
|
+
if "/" in img:
|
|
85
|
+
return img.rsplit("/", 1)[-1].split(":")[0]
|
|
86
|
+
return img.split(":")[0]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def fetch_oidc_token(audience: str) -> str:
|
|
90
|
+
url = os.environ.get("ACTIONS_ID_TOKEN_REQUEST_URL") or ""
|
|
91
|
+
tok = os.environ.get("ACTIONS_ID_TOKEN_REQUEST_TOKEN") or ""
|
|
92
|
+
if not url or not tok:
|
|
93
|
+
return ""
|
|
94
|
+
sep = "&" if "?" in url else "?"
|
|
95
|
+
req = urllib.request.Request(
|
|
96
|
+
f"{url}{sep}audience={urllib.parse.quote(audience, safe='')}",
|
|
97
|
+
headers={"Authorization": f"Bearer {tok}", "Accept": "application/json"},
|
|
98
|
+
)
|
|
99
|
+
try:
|
|
100
|
+
with urllib.request.urlopen(req, timeout=20) as resp:
|
|
101
|
+
data = json.loads(resp.read().decode())
|
|
102
|
+
return str(data.get("value") or "")
|
|
103
|
+
except Exception as e:
|
|
104
|
+
print(f"notify-vps: OIDC mint failed: {e}", file=sys.stderr)
|
|
105
|
+
return ""
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _post(url: str, body: dict, *, bearer: str = "", secret: str = "") -> tuple[int, str]:
|
|
109
|
+
headers = {"Content-Type": "application/json"}
|
|
110
|
+
if bearer:
|
|
111
|
+
headers["Authorization"] = f"Bearer {bearer}"
|
|
112
|
+
if secret:
|
|
113
|
+
headers["X-Vibed-Secret"] = secret
|
|
114
|
+
req = urllib.request.Request(
|
|
115
|
+
url,
|
|
116
|
+
data=json.dumps(body).encode(),
|
|
117
|
+
method="POST",
|
|
118
|
+
headers=headers,
|
|
119
|
+
)
|
|
120
|
+
try:
|
|
121
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
122
|
+
return resp.status, resp.read().decode()
|
|
123
|
+
except urllib.error.HTTPError as e:
|
|
124
|
+
return e.code, e.read().decode(errors="replace")
|
|
125
|
+
except Exception as e:
|
|
126
|
+
return 0, str(e)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def main() -> int:
|
|
130
|
+
ap = argparse.ArgumentParser()
|
|
131
|
+
ap.add_argument("--image", required=True)
|
|
132
|
+
ap.add_argument("--tag", default="main")
|
|
133
|
+
ap.add_argument("--config", default="")
|
|
134
|
+
ap.add_argument("--url", action="append", default=[], help="Override webhook URL (repeatable)")
|
|
135
|
+
ap.add_argument("--strict", action="store_true", help="Exit 1 if notify fails")
|
|
136
|
+
args = ap.parse_args()
|
|
137
|
+
|
|
138
|
+
spec = {"url": "", "fallbackUrl": "", "token": ""}
|
|
139
|
+
if not args.url:
|
|
140
|
+
roots = []
|
|
141
|
+
if args.config:
|
|
142
|
+
roots.append(Path(args.config))
|
|
143
|
+
cwd = Path.cwd()
|
|
144
|
+
roots.extend(
|
|
145
|
+
[
|
|
146
|
+
cwd / "dist" / "packageconfig.yaml",
|
|
147
|
+
cwd / "packageconfig.yaml",
|
|
148
|
+
cwd / "templates" / "vibed-infra-config.yml",
|
|
149
|
+
]
|
|
150
|
+
)
|
|
151
|
+
conf_path = next((p for p in roots if p.is_file()), None)
|
|
152
|
+
if conf_path:
|
|
153
|
+
spec = _load_webhook(conf_path)
|
|
154
|
+
elif not args.url:
|
|
155
|
+
print("notify-vps: no packageconfig (set gateway.publicIp + sites[].host)", file=sys.stderr)
|
|
156
|
+
return 1 if args.strict else 0
|
|
157
|
+
|
|
158
|
+
token = os.environ.get("VIBED_WEBHOOK_SECRET") or spec.get("token") or ""
|
|
159
|
+
urls = list(args.url) if args.url else [u for u in (spec.get("url"), spec.get("fallbackUrl")) if u]
|
|
160
|
+
seen: set[str] = set()
|
|
161
|
+
urls = [u for u in urls if not (u in seen or seen.add(u))]
|
|
162
|
+
has_oidc = bool(os.environ.get("ACTIONS_ID_TOKEN_REQUEST_URL"))
|
|
163
|
+
if not urls:
|
|
164
|
+
print("notify-vps: skipped (no webhook url)", file=sys.stderr)
|
|
165
|
+
return 1 if args.strict else 0
|
|
166
|
+
if not has_oidc and not token:
|
|
167
|
+
print("notify-vps: skipped (no OIDC env and no token)", file=sys.stderr)
|
|
168
|
+
return 1 if args.strict else 0
|
|
169
|
+
|
|
170
|
+
pkg = _package_from_image(args.image)
|
|
171
|
+
payload = {"package": pkg, "tag": args.tag, "image": args.image}
|
|
172
|
+
last_err = ""
|
|
173
|
+
for url in urls:
|
|
174
|
+
bearer = fetch_oidc_token(url) if has_oidc else ""
|
|
175
|
+
secret = "" if bearer else token
|
|
176
|
+
if not bearer and not secret:
|
|
177
|
+
last_err = f"{url}: no OIDC token"
|
|
178
|
+
print(f"notify-vps: {last_err}", file=sys.stderr)
|
|
179
|
+
continue
|
|
180
|
+
try:
|
|
181
|
+
code, text = _post(url, payload, bearer=bearer, secret=secret)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
code, text = 0, str(e)
|
|
184
|
+
if code == 200:
|
|
185
|
+
print(f"notify-vps: {url} → {text.strip()}")
|
|
186
|
+
return 0
|
|
187
|
+
last_err = f"{url} HTTP {code}: {text[:200]}"
|
|
188
|
+
print(f"notify-vps: {last_err}", file=sys.stderr)
|
|
189
|
+
print(f"notify-vps: failed ({last_err})", file=sys.stderr)
|
|
190
|
+
return 1 if args.strict else 0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
raise SystemExit(main())
|
|
@@ -4,12 +4,19 @@
|
|
|
4
4
|
#
|
|
5
5
|
# jobs:
|
|
6
6
|
# build-api:
|
|
7
|
-
# uses:
|
|
7
|
+
# uses: naiemk/vibed-infra/.github/workflows/docker-build-reusable.yml@main
|
|
8
8
|
# with:
|
|
9
9
|
# dockerfile: deploy/Dockerfile.api
|
|
10
10
|
# image: ghcr.io/${{ github.repository_owner }}/my-api
|
|
11
11
|
# context: .
|
|
12
|
+
# permissions:
|
|
13
|
+
# contents: read
|
|
14
|
+
# packages: write
|
|
15
|
+
# id-token: write
|
|
12
16
|
# secrets: inherit
|
|
17
|
+
#
|
|
18
|
+
# After push to the default branch, notifies the VPS using GitHub Actions OIDC
|
|
19
|
+
# (audience = webhook URL). No GitHub Packages webhook and no extra secrets.
|
|
13
20
|
|
|
14
21
|
name: Reusable docker build (single image)
|
|
15
22
|
|
|
@@ -26,14 +33,26 @@ on:
|
|
|
26
33
|
required: false
|
|
27
34
|
type: string
|
|
28
35
|
default: .
|
|
36
|
+
notify:
|
|
37
|
+
required: false
|
|
38
|
+
type: boolean
|
|
39
|
+
default: true
|
|
40
|
+
secrets:
|
|
41
|
+
VIBED_WEBHOOK_SECRET:
|
|
42
|
+
required: false
|
|
29
43
|
|
|
30
44
|
permissions:
|
|
31
45
|
contents: read
|
|
32
46
|
packages: write
|
|
47
|
+
id-token: write
|
|
33
48
|
|
|
34
49
|
jobs:
|
|
35
50
|
build-push:
|
|
36
51
|
runs-on: ubuntu-latest
|
|
52
|
+
permissions:
|
|
53
|
+
contents: read
|
|
54
|
+
packages: write
|
|
55
|
+
id-token: write
|
|
37
56
|
steps:
|
|
38
57
|
- uses: actions/checkout@v4
|
|
39
58
|
- uses: docker/setup-buildx-action@v3
|
|
@@ -59,3 +78,15 @@ jobs:
|
|
|
59
78
|
labels: |
|
|
60
79
|
org.opencontainers.image.revision=${{ github.sha }}
|
|
61
80
|
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
|
81
|
+
- name: Notify VPS to pull
|
|
82
|
+
if: inputs.notify && github.event.repository.default_branch == github.ref_name
|
|
83
|
+
continue-on-error: true
|
|
84
|
+
env:
|
|
85
|
+
VIBED_WEBHOOK_SECRET: ${{ secrets.VIBED_WEBHOOK_SECRET }}
|
|
86
|
+
run: |
|
|
87
|
+
script=dist/notify-vps-pull.py
|
|
88
|
+
if [[ ! -f "$script" ]]; then
|
|
89
|
+
echo "notify-vps: dist/notify-vps-pull.py missing — re-package and commit dist/"
|
|
90
|
+
exit 0
|
|
91
|
+
fi
|
|
92
|
+
python3 "$script" --image "${{ inputs.image }}" --tag main
|
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
|
|