feat(infra): netcup-vps — podman+k3s, PostgreSQL, Caddy, Prometheus, full ARM64 math stack

Swap Docker for Podman + k3s (single-node server).

New services:
- PostgreSQL 16 with JIT + tuned memory (16GB shared_buffers, 48GB cache)
- Caddy reverse proxy (HTTPS for LSP endpoints — needs domain)
- Prometheus node exporter (port 9100)
- NixOS weekly upgrade timer
- Health watchdog (restarts LSP/Ollama if unhealthy)

New packages (ARM64-optimized):
- openblas, blis, lapack — multi-threaded linear algebra
- petsc, slepc — sparse/eigenvalue solvers
- flintqs, pari, gap, singular — number theory / algebra
- symengine — fast C++ symbolic (SymPy backend)
- fftw, suitesparse — FFT and sparse direct solvers
- z3, julia_11 — SMT and JIT numerics

k3s ports: 6443, 2379, 2380
Firewall updated accordingly.
This commit is contained in:
Brandon Schneider 2026-05-29 14:52:20 -05:00
parent 24d9206857
commit 4bc9de1e09
2 changed files with 233 additions and 22 deletions

View file

@ -88,7 +88,8 @@
description = "Research Stack developer";
extraGroups = [
"wheel"
"docker"
"docker" # podman docker compat socket
"k3s" # k3s API access
"keys"
"tailscale"
];
@ -277,6 +278,27 @@
};
};
# ── PostgreSQL (ENE database) ───────────────────────────────────────────────
services.postgresql = {
enable = true;
package = pkgs.postgresql_16;
# Enable JIT for analytical queries on ENE data
settings.jit = "on";
# Memory tuning for 64GB RAM
settings.shared_buffers = "16GB";
settings.effective_cache_size = "48GB";
settings.work_mem = "256MB";
settings.maintenance_work_mem = "2GB";
settings.effective_io_concurrency = 200;
settings.max_worker_processes = "16";
# Enable parallel query execution
settings.max_parallel_workers_per_gather = "8";
settings.max_parallel_workers = "16";
# Logging
settings.log_destination = "stderr";
settings.log_line_prefix = "ene %p %u@%d ";
};
# ── ENE database restoration (oneshot) ───────────────────────────────────
# Copies backed-up ENE data from external disk to the VPS
# Run manually after mounting the backup drive:
@ -295,15 +317,16 @@
export PGUSER=postgres
export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
echo "Restoring ENE schema..."
echo "Creating ENE database..."
sudo -u postgres psql -c "CREATE DATABASE ene;" 2>/dev/null || true
sudo -u postgres pg_restore -C -d postgres "${backupDir}/rds_dump/ene_schema.sql" 2>/dev/null || true
echo "Restoring ENE schema..."
sudo -u postgres psql -d ene -f "${backupDir}/rds_dump/ene_schema.sql" 2>/dev/null || true
echo "Restoring ENE data..."
sudo -u postgres pg_restore -C -d ene "${backupDir}/rds_dump/ene_full.dump" 2>/dev/null || true
sudo -u postgres pg_restore -d ene "${backupDir}/rds_dump/ene_full.dump" 2>/dev/null || true
echo "Importing CSV tables..."
# Import CSVs (list tables that need CSV import)
for tbl in $(ls "${backupDir}/rds_tables/"*.csv 2>/dev/null | xargs -I{} basename {} .csv); do
echo " Importing $tbl..."
sudo -u postgres psql -d ene -c "\\COPY $tbl FROM '${backupDir}/rds_tables/$tbl.csv' WITH (FORMAT csv, HEADER true)" 2>/dev/null || true
@ -317,11 +340,134 @@
};
};
# ── Docker (for x86_64 images via nix-ld) ────────────────────────────────
virtualisation.docker = {
# ── Caddy reverse proxy (HTTPS for LSP endpoints) ──────────────────────────
# Certificates via Let's Encrypt / Caddy DNS challenge (configure domain first)
services.caddy = {
enable = true;
autoPrune.enable = true;
enableOnBoot = true;
virtualHosts = {
# TLS will work once a domain points to this VPS
# "rs-vps.example.com" = {
# extraConfig = ''
# reverse_proxy /lean* localhost:8765
# reverse_proxy /python* localhost:8767
# reverse_proxy /ollama* localhost:11434
# '';
# };
};
globalConfig = ''
admin off
auto_https off
'';
};
# ── Prometheus node exporter (port 9100) ──────────────────────────────────
# Exposes hardware/OS metrics for monitoring (Uptime Kuma, etc.)
services.prometheus.exporters = {
node = {
enable = true;
port = 9100;
enabledCollectors = [ "systemd" "logind" ];
# Don't firewall this — only expose on internal interfaces
openFirewall = false;
};
};
# ── Automatic NixOS upgrades (weekly) ───────────────────────────────────
systemd.timers.nix-upgrade = {
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = "weekly";
persistent = true;
};
systemd.services.nix-upgrade = {
description = "NixOS channel upgrade";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.nix}/bin/nix-channel --update nixos";
User = "root";
};
};
# ── Health check watchdog ────────────────────────────────────────────────
# Restarts LSP/Ollama if they become unresponsive
systemd.services.health-check = {
description = "LSP and Ollama health watchdog";
after = [
"lean-lsp-mcp.service"
"lean-lsp-mathlib.service"
"pylsp.service"
"ollama.service"
];
wantedBy = [ "multi-user.target" ];
script = ''
#!${pkgs.bash}/bin/bash
set -euo pipefail
check_service() {
local name="$1"; shift
local url="$1"; shift
if ! curl -sf --max-time 5 "$url" > /dev/null 2>&1; then
echo "[health] $name unhealthy at $url restarting..."
systemctl restart "$name"
fi
}
# Lean LSP (streamable-http health check via --timeout flag)
# check_service lean-lsp-mcp http://localhost:8765/health 2>/dev/null || true
# Python LSP has no built-in health endpoint — check port
if ! nc -z localhost 8767 2>/dev/null; then
echo "[health] pylsp port 8767 not listening restarting..."
systemctl restart pylsp
fi
# Ollama API
if ! curl -sf --max-time 5 http://localhost:11434/api/tags > /dev/null 2>&1; then
echo "[health] ollama unhealthy restarting..."
systemctl restart ollama
fi
'';
serviceConfig = {
Type = "oneshot";
# Run every 5 minutes
};
};
systemd.timers.health-check = {
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = "*-*-* *:0/5:00";
persistent = true;
randomizedDelaySec = 30;
};
# ── Podman (container runtime) ───────────────────────────────────────────────
# Replaces Docker; manages containers without a daemon
virtualisation.podman = {
enable = true;
# Docker-compatible socket for tools that expect Docker
dockerCompat = true;
defaultLocks = "/var/lib/containers.lock";
storage.settings = {
# Use the 2TB disk for container storage
storage.rootlessStoragePath = "/var/lib/containers";
};
};
# ── k3s (lightweight Kubernetes) ─────────────────────────────────────────
# Single-node k3s for orchestrating long-running compute workloads
services.k3s = {
enable = true;
# Token and certs for node authentication
# token = "changeme"; # Set a secure token
# Master role only (no agents)
role = "server";
# Disable traefik (we have Caddy for ingress)
disableAgent = true;
# Extra args for the server
extraFlags = [
"--disable traefik"
"--disable servicelb"
"--disable metrics-server"
"--write-kubeconfig-mode 0644"
];
# Port config
port = 6443;
};
# ── Networking ─────────────────────────────────────────────────────────────
@ -329,11 +475,15 @@
enable = true;
allowedTCPPorts = [
22 # SSH
80 # HTTP
443 # HTTPS
80 # HTTP (Caddy)
443 # HTTPS (Caddy)
6443 # k3s Kubernetes API
2379 # etcd client
2380 # etcd peer
8765 # Lean LSP (v4.19.0)
8766 # Lean LSP (v4.30.0-rc2)
8767 # Python LSP
9100 # Prometheus node exporter
11434 # Ollama
];
allowedUDPPorts = [ ];
@ -359,8 +509,25 @@
openssl pkg-config
graphviz
postgresql_16
docker containerd
podman
tailscale # mesh networking
netcat-openbsd # for health check port probing
# ── ARM64-optimized HPC / math packages ───────────────────────────
openblas # Multi-threaded BLAS, ARM64 Neoverse-optimized
blis # Fast BLIS on ARM64
lapack
petsc # Scientific computing (sparse/direct solvers)
slepc # Eigenvalue solver (PETSc-based)
flintqs # Fast FLINT (factorization, primality)
pari # PARI/GP (number theory)
gap # Groups, Algorithms, Programming
singular # Polynomial algebra, Gröbner bases
symengine # Fast C++ symbolic (SymPy backend)
fftw # FFT (single/double precision)
suitesparse # CHOLMOD, UMFPACK, SPQR
z3 # SMT solver
julia_11 # Julia 1.11 (good ARM64 SIMD)
];
# ── Performance tuning for 64GB RAM / 18 cores ────────────────────────────

View file

@ -86,8 +86,6 @@
texliveFull
# Math CLI tools
gnuplot maxima wxmaxima octave
# CAS
# sage # too heavy for server; use lean + sympy
# Typesetting
typst
# Network / TLS
@ -102,18 +100,64 @@
graphviz
# PostgreSQL client
postgresql_16
# Docker (for running pre-built x86_64 images via nix-ld)
docker
# Containerd for docker
containerd
# Podman container runtime (Docker-compatible)
podman
# nix-ld for running x86_64 binaries on ARM64
# (built from nixpkgs-stable to ensure binary cache availability)
(import nixpkgs-stable {
system = "x86_64-linux";
config.allowUnfree = false;
}).nix-ld
];
# ── ARM64-optimized HPC / math packages ────────────────────────────────
# Key insight: ARM64 Neoverse N1 (Ampere Altra) has excellent SIMD
# (ARMv8.2-A SVE/SVE2), strong memory bandwidth, and cache hierarchy
# similar to AMD Zen. These packages are built with ARM64 in mind.
mathPackages = with pkgs; [
# ── Linear algebra (BLAS/LAPACK ecosystem) ───────────────────────
openblas # Multi-threaded, ARM64 Neoverse-optimized BLAS
blis # BLIS — fast on ARM64, cleaner than OpenBLAS
lapack # Reference LAPACK (used via OpenBLAS/BLIS backends)
python311Packages.scipy # Uses OpenBLAS by default
# ── Sparse / iterative solvers ────────────────────────────────────
petsc # Portable, extensible Toolkit for Scientific Computing
slepc # Eigenvalue solver (builds on PETSc)
# ── Number theory / algebra ───────────────────────────────────────
flintqs # Fast multi-method FLINT for factoring, primality
pari # PARI/GP — number theory, algebraic number theory
gap # Groups, Algorithms, Programming — computational algebra
singular # Polynomial computation, Gröbner bases
# ── Fast symbolic engine (C++ backend for SymPy) ──────────────────
symengine # Fast C++ symbolic library; sympy can use it as backend
# ── FFT / signal processing ─────────────────────────────────────
fftw # Fast Fourier Transform (single precision)
fftwDouble # Double precision FFTW (separate package)
# ── Sparse matrix suites ───────────────────────────────────────────
suitesparse # CHOLMOD, UMFPACK, SPQR, etc. — sparse direct solvers
# ── Computer Algebra System ──────────────────────────────────────
# sage is too large for a server build (~15GB); use symengine + sympy + pari
# maxima is already in corePackages
# ── Polynomial systems / SMT ──────────────────────────────────────
z3 # Microsoft Z3 (SMT solver, C++ backend)
python311Packages.z3 # Python bindings for Z3
# ── Julia (excellent ARM64 support, fast JIT) ───────────────────
julia_11 # Julia 1.11 — good ARM64 SIMD vectorization
# ── R with optimized BLAS ─────────────────────────────────────────
rPackages.override { packages = with pkgs.rPackages; [
RcppArmadillo # Armadillo via Rcpp (uses OpenBLAS)
RcppEigen # Eigen via Rcpp
]; }
];
# ── Python packages (full science stack) ──────────────────────────────
pythonPackages = with pkgs.python311Packages; [
numpy scipy sympy pandas matplotlib seaborn
@ -161,7 +205,7 @@
in rec {
packages.${system} = {
inherit corePackages pythonPackages;
inherit corePackages pythonPackages mathPackages;
# ── Lean LSP service (port 8765) ──────────────────────────────────
lean-lsp-mcp = elan-lsp { toolchain = "4.19.0"; port = 8765; };
@ -187,9 +231,9 @@
# Convenience: nix build .#packages.aarch64-linux.lean-lsp-mcp
defaultPackage.${system} = packages.${system}.lean-lsp-mcp;
# ── Dev shell ───────────────────────────────────────────────────────
# ── Dev shell ───────────────────────────────────────────────────────
devShells.${system}.default = pkgs.mkShell {
packages = corePackages ++ pythonPackages;
packages = corePackages ++ pythonPackages ++ mathPackages;
shellHook = ''
echo "Research Stack netcup VPS dev shell (ARM64, NixOS 25.11)"
export PS1='\[\033[1;34m\][rs-vps]\[\033[0m\] \w \$ '