vibed-infra 0.3.0 → 0.4.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 +50 -51
- package/install-auto-update.sh +10 -2
- package/install.sh +14 -5
- package/lib/__pycache__/load_config.cpython-312.pyc +0 -0
- package/lib/__pycache__/product_config.cpython-312.pyc +0 -0
- package/lib/package.py +259 -0
- package/lib/product_config.py +219 -0
- package/lib/prompt.sh +9 -7
- package/package.json +4 -2
- package/package.sh +7 -0
- package/schema/packageconfig.md +59 -71
- package/skills/infra-cicd/SKILL.md +15 -18
- package/skills/infra-packager/SKILL.md +26 -24
- package/skills/system-gateway/SKILL.md +14 -17
- package/templates/docker-compose.workers.yml +9 -2
- package/templates/generic/gateway/nginx.conf +23 -0
- package/templates/generic/gen-dev-certs.sh +16 -0
- package/templates/generic/start-api.sh +58 -0
- package/templates/generic/start-gateway.sh +93 -0
- package/templates/generic/start-nodes.sh +31 -0
- package/templates/generic/start-ui.sh +40 -0
- package/templates/generic/update-api.sh +39 -0
- package/templates/generic/update-gateway.sh +41 -0
- package/templates/generic/update-nodes.sh +37 -0
- package/templates/generic/update-ui.sh +39 -0
package/README.md
CHANGED
|
@@ -1,59 +1,63 @@
|
|
|
1
1
|
# vibed-infra
|
|
2
2
|
|
|
3
|
-
Product-agnostic VPS deployment packager: wget
|
|
3
|
+
Product-agnostic VPS deployment packager: **`package.sh`** turns four YAML configs into a committed **`dist/`** tree operators wget on the box — install scripts, generic start/update helpers, env examples, and gateway nginx.
|
|
4
4
|
|
|
5
5
|
Published on npm as **`vibed-infra`**.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Product maintainer flow
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
npm install vibed-infra
|
|
11
|
-
# or in a monorepo sibling checkout:
|
|
12
|
-
# "vibed-infra": "file:../vibed-infra"
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Resolve the packager root from Node:
|
|
9
|
+
Author **`templates/`** in your product repo:
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
|------|---------|
|
|
13
|
+
| `vibed-infra-config.yml` | Product name, template refs, edge network, gateway `sites[]`, auto-update |
|
|
14
|
+
| `api-config.yaml` | API image, port, opaque `config:` |
|
|
15
|
+
| `ui-config.yaml` | UI image, port |
|
|
16
|
+
| `nodes-config.yaml` | Worker image, opaque `config:` |
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
Then package and commit **`dist/`**:
|
|
22
19
|
|
|
23
20
|
```bash
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
PRODUCT_RAW=/path/to/product/deploy/templates \
|
|
27
|
-
bash /path/to/node_modules/vibed-infra/install.sh --profile api
|
|
21
|
+
./package.sh # exec vibed-infra packager → writes dist/
|
|
22
|
+
git add dist && git commit && git push
|
|
28
23
|
```
|
|
29
24
|
|
|
30
|
-
|
|
25
|
+
Resolve the packager from npm:
|
|
31
26
|
|
|
32
27
|
```bash
|
|
33
|
-
|
|
28
|
+
node -e "console.log(require('path').dirname(require.resolve('vibed-infra/package.json')))"
|
|
34
29
|
```
|
|
35
30
|
|
|
36
|
-
|
|
31
|
+
Example product: [`examples/vps-hello`](examples/vps-hello) (`app/`, `build-images.sh`, four YAMLs, committed `dist/`).
|
|
32
|
+
|
|
33
|
+
## Operator flow (wget on VPS)
|
|
37
34
|
|
|
38
35
|
```bash
|
|
39
|
-
wget -qO- https://raw.githubusercontent.com/
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
wget -qO- https://raw.githubusercontent.com/ORG/REPO/main/dist/install-api.sh | bash
|
|
37
|
+
wget -qO- .../dist/install-nodes.sh | bash
|
|
38
|
+
wget -qO- .../dist/install-ui.sh | bash
|
|
39
|
+
wget -qO- .../dist/install-gateway.sh | bash
|
|
40
|
+
# edit each install dir .env, then ./start-*.sh
|
|
44
41
|
```
|
|
45
42
|
|
|
43
|
+
| Profile | Role |
|
|
44
|
+
|---------|------|
|
|
45
|
+
| `api` | Backend on shared edge network |
|
|
46
|
+
| `ui` | UI container (separate install) |
|
|
47
|
+
| `nodes` | Worker compose on edge network |
|
|
48
|
+
| `gateway` | HTTPS nginx only (API + UI must already run) |
|
|
49
|
+
|
|
46
50
|
## Layout
|
|
47
51
|
|
|
48
52
|
| Path | Purpose |
|
|
49
53
|
|------|---------|
|
|
54
|
+
| `package.sh` / `lib/package.py` | Build product `dist/` from four YAMLs + generic templates |
|
|
50
55
|
| `install.sh` | Single entrypoint (`INFRA_PROFILE` or `--profile`) |
|
|
51
|
-
| `start.sh` / `update.sh` | Generic lifecycle in install dir |
|
|
52
56
|
| `install-auto-update.sh` | Cron from profile flags |
|
|
53
|
-
| `lib/` | fetch, env, tls, prompt, generate |
|
|
54
|
-
| `templates/` | Generic
|
|
55
|
-
| `schema/packageconfig.md` |
|
|
56
|
-
| `examples/vps-hello/` | Full
|
|
57
|
+
| `lib/` | fetch, env, tls, prompt, generate, product_config |
|
|
58
|
+
| `templates/generic/` | Generic start/update/env/certs/gateway |
|
|
59
|
+
| `schema/packageconfig.md` | Four-file product schema + compiled packageconfig |
|
|
60
|
+
| `examples/vps-hello/` | Full example with dist e2e tests |
|
|
57
61
|
| `skills/` | Cursor skills |
|
|
58
62
|
| `github/workflows/` | Reusable GHCR build workflow |
|
|
59
63
|
|
|
@@ -62,11 +66,21 @@ wget -qO- https://raw.githubusercontent.com/naiemk/vibed-infra/main/install.sh |
|
|
|
62
66
|
| Variable | Meaning |
|
|
63
67
|
|----------|---------|
|
|
64
68
|
| `PACKAGER_RAW` | Base URL or local path to this package root |
|
|
65
|
-
| `PACKAGECONFIG_URL` | Product `packageconfig.yaml` (URL or path) |
|
|
66
|
-
| `PRODUCT_RAW` | Product
|
|
67
|
-
| `INFRA_PROFILE` | `api`, `nodes`, or `gateway` |
|
|
69
|
+
| `PACKAGECONFIG_URL` | Product `packageconfig.yaml` or `dist/` URL (URL or path) |
|
|
70
|
+
| `PRODUCT_RAW` | Product `dist/` base (templates + install scripts) |
|
|
71
|
+
| `INFRA_PROFILE` | `api`, `ui`, `nodes`, or `gateway` |
|
|
68
72
|
| `INSTALL_DIR` | Target directory (default `.`) |
|
|
69
73
|
|
|
74
|
+
## Tests
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm test # package + dry-run all dist/install-*.sh
|
|
78
|
+
npm run test:dist # build images + e2e api, ui, nodes (sequential)
|
|
79
|
+
./examples/vps-hello/test-dist.sh --profile api # single profile
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
CI: `validate` job then parallel `dist-e2e` matrix (`api`, `ui`, `nodes`).
|
|
83
|
+
|
|
70
84
|
## Publishing (npm)
|
|
71
85
|
|
|
72
86
|
Every **merge to `main`** runs [Publish npm](.github/workflows/publish.yml):
|
|
@@ -75,25 +89,10 @@ Every **merge to `main`** runs [Publish npm](.github/workflows/publish.yml):
|
|
|
75
89
|
2. Commits `chore: release vibed-infra v…`, tags `v…`, pushes
|
|
76
90
|
3. Runs `npm publish` (needs repo secret `NPM_TOKEN`)
|
|
77
91
|
|
|
78
|
-
**Major versions** — set `version` in `package.json` yourself (e.g. `1.0.0`) and include **`[major]`** in the commit message
|
|
79
|
-
|
|
80
|
-
Manual run: Actions → Publish npm → `workflow_dispatch` (`minor` / `major` / `none`).
|
|
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.
|
|
81
93
|
|
|
82
|
-
Set repository secret **`NPM_TOKEN`**
|
|
94
|
+
Set repository secret **`NPM_TOKEN`** before the first merge to `main`.
|
|
83
95
|
|
|
84
|
-
Ordinary PRs only run CI (`npm test`); they do **not** publish.
|
|
85
|
-
|
|
86
|
-
## Example (VPS)
|
|
87
|
-
|
|
88
|
-
[`examples/vps-hello`](examples/vps-hello) is a complete product that uses this packager: `packageconfig.yaml`, install wrappers, start/update scripts, and a tiny API/UI/worker you can build on the box.
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
git clone https://github.com/naiemk/vibed-infra.git
|
|
92
|
-
cd vibed-infra
|
|
93
|
-
./examples/vps-hello/scripts/build-images.sh
|
|
94
|
-
mkdir -p ~/hello-vps/api && cd ~/hello-vps/api
|
|
95
|
-
bash /path/to/vibed-infra/examples/vps-hello/install/install-api.sh
|
|
96
|
-
# edit .env, then ./start-hello-api.sh
|
|
97
|
-
```
|
|
96
|
+
Ordinary PRs only run CI (`npm test` + dist e2e); they do **not** publish.
|
|
98
97
|
|
|
99
98
|
Runbook: [`examples/vps-hello/README.md`](examples/vps-hello/README.md).
|
package/install-auto-update.sh
CHANGED
|
@@ -19,14 +19,17 @@ fi
|
|
|
19
19
|
if [[ -z "$ROLE" && -n "$PROFILE" ]]; then
|
|
20
20
|
case "$PROFILE" in
|
|
21
21
|
api) ROLE=backend ;;
|
|
22
|
+
ui) ROLE=ui ;;
|
|
22
23
|
nodes) ROLE=workers ;;
|
|
23
24
|
gateway) ROLE=gateway ;;
|
|
24
25
|
esac
|
|
25
26
|
fi
|
|
26
27
|
if [[ -z "$ROLE" ]]; then
|
|
27
|
-
if compgen -G "start-*-gateway.sh" >/dev/null || [[ -f docker-compose.gateway.yml ]]; then
|
|
28
|
+
if [[ -f start-gateway.sh ]] || compgen -G "start-*-gateway.sh" >/dev/null || [[ -f docker-compose.gateway.yml ]]; then
|
|
28
29
|
ROLE=gateway
|
|
29
|
-
elif compgen -G "start-*-
|
|
30
|
+
elif [[ -f start-ui.sh ]] || compgen -G "start-*-ui.sh" >/dev/null; then
|
|
31
|
+
ROLE=ui
|
|
32
|
+
elif [[ -f start-nodes.sh ]] || compgen -G "start-*-nodes.sh" >/dev/null || [[ -f docker-compose.workers.yml ]]; then
|
|
30
33
|
ROLE=workers
|
|
31
34
|
else
|
|
32
35
|
ROLE=backend
|
|
@@ -47,6 +50,11 @@ case "$ROLE" in
|
|
|
47
50
|
INTERVAL_MIN="${API_AUTO_UPDATE_INTERVAL_MIN:-30}"
|
|
48
51
|
CRON_OFFSET=0
|
|
49
52
|
;;
|
|
53
|
+
ui)
|
|
54
|
+
if role_auto_update_on UI_AUTO_UPDATE; then cron_enabled=1; fi
|
|
55
|
+
INTERVAL_MIN="${UI_AUTO_UPDATE_INTERVAL_MIN:-20}"
|
|
56
|
+
CRON_OFFSET=15
|
|
57
|
+
;;
|
|
50
58
|
workers|nodes)
|
|
51
59
|
ROLE=nodes
|
|
52
60
|
if role_auto_update_on NODES_AUTO_UPDATE; then cron_enabled=1; fi
|
package/install.sh
CHANGED
|
@@ -18,6 +18,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
18
18
|
for c in "${_comps[@]}"; do
|
|
19
19
|
case "$c" in
|
|
20
20
|
app|backend|api) PROFILE=api; break ;;
|
|
21
|
+
ui) PROFILE=ui; break ;;
|
|
21
22
|
workers|nodes) PROFILE=nodes; break ;;
|
|
22
23
|
gateway) PROFILE=gateway; break ;;
|
|
23
24
|
esac
|
|
@@ -26,7 +27,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
26
27
|
;;
|
|
27
28
|
--dest) DEST="$2"; shift 2 ;;
|
|
28
29
|
-h|--help)
|
|
29
|
-
echo "usage: install.sh [--profile api|nodes|gateway] [--dest DIR]"
|
|
30
|
+
echo "usage: install.sh [--profile api|ui|nodes|gateway] [--dest DIR]"
|
|
30
31
|
echo "env: PACKAGECONFIG_URL PRODUCT_RAW PACKAGER_RAW INFRA_PROFILE INSTALL_DIR"
|
|
31
32
|
exit 0
|
|
32
33
|
;;
|
|
@@ -160,16 +161,24 @@ if [[ -n "$COMPOSE_MAINNET" ]]; then
|
|
|
160
161
|
infra_write_template "${PRODUCT_RAW}/${COMPOSE_MAINNET}" "$DEST/${COMPOSE_MAINNET}" 0
|
|
161
162
|
fi
|
|
162
163
|
|
|
163
|
-
# Generic infra compose templates
|
|
164
|
+
# Generic infra compose templates — only when product did not supply compose in packageconfig.
|
|
164
165
|
case "$ROLE" in
|
|
165
166
|
backend)
|
|
166
|
-
|
|
167
|
+
if [[ -z "$COMPOSE_TPL" ]]; then
|
|
168
|
+
infra_write_template "${PACKAGER_RAW}/templates/docker-compose.backend.yml" "$DEST/docker-compose.backend.yml" 0
|
|
169
|
+
fi
|
|
170
|
+
;;
|
|
171
|
+
ui)
|
|
167
172
|
;;
|
|
168
173
|
workers)
|
|
169
|
-
|
|
174
|
+
if [[ -z "$COMPOSE_TPL" ]]; then
|
|
175
|
+
infra_write_template "${PACKAGER_RAW}/templates/docker-compose.workers.yml" "$DEST/docker-compose.workers.yml" 0
|
|
176
|
+
fi
|
|
170
177
|
;;
|
|
171
178
|
gateway)
|
|
172
|
-
|
|
179
|
+
if [[ -z "$COMPOSE_MAINNET" && -z "$COMPOSE_TPL" ]]; then
|
|
180
|
+
infra_write_template "${PACKAGER_RAW}/templates/docker-compose.gateway.yml" "$DEST/docker-compose.gateway.yml" 0
|
|
181
|
+
fi
|
|
173
182
|
;;
|
|
174
183
|
esac
|
|
175
184
|
|
|
Binary file
|
|
Binary file
|
package/lib/package.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Build product dist/ from templates/*.yaml — operators wget install-*.sh from dist."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import shutil
|
|
7
|
+
import stat
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
12
|
+
from product_config import ( # noqa: E402
|
|
13
|
+
compile_packageconfig,
|
|
14
|
+
dump_yaml,
|
|
15
|
+
load_product,
|
|
16
|
+
write_app_config,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _chmod_x(path: Path) -> None:
|
|
21
|
+
path.chmod(path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _write(path: Path, text: str, executable: bool = False) -> None:
|
|
25
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
path.write_text(text, encoding="utf-8")
|
|
27
|
+
if executable:
|
|
28
|
+
_chmod_x(path)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _copy_generic(packager: Path, out: Path, name: str) -> None:
|
|
32
|
+
src = packager / "templates" / "generic" / name
|
|
33
|
+
if not src.is_file():
|
|
34
|
+
raise FileNotFoundError(f"missing generic template {src}")
|
|
35
|
+
shutil.copy2(src, out / name)
|
|
36
|
+
_chmod_x(out / name)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _env_api(meta: dict, conf: dict, api: dict) -> str:
|
|
40
|
+
au = conf["profiles"]["api"]["autoUpdate"]
|
|
41
|
+
return f"""# Generated by vibed-infra package — edit .env on the VPS, not in git dist.
|
|
42
|
+
API_TOKEN=change-me-api-token
|
|
43
|
+
DOCKER_NETWORK={meta['network']}
|
|
44
|
+
DOCKER_NAME={meta['apiContainer']}
|
|
45
|
+
BACKEND_IMAGE={conf['images']['backend']}
|
|
46
|
+
HOST_PORT={meta['apiPort']}
|
|
47
|
+
DATA_DIR=./data
|
|
48
|
+
CONFIG_FILE=api-app.yaml
|
|
49
|
+
API_MEMORY_LIMIT=256m
|
|
50
|
+
{au['flag']}=0
|
|
51
|
+
{au['intervalEnv']}=30
|
|
52
|
+
{au['stopTimeoutEnv']}=60
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _env_ui(meta: dict, conf: dict, ui: dict) -> str:
|
|
57
|
+
au = conf["profiles"]["ui"]["autoUpdate"]
|
|
58
|
+
return f"""# Generated by vibed-infra package
|
|
59
|
+
DOCKER_NETWORK={meta['network']}
|
|
60
|
+
UI_NAME={meta['uiContainer']}
|
|
61
|
+
UI_IMAGE={conf['images']['ui']}
|
|
62
|
+
UI_MEMORY_LIMIT=32m
|
|
63
|
+
{au['flag']}=0
|
|
64
|
+
{au['intervalEnv']}=20
|
|
65
|
+
{au['stopTimeoutEnv']}=30
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _env_nodes(meta: dict, conf: dict, nodes: dict) -> str:
|
|
70
|
+
au = conf["profiles"]["nodes"]["autoUpdate"]
|
|
71
|
+
api_host = meta["apiContainer"]
|
|
72
|
+
return f"""# Generated by vibed-infra package
|
|
73
|
+
API_URL=http://{api_host}:{meta['apiPort']}
|
|
74
|
+
API_TOKEN=change-me-api-token
|
|
75
|
+
DOCKER_NETWORK={meta['network']}
|
|
76
|
+
WORKER_IMAGE={conf['images']['worker']}
|
|
77
|
+
WORKER_CONTAINER_NAME={meta['workerContainer']}
|
|
78
|
+
WORKER_ROLE=heartbeat
|
|
79
|
+
INTERVAL_SEC=60
|
|
80
|
+
WORKER_MEMORY_LIMIT=64m
|
|
81
|
+
{au['flag']}=0
|
|
82
|
+
{au['intervalEnv']}=30
|
|
83
|
+
{au['stopTimeoutEnv']}=30
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _env_gateway(meta: dict, conf: dict, sites: list) -> str:
|
|
88
|
+
au = conf["profiles"]["gateway"]["autoUpdate"]
|
|
89
|
+
host = sites[0]["host"] if sites else "example.com"
|
|
90
|
+
cert_dir = sites[0].get("tlsCertDir", f"/etc/letsencrypt/live/{host}") if sites else "/etc/letsencrypt/live/example.com"
|
|
91
|
+
flags = au.get("flags") or ["GATEWAY_AUTO_UPDATE"]
|
|
92
|
+
flag_lines = "\n".join(f"{f}=0" for f in flags)
|
|
93
|
+
return f"""# Generated by vibed-infra package
|
|
94
|
+
DOCKER_NETWORK={meta['network']}
|
|
95
|
+
GATEWAY_NAME={meta['gatewayContainer']}
|
|
96
|
+
NGINX_IMAGE={conf['images']['nginx']}
|
|
97
|
+
TLS_FULLCHAIN={cert_dir}/fullchain.pem
|
|
98
|
+
TLS_PRIVKEY={cert_dir}/privkey.pem
|
|
99
|
+
CERTBOT_WWW=./certbot-www
|
|
100
|
+
HTTP_PORT=80
|
|
101
|
+
HTTPS_PORT=443
|
|
102
|
+
GATEWAY_MEMORY_LIMIT=64m
|
|
103
|
+
{flag_lines}
|
|
104
|
+
{au.get('intervalEnv', 'GATEWAY_AUTO_UPDATE_INTERVAL_MIN')}=20
|
|
105
|
+
{au.get('stopTimeoutEnv', 'GATEWAY_STOP_TIMEOUT')}=30
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _compose_workers(meta: dict, conf: dict) -> str:
|
|
110
|
+
return f"""# Generated by vibed-infra package
|
|
111
|
+
networks:
|
|
112
|
+
edge:
|
|
113
|
+
external: true
|
|
114
|
+
name: ${{DOCKER_NETWORK:-{meta['network']}}}
|
|
115
|
+
|
|
116
|
+
services:
|
|
117
|
+
worker:
|
|
118
|
+
image: ${{WORKER_IMAGE:-{conf['images']['worker']}}}
|
|
119
|
+
container_name: ${{WORKER_CONTAINER_NAME:-{meta['workerContainer']}}}
|
|
120
|
+
restart: unless-stopped
|
|
121
|
+
mem_limit: ${{WORKER_MEMORY_LIMIT:-64m}}
|
|
122
|
+
env_file: .env
|
|
123
|
+
environment:
|
|
124
|
+
WORKER_ROLE: ${{WORKER_ROLE:-heartbeat}}
|
|
125
|
+
WORKER_CONFIG: /config/workers.yaml
|
|
126
|
+
API_URL: ${{API_URL:-http://{meta['apiContainer']}:{meta['apiPort']}}}
|
|
127
|
+
INTERVAL_SEC: ${{INTERVAL_SEC:-60}}
|
|
128
|
+
volumes:
|
|
129
|
+
- ./logs:/data/logs
|
|
130
|
+
- ./nodes-workers.yaml:/config/workers.yaml:ro
|
|
131
|
+
extra_hosts:
|
|
132
|
+
- "host.docker.internal:host-gateway"
|
|
133
|
+
networks:
|
|
134
|
+
- edge
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _install_wrapper(profile: str, packager_raw: str) -> str:
|
|
139
|
+
return f"""#!/usr/bin/env bash
|
|
140
|
+
# wget install — profile {profile}. Generated by vibed-infra package.
|
|
141
|
+
set -euo pipefail
|
|
142
|
+
SCRIPT_DIR="$(cd "$(dirname "${{BASH_SOURCE[0]:-$0}}")" && pwd)"
|
|
143
|
+
PACKAGER_RAW="${{PACKAGER_RAW:-{packager_raw}}}"
|
|
144
|
+
export PACKAGECONFIG_URL="${{PACKAGECONFIG_URL:-$SCRIPT_DIR/packageconfig.yaml}}"
|
|
145
|
+
export PRODUCT_RAW="${{PRODUCT_RAW:-$SCRIPT_DIR}}"
|
|
146
|
+
export INFRA_PROFILE={profile}
|
|
147
|
+
export INSTALL_DIR="${{INSTALL_DIR:-.}}"
|
|
148
|
+
if [[ "$PACKAGER_RAW" =~ ^/ ]]; then
|
|
149
|
+
exec bash "$PACKAGER_RAW/install.sh" --profile {profile}
|
|
150
|
+
fi
|
|
151
|
+
if command -v curl >/dev/null 2>&1; then
|
|
152
|
+
exec bash <(curl -fsSL "${{PACKAGER_RAW}}/install.sh") --profile {profile}
|
|
153
|
+
else
|
|
154
|
+
exec bash <(wget -qO- "${{PACKAGER_RAW}}/install.sh") --profile {profile}
|
|
155
|
+
fi
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def build_dist(product_dir: Path, out_dir: Path, packager_root: Path) -> None:
|
|
160
|
+
product_dir = product_dir.resolve()
|
|
161
|
+
out_dir = out_dir.resolve()
|
|
162
|
+
packager_root = packager_root.resolve()
|
|
163
|
+
|
|
164
|
+
if out_dir.exists():
|
|
165
|
+
shutil.rmtree(out_dir)
|
|
166
|
+
out_dir.mkdir(parents=True)
|
|
167
|
+
|
|
168
|
+
product = load_product(product_dir)
|
|
169
|
+
tpl = product["templates_dir"]
|
|
170
|
+
raw_base = str(out_dir) # local dist path; operators override PRODUCT_RAW via wget URL
|
|
171
|
+
|
|
172
|
+
conf = compile_packageconfig(
|
|
173
|
+
product,
|
|
174
|
+
raw_base=raw_base,
|
|
175
|
+
packager_raw=str(packager_root),
|
|
176
|
+
)
|
|
177
|
+
meta = conf["_meta"]
|
|
178
|
+
comps = product["components"]
|
|
179
|
+
sites = conf["profiles"]["gateway"].get("sites") or []
|
|
180
|
+
|
|
181
|
+
# Copy source configs into dist
|
|
182
|
+
shutil.copy2(tpl / "vibed-infra-config.yml", out_dir / "vibed-infra-config.yml")
|
|
183
|
+
refs = product["infra"].get("templates") or {}
|
|
184
|
+
for dst, key in (
|
|
185
|
+
("api-config.yaml", "api"),
|
|
186
|
+
("ui-config.yaml", "ui"),
|
|
187
|
+
("nodes-config.yaml", "nodes"),
|
|
188
|
+
):
|
|
189
|
+
src_name = refs.get(key) or f"{key}-config.yaml"
|
|
190
|
+
shutil.copy2(tpl / src_name, out_dir / dst)
|
|
191
|
+
|
|
192
|
+
_write(out_dir / "packageconfig.yaml", dump_yaml(conf))
|
|
193
|
+
|
|
194
|
+
write_app_config(comps["api"], out_dir / "api-app.yaml")
|
|
195
|
+
write_app_config(comps["nodes"], out_dir / "nodes-workers.yaml")
|
|
196
|
+
|
|
197
|
+
_write(out_dir / ".env.api.example", _env_api(meta, conf, comps["api"]))
|
|
198
|
+
_write(out_dir / ".env.ui.example", _env_ui(meta, conf, comps["ui"]))
|
|
199
|
+
_write(out_dir / ".env.nodes.example", _env_nodes(meta, conf, comps["nodes"]))
|
|
200
|
+
_write(out_dir / ".env.gateway.example", _env_gateway(meta, conf, sites))
|
|
201
|
+
_write(out_dir / "docker-compose.workers.yml", _compose_workers(meta, conf))
|
|
202
|
+
|
|
203
|
+
gw_dir = out_dir / "gateway"
|
|
204
|
+
gw_dir.mkdir()
|
|
205
|
+
shutil.copy2(packager_root / "templates" / "generic" / "gateway" / "nginx.conf", gw_dir / "nginx.conf")
|
|
206
|
+
|
|
207
|
+
for script in (
|
|
208
|
+
"start-api.sh",
|
|
209
|
+
"start-ui.sh",
|
|
210
|
+
"start-nodes.sh",
|
|
211
|
+
"start-gateway.sh",
|
|
212
|
+
"update-api.sh",
|
|
213
|
+
"update-ui.sh",
|
|
214
|
+
"update-nodes.sh",
|
|
215
|
+
"update-gateway.sh",
|
|
216
|
+
"gen-dev-certs.sh",
|
|
217
|
+
):
|
|
218
|
+
_copy_generic(packager_root, out_dir, script)
|
|
219
|
+
|
|
220
|
+
packager_raw_url = "https://raw.githubusercontent.com/naiemk/vibed-infra/main"
|
|
221
|
+
for profile in ("api", "ui", "nodes", "gateway"):
|
|
222
|
+
name = f"install-{profile}.sh"
|
|
223
|
+
_write(out_dir / name, _install_wrapper(profile, packager_raw_url), executable=True)
|
|
224
|
+
|
|
225
|
+
readme = f"""# dist — generated by vibed-infra package
|
|
226
|
+
|
|
227
|
+
Do not edit by hand. Run `../package.sh` from the product root and commit.
|
|
228
|
+
|
|
229
|
+
## VPS install
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
wget -qO- https://raw.githubusercontent.com/ORG/REPO/main/dist/install-api.sh | bash
|
|
233
|
+
wget -qO- https://raw.githubusercontent.com/ORG/REPO/main/dist/install-ui.sh | bash
|
|
234
|
+
wget -qO- https://raw.githubusercontent.com/ORG/REPO/main/dist/install-nodes.sh | bash
|
|
235
|
+
wget -qO- https://raw.githubusercontent.com/ORG/REPO/main/dist/install-gateway.sh | bash
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Network: `{meta['network']}`. API container: `{meta['apiContainer']}`.
|
|
239
|
+
"""
|
|
240
|
+
_write(out_dir / "README.md", readme)
|
|
241
|
+
print(f"packaged: {out_dir}")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def main() -> int:
|
|
245
|
+
ap = argparse.ArgumentParser(description="Build product dist/ for VPS wget install")
|
|
246
|
+
ap.add_argument("--product", required=True, help="Product root (contains templates/)")
|
|
247
|
+
ap.add_argument("--out", required=True, help="Output dist directory")
|
|
248
|
+
ap.add_argument(
|
|
249
|
+
"--packager",
|
|
250
|
+
default=str(Path(__file__).resolve().parent.parent),
|
|
251
|
+
help="vibed-infra root",
|
|
252
|
+
)
|
|
253
|
+
args = ap.parse_args()
|
|
254
|
+
build_dist(Path(args.product), Path(args.out), Path(args.packager))
|
|
255
|
+
return 0
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
if __name__ == "__main__":
|
|
259
|
+
sys.exit(main())
|