315 lines
8.8 KiB
JSON
315 lines
8.8 KiB
JSON
{
|
|
"_meta": {
|
|
"status": "UNVERIFIED — derived from the repos, not from the estate",
|
|
"why": "ppl/infra/ was written and never applied: no ~/.pulumi, no infra/venv, no stack state, files dated 'mar 6'. The estate was built in the console and the IaC is aspirational. B1's inventory is what replaces these values with observed ones; until it runs, every field here is a CLAIM.",
|
|
"never_record": "credential values. Resource ids and settings only. nova's gateway secret is deliberately absent from this file even though it is committed in plaintext in ppl/gateway/nginx/conf.d/nova.conf — see services[].raw.",
|
|
"sources": [
|
|
"ppl/infra/__main__.py",
|
|
"ppl/ctrl/dns.sh",
|
|
"ppl/ctrl/certs.sh",
|
|
"ppl/gateway/docker-compose.yml",
|
|
"ppl/gateway/nginx/conf.d/",
|
|
"ppl/local/Caddyfile"
|
|
],
|
|
"placement": {
|
|
"box": "a container on the estate's own docker network — upstream is the container name",
|
|
"local": "a rig cluster on a peer, reached over the overlay — upstream is that peer's address",
|
|
"instance": "a dedicated cloud instance on the overlay — same rendering as `local`",
|
|
"hosted": "a managed endpoint. Declared so moving to one is a one-line change; unused.",
|
|
"_why": "A service says WHERE it runs. How it is reached follows from that, and the three properties of a static-upstream vhost — upstream{}, no resolver, no set $var — are one decision rather than three."
|
|
}
|
|
},
|
|
"domain": "mcrn.ar",
|
|
"local_domain": "local.ar",
|
|
"host": "mcrn",
|
|
"host_admin": "mcrn-admin",
|
|
"instance": {
|
|
"type": "t3.small",
|
|
"disk_gb": 30,
|
|
"disk_type": "gp3",
|
|
"image": "debian-12",
|
|
"user": "mariano"
|
|
},
|
|
"firewall": [
|
|
{
|
|
"port": 22,
|
|
"proto": "tcp",
|
|
"desc": "SSH"
|
|
},
|
|
{
|
|
"port": 80,
|
|
"proto": "tcp",
|
|
"desc": "HTTP"
|
|
},
|
|
{
|
|
"port": 443,
|
|
"proto": "tcp",
|
|
"desc": "HTTPS"
|
|
},
|
|
{
|
|
"port": 3022,
|
|
"proto": "tcp",
|
|
"desc": "Gitea SSH",
|
|
"note": "compose maps 3022:22 but GITEA__server__SSH_PORT=22, so gitea advertises :22 in clone URLs while listening on :3022. B1 confirms which is real."
|
|
},
|
|
{
|
|
"port": 51820,
|
|
"proto": "udp",
|
|
"desc": "WireGuard",
|
|
"note": "ABSENT from ppl/infra/__main__.py's four rules — but the tunnel is live (ping 10.8.0.1 succeeds), so the real security group must already allow it. The code therefore does not describe the estate. Confirm in V1."
|
|
}
|
|
],
|
|
"network": {
|
|
"docker_network": "gateway",
|
|
"docker_network_note": "A fixed, externally-joinable bridge name. Every unrelated app stack on the box joins it so nginx can resolve them by container name. This is why the gateway compose declares 8 services while nginx routes 20+ hostnames.",
|
|
"wireguard_moved": "superseded by the top-level `vpn` block"
|
|
},
|
|
"vpn": {
|
|
"_status": "PARTIAL — addresses and subnet verified from the live wg0 interface on nrft. Peer public keys, endpoints, allowed-ips and keepalive still need `wg show` capture on both ends (V1). Nulls below are unknowns, not defaults.",
|
|
"_never_record": "private keys. `wg show` prints 'private key: (hidden)' and is the safe capture command. `wg showconf` dumps PrivateKey= in clear — never use it.",
|
|
"overlays": {
|
|
"estate": {
|
|
"purpose": "Connects the estate's machines across clouds without a shared VPC, and carries everything that does not need to be publicly reachable.",
|
|
"subnet": "10.8.0.0/24",
|
|
"listen_port": 51820,
|
|
"peers": {
|
|
"box": {
|
|
"address": "10.8.0.1",
|
|
"role": "hub",
|
|
"note": "mcrn.ar. Has a public IP, so it is the peer others dial. Carries the registry (:5000) and woodpecker's gRPC (:9000), both bound to this address and therefore overlay-only.",
|
|
"endpoint": null,
|
|
"public_key": null,
|
|
"allowed_ips": null
|
|
},
|
|
"nrft": {
|
|
"address": "10.8.0.2",
|
|
"role": "roaming",
|
|
"note": "The dev box. Behind NAT, so it must initiate and needs PersistentKeepalive. Verified: wg0 UP at 10.8.0.2/24, ping 10.8.0.1 0% loss at 153ms.",
|
|
"endpoint": null,
|
|
"public_key": null,
|
|
"allowed_ips": null,
|
|
"keepalive": null
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"databases": [
|
|
"gitea",
|
|
"woodpecker",
|
|
"umami"
|
|
],
|
|
"certs": {
|
|
"issued": [
|
|
"mcrn.ar",
|
|
"*.mcrn.ar",
|
|
"*.spr.mcrn.ar"
|
|
],
|
|
"issued_source": "ppl/ctrl/certs.sh:92 — the -d flags passed to certbot",
|
|
"note": "What the cert ACTUALLY covers. estate_sans() derives what the services NEED. check.sh compares the two; the difference is the finding, not a restatement."
|
|
},
|
|
"services": [
|
|
{
|
|
"name": "gitea",
|
|
"host": "git",
|
|
"upstream": "gitea:3000",
|
|
"targets": [
|
|
"aws"
|
|
]
|
|
},
|
|
{
|
|
"name": "woodpecker",
|
|
"host": "ci",
|
|
"upstream": "woodpecker-server:8000",
|
|
"targets": [
|
|
"aws"
|
|
]
|
|
},
|
|
{
|
|
"name": "registry",
|
|
"host": "registry",
|
|
"upstream": "registry:5000",
|
|
"targets": [
|
|
"aws"
|
|
]
|
|
},
|
|
{
|
|
"name": "umami",
|
|
"host": "analytics",
|
|
"upstream": "umami:3000",
|
|
"targets": [
|
|
"aws"
|
|
]
|
|
},
|
|
{
|
|
"name": "docserve",
|
|
"host": "docs",
|
|
"upstream": "docserve:8020",
|
|
"targets": [
|
|
"aws"
|
|
]
|
|
},
|
|
{
|
|
"name": "ghost",
|
|
"host": "notes",
|
|
"upstream": "ghost:2368",
|
|
"targets": [
|
|
"aws"
|
|
]
|
|
},
|
|
{
|
|
"name": "deskmeter",
|
|
"host": "deskmeter",
|
|
"upstream": "dmweb:10000",
|
|
"targets": [
|
|
"aws"
|
|
],
|
|
"local_port": 10000
|
|
},
|
|
{
|
|
"name": "sysmonstm",
|
|
"host": "sysmonstm",
|
|
"upstream": "sysmonstm-edge:8080",
|
|
"targets": [
|
|
"aws"
|
|
],
|
|
"local_port": 8020
|
|
},
|
|
{
|
|
"name": "malvalava",
|
|
"host": "malvalava",
|
|
"upstream": "mlvclean-frontend:80",
|
|
"targets": [
|
|
"aws"
|
|
],
|
|
"local_port": 30090
|
|
},
|
|
{
|
|
"name": "soleprint",
|
|
"host": "soleprint",
|
|
"upstream": "soleprint:8000",
|
|
"targets": [
|
|
"aws"
|
|
],
|
|
"local_port": 12000
|
|
},
|
|
{
|
|
"name": "dlt",
|
|
"host": "dlt.spr",
|
|
"upstream": "dlt_spr:8000",
|
|
"targets": [
|
|
"aws"
|
|
]
|
|
},
|
|
{
|
|
"name": "sample",
|
|
"host": "sample.spr",
|
|
"upstream": "sample_spr:8000",
|
|
"targets": [
|
|
"aws"
|
|
]
|
|
},
|
|
{
|
|
"name": "mariano",
|
|
"host": "mariano",
|
|
"kind": "static",
|
|
"targets": [
|
|
"aws"
|
|
]
|
|
},
|
|
{
|
|
"name": "rigui",
|
|
"host": "rig",
|
|
"kind": "static",
|
|
"targets": [
|
|
"aws"
|
|
],
|
|
"local_port": 20310
|
|
},
|
|
{
|
|
"name": "unt",
|
|
"host": "unt",
|
|
"targets": [
|
|
"local"
|
|
],
|
|
"local_port": 8040
|
|
},
|
|
{
|
|
"name": "mpr",
|
|
"host": "mpr",
|
|
"targets": [
|
|
"local"
|
|
],
|
|
"local_port": 30080
|
|
},
|
|
{
|
|
"name": "nvi",
|
|
"host": "nvi",
|
|
"targets": [
|
|
"local"
|
|
],
|
|
"local_port": 8060
|
|
},
|
|
{
|
|
"name": "eth",
|
|
"host": "eth",
|
|
"targets": [
|
|
"local"
|
|
],
|
|
"local_port": 8050
|
|
},
|
|
{
|
|
"name": "amar",
|
|
"host": "amar",
|
|
"targets": [
|
|
"local"
|
|
],
|
|
"local_port": 8030
|
|
},
|
|
{
|
|
"name": "nova",
|
|
"host": "nova",
|
|
"upstream": "nova-ui:80",
|
|
"targets": [
|
|
"aws"
|
|
],
|
|
"raw": true,
|
|
"raw_why": "Gated on an X-Gateway-Secret header whose value is committed in plaintext. The value is NOT recorded here. Worse: stellarair.conf proxies to the SAME nova-ui:80 upstream WITHOUT the check, so the gate is bypassable by hostname. Stays hand-written until that is decided."
|
|
},
|
|
{
|
|
"name": "stellarair",
|
|
"host": "stellarair",
|
|
"upstream": "nova-ui:80",
|
|
"targets": [
|
|
"aws"
|
|
],
|
|
"raw": true,
|
|
"raw_why": "See nova. Same upstream, no header gate."
|
|
},
|
|
{
|
|
"name": "langfuse",
|
|
"host": "langfuse",
|
|
"local_host": "lng",
|
|
"placement": "local",
|
|
"peer": "nrft",
|
|
"port": 3000,
|
|
"targets": [
|
|
"aws",
|
|
"local"
|
|
],
|
|
"local_port": 3000,
|
|
"note": "One service, one socket, two names. It was two entries with one flagged `raw`; placement is what made the exception expressible, so it is generated now."
|
|
},
|
|
{
|
|
"name": "legacy",
|
|
"host": "*.soleprint",
|
|
"upstream": "soleprint:8000",
|
|
"targets": [
|
|
"aws"
|
|
],
|
|
"raw": true,
|
|
"raw_why": "A regex server_name with a named capture plus sub_filter injection — not expressible as a template. ALSO BROKEN: its /api/, /admin/, /static/ and / blocks proxy to 127.0.0.1, i.e. inside the nginx container where nothing listens, so every legacy room 502s. Only /wrapper/ uses the correct container-name form."
|
|
}
|
|
]
|
|
}
|