My personal NUR repository.
scrobblex— self-hosted Plex-to-Trakt scrobbler.trek— self-hosted, collaborative travel planner (liketrek/TREK).yamtrack— self-hosted media tracker (movies, TV, anime, manga, games, books, ...). Built from source against nixpkgs' Python packages; the package itself does not include the upstream Docker image's nginx/supervisord layer, butnixosModules.yamtrackprovides an equivalent co-located proxy of its own — see below.
nixosModules.scrobblex— runs scrobblex as a systemd service.nixosModules.trek— runs TREK as a systemd service.nixosModules.yamtrack— runs Yamtrack's web server, migrations and Celery worker/beat as systemd services.
{
imports = [ inputs.nur.repos.msaxena.modules.nixos.scrobblex ];
services.scrobblex = {
enable = true;
environmentFile = "/run/secrets/scrobblex";
};
}{
imports = [ inputs.nur.repos.msaxena.modules.nixos.trek ];
services.trek = {
enable = true;
allowedOrigins = [ "https://trek.example.com" ];
environmentFiles = [ "/run/secrets/trek-env" ]; # ENCRYPTION_KEY, etc.
};
}services.trek covers most of upstream's .env.example as typed options
(session/cookie settings, OIDC, MCP, Overpass, ...); anything not covered
can be set via services.trek.environment (non-secret) or
services.trek.environmentFiles (secret). Two things worth calling out:
- impermanence: state lives entirely under
services.trek.dataDir(/var/lib/trekby default), fully managed by systemd'sStateDirectory=. Add that one path to your persistence config and nothing else needs to change — the persistent bind mount is already in place before the service starts. One caveat: the service runs as a statictrekuser with no pinned uid/gid, so if you also wipe/etcon boot, its uid can be reassigned across activations (whenever the set of other uid-less system users on the box changes), and systemd will recursively re-chown the whole state tree the next time it notices a mismatch. Persist/etc/passwdand/etc/grouptoo (a common impermanence pattern already, not unique to this module) or pinusers.users.trek.uid/users.groups.trek.gidyourself if you want the uid to never move. - sops-nix:
environmentFilestakes a list of paths, so[ config.sops.secrets.trek-env.path ](a templated sops-nix secret combiningENCRYPTION_KEY,OIDC_CLIENT_SECRET, etc.) or one path per sops secret both work directly. Because the service runs as a statictrekuser (notDynamicUser),sops.secrets.foo.owner = "trek"also works if you ever need a file-based secret outsideenvironmentFiles.
Exposes the app's full configuration surface as typed options; see
nixos-modules/yamtrack.nix for the complete list (providers, database,
redis, OAuth/social login, etc). Credential-shaped settings (the Django
secret key, provider API keys, a remote database password, ...) are
deliberately not plain Nix options — they go through environmentFiles, a
list of KEY=value files loaded by systemd (EnvironmentFile=), so nothing
sensitive ends up in the Nix store. That option's description lists every
supported key.
{
imports = [ inputs.nur.repos.msaxena.modules.nixos.yamtrack ];
services.yamtrack = {
enable = true;
urls = [ "https://yamtrack.example.com" ];
database.createLocally = true; # local Postgres, peer-authenticated, no password
environmentFiles = [ config.sops.secrets."yamtrack-env".path ];
};
}Proxy included: services.yamtrack.host/port (default 127.0.0.1:8000)
are a small co-located Caddy instance the module runs in front of gunicorn —
mirroring upstream's own Docker image, which bundles nginx alongside
gunicorn for exactly the same reason. It serves /static/ directly from
the package's collectstatic output and sets X-Real-IP on the way to
gunicorn, which django-allauth's per-IP signup/login rate limiter requires
(without it, every registration/login attempt 403s even though every
systemd unit reports healthy). Gunicorn itself no longer binds
host/port — it's loopback-only on a fixed internal port that isn't part
of this module's option surface. Breaking change if upgrading from an
older version of this module: host/port used to be gunicorn's own bind
address, and every consumer had to put their own reverse proxy in front to
get working static assets and correct client-IP detection at all — anything
that assumed host:port was gunicorn (firewall rules, an external
proxy's upstream target) needs re-checking.
State: with the sqlite default, only /var/lib/yamtrack needs to survive a
reboot (add it to your impermanence persistence list); with
database.createLocally, persist /var/lib/postgresql instead. Redis holds
only cache/broker data and needs no persistence either way. As with trek
above, the service runs as a static yamtrack user with no pinned uid/gid —
persist /etc/passwd//etc/group too, or pin
users.users.yamtrack.uid/users.groups.yamtrack.gid, if you want its
identity (and therefore the state tree's ownership) to never move across
boots. The static user also means sops.secrets.foo.owner = "yamtrack"
works, if environmentFiles alone isn't enough for some future use case.