Kieran's opinionated (and probably slightly dumb) nix config
mkService#
modules/lib/mkService.nix is the service factory used by most atelier services. It takes a set of parameters and returns a NixOS module with standardized options, systemd service, Caddy reverse proxy, and backup integration.
Factory parameters#
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
string | required | Service identity — used for user, group, systemd unit, and option namespace |
description |
string | "<name> service" |
Human-readable description |
defaultPort |
int | 3000 |
Default port if not overridden in config |
runtime |
string | "bun" |
"bun", "node", or "custom" |
entryPoint |
string | "src/index.ts" |
Script to run (ignored if startCommand is set) |
startCommand |
string | null |
Override the full start command |
extraOptions |
attrset | {} |
Additional NixOS options for this service |
extraConfig |
function | cfg: {} |
Additional NixOS config when enabled (receives the service config) |
Options#
Every mkService module creates options under atelier.services.<name>:
Core#
| Option | Type | Default | Description |
|---|---|---|---|
enable |
bool | false |
Enable the service |
domain |
string | required | Domain for Caddy reverse proxy |
port |
port | defaultPort |
Port the service listens on |
dataDir |
path | "/var/lib/<name>" |
Data storage directory |
secretsFile |
path or null | null |
Agenix secrets environment file |
repository |
string or null | null |
Git repo URL — cloned once on first start |
healthUrl |
string or null | null |
Health check URL for monitoring |
environment |
attrset | {} |
Additional environment variables |
Data declarations#
Used by the backup system to automatically discover what to back up.
| Option | Type | Default | Description |
|---|---|---|---|
data.sqlite |
string or null | null |
SQLite database path (WAL checkpoint + stop/start during backup) |
data.postgres |
string or null | null |
PostgreSQL database name (pg_dump during backup) |
data.files |
list of strings | [] |
Additional file paths to back up |
data.exclude |
list of strings | ["*.log", "node_modules", ...] |
Glob patterns to exclude |
Caddy#
| Option | Type | Default | Description |
|---|---|---|---|
caddy.enable |
bool | true |
Enable Caddy reverse proxy |
caddy.extraConfig |
string | "" |
Additional Caddy directives |
caddy.rateLimit.enable |
bool | false |
Enable rate limiting |
caddy.rateLimit.events |
int | 60 |
Requests per window |
caddy.rateLimit.window |
string | "1m" |
Rate limit time window |
What it sets up#
- System user and group — dedicated user in the
servicesgroup with sudo forsystemctl restart/stop/start/status - Systemd service —
ExecStartPrecreates dirs as root,preStartclones repo and installs deps,ExecStartruns the application - Caddy virtual host — TLS via Cloudflare DNS challenge, reverse proxy to localhost port
- Port conflict detection — assertions prevent two services from binding the same port
- Security hardening —
NoNewPrivileges,ProtectSystem=strict,ProtectHome,PrivateTmp
Example#
Minimal service module:
let
mkService = import ../../lib/mkService.nix;
in
mkService {
name = "myapp";
description = "My application";
defaultPort = 3000;
runtime = "bun";
entryPoint = "src/index.ts";
extraConfig = cfg: {
systemd.services.myapp.serviceConfig.Environment = [
"DATABASE_PATH=${cfg.dataDir}/data/app.db"
];
atelier.services.myapp.data = {
sqlite = "${cfg.dataDir}/data/app.db";
};
};
}
Then enable in the machine config:
atelier.services.myapp = {
enable = true;
domain = "myapp.dunkirk.sh";
repository = "https://github.com/taciturnaxolotl/myapp";
secretsFile = config.age.secrets.myapp.path;
healthUrl = "https://myapp.dunkirk.sh/health";
};