Compare commits
31 commits
bump-river
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
751b7629e4 | ||
|
|
c814379f7c | ||
|
|
6ab901d11f | ||
|
|
3ace2e661b | ||
|
|
03a4a1a7ef | ||
|
|
12b57f221e | ||
|
|
f10ebc4e77 | ||
| cf24c6c044 | |||
|
|
06afcbb818 | ||
|
|
2c250a3437 | ||
| e75e3a7c6a | |||
|
|
fe92602302 | ||
| 1989e54626 | |||
|
|
a033dc46fe | ||
| 07dfc819a9 | |||
|
|
034e422797 | ||
| eecfe98102 | |||
|
|
51042197ba | ||
|
|
a8cca03c0e | ||
|
|
67bf9d18bc | ||
|
|
671ff1d774 | ||
|
|
aee8dbda75 | ||
|
|
2971282c45 | ||
|
|
64672a1cde | ||
|
|
45d673c292 | ||
|
|
2a2c524d16 | ||
|
|
1f9b202109 | ||
|
|
88acec9159 | ||
|
|
56bb66c0b7 | ||
|
|
b4d9f3d619 | ||
| 21d098d4be |
16 changed files with 426 additions and 26 deletions
106
nixos/CLAUDE.md
106
nixos/CLAUDE.md
|
|
@ -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).
|
||||
|
|
|
|||
56
nixos/arion-atitraining/arion-compose.nix
Normal file
56
nixos/arion-atitraining/arion-compose.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
3
nixos/arion-atitraining/arion-pkgs.nix
Normal file
3
nixos/arion-atitraining/arion-pkgs.nix
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import <nixpkgs> {
|
||||
system = "x86_64-linux";
|
||||
}
|
||||
|
|
@ -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
|
||||
''
|
||||
];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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" ];
|
||||
|
|
|
|||
34
nixos/atitraining/Dockerfile
Normal file
34
nixos/atitraining/Dockerfile
Normal 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"]
|
||||
6
nixos/atitraining/entrypoint.sh
Normal file
6
nixos/atitraining/entrypoint.sh
Normal 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 "$@"
|
||||
25
nixos/atitraining/wp-init-bg.sh
Normal file
25
nixos/atitraining/wp-init-bg.sh
Normal 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
|
||||
|
|
@ -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).
|
||||
|
|
|
|||
131
nixos/linux.nix
131
nixos/linux.nix
|
|
@ -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"
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
5
nixos/secrets/coldairnetworks-db-pgadmin.age
Normal file
5
nixos/secrets/coldairnetworks-db-pgadmin.age
Normal 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’Å´Ú]õuü–üÖÁ1ð_bn1û×*ß䘂Rùœ¦:òW+ú@<40>ï'¼.w#»êÝ”=%'°e¨4Ü8
tnžn׬
|
||||
BIN
nixos/secrets/coldairnetworks-db-postgres.age
Normal file
BIN
nixos/secrets/coldairnetworks-db-postgres.age
Normal file
Binary file not shown.
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
BIN
nixos/secrets/openai-api-key.age
Normal file
BIN
nixos/secrets/openai-api-key.age
Normal file
Binary file not shown.
|
|
@ -238,6 +238,8 @@ in {
|
|||
fi
|
||||
}
|
||||
|
||||
unset SSH_ASKPASS
|
||||
|
||||
WORDCHARS='*?[]~=&;!#$%^(){}<>'
|
||||
|
||||
# fixes duplication of commands when using tab-completion
|
||||
|
|
|
|||
Loading…
Reference in a new issue