Compare commits

..

31 commits

Author SHA1 Message Date
Philip Peterson
751b7629e4 fix atitraining 400 bad request from duplicate proxy headers
nginx sent Host/X-Real-IP/X-Forwarded-For/X-Forwarded-Proto twice
(explicit proxy_set_header plus the included recommended-proxy-headers
file), which Apache rejected as a malformed request due to the
duplicate Host header. Removed the redundant explicit headers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-30 20:56:54 -07:00
Philip Peterson
c814379f7c fix wordpress 2026-06-28 21:11:16 -07:00
Philip Peterson
6ab901d11f add Ollama with qwen2.5-coder:1.5b for local LLM inference
Adds the ollama/ollama container on port 11434, a persistent model
store at /var/ollama, and a oneshot service that pulls the model on
first boot. Opens port 11434 in the firewall.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 20:12:56 -07:00
Philip Peterson
3ace2e661b add wordpress 2026-06-25 21:43:10 -07:00
Philip Peterson
03a4a1a7ef tweak SAN 2026-06-25 03:26:19 -07:00
Philip Peterson
12b57f221e Merge branch 'main' of https://forge.quinefoundation.com/Cold-Air-Networks/petersweb-infra 2026-06-25 01:47:08 -07:00
Philip Peterson
f10ebc4e77 Chmod 2026-06-25 01:46:57 -07:00
cf24c6c044 Merge branch 'main' of https://forge.quinefoundation.com/Cold-Air-Networks/petersweb-infra 2026-06-25 00:37:44 -08:00
Philip Peterson
06afcbb818 Remove askpass 2026-06-25 01:37:26 -07:00
Philip Peterson
2c250a3437 Merge branch 'main' of github.com:philip-peterson/petersweb-infra 2026-06-25 01:36:20 -07:00
e75e3a7c6a Merge branch 'main' of https://forge.quinefoundation.com/Cold-Air-Networks/petersweb-infra 2026-06-25 00:33:37 -08:00
Philip Peterson
fe92602302 wip 2026-06-25 01:33:30 -07:00
1989e54626 Merge branch 'main' of https://forge.quinefoundation.com/Cold-Air-Networks/petersweb-infra 2026-06-25 00:29:34 -08:00
Philip Peterson
a033dc46fe postgres: enable SSL with self-signed cert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 01:28:53 -07:00
07dfc819a9 Merge branch 'main' of https://forge.quinefoundation.com/Cold-Air-Networks/petersweb-infra 2026-06-23 01:41:00 -08:00
Philip Peterson
034e422797 Use postgres hosted 2026-06-23 02:40:51 -07:00
eecfe98102 add secrets 2026-06-23 01:34:58 -08:00
Philip Peterson
51042197ba Add openai creds 2026-06-20 09:39:19 -07:00
Philip Peterson
a8cca03c0e openclaw: switch to built-in Workbench, drop control-center container
The openclaw package bundles a Control UI (Workbench) served directly
at the gateway port. Drop the separate openclaw-control-center app
container and point nginx at port 18789 instead of 4310. Added
X-Forwarded-Proto/For headers so the gateway can see the real client
address through nginx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 04:13:27 -07:00
Philip Peterson
67bf9d18bc docs: add OpenClaw section to CLAUDE.md
Documents the two-container setup, volume/auth gotchas, nginx SSL
configuration, control center startup sequence, and usage connector
source status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 04:09:46 -07:00
Philip Peterson
671ff1d774 openclaw: fix app container mount so CLI probes can connect to gateway
Remove :ro from the .openclaw volume so the CLI can write state files
on startup (it was crashing immediately with EROFS). Add
OPENCLAW_GATEWAY_TOKEN so the CLI bypasses the device identity
handshake when auth=none, allowing the gateway reachability probe to
succeed before the device is auto-approved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 04:00:03 -07:00
Philip Peterson
aee8dbda75 openclaw: mount gateway node_modules into control center for CLI access
Control center runs 'openclaw status --json' as a subprocess to probe
the gateway. Mount the gateway install volume and set OPENCLAW_BIN_PATH
so the control center can find the binary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 03:45:28 -07:00
Philip Peterson
2971282c45 openclaw: use --dev --auth none for gateway startup
Newer openclaw (2026.6.6) requires more config than our minimal json.
--dev creates a working config if missing, --auth none skips token
auth since the gateway is loopback-only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 03:36:21 -07:00
Philip Peterson
64672a1cde openclaw: add gateway as separate host-network container
Gateway runs in its own node:22-alpine container with host networking,
installs openclaw@latest on first boot (persisted to /var/openclaw/gateway).
Control center also switches to host networking so ws://127.0.0.1:18789
reaches the gateway's loopback. UI_BIND_ADDRESS locked to 127.0.0.1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 03:30:13 -07:00
Philip Peterson
45d673c292 openclaw: default to English and add basic auth
- Patch app source at startup to change default language from zh to en
- Add basicAuth (ironmagma) to nginx vhost for claw.quineglobal.com

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 03:13:31 -07:00
Philip Peterson
2a2c524d16 fix claw.quineglobal.com redirect loop: forceSSL -> addSSL
Cloudflare is in Flexible SSL mode so it hits our origin over HTTP.
forceSSL was causing an infinite HTTP->HTTPS redirect loop. Switch to
addSSL so HTTPS still works for direct connections while HTTP serves
normally through Cloudflare.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 03:00:09 -07:00
Philip Peterson
1f9b202109 fix openclaw binding to 0.0.0.0 so port forwarding works in container
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 02:45:16 -07:00
Philip Peterson
88acec9159 Merge branch 'main' of https://forge.quinefoundation.com/Cold-Air-Networks/petersweb-infra 2026-06-14 02:33:44 -07:00
Philip Peterson
56bb66c0b7 update 2026-06-14 02:33:18 -07:00
Philip Peterson
b4d9f3d619 fix digests 2026-06-14 02:21:20 -07:00
21d098d4be bump riverside to 1b6a710 (#2)
Automated bump from Cold-Air-Networks/customer-riverside@1b6a71060a

Co-authored-by: CI <ci@quinefoundation.com>
Reviewed-on: #2
2026-06-14 01:07:59 -08:00
16 changed files with 426 additions and 26 deletions

View file

@ -76,6 +76,8 @@ Note: `secrets/default.nix` is the agenix recipients file. Agenix looks for `sec
| `gitea-runner-ubuntu.service` | Forgejo (Gitea) Actions CI runner, uses docker images |
| `forgejo-arion.service` | Forgejo itself, run via Arion/Podman |
| `riverside-arion.service` | Riverside app, run via Arion/Docker |
| `podman-coldairnetworks-postgres.service` | PostgreSQL 16 on port 5432 (publicly exposed) |
| `podman-coldairnetworks-pgadmin.service` | pgAdmin 4 on port 5050 (localhost only) |
| `podman-navidrome.service` | Navidrome music server on port 4533 |
| `podman-nextcloud.service` | Nextcloud/SSH container on port 8087 |
| `podman-sync.io.service` | sync.io app on port 9090 |
@ -92,6 +94,36 @@ Note: `secrets/default.nix` is the agenix recipients file. Agenix looks for `sec
- `DOCKER_HOST` for the gitea-runner is set to `unix:///run/podman/podman.sock`.
- The gitea-runner runs docker images for CI jobs, so the `gitea-runner` user is in the `docker` and `podman` supplementary groups.
## PostgreSQL / pgAdmin (coldairnetworks)
Two Podman containers defined in `linux.nix` under `virtualisation.oci-containers`.
| Container | Image | Port | Role |
|---|---|---|---|
| `coldairnetworks-postgres` | `postgres:16` | 5432 (public) | PostgreSQL database |
| `coldairnetworks-pgadmin` | `dpage/pgadmin4` | 5050 (localhost) | pgAdmin 4 web UI |
### Credential files (not in git — create manually on server)
| Path | Contents |
|---|---|
| `/var/coldairnetworks-db/postgres.env` | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` |
| `/var/coldairnetworks-db/pgadmin.env` | `PGADMIN_DEFAULT_EMAIL`, `PGADMIN_DEFAULT_PASSWORD` |
| `/var/coldairnetworks-db/htpasswd` | nginx basic auth — generate with `htpasswd -c /var/coldairnetworks-db/htpasswd <user>` |
### Data directories
| Host path | Purpose |
|---|---|
| `/var/coldairnetworks-db/postgres` | PostgreSQL data (owned root:root) |
| `/var/coldairnetworks-db/pgadmin` | pgAdmin state (owned uid 5050 — the pgAdmin container user) |
### Access
- **Web UI**: `https://db.coldairnetworks.com` — nginx basic auth first, then pgAdmin login
- **Direct connection**: `psql -h mainframe.philippeterson.com -U admin -d coldairnetworks` (port 5432 open in firewall)
- **pgAdmin → PostgreSQL**: when adding a server in pgAdmin, use `host.containers.internal` as the hostname (Podman host gateway), port 5432
## VNC desktop
`podman-vnc-desktop.service` runs a KDE Plasma desktop inside a container, accessible via noVNC at `localhost:6080` (reverse-proxied by nginx). The image is built locally — no registry involved.
@ -108,6 +140,80 @@ Note: `secrets/default.nix` is the agenix recipients file. Agenix looks for `sec
- ACME certs issued via DNS challenge for `philippeterson.com` and `webdav.philippeterson.com`.
- Forgejo accessible on ports 3000 (HTTP) and 2200 (SSH).
## OpenClaw
OpenClaw runs as two Arion/Podman containers defined in `arion-openclaw/arion-compose.nix`, both using `network_mode = "host"` so they share the host's `127.0.0.1`.
| Container | Name | Port | Role |
|---|---|---|---|
| `openclaw-gateway` | `node:22-alpine` | 18789 (WebSocket) | OpenClaw Gateway (`openclaw@latest`) |
| `openclaw` | `node:22-alpine` | 4310 (HTTP) | OpenClaw Control Center (SSR UI) |
### Volumes and paths
| Host path | Container path | Notes |
|---|---|---|
| `/var/openclaw/gateway` | `/app` (gateway), `/gateway` (app) | npm install location for `openclaw` package |
| `/var/openclaw/app` | `/app` | Control center git clone + runtime files |
| `/root/.openclaw` | `/root/.openclaw` | OpenClaw home; shared **read-write** by both containers |
`/root/.openclaw` must be **writable** in the app container (not `:ro`) — the CLI writes state files at startup and connection probes fail with EROFS otherwise.
The CLI's effective state dir is `/root/.openclaw/.openclaw/` (double-nested: the CLI treats `OPENCLAW_HOME` as HOME and appends `.openclaw/` internally).
### Auth and connectivity
- Gateway runs with `--auth none --dev`. In `--auth none` mode, clients must still present either a device identity (challenge-response) or any token via `OPENCLAW_GATEWAY_TOKEN`.
- `OPENCLAW_GATEWAY_TOKEN=openclaw-local-dev` is set in the app container — this lets the CLI probes connect immediately without waiting for device auto-approval.
- Device identity lives at `/root/.openclaw/.openclaw/identity/device.json`. In `--dev` mode the gateway auto-approves the local device after first contact.
- The control center calls `openclaw status --json` and `openclaw gateway status --json` as CLI subprocesses (not via WebSocket directly). The binary path is set via `OPENCLAW_BIN_PATH=/gateway/node_modules/.bin/openclaw`.
### nginx
`claw.quineglobal.com` is proxied to `127.0.0.1:4310`. Key settings:
- `forceSSL = false; addSSL = true` — Cloudflare Flexible SSL sends plain HTTP to origin; `forceSSL = true` would create a redirect loop.
- `basicAuthFile = "/var/openclaw/htpasswd"` — credentials: `ironmagma / Nargism333`.
- WebSocket upgrade headers are set (`Upgrade`, `Connection: upgrade`) so the control center's live-update SSE works through the proxy.
### Control center startup sequence
The app container startup script (in `arion-compose.nix`):
1. `apk add git`
2. Clones `https://github.com/TianyiDataScience/openclaw-control-center.git` to `/app/repo` (once)
3. Patches `src/ui/server.ts` and `src/runtime/ui-preferences.ts` via `sed` to default language to `"en"` instead of `"zh"`
4. `npm install && npm run build && npm run dev:ui`
### Usage connector sources
The Settings → Usage panel tracks 6 data sources. Current status:
| Source | Status | How to connect |
|---|---|---|
| Context capacity | Connected | `runtime/model-context-catalog.json` exists at `/var/openclaw/app/repo/runtime/` |
| Provider attribution | Connected | Derived from context catalog |
| Digest history | Partial (auto) | Builds up as the monitor runs over time |
| Request counts | Not connected | Needs real AI requests through the gateway |
| Budget limit | Not connected | Add cost thresholds to agent config |
| Subscription usage | Not connected | Add `runtime/subscription-snapshot.json` or provider billing snapshot |
The `model-context-catalog.json` format:
```json
{ "models": [{ "match": "gpt-5.5", "contextWindowTokens": 200000, "provider": "openai" }, ...] }
```
`match` is compared case-insensitively against the model name reported by the runtime.
### Restarting / rebuilding
After changing `arion-compose.nix`, a `nixos-rebuild switch` regenerates the compose YAML but **does not recreate running containers**. You must force recreation:
```bash
podman rm -f openclaw # or openclaw-gateway
systemctl restart arion-openclaw
```
### Cloudflare SSL gotcha
This server sits behind Cloudflare in **Flexible** mode (Cloudflare → origin over plain HTTP). Any `nginx.nix` virtualHost for a Cloudflare-proxied domain must use `forceSSL = false; addSSL = true`, not `forceSSL = true`. The latter causes an infinite redirect loop because Cloudflare sends HTTP but nginx redirects to HTTPS, which Cloudflare re-proxies as HTTP again.
## Known gotchas
- `gitea-runner` is a `DynamicUser` in the systemd service, so it has no persistent uid. Setting `age.secrets.forgejo-runner-token.owner = "gitea-runner"` causes a chown error at activation; use `owner = "root"` instead (the service reads it via `EnvironmentFile` which runs as root before privilege drop).

View file

@ -0,0 +1,56 @@
{ pkgs, ... }:
{
project.name = "atitraining";
networks.atitraining.external = false;
services = {
db = {
service = {
image = "mariadb:11.4";
container_name = "atitraining-db";
restart = "unless-stopped";
networks = [ "atitraining" ];
volumes = [
"/var/atitraining/db:/var/lib/mysql"
];
environment = {
MARIADB_DATABASE = "atitraining";
MARIADB_USER = "atitraining";
MARIADB_PASSWORD = "atitraining";
MARIADB_ROOT_PASSWORD = "atitrainingroot";
};
healthcheck = {
test = [ "CMD" "healthcheck.sh" "--connect" "--innodb_initialized" ];
interval = "10s";
timeout = "5s";
retries = 10;
};
};
};
wordpress = {
service = {
# Image pre-built by build-atitraining-image.service from nixos/atitraining/Dockerfile
# Pinned to WordPress 6.7.2-php8.3-apache.
image = "atitraining-wordpress:local";
container_name = "atitraining-wordpress";
restart = "unless-stopped";
networks = [ "atitraining" ];
depends_on = [ "db" ];
ports = [ "127.0.0.1:3015:80" ];
volumes = [
"/var/atitraining/wp-uploads:/var/www/html/wp-content/uploads"
];
environment = {
WORDPRESS_DB_HOST = "db";
WORDPRESS_DB_NAME = "atitraining";
WORDPRESS_DB_USER = "atitraining";
WORDPRESS_DB_PASSWORD = "atitraining";
# Change this after first login via /wp-admin
WORDPRESS_ADMIN_PASSWORD = "changeme";
};
};
};
};
}

View file

@ -0,0 +1,3 @@
import <nixpkgs> {
system = "x86_64-linux";
}

View file

@ -2,41 +2,29 @@
{
project.name = "openclaw";
networks.openclaw.external = false;
services = {
app = {
gateway = {
service = {
image = "node:lts-alpine";
container_name = "openclaw";
image = "node:22-alpine";
container_name = "openclaw-gateway";
restart = "unless-stopped";
networks = [ "openclaw" ];
network_mode = "host";
volumes = [
"/var/openclaw/app:/app"
"/root/.openclaw:/root/.openclaw:ro"
"/var/openclaw/gateway:/app"
"/root/.openclaw:/root/.openclaw"
];
ports = [ "127.0.0.1:4310:4310" ];
environment = {
PORT = "4310";
OPENCLAW_HOME = "/root/.openclaw";
};
env_file = [ "/run/agenix/openai-api-key" ];
command = [
"sh" "-c"
''
set -e
apk add --no-cache git
if [ ! -d /app/repo ]; then
git clone https://github.com/TianyiDataScience/openclaw-control-center.git /app/repo
if [ ! -f /app/node_modules/.bin/openclaw ]; then
cd /app && npm install openclaw@latest
fi
cd /app/repo
if [ ! -f .env ]; then
cp .env.example .env
sed -i "s|OPENCLAW_HOME=.*|OPENCLAW_HOME=/root/.openclaw|" .env
sed -i "s|PORT=.*|PORT=4310|" .env
fi
npm install
npm run build
exec npm run dev:ui
exec /app/node_modules/.bin/openclaw gateway --port 18789 --dev --auth none
''
];
};

View file

@ -7,7 +7,7 @@
services = {
app = {
service = {
image = "forge.quinefoundation.com/ironmagma/riverside@sha256:e9107d74975a1aae8b913cf90d2b9b8f6b2cde04fd09a1e07045ec0ab74593d0";
image = "forge.quinefoundation.com/cold-air-networks/customer-riverside@sha256:e9107d74975a1aae8b913cf90d2b9b8f6b2cde04fd09a1e07045ec0ab74593d0";
container_name = "riverside";
restart = "unless-stopped";
networks = [ "riverside" ];

View file

@ -0,0 +1,34 @@
FROM wordpress:6.7.2-php8.3-apache
# Install WP-CLI and dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
less \
default-mysql-client \
unzip \
subversion \
&& curl -sL -o /usr/local/bin/wp \
https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
&& chmod +x /usr/local/bin/wp \
&& rm -rf /var/lib/apt/lists/*
# Pre-install the p2 theme via SVN (removed from downloads.wordpress.org).
RUN svn export https://themes.svn.wordpress.org/p2/1.5.8/ /var/www/html/wp-content/themes/p2 \
&& chown -R www-data:www-data /var/www/html/wp-content/themes/p2
# Tell WordPress it's behind an HTTPS reverse proxy
ENV WORDPRESS_CONFIG_EXTRA="if (isset(\$_SERVER['HTTP_X_FORWARDED_PROTO']) && \$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { \$_SERVER['HTTPS'] = 'on'; }"
# Blog identity — baked into the image, overridable at runtime
ENV WORDPRESS_BLOG_TITLE="ATI Training"
ENV WORDPRESS_BLOG_TAGLINE="advanced training intelligence"
ENV WORDPRESS_ADMIN_USER="admin"
ENV WORDPRESS_ADMIN_EMAIL="peterson@sent.com"
ENV WORDPRESS_ACTIVE_THEME="p2"
ENV WORDPRESS_SITEURL="https://atitraining.coldairnetworks.com"
COPY wp-init-bg.sh /usr/local/bin/wp-init-bg.sh
COPY entrypoint.sh /usr/local/bin/atitraining-entrypoint.sh
RUN chmod +x /usr/local/bin/wp-init-bg.sh /usr/local/bin/atitraining-entrypoint.sh
ENTRYPOINT ["/usr/local/bin/atitraining-entrypoint.sh"]
CMD ["apache2-foreground"]

View file

@ -0,0 +1,6 @@
#!/bin/bash
set -e
# Run WP-CLI setup in background; it waits for wp-config.php and the DB
/usr/local/bin/wp-init-bg.sh &
# Hand off to the official WordPress entrypoint (writes wp-config.php, starts Apache)
exec /usr/local/bin/docker-entrypoint.sh "$@"

View file

@ -0,0 +1,25 @@
#!/bin/bash
set -e
# Wait for the official WP entrypoint to write wp-config.php
until [ -f /var/www/html/wp-config.php ]; do sleep 1; done
# Wait for the database to accept connections
until wp --allow-root --path=/var/www/html db check 2>/dev/null; do
sleep 3
done
# Perform first-time install only if WordPress isn't installed yet
if ! wp --allow-root --path=/var/www/html core is-installed 2>/dev/null; then
wp --allow-root --path=/var/www/html core install \
--url="${WORDPRESS_SITEURL:-https://atitraining.coldairnetworks.com}" \
--title="${WORDPRESS_BLOG_TITLE:-ATI Training}" \
--admin_user="${WORDPRESS_ADMIN_USER:-admin}" \
--admin_password="${WORDPRESS_ADMIN_PASSWORD:-changeme}" \
--admin_email="${WORDPRESS_ADMIN_EMAIL:-peterson@sent.com}" \
--skip-email
wp --allow-root --path=/var/www/html theme activate \
"${WORDPRESS_ACTIVE_THEME:-p2}"
wp --allow-root --path=/var/www/html option update blogdescription \
"${WORDPRESS_BLOG_TAGLINE:-advanced training intelligence}"
fi

View file

@ -11,7 +11,10 @@
8082 #webdav
8087 #nextcloud
5432 #coldairnetworks postgres
9090 #sync.io
11434 #ollama
];
# Allow DNS from all podman bridge networks (10.89.0.0/16).

View file

@ -31,6 +31,8 @@
pullomatic = "${pullomaticPkg}/bin/pullomatic";
atitrainingContext = builtins.path { path = ./atitraining; name = "atitraining"; };
in {
imports = [
(import ./cloned_repos {inherit pkgs pullomatic lib;})
@ -86,6 +88,21 @@ in {
file = ./secrets/coldairnetworks.age;
owner = "root";
};
openai-api-key = {
file = ./secrets/openai-api-key.age;
owner = "root";
};
coldairnetworks-db-postgres = {
file = ./secrets/coldairnetworks-db-postgres.age;
owner = "root";
};
coldairnetworks-db-pgadmin = {
file = ./secrets/coldairnetworks-db-pgadmin.age;
owner = "root";
};
};
environment.systemPackages = [
@ -174,6 +191,7 @@ in {
projects.pluto.settings = import ./arion-pluto/arion-compose.nix;
projects.paperless.settings = import ./arion-paperless/arion-compose.nix;
projects.openclaw.settings = import ./arion-openclaw/arion-compose.nix;
projects.atitraining.settings = import ./arion-atitraining/arion-compose.nix;
};
# The arion NixOS module sets backend = "podman-socket" but doesn't inject
@ -184,6 +202,46 @@ in {
systemd.services.arion-pluto.environment.DOCKER_HOST = "unix:///run/podman/podman.sock";
systemd.services.arion-paperless.environment.DOCKER_HOST = "unix:///run/podman/podman.sock";
systemd.services.arion-openclaw.environment.DOCKER_HOST = "unix:///run/podman/podman.sock";
systemd.services.arion-atitraining.environment.DOCKER_HOST = "unix:///run/podman/podman.sock";
# Build the WordPress image for atitraining before arion starts it.
# restartTriggers ensures the image rebuilds whenever nixos/atitraining/ changes.
systemd.services.build-atitraining-image = {
description = "Build atitraining WordPress image";
wantedBy = [ "arion-atitraining.service" ];
before = [ "arion-atitraining.service" ];
restartTriggers = [ atitrainingContext ];
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
podman build -t atitraining-wordpress:local ${atitrainingContext}
'';
};
systemd.services.arion-atitraining.after = lib.mkAfter [ "build-atitraining-image.service" ];
systemd.services.arion-atitraining.wants = [ "build-atitraining-image.service" ];
systemd.services.ollama-pull = {
description = "Pull qwen2.5-coder:1.5b model into Ollama";
wantedBy = [ "multi-user.target" ];
after = [ "podman-ollama.service" ];
wants = [ "podman-ollama.service" ];
path = [ pkgs.curl ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
until curl -sf http://localhost:11434/api/tags > /dev/null 2>&1; do
echo "Waiting for ollama..."
sleep 3
done
curl -sf http://localhost:11434/api/pull \
-d '{"name":"qwen2.5-coder:1.5b","stream":false}'
'';
};
systemd.services.novnc = {
description = "noVNC WebSocket proxy for VNC desktop";
@ -231,6 +289,36 @@ in {
};
};
systemd.services.coldairnetworks-postgres-ssl-init = {
description = "Generate self-signed SSL cert for coldairnetworks PostgreSQL";
wantedBy = [ "podman-coldairnetworks-postgres.service" ];
before = [ "podman-coldairnetworks-postgres.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.openssl ];
script = ''
SSL_DIR=/var/coldairnetworks-db/ssl
NEEDS_REGEN=0
if [ ! -f "$SSL_DIR/server.crt" ]; then
NEEDS_REGEN=1
elif ! openssl x509 -in "$SSL_DIR/server.crt" -text -noout 2>/dev/null | grep -q "db.coldairnetworks.com"; then
NEEDS_REGEN=1
fi
if [ "$NEEDS_REGEN" = "1" ]; then
openssl req -new -x509 -days 3650 -nodes \
-subj "/CN=mainframe.philippeterson.com" \
-addext "subjectAltName=DNS:mainframe.philippeterson.com,DNS:db.coldairnetworks.com" \
-keyout "$SSL_DIR/server.key" \
-out "$SSL_DIR/server.crt"
chmod 600 "$SSL_DIR/server.key"
chmod 644 "$SSL_DIR/server.crt"
chown 999:999 "$SSL_DIR/server.key" "$SSL_DIR/server.crt"
fi
'';
};
systemd.tmpfiles.rules = [
"d /home/ironmagma/.config 0755 ${username} users"
"d /root/.config 0755 ${username} users"
@ -242,10 +330,16 @@ in {
"d /var/paperless/consume 0755 root root"
"d /var/paperless/postgres 0755 root root"
"d /var/paperless/redis 0755 root root"
"d /var/openclaw/app 0755 root root"
"d /var/openclaw/gateway 0755 root root"
"d /var/riverside/files 0755 root root"
"d /var/riverside/postgres 0755 root root"
"d /var/lib/gitea-runner/ubuntu 0755 gitea-runner gitea-runner"
"d /var/coldairnetworks-db/postgres 0755 root root"
"d /var/coldairnetworks-db/pgadmin 0700 5050 5050"
"d /var/coldairnetworks-db/ssl 0755 root root"
"d /var/atitraining/db 0755 root root"
"d /var/atitraining/wp-uploads 0755 root root"
"d /var/ollama 0755 root root"
];
networking.hostName = "${hostname}";
@ -296,6 +390,31 @@ in {
# ports = ["8081:80"];
# };
"coldairnetworks-postgres" = {
autoStart = true;
image = "postgres:16";
ports = [ "5432:5432" ];
volumes = [
"/var/coldairnetworks-db/postgres:/var/lib/postgresql/data"
"/var/coldairnetworks-db/ssl:/run/ssl:ro"
];
environmentFiles = [ config.age.secrets.coldairnetworks-db-postgres.path ];
cmd = [
"postgres"
"-c" "ssl=on"
"-c" "ssl_cert_file=/run/ssl/server.crt"
"-c" "ssl_key_file=/run/ssl/server.key"
];
};
"coldairnetworks-pgadmin" = {
autoStart = true;
image = "dpage/pgadmin4";
ports = [ "127.0.0.1:5050:80" ];
volumes = [ "/var/coldairnetworks-db/pgadmin:/var/lib/pgadmin" ];
environmentFiles = [ config.age.secrets.coldairnetworks-db-pgadmin.path ];
};
"navidrome" = {
autoStart = true;
environment = {
@ -344,6 +463,14 @@ in {
user = "0"; # run as root
};
"ollama" = {
autoStart = true;
image = "ollama/ollama:latest";
ports = ["11434:11434"];
volumes = ["/var/ollama:/root/.ollama"];
environment = {};
};
"blog-quine" = {
autoStart = true;
image = "quineglobal/blog-quine@sha256:88097e4867a99a375db490bf7a989c122653cdb48bfdf6d9ad5e2f6a0bfb2d38";
@ -460,5 +587,7 @@ in {
"acme-selfsigned-webdav.philippeterson.com.service"
"acme-selfsigned-pluto.philippeterson.com.service"
"acme-selfsigned-paperless.philippeterson.com.service"
"acme-selfsigned-db.coldairnetworks.com.service"
"acme-selfsigned-atitraining.coldairnetworks.com.service"
];
}

View file

@ -54,15 +54,19 @@
"claw.quineglobal.com" = {
enableACME = true;
forceSSL = true;
forceSSL = false;
addSSL = true;
basicAuthFile = "/var/openclaw/htpasswd";
locations."/" = {
proxyPass = "http://127.0.0.1:4310/";
proxyPass = "http://127.0.0.1:18789/";
extraConfig = ''
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 86400;
'';
};
@ -85,6 +89,36 @@
proxyPass = "http://127.0.0.1:3011/";
};
};
"atitraining.coldairnetworks.com" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:3015/";
};
};
"db.coldairnetworks.com" = {
enableACME = true;
forceSSL = true;
basicAuthFile = "/var/coldairnetworks-db/htpasswd";
locations."/" = {
proxyPass = "http://127.0.0.1:5050/";
extraConfig = ''
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
client_max_body_size 100M;
'';
};
};
"quineglobal.com" = {
enableACME = false;
forceSSL = false;

View file

@ -0,0 +1,5 @@
age-encryption.org/v1
-> ssh-ed25519 NFD/vg J4wIMQPFUPgrZ0Nc7jzXP2+iyBWAf3nuGtTEsqo3pF0
tjt1/xOiC9FIDDMjBSMpCNtqOKCkTmy0lsUl0jk6mLs
--- H8bQNc9LNjqhCUCMPP0hx/L74tFeo4cNf1s/kvn0Vqk
®±£}ÏYò¸Ñl<E280B9>@P‰é€b,¨Ò4þøS<C3B8>¥u Í»uh0£ë rŽ?PÿÚ<C3BF> dwnÅ´Ú]õ üÖÁ1ð_bn1û×*ßä˜Rùœ¦:òW+ú@<40>ï'¼.w#»­êÝ”=%'°e¨4Ü8 tnžn׬

Binary file not shown.

View file

@ -34,4 +34,13 @@ in {
# DATABASE_URL=<supabase postgres dsn>
# BETTER_AUTH_SECRET=<secret>
"./coldairnetworks.age".publicKeys = [mainframePublicKey];
# OPENAI_API_KEY
"./openai-api-key.age".publicKeys = [mainframePublicKey];
# POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB
"./coldairnetworks-db-postgres.age".publicKeys = [mainframePublicKey];
# PGADMIN_DEFAULT_EMAIL, PGADMIN_DEFAULT_PASSWORD
"./coldairnetworks-db-pgadmin.age".publicKeys = [mainframePublicKey];
}

Binary file not shown.

View file

@ -238,6 +238,8 @@ in {
fi
}
unset SSH_ASKPASS
WORDCHARS='*?[]~=&;!#$%^(){}<>'
# fixes duplication of commands when using tab-completion