15 KiB
AGENTS.md
This file teaches AI agents how to navigate and work with this NixOS
configuration repository (sid.ovh).
Repository Overview
This is a NixOS flake managing three hosts:
| Host | IP (Tailscale) | Role |
|---|---|---|
sid |
100.64.0.6 |
VPS — reverse proxy, mail, matrix, headscale |
rx4 |
100.64.0.10 |
Home server — open-webui, forgejo, vaultwarden, … |
vde |
100.64.0.1 |
Desktop / workstation (not in use at the moment) |
Deployment is done via deploy-rs through a Forgejo CI pipeline
(.forgejo/workflows/deploy-configs.yml).
The synix Flake Input
What it is
synix is the owner's personal NixOS library flake, hosted at:
https://git.sid.ovh/sid/synix.git
It provides:
- NixOS modules (
inputs.synix.nixosModules.*) — opinionated, reusable service configurations used heavily across all three hosts. - Home Manager modules (
inputs.synix.homeModules.*) — desktop / user-space configurations (Hyprland, nixvim, waybar, …). - Packages (
inputs.synix.packages.<system>.*, also accessible via thesynix-packagesoverlay aspkgs.synix.*) — custom packages not in nixpkgs. - Overlays (
inputs.synix.overlays.*) — nixpkgs modifications. - A utility library (
inputs.synix.lib/lib.utils) — helper functions such asmkVirtualHostandmkReverseProxyOption. - Templates — starter flake templates for servers, desktops, VMs, etc.
- Apps — convenience scripts (
deploy,rebuild,install, …).
How it is consumed in sid.ovh
In flake.nix:
inputs = {
synix.url = "git+https://git.sid.ovh/sid/synix.git?ref=release-25.11";
synix.inputs.nixpkgs.follows = "nixpkgs";
# development overrides (commented out normally):
# synix.url = "git+https://git.sid.ovh/sid/synix.git?ref=develop";
# synix.url = "git+file:///home/sid/src/synix"; # local checkout
...
};
The active branch is release-25.11, aligned with nixpkgs
nixos-25.11. A develop branch exists for unstable work. A local
checkout can be used for testing by switching to the git+file:// URL.
synix Module Reference
All NixOS modules are under modules/nixos/ in the synix repo and
exposed as inputs.synix.nixosModules.<name>.
Infrastructure / base modules
| Module name | Path in synix | Purpose |
|---|---|---|
common |
modules/nixos/common/ |
Base system config (boot, networking, nix settings, zsh, sudo, htop, locale) |
device.server |
modules/nixos/device/server.nix |
Server profile (no GUI, sensible server defaults) |
device.desktop |
modules/nixos/device/desktop.nix |
Desktop profile |
device.laptop |
modules/nixos/device/laptop.nix |
Laptop profile |
device.vm |
modules/nixos/device/vm.nix |
VM profile |
sops |
modules/nixos/sops/ |
sops-nix wiring (age keys, secret paths) |
openssh |
modules/nixos/openssh/ |
Hardened OpenSSH configuration |
normalUsers |
modules/nixos/normalUsers/ |
Declarative normal-user creation with SSH keys |
tailscale |
modules/nixos/tailscale/ |
Tailscale VPN with multiple tailnet support |
nginx |
modules/nixos/nginx/ |
Opinionated nginx base (ACME, SSL helpers) |
Service modules used in sid.ovh
| Module name | Path in synix | Used by host | Notes |
|---|---|---|---|
headscale |
modules/nixos/headscale/ |
sid | Self-hosted Tailscale control plane |
headplane |
modules/nixos/headplane/ |
sid | Headscale web UI |
mailserver |
modules/nixos/mailserver/ |
sid | Wraps nixos-mailserver |
matrix-synapse |
modules/nixos/matrix-synapse/ |
sid | Matrix homeserver + bridges (WhatsApp, Signal) + LiveKit |
maubot |
modules/nixos/maubot/ |
sid | Matrix bot framework |
baibot |
modules/nixos/baibot/ |
sid | AI Matrix bot |
coturn |
modules/nixos/coturn/ |
sid | TURN/STUN server (Matrix calls) |
radicale |
modules/nixos/radicale/ |
sid | CalDAV/CardDAV server |
rss-bridge |
modules/nixos/rss-bridge/ |
sid | RSS-Bridge PHP app |
miniflux |
modules/nixos/miniflux/ |
rx4 | RSS reader |
jirafeau |
modules/nixos/jirafeau/ |
rx4 | File sharing |
open-webui-oci |
modules/nixos/open-webui-oci/ |
rx4 | Open WebUI via Podman OCI container |
mcpo |
modules/nixos/mcpo/ |
rx4 | MCP-to-OpenAPI proxy (AI tool server) |
print-server |
modules/nixos/print-server/ |
rx4 | CUPS print server |
virtualisation |
modules/nixos/virtualisation/ |
vde | VFIO / KVM / QEMU / kvmfr setup |
Other available modules (not currently active in sid.ovh)
audio, bluetooth, amd, nvidia, jellyfin, i2pd,
ftp-webserver, webPage, windows-oci, librechat-oci,
nostr-relay, ollama, cifsMount, hyprland (NixOS-level),
normalUsers.
synix Packages (pkgs.synix.*)
Custom packages provided by synix and available via the
synix-packages overlay (applied in
modules/nixos/common/overlays.nix):
| Package name | What it is |
|---|---|
pkgs.synix.mcpo |
MCP-to-OpenAPI proxy server |
pkgs.synix.fetcher-mcp |
MCP tool: web page fetcher |
pkgs.synix.baibot |
AI Matrix bot |
pkgs.synix.jirafeau |
Jirafeau file-sharing webapp |
pkgs.synix.jirafeau-cli |
CLI client for Jirafeau |
pkgs.synix.marker-pdf |
PDF-to-markdown converter |
pkgs.synix.mcpo |
MCP proxy |
pkgs.synix.systemctl-tui |
TUI for systemctl |
pkgs.synix.quicknote |
Quick note shell script |
pkgs.synix.synix-docs |
Built MkDocs documentation site |
pkgs.synix.bulk-rename |
Batch file renaming script |
pkgs.synix.pyman |
Python man-page helper |
The full list lives in pkgs/default.nix in the synix repo. All
packages are also accessible as inputs.synix.packages.<system>.<name>.
synix Utility Library (lib.utils)
Exposed as inputs.synix.lib and merged into the flake's lib in
flake.nix:
lib = nixpkgs.lib.extend (final: prev: inputs.synix.lib or { });
Key helpers (defined in lib/utils.nix):
| Function | What it does |
|---|---|
lib.utils.mkVirtualHost |
Builds a standard nginx virtualHosts entry with optional SSL, upstream address, port, and extra config |
lib.utils.mkReverseProxyOption |
Generates a standard NixOS option set for enabling a reverse-proxy sub-config on a module |
Usage example from hosts/sid/services/nginx.nix:
virtualHosts."${constants.services.forgejo.fqdn}" = mkVirtualHost {
inherit ssl;
address = constants.hosts.rx4.ip;
port = constants.services.forgejo.port;
};
How synix Modules Are Imported
In each host's default.nix, synix modules are imported directly from
the inputs special arg:
# hosts/rx4/default.nix
imports = [
inputs.synix.nixosModules.common # base system config
inputs.synix.nixosModules.device.server # server profile
inputs.synix.nixosModules.openssh # hardened SSH
# …service-specific modules in hosts/rx4/services/
];
Service files then add more synix modules as needed:
# hosts/rx4/services/open-webui-oci.nix
imports = [
inputs.synix.nixosModules.open-webui-oci
inputs.synix.nixosModules.mcpo
];
Overlays Applied
modules/nixos/common/overlays.nix applies five overlays to every host:
| Overlay name | Source | Accessible as |
|---|---|---|
synix-packages |
synix |
pkgs.synix.* |
local-packages |
pkgs/ in repo |
pkgs.local.* |
modifications |
overlays/ |
replaces pkgs in place |
old-stable-packages |
nixpkgs-25.05 |
pkgs.old-stable.* |
unstable-packages |
nixos-unstable |
pkgs.unstable.* |
synix also supplies its own modifications overlay
(inputs.synix.overlays.modifications), merged into the local
modifications overlay in overlays/default.nix.
Centralized Logging Architecture
All hosts ship their systemd journal to a central receiver running on
sid over HTTP using systemd-journal-remote / systemd-journal-upload.
Receiver (sid)
services.journald.remote = {
enable = true;
listen = "http";
port = 19532;
settings.Remote.SplitMode = "host"; # one journal dir per sending host
};
users.users.sid.extraGroups = [
"systemd-journal"
"systemd-journal-remote"
];
Key points:
- Listens on
http://0.0.0.0:19532(plain HTTP, Tailscale-only network). SplitMode = "host"stores each remote host's entries under/var/log/journal/remote/, separated by hostname.
Senders (all hosts via modules/nixos/common/journald.nix)
services.journald.upload = {
enable = true;
settings.Upload.URL = "http://100.64.0.5:19532";
};
Note: The upload URL uses
100.64.0.5— the Tailscale transport IP forsid(distinct from the advertised IP100.64.0.6inconstants.nix; verify withip addr show tailscale0onsidif queries fail).
This module is applied to every host via
outputs.nixosModules.common → modules/nixos/common/default.nix.
Querying Remote Journals
All queries run on sid, reading from /var/log/journal/remote/.
General pattern
journalctl -D /var/log/journal/remote/ \
_HOSTNAME=<hostname> \
[SYSTEMD_UNIT=<unit>] \
[other filters…] \
--no-pager [-n <lines>] [-f] [-S <since>] [-U <until>]
| Flag / Field | Purpose |
|---|---|
-D /var/log/journal/remote/ |
Read the remote journal directory |
_HOSTNAME=rx4 |
Filter to entries from host rx4 |
SYSTEMD_UNIT=podman-open-webui.service |
Filter to a specific systemd unit |
--no-pager |
Don't page output; good for scripts |
-n 20 |
Show last 20 lines |
-f |
Follow (tail) mode |
-S / -U |
Since / Until time bounds |
Canonical example — last 20 lines of open-webui on rx4
journalctl \
-D /var/log/journal/remote/ \
_HOSTNAME=rx4 \
SYSTEMD_UNIT=podman-open-webui.service \
--no-pager -n 20
This works because:
rx4uploads its journal tosid:19532.- The receiver stores it under
/var/log/journal/remote/withSplitMode=host, preserving the_HOSTNAMEfield. _HOSTNAME=rx4narrows to that host's entries.SYSTEMD_UNIT=podman-open-webui.servicetargets the OCI container unit for Open WebUI (defined inhosts/rx4/services/open-webui-oci.nix).
Other useful queries
# All logs from rx4 in the last hour
journalctl -D /var/log/journal/remote/ _HOSTNAME=rx4 --no-pager -S "1 hour ago"
# Forgejo on rx4
journalctl -D /var/log/journal/remote/ _HOSTNAME=rx4 \
SYSTEMD_UNIT=forgejo.service --no-pager -n 50
# Vaultwarden on rx4
journalctl -D /var/log/journal/remote/ _HOSTNAME=rx4 \
SYSTEMD_UNIT=vaultwarden.service --no-pager -n 50
# Nginx on rx4
journalctl -D /var/log/journal/remote/ _HOSTNAME=rx4 \
SYSTEMD_UNIT=nginx.service --no-pager -n 50
# Matrix-synapse on sid
journalctl -D /var/log/journal/remote/ _HOSTNAME=sid \
SYSTEMD_UNIT=matrix-synapse.service --no-pager -n 50
# Follow logs live
journalctl -D /var/log/journal/remote/ _HOSTNAME=rx4 \
SYSTEMD_UNIT=podman-open-webui.service -f
OCI container unit names
Containers managed by virtualisation.oci-containers get units named
podman-<container-attr>.service:
| Container attr | Unit name | Host |
|---|---|---|
open-webui |
podman-open-webui.service |
rx4 |
rsshub-rsshub |
podman-rsshub-rsshub.service |
rx4 |
rsshub-redis |
podman-rsshub-redis.service |
rx4 |
rsshub-browserless |
podman-rsshub-browserless.service |
rx4 |
rsshub-real-browser |
podman-rsshub-real-browser.service |
rx4 |
Permissions
User sid must be in both systemd-journal and
systemd-journal-remote to read /var/log/journal/remote/:
users.users.sid.extraGroups = [
"systemd-journal"
"systemd-journal-remote"
];
Verify with id sid on sid.
Key File Locations
| Path | Purpose |
|---|---|
flake.nix |
Flake inputs, host configs, deploy nodes |
constants.nix |
Canonical IPs, FQDNs, service ports |
modules/nixos/common/journald.nix |
Enables journald.upload on all hosts |
modules/nixos/common/default.nix |
Imports all common modules |
modules/nixos/common/overlays.nix |
Applies all five overlays |
hosts/<name>/default.nix |
Per-host top-level import list |
hosts/<name>/services/ |
Per-host service configurations |
hosts/<name>/secrets/secrets.yaml |
sops-encrypted secrets for that host |
overlays/default.nix |
Local overlay definitions |
pkgs/ |
Local packages (pkgs.local.*) |
users/sid/ |
User account + SSH public key |
Troubleshooting
| Symptom | Check |
|---|---|
| No journal entries for a host | systemctl status systemd-journal-upload on the source host |
| Permission denied reading remote journal | id sid on sid — needs systemd-journal + systemd-journal-remote |
| Wrong unit name | systemctl list-units --type=service on the source host |
| Receiver unreachable | ss -tlnp | grep 19532 on sid |
| Upload URL IP mismatch | Upload uses 100.64.0.5; verify ip addr show tailscale0 on sid |
| synix module not found | Check the branch in flake.nix; run nix flake update synix to refresh |
| Want to test a synix change locally | Switch input URL to git+file:///home/sid/src/synix |