Kieran's opinionated (and probably slightly dumb) nix config
1# mkService
2
3`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.
4
5## Factory parameters
6
7| Parameter | Type | Default | Description |
8|-----------|------|---------|-------------|
9| `name` | string | *required* | Service identity — used for user, group, systemd unit, and option namespace |
10| `description` | string | `"<name> service"` | Human-readable description |
11| `defaultPort` | int | `3000` | Default port if not overridden in config |
12| `runtime` | string | `"bun"` | `"bun"`, `"node"`, or `"custom"` |
13| `entryPoint` | string | `"src/index.ts"` | Script to run (ignored if `startCommand` is set) |
14| `startCommand` | string | `null` | Override the full start command |
15| `extraOptions` | attrset | `{}` | Additional NixOS options for this service |
16| `extraConfig` | function | `cfg: {}` | Additional NixOS config when enabled (receives the service config) |
17
18## Options
19
20Every mkService module creates options under `atelier.services.<name>`:
21
22### Core
23
24| Option | Type | Default | Description |
25|--------|------|---------|-------------|
26| `enable` | bool | `false` | Enable the service |
27| `domain` | string | *required* | Domain for Caddy reverse proxy |
28| `port` | port | `defaultPort` | Port the service listens on |
29| `dataDir` | path | `"/var/lib/<name>"` | Data storage directory |
30| `secretsFile` | path or null | `null` | Agenix secrets environment file |
31| `repository` | string or null | `null` | Git repo URL — cloned once on first start |
32| `healthUrl` | string or null | `null` | Health check URL for monitoring |
33| `environment` | attrset | `{}` | Additional environment variables |
34
35### Data declarations
36
37Used by the backup system to automatically discover what to back up.
38
39| Option | Type | Default | Description |
40|--------|------|---------|-------------|
41| `data.sqlite` | string or null | `null` | SQLite database path (WAL checkpoint + stop/start during backup) |
42| `data.postgres` | string or null | `null` | PostgreSQL database name (pg_dump during backup) |
43| `data.files` | list of strings | `[]` | Additional file paths to back up |
44| `data.exclude` | list of strings | `["*.log", "node_modules", ...]` | Glob patterns to exclude |
45
46### Caddy
47
48| Option | Type | Default | Description |
49|--------|------|---------|-------------|
50| `caddy.enable` | bool | `true` | Enable Caddy reverse proxy |
51| `caddy.extraConfig` | string | `""` | Additional Caddy directives |
52| `caddy.rateLimit.enable` | bool | `false` | Enable rate limiting |
53| `caddy.rateLimit.events` | int | `60` | Requests per window |
54| `caddy.rateLimit.window` | string | `"1m"` | Rate limit time window |
55
56## What it sets up
57
58- **System user and group** — dedicated user in the `services` group with sudo for `systemctl restart/stop/start/status`
59- **Systemd service** — `ExecStartPre` creates dirs as root, `preStart` clones repo and installs deps, `ExecStart` runs the application
60- **Caddy virtual host** — TLS via Cloudflare DNS challenge, reverse proxy to localhost port
61- **Port conflict detection** — assertions prevent two services from binding the same port
62- **Security hardening** — `NoNewPrivileges`, `ProtectSystem=strict`, `ProtectHome`, `PrivateTmp`
63
64## Example
65
66Minimal service module:
67
68```nix
69let
70 mkService = import ../../lib/mkService.nix;
71in
72mkService {
73 name = "myapp";
74 description = "My application";
75 defaultPort = 3000;
76 runtime = "bun";
77 entryPoint = "src/index.ts";
78
79 extraConfig = cfg: {
80 systemd.services.myapp.serviceConfig.Environment = [
81 "DATABASE_PATH=${cfg.dataDir}/data/app.db"
82 ];
83
84 atelier.services.myapp.data = {
85 sqlite = "${cfg.dataDir}/data/app.db";
86 };
87 };
88}
89```
90
91Then enable in the machine config:
92
93```nix
94atelier.services.myapp = {
95 enable = true;
96 domain = "myapp.dunkirk.sh";
97 repository = "https://github.com/taciturnaxolotl/myapp";
98 secretsFile = config.age.secrets.myapp.path;
99 healthUrl = "https://myapp.dunkirk.sh/health";
100};
101```