vibed-infra 0.3.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 +99 -0
- package/github/workflows/docker-build-reusable.yml +61 -0
- package/install-auto-update.sh +125 -0
- package/install.sh +259 -0
- package/lib/__pycache__/load_config.cpython-312.pyc +0 -0
- package/lib/env.sh +224 -0
- package/lib/fetch.sh +49 -0
- package/lib/generate.py +119 -0
- package/lib/load_config.py +110 -0
- package/lib/prompt.sh +25 -0
- package/lib/tls.sh +59 -0
- package/package.json +34 -0
- package/schema/packageconfig.md +88 -0
- package/skills/infra-cicd/SKILL.md +47 -0
- package/skills/infra-packager/SKILL.md +54 -0
- package/skills/system-gateway/SKILL.md +52 -0
- package/start.sh +15 -0
- package/templates/docker-compose.backend.yml +32 -0
- package/templates/docker-compose.gateway.yml +25 -0
- package/templates/docker-compose.workers.yml +17 -0
- package/templates/nginx/nginx.conf +21 -0
- package/update.sh +15 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# packageconfig.yaml schema
|
|
2
|
+
|
|
3
|
+
Product-owned file. Infra reads **only** the fields below.
|
|
4
|
+
|
|
5
|
+
## Top level
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
name: my-product # slug for cron markers / logs
|
|
9
|
+
version: 1
|
|
10
|
+
|
|
11
|
+
rawBase: https://raw.githubusercontent.com/org/repo/main/deploy/templates
|
|
12
|
+
packagerRaw: https://raw.githubusercontent.com/naiemk/vibed-infra/main
|
|
13
|
+
|
|
14
|
+
network:
|
|
15
|
+
edge: my-product-edge # shared Docker network for gateway + APIs
|
|
16
|
+
|
|
17
|
+
images:
|
|
18
|
+
backend: ghcr.io/org/my-api:main
|
|
19
|
+
ui: ghcr.io/org/my-ui:main
|
|
20
|
+
worker: ghcr.io/org/my-worker:main
|
|
21
|
+
nginx: ghcr.io/org/my-nginx:main # optional; default nginx:alpine + mounted conf
|
|
22
|
+
|
|
23
|
+
autoUpdate:
|
|
24
|
+
lockFile: /var/lock/infra-auto-update.lock # optional
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## profiles
|
|
28
|
+
|
|
29
|
+
Each install directory maps to one profile key (`api`, `nodes`, `gateway`, …).
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
profiles:
|
|
33
|
+
api:
|
|
34
|
+
role: backend # backend | workers | gateway
|
|
35
|
+
templates:
|
|
36
|
+
config: app.yaml # fetched from rawBase; never overwritten if exists
|
|
37
|
+
envExample: .env.api.example
|
|
38
|
+
startScript: start-api.sh # copied from rawBase or generated
|
|
39
|
+
updateScript: update-api.sh
|
|
40
|
+
autoUpdate:
|
|
41
|
+
flag: API_AUTO_UPDATE
|
|
42
|
+
intervalEnv: API_AUTO_UPDATE_INTERVAL_MIN
|
|
43
|
+
offset: 0 # cron minute offset (api :00, nodes :10, gateway :20)
|
|
44
|
+
stopTimeoutEnv: API_STOP_TIMEOUT
|
|
45
|
+
extras: [] # optional scripts from rawBase (e.g. register-node.sh)
|
|
46
|
+
|
|
47
|
+
nodes:
|
|
48
|
+
role: workers
|
|
49
|
+
templates:
|
|
50
|
+
config: workers.yaml
|
|
51
|
+
envExample: .env.nodes.example
|
|
52
|
+
compose: docker-compose.workers.yml
|
|
53
|
+
workers:
|
|
54
|
+
services:
|
|
55
|
+
- name: worker-a
|
|
56
|
+
containerName: my-worker-a
|
|
57
|
+
roleEnv: WORKER_ROLE=a
|
|
58
|
+
activityLog: /data/logs/a.jsonl
|
|
59
|
+
- name: worker-b
|
|
60
|
+
profile: optional # compose profile name
|
|
61
|
+
autoUpdate: { flag: NODES_AUTO_UPDATE, offset: 10, ... }
|
|
62
|
+
|
|
63
|
+
gateway:
|
|
64
|
+
role: gateway
|
|
65
|
+
mode: standalone # standalone | bundled
|
|
66
|
+
templates:
|
|
67
|
+
envExample: .env.gateway.example
|
|
68
|
+
nginxInclude: gateway/extra.conf # optional product snippet
|
|
69
|
+
sites:
|
|
70
|
+
- host: app.example.com
|
|
71
|
+
aliases: [www.app.example.com]
|
|
72
|
+
backend: app-api
|
|
73
|
+
backendPort: 8080
|
|
74
|
+
ui: app-ui
|
|
75
|
+
uiPort: 80
|
|
76
|
+
healthPath: /api/health # optional; default /api/health
|
|
77
|
+
createPath: /api/items # optional rate-limited POST; omitted if unset
|
|
78
|
+
tlsCertDir: /etc/letsencrypt/live/app.example.com
|
|
79
|
+
autoUpdate:
|
|
80
|
+
flags: [UI_AUTO_UPDATE, GATEWAY_AUTO_UPDATE]
|
|
81
|
+
offset: 20
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Infra ignores
|
|
85
|
+
|
|
86
|
+
- Keys inside `config` YAML templates
|
|
87
|
+
- Secret names inside `.env` (opaque strings)
|
|
88
|
+
- Application health-check response bodies
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: infra-cicd
|
|
3
|
+
description: >-
|
|
4
|
+
Set up GitHub Actions CI/CD for infra-packaged products: GHCR image build,
|
|
5
|
+
compose smoke, and install e2e against published images. Use when adding deploy
|
|
6
|
+
pipelines for Backend/UI/worker images.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Infra packager CI/CD
|
|
10
|
+
|
|
11
|
+
## Image build (GHCR)
|
|
12
|
+
|
|
13
|
+
Use reusable workflow [`github/workflows/docker-build-reusable.yml`](../../github/workflows/docker-build-reusable.yml):
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
jobs:
|
|
17
|
+
images:
|
|
18
|
+
uses: naiemk/vibed-infra/.github/workflows/docker-build-reusable.yml@main
|
|
19
|
+
with:
|
|
20
|
+
dockerfile: deploy/Dockerfile.api
|
|
21
|
+
image: ghcr.io/${{ github.repository_owner }}/my-api
|
|
22
|
+
secrets: inherit
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or copy the workflow into the product repo. Tags: `:main`, `main-<sha>`, semver on tag push.
|
|
26
|
+
|
|
27
|
+
## Install e2e (optional job)
|
|
28
|
+
|
|
29
|
+
1. Serve repo over HTTP (Python `http.server` at repo root or multi-path).
|
|
30
|
+
2. Set `PACKAGER_RAW` to the packager (npm path, git checkout, or HTTP) and `PACKAGECONFIG_URL=.../deploy/packageconfig.yaml`.
|
|
31
|
+
3. Optionally set `PRODUCT_RAW` if it should differ from `packageconfig.rawBase`.
|
|
32
|
+
4. Run the product `install-api.sh` into a temp dir.
|
|
33
|
+
5. Fill `.env` with test secrets; `./start-*.sh`; curl health.
|
|
34
|
+
|
|
35
|
+
See [`examples/vps-hello/scripts/try-install.sh`](../../examples/vps-hello/scripts/try-install.sh) for a packager-only dry-run.
|
|
36
|
+
|
|
37
|
+
## packageconfig in CI
|
|
38
|
+
|
|
39
|
+
- `IMAGE_TAG=main` in system tests matches GHCR `:main` from default branch push.
|
|
40
|
+
- Keep `rawBase` pointing at `main` branch raw URLs for operator wget; override with `PRODUCT_RAW` on a feature branch.
|
|
41
|
+
|
|
42
|
+
## Checklist
|
|
43
|
+
|
|
44
|
+
- [ ] Dockerfiles under `deploy/`
|
|
45
|
+
- [ ] `deploy/packageconfig.yaml` images match GHCR names
|
|
46
|
+
- [ ] Install dry-run or workflow job passes
|
|
47
|
+
- [ ] Secrets not in templates — only `.env.example` placeholders
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: infra-packager
|
|
3
|
+
description: >-
|
|
4
|
+
Wire a new product to the infra VPS packager: packageconfig.yaml, Docker images,
|
|
5
|
+
deploy/templates, thin install wrappers, and CI. Use when adding Backend/UI/worker
|
|
6
|
+
deploy to a repo or migrating from ad-hoc deploy/install scripts.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Infra packager — onboard a product
|
|
10
|
+
|
|
11
|
+
## When to use
|
|
12
|
+
|
|
13
|
+
- New repo needs wget VPS install (backend + UI + workers + HTTPS gateway)
|
|
14
|
+
- Splitting deploy scripts into reusable vibed-infra + product templates
|
|
15
|
+
|
|
16
|
+
## Steps
|
|
17
|
+
|
|
18
|
+
1. **Depend on** [`vibed-infra`](https://www.npmjs.com/package/vibed-infra) (`npm install vibed-infra`) or wget `install.sh` from this repo. Set `packagerRaw` in packageconfig.
|
|
19
|
+
|
|
20
|
+
2. **Create** `deploy/packageconfig.yaml` — see [`schema/packageconfig.md`](../../schema/packageconfig.md) and the VPS example [`examples/vps-hello/packageconfig.yaml`](../../examples/vps-hello/packageconfig.yaml).
|
|
21
|
+
|
|
22
|
+
3. **Add templates** under `deploy/templates/`:
|
|
23
|
+
- `.env.*.example` (secrets — opaque to infra)
|
|
24
|
+
- App config YAML (opaque)
|
|
25
|
+
- `start-*.sh` / `update-*.sh` (or use generic `start.sh`)
|
|
26
|
+
- Worker `docker-compose.*.yml` if multi-runner
|
|
27
|
+
|
|
28
|
+
4. **Thin wrappers** (set profile + product URLs):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# deploy/install/install-api.sh
|
|
32
|
+
export INFRA_PROFILE=api
|
|
33
|
+
export PACKAGECONFIG_URL=https://raw.githubusercontent.com/ORG/REPO/main/deploy/packageconfig.yaml
|
|
34
|
+
export PRODUCT_RAW=https://raw.githubusercontent.com/ORG/REPO/main/deploy/templates
|
|
35
|
+
wget -qO- https://raw.githubusercontent.com/naiemk/vibed-infra/main/install.sh | bash
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
5. **Build images** — Dockerfiles in `deploy/`; push to GHCR; reference in `packageconfig.images`.
|
|
39
|
+
|
|
40
|
+
6. **CI** — use [`github/workflows/docker-build-reusable.yml`](../../github/workflows/docker-build-reusable.yml); add install e2e serving the packager + `deploy/templates/` over HTTP.
|
|
41
|
+
|
|
42
|
+
7. **VPS** — per component directory:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
mkdir -p ~/app/api && cd ~/app/api
|
|
46
|
+
wget -qO- .../deploy/install/install-api.sh | bash
|
|
47
|
+
# edit .env, then ./start-api.sh (or ./start.sh)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Rules
|
|
51
|
+
|
|
52
|
+
- Infra never parses app config keys — only image names, ports, volume paths, site hostnames.
|
|
53
|
+
- Never overwrite existing `.env` on re-install.
|
|
54
|
+
- Gateway container names must match `sites[].backend` / `sites[].ui` on shared `network.edge`.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: system-gateway
|
|
3
|
+
description: >-
|
|
4
|
+
Configure a system-wide HTTPS gateway on one VPS for multiple products/domains.
|
|
5
|
+
Use when several infra-packaged projects share one host, edge network, and Let's Encrypt.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# System-wide gateway (multi-project)
|
|
9
|
+
|
|
10
|
+
## Model
|
|
11
|
+
|
|
12
|
+
- One **gateway** install dir per host (or per edge): nginx on ports 80/443.
|
|
13
|
+
- Each product **backend** + **UI** containers join the same Docker network (`network.edge` from each product's packageconfig — use one shared name, e.g. `vps-edge`).
|
|
14
|
+
- Gateway `sites[]` in one product's packageconfig **or** a dedicated `gateway-only` packageconfig lists all vhosts.
|
|
15
|
+
|
|
16
|
+
## Steps
|
|
17
|
+
|
|
18
|
+
1. Pick shared network: `DOCKER_NETWORK=vps-edge` in every product `.env`.
|
|
19
|
+
|
|
20
|
+
2. Install APIs/UIs with distinct container names:
|
|
21
|
+
- `product-a-api`, `product-a-ui`
|
|
22
|
+
- `product-b-api`, `product-b-ui`
|
|
23
|
+
|
|
24
|
+
3. Install gateway once:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
mkdir -p ~/vps/gateway && cd ~/vps/gateway
|
|
28
|
+
wget -qO- .../install-gateway.sh | bash
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
4. Edit gateway `packageconfig` `sites[]` (or merge generated `gateway/conf.d/domains.conf`) so each `host` maps to the correct `backend` + `ui` container names.
|
|
32
|
+
|
|
33
|
+
5. **TLS** — single SAN cert or per-host certs:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
sudo certbot certonly --standalone \
|
|
37
|
+
-d app-a.example.com -d app-b.example.com -d www.app-b.example.com
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Set `TLS_FULLCHAIN` / `TLS_PRIVKEY` in gateway `.env`. Defaults come from the first `sites[].tlsCertDir` (or `/etc/letsencrypt/live/<host>`).
|
|
41
|
+
|
|
42
|
+
6. **Auto-update** — stagger cron: APIs :00, workers :10, gateway :20 (infra default).
|
|
43
|
+
|
|
44
|
+
## Dual-domain on one host
|
|
45
|
+
|
|
46
|
+
Two APIs + two UIs on one edge network, one nginx `sites[]` list. See [`examples/vps-hello/packageconfig.yaml`](../../examples/vps-hello/packageconfig.yaml) for a single-site starting point.
|
|
47
|
+
|
|
48
|
+
## Pitfalls
|
|
49
|
+
|
|
50
|
+
- API must listen on a container name resolvable by nginx (`app-api:8080`, not `localhost`).
|
|
51
|
+
- Do not bind host port 443 twice — only gateway publishes 443.
|
|
52
|
+
- Pull UI/nginx **before** stop on gateway update (infra update scripts do this).
|
package/start.sh
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Generic start — delegates to profile startScript from .infra-profile.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
cd "$SCRIPT_DIR"
|
|
6
|
+
if [[ -f .infra-profile ]]; then
|
|
7
|
+
# shellcheck source=/dev/null
|
|
8
|
+
source .infra-profile
|
|
9
|
+
fi
|
|
10
|
+
TARGET="${START_SCRIPT:-start.sh}"
|
|
11
|
+
if [[ -x "$SCRIPT_DIR/$TARGET" && "$TARGET" != "start.sh" ]]; then
|
|
12
|
+
exec "$SCRIPT_DIR/$TARGET" "$@"
|
|
13
|
+
fi
|
|
14
|
+
echo "No profile start script — run product-specific start script listed in .infra-profile" >&2
|
|
15
|
+
exit 1
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Generic backend service (optional compose path).
|
|
2
|
+
# Most products use profile start script (docker create + config cp) for remote Docker compatibility.
|
|
3
|
+
#
|
|
4
|
+
# Usage: docker compose --env-file .env -f docker-compose.backend.yml up -d
|
|
5
|
+
|
|
6
|
+
services:
|
|
7
|
+
backend:
|
|
8
|
+
image: ${BACKEND_IMAGE:-ghcr.io/example/backend:main}
|
|
9
|
+
container_name: ${DOCKER_NAME:-backend}
|
|
10
|
+
restart: ${RESTART:-unless-stopped}
|
|
11
|
+
mem_limit: ${BACKEND_MEMORY_LIMIT:-384m}
|
|
12
|
+
env_file: .env
|
|
13
|
+
environment:
|
|
14
|
+
CONFIG_PATH: /config/server.yaml
|
|
15
|
+
DB_PATH: /data/app.db
|
|
16
|
+
ports:
|
|
17
|
+
- "${HOST_PORT:-8080}:8080"
|
|
18
|
+
volumes:
|
|
19
|
+
- ${DATA_DIR:-./data}:/data
|
|
20
|
+
- ${CONFIG_FILE:-./app.yaml}:/config/server.yaml:ro
|
|
21
|
+
networks:
|
|
22
|
+
- edge
|
|
23
|
+
healthcheck:
|
|
24
|
+
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/api/health || exit 1"]
|
|
25
|
+
interval: 10s
|
|
26
|
+
timeout: 5s
|
|
27
|
+
retries: 6
|
|
28
|
+
|
|
29
|
+
networks:
|
|
30
|
+
edge:
|
|
31
|
+
name: ${DOCKER_NETWORK:-app-edge}
|
|
32
|
+
external: false
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Standalone HTTPS gateway: nginx + optional UI containers on shared edge network.
|
|
2
|
+
# Prefer product-specific start script when nginx image bundles domains; this template uses mounted conf.
|
|
3
|
+
|
|
4
|
+
services:
|
|
5
|
+
gateway:
|
|
6
|
+
image: ${NGINX_IMAGE:-nginx:alpine}
|
|
7
|
+
container_name: ${GATEWAY_NAME:-app-gateway}
|
|
8
|
+
restart: unless-stopped
|
|
9
|
+
mem_limit: ${GATEWAY_MEMORY_LIMIT:-64m}
|
|
10
|
+
ports:
|
|
11
|
+
- "${HTTP_PORT:-80}:80"
|
|
12
|
+
- "${HTTPS_PORT:-443}:443"
|
|
13
|
+
volumes:
|
|
14
|
+
- ./gateway/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
15
|
+
- ./gateway/conf.d:/etc/nginx/conf.d:ro
|
|
16
|
+
- ${TLS_FULLCHAIN:-/etc/letsencrypt/live/example.com/fullchain.pem}:/etc/nginx/certs/fullchain.pem:ro
|
|
17
|
+
- ${TLS_PRIVKEY:-/etc/letsencrypt/live/example.com/privkey.pem}:/etc/nginx/certs/privkey.pem:ro
|
|
18
|
+
- ${CERTBOT_WWW:-/var/www/certbot}:/var/www/certbot:ro
|
|
19
|
+
networks:
|
|
20
|
+
- edge
|
|
21
|
+
|
|
22
|
+
networks:
|
|
23
|
+
edge:
|
|
24
|
+
name: ${DOCKER_NETWORK:-app-edge}
|
|
25
|
+
external: true
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generic worker runners — customize via packageconfig workers.services[].
|
|
2
|
+
# Product install copies a concrete compose (e.g. docker-compose.sweepers.yml) from templates/.
|
|
3
|
+
|
|
4
|
+
services:
|
|
5
|
+
worker:
|
|
6
|
+
image: ${WORKER_IMAGE:-ghcr.io/example/worker:main}
|
|
7
|
+
container_name: ${WORKER_CONTAINER_NAME:-app-worker}
|
|
8
|
+
restart: unless-stopped
|
|
9
|
+
mem_limit: ${WORKER_MEMORY_LIMIT:-192m}
|
|
10
|
+
env_file: .env
|
|
11
|
+
environment:
|
|
12
|
+
WORKER_CONFIG: /config/workers.yaml
|
|
13
|
+
volumes:
|
|
14
|
+
- ./logs:/data/logs
|
|
15
|
+
- ${CONFIG_FILE:-./workers.yaml}:/config/workers.yaml:ro
|
|
16
|
+
extra_hosts:
|
|
17
|
+
- "host.docker.internal:host-gateway"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
user nginx;
|
|
2
|
+
worker_processes auto;
|
|
3
|
+
error_log /var/log/nginx/error.log warn;
|
|
4
|
+
pid /var/run/nginx.pid;
|
|
5
|
+
|
|
6
|
+
events {
|
|
7
|
+
worker_connections 1024;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
http {
|
|
11
|
+
include /etc/nginx/mime.types;
|
|
12
|
+
default_type application/octet-stream;
|
|
13
|
+
sendfile on;
|
|
14
|
+
keepalive_timeout 65;
|
|
15
|
+
|
|
16
|
+
limit_req_zone $binary_remote_addr zone=api_create:10m rate=1r/s;
|
|
17
|
+
limit_req_zone $binary_remote_addr zone=api_public:10m rate=20r/s;
|
|
18
|
+
limit_conn_zone $binary_remote_addr zone=addr:10m;
|
|
19
|
+
|
|
20
|
+
include /etc/nginx/conf.d/*.conf;
|
|
21
|
+
}
|
package/update.sh
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Generic update — delegates to profile updateScript from .infra-profile.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
cd "$SCRIPT_DIR"
|
|
6
|
+
if [[ -f .infra-profile ]]; then
|
|
7
|
+
# shellcheck source=/dev/null
|
|
8
|
+
source .infra-profile
|
|
9
|
+
fi
|
|
10
|
+
TARGET="${UPDATE_SCRIPT:-update.sh}"
|
|
11
|
+
if [[ -x "$SCRIPT_DIR/$TARGET" && "$TARGET" != "update.sh" ]]; then
|
|
12
|
+
exec "$SCRIPT_DIR/$TARGET" "$@"
|
|
13
|
+
fi
|
|
14
|
+
echo "No profile update script — run product-specific update script listed in .infra-profile" >&2
|
|
15
|
+
exit 1
|