change domains, ips and make flake check work

This commit is contained in:
Lars Nolden
2026-06-26 16:12:57 +02:00
parent 9a6a901dfe
commit 7579d4dcf5
12 changed files with 60 additions and 29 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
run: | run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
install -m 600 /dev/stdin ~/.ssh/id_ed25519 <<< "${{ secrets.DEPLOY_SSH_KEY }}" install -m 600 /dev/stdin ~/.ssh/id_ed25519 <<< "${{ secrets.DEPLOY_SSH_KEY }}"
ssh-keyscan -H db-host.gebos.internal app-host.gebos.internal ingest.ge-bos.de >> ~/.ssh/known_hosts 2>/dev/null || true ssh-keyscan -H db.gebos.online app.gebos.online ingest.gebos.online >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Build all closures - name: Build all closures
run: nix flake check --no-build run: nix flake check --no-build
+18 -5
View File
@@ -9,15 +9,28 @@ and CI/CD pipelines for the project. Everything is one `flake.nix`.
Three hosts (see issue #21 for the full design discussion): Three hosts (see issue #21 for the full design discussion):
| Host | Role | Public hostname | | Host | Role | Public hostname | Private IP |
| ------------- | ----------------------------------------- | ---------------------- | | ------------- | ----------------------------------------- | ---------------------- | ---------- |
| `mqtt-ingest` | HiveMQ CE + Go MQTT→Postgres ingester | `ingest.ge-bos.de` | | `mqtt-ingest` | HiveMQ CE + Go MQTT→Postgres ingester | `ingest.gebos.online` | `10.0.0.4` |
| `db-host` | Postgres 17 + TimescaleDB (telemetry+auth)| internal only | | `db-host` | Postgres 17 + TimescaleDB (telemetry+auth)| `db.gebos.online` (SSH only) | `10.0.0.2` |
| `app-host` | Caddy + Kong + Supabase (compose) + SPA | `app.ge-bos.de`, `api.ge-bos.de` | | `app-host` | Caddy + Kong + Supabase (compose) + SPA | `app.gebos.online`, `api.gebos.online` | `10.0.0.3` |
Public REST surface is PostgREST + SQL `/rpc/` functions, fronted by Kong, TLS-terminated by Caddy. Public REST surface is PostgREST + SQL `/rpc/` functions, fronted by Kong, TLS-terminated by Caddy.
Supabase Studio is bound to `127.0.0.1` on `app-host` — reach it with `ssh -L 3000:127.0.0.1:3000 app-host`. Supabase Studio is bound to `127.0.0.1` on `app-host` — reach it with `ssh -L 3000:127.0.0.1:3000 app-host`.
### Networking
There is no private DNS. Each host has a public hostname (used for `deploy-rs`
SSH access from the Gitea runner, which is **not** on the private network, plus
TLS ingress where applicable), and a static `10.0.0.0/8` IP used for all
host-to-host traffic:
- `db.gebos.online` is for SSH/deploy only — Postgres is never exposed publicly.
- `app-host` and `mqtt-ingest` reach Postgres at `10.0.0.2:5432` over the private
network.
- `db-host` only accepts Postgres (port 5432) from `10.0.0.0/8` (firewall rule
in `nix/hosts/db-host.nix`).
## Repo layout ## Repo layout
``` ```
+2 -3
View File
@@ -1,12 +1,11 @@
{ buildNpmPackage, lib }: { buildNpmPackage }:
buildNpmPackage { buildNpmPackage {
pname = "gebos-frontend"; pname = "gebos-frontend";
version = "0.0.0"; version = "0.0.0";
src = ./.; src = ./.;
# Filled in once package-lock.json exists. npmDepsHash = "sha256-GeXgNbf94QRLNaZP8ma2vx19Vzm8jDWPeOTvE9exwm0=";
npmDepsHash = lib.fakeHash;
installPhase = '' installPhase = ''
runHook preInstall runHook preInstall
+1 -1
View File
@@ -11,7 +11,7 @@ const DEV_SUPABASE_ANON_KEY =
const SUPABASE_URL = const SUPABASE_URL =
import.meta.env.VITE_SUPABASE_URL ?? import.meta.env.VITE_SUPABASE_URL ??
(import.meta.env.DEV ? DEV_SUPABASE_URL : "https://api.ge-bos.de"); (import.meta.env.DEV ? DEV_SUPABASE_URL : "https://api.gebos.online");
const SUPABASE_ANON_KEY = const SUPABASE_ANON_KEY =
import.meta.env.VITE_SUPABASE_ANON_KEY ?? import.meta.env.VITE_SUPABASE_ANON_KEY ??
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+6 -3
View File
@@ -18,8 +18,11 @@ in
nodes = { nodes = {
# Order matters: the Gitea Actions workflow invokes them in this sequence. # Order matters: the Gitea Actions workflow invokes them in this sequence.
db-host = mkNode "db-host" "db-host.gebos.internal"; # TODO: real address # SSH/deploy targets use public hostnames because the Gitea runner is not on
app-host = mkNode "app-host" "app-host.gebos.internal"; # TODO: real address # the private network. Host-to-host traffic still uses the 10.0.0.0/8 IPs.
mqtt-ingest = mkNode "mqtt-ingest" "ingest.ge-bos.de"; # See the host address table in README.md.
db-host = mkNode "db-host" "db.gebos.online";
app-host = mkNode "app-host" "app.gebos.online";
mqtt-ingest = mkNode "mqtt-ingest" "ingest.gebos.online";
}; };
} }
+16 -5
View File
@@ -38,6 +38,13 @@ let
VITE_SUPABASE_URL = dev.supabaseUrl; VITE_SUPABASE_URL = dev.supabaseUrl;
VITE_SUPABASE_ANON_KEY = dev.anonKey; VITE_SUPABASE_ANON_KEY = dev.anonKey;
}; };
# services-flake ships no MQTT broker, so run mosquitto as a plain
# process-compose process. Anonymous access on the dev listener only.
mosquittoConf = pkgs.writeText "mosquitto-dev.conf" ''
listener ${toString dev.mqttPort} ${dev.mqttHost}
allow_anonymous true
'';
in in
{ {
imports = [ services-flake.processComposeModules.default ]; imports = [ services-flake.processComposeModules.default ];
@@ -48,14 +55,18 @@ in
port = dev.pgPort; port = dev.pgPort;
initialDatabases = [{ name = dev.pgDB; }]; initialDatabases = [{ name = dev.pgDB; }];
}; };
mosquitto."broker" = {
enable = true;
port = dev.mqttPort;
};
}; };
settings.processes = { settings.processes = {
broker = {
command = "${pkgs.mosquitto}/bin/mosquitto -c ${mosquittoConf}";
readiness_probe = {
exec.command = "${pkgs.mosquitto}/bin/mosquitto_sub -h ${dev.mqttHost} -p ${toString dev.mqttPort} -t '$$SYS/#' -C 1 -W 2";
initial_delay_seconds = 1;
period_seconds = 2;
};
};
ingester = { ingester = {
command = "${pkgs.go}/bin/go run ./ingester"; command = "${pkgs.go}/bin/go run ./ingester";
environment = ingesterEnv; environment = ingesterEnv;
+3 -3
View File
@@ -8,7 +8,7 @@
services.gebos.supabase = { services.gebos.supabase = {
enable = true; enable = true;
# Compose stack points at external db-host instead of the bundled `db` service. # Compose stack points at external db-host instead of the bundled `db` service.
postgresHost = "db-host.gebos.internal"; # TODO: real internal hostname postgresHost = "10.0.0.2"; # db-host on the private network
postgresPort = 5432; postgresPort = 5432;
# Studio bound to loopback only — reach via `ssh -L 3000:127.0.0.1:3000 app-host`. # Studio bound to loopback only — reach via `ssh -L 3000:127.0.0.1:3000 app-host`.
studioBindAddress = "127.0.0.1"; studioBindAddress = "127.0.0.1";
@@ -18,8 +18,8 @@
enable = true; enable = true;
# Caddy terminates TLS, proxies to Kong on 127.0.0.1:8000. # Caddy terminates TLS, proxies to Kong on 127.0.0.1:8000.
sites = { sites = {
"api.ge-bos.de" = { upstream = "127.0.0.1:8000"; }; # → Kong → PostgREST/GoTrue "api.gebos.online" = { upstream = "127.0.0.1:8000"; }; # → Kong → PostgREST/GoTrue
"app.ge-bos.de" = { staticRoot = "${config.services.gebos.frontend.package}/share/frontend"; }; "app.gebos.online" = { staticRoot = "${config.services.gebos.frontend.package}/share/frontend"; };
}; };
}; };
+4
View File
@@ -23,6 +23,10 @@
enable = true; enable = true;
settings.PasswordAuthentication = false; settings.PasswordAuthentication = false;
}; };
# TODO: restrict inbound SSH (port 22) to the Gitea deployment runner's IP
# only, instead of leaving it open to the world. Likely via
# services.openssh.openFirewall = false + a firewall.extraInputRules rule
# accepting tcp dport 22 from the runner's source address.
networking.firewall.enable = true; networking.firewall.enable = true;
+3 -3
View File
@@ -15,15 +15,15 @@
services.gebos.ingester = { services.gebos.ingester = {
enable = true; enable = true;
# mqttBroker defaults to tcp://127.0.0.1:1883 (the local HiveMQ). # mqttBroker defaults to tcp://127.0.0.1:1883 (the local HiveMQ).
postgresUrl = "postgres://gebos_ingest@db-host.gebos.internal:5432/postgres?sslmode=require"; postgresUrl = "postgres://gebos_ingest@10.0.0.2:5432/postgres?sslmode=require";
# GEBOS_POSTGRES_PASSWORD sourced from /run/secrets/gebos-env (sops-nix). # GEBOS_POSTGRES_PASSWORD sourced from /run/secrets/gebos-env (sops-nix).
}; };
# Caddy fronts TLS for `ingest.ge-bos.de` and forwards to HiveMQ. # Caddy fronts TLS for `ingest.gebos.online` and forwards to HiveMQ.
services.gebos.caddy = { services.gebos.caddy = {
enable = true; enable = true;
sites = { sites = {
"ingest.ge-bos.de" = { tcpProxy = "127.0.0.1:8883"; }; "ingest.gebos.online" = { tcpProxy = "127.0.0.1:8883"; };
}; };
}; };
+1 -1
View File
@@ -39,7 +39,7 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
services.caddy = { services.caddy = {
enable = true; enable = true;
email = "ops@ge-bos.de"; # TODO: confirm ACME contact email = "ops@gebos.online"; # TODO: confirm ACME contact
extraConfig = lib.concatStringsSep "\n" extraConfig = lib.concatStringsSep "\n"
(lib.mapAttrsToList siteBlock cfg.sites); (lib.mapAttrsToList siteBlock cfg.sites);
}; };
+4 -4
View File
@@ -16,7 +16,7 @@ services:
image: kong:2.8.1 image: kong:2.8.1
restart: unless-stopped restart: unless-stopped
ports: ports:
- "127.0.0.1:8000:8000/tcp" # Caddy proxies api.ge-bos.de here - "127.0.0.1:8000:8000/tcp" # Caddy proxies api.gebos.online here
environment: environment:
KONG_DATABASE: "off" KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
@@ -29,10 +29,10 @@ services:
environment: environment:
GOTRUE_DB_DRIVER: postgres GOTRUE_DB_DRIVER: postgres
GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
GOTRUE_SITE_URL: https://app.ge-bos.de GOTRUE_SITE_URL: https://app.gebos.online
GOTRUE_JWT_SECRET: ${JWT_SECRET} GOTRUE_JWT_SECRET: ${JWT_SECRET}
GOTRUE_JWT_EXP: "3600" GOTRUE_JWT_EXP: "3600"
API_EXTERNAL_URL: https://api.ge-bos.de API_EXTERNAL_URL: https://api.gebos.online
rest: rest:
image: postgrest/postgrest:v12.0.2 image: postgrest/postgrest:v12.0.2
@@ -52,7 +52,7 @@ services:
STUDIO_PG_META_URL: http://meta:8080 STUDIO_PG_META_URL: http://meta:8080
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
DEFAULT_ORGANIZATION_NAME: Gebos DEFAULT_ORGANIZATION_NAME: Gebos
SUPABASE_URL: https://api.ge-bos.de SUPABASE_URL: https://api.gebos.online
DASHBOARD_USERNAME: gebos DASHBOARD_USERNAME: gebos
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD} DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD}