{ config, lib, pkgs, ... }: with lib; let cfg = config.services.yami-dev-stack; in { options.services.yami-dev-stack = { enable = mkEnableOption "Yami development stack (gitea, postgres, docker, runner)"; user = mkOption { type = types.str; description = "User that will own runner token and be added to docker group"; }; }; config = mkIf cfg.enable { # ── Systemd target ───────────────────────────────────────────────────────── # Manual-only: nothing in wantedBy, so it never starts on boot. systemd.targets.yami-dev = { description = "Yami development stack (gitea, postgres, docker, runner)"; wantedBy = [ ]; }; # ── PostgreSQL 16 ────────────────────────────────────────────────────────── services.postgresql = { enable = true; package = pkgs.postgresql_16; ensureDatabases = [ "gitea" ]; ensureUsers = [ { name = "gitea"; ensureDBOwnership = true; } ]; }; # Pull postgres under yami-dev.target; stop it when target stops. systemd.services.postgresql = { wantedBy = mkForce [ "yami-dev.target" ]; partOf = [ "yami-dev.target" ]; }; # ── Gitea ────────────────────────────────────────────────────────────────── services.gitea = { enable = true; appName = "Yami Git"; # LFS enabled via the NixOS helper so the module manages LFS_JWT_SECRET # (writing it to app.ini, which is read-only by design). lfs.enable = true; database = { type = "postgres"; socket = "/run/postgresql"; name = "gitea"; user = "gitea"; }; settings = { server = { DOMAIN = "localhost"; ROOT_URL = "http://localhost:3000/"; HTTP_ADDR = "0.0.0.0"; HTTP_PORT = 3000; SSH_PORT = 2222; START_SSH_SERVER = true; }; service = { DISABLE_REGISTRATION = true; DEFAULT_KEEP_EMAIL_PRIVATE = true; }; repository = { DEFAULT_BRANCH = "main"; ENABLE_PUSH_CREATE_USER = true; ENABLE_PUSH_CREATE_ORG = true; }; actions = { ENABLED = true; DEFAULT_ACTIONS_URL = "github"; LOG_RETENTION_DAYS = 14; ARTIFACT_RETENTION_DAYS = 30; }; packages = { ENABLED = true; }; session = { COOKIE_SECURE = false; }; "cron.git_gc_repos" = { ENABLED = true; SCHEDULE = "@every 72h"; ARGS = "--aggressive --prune=now"; }; }; }; systemd.services.gitea = { wantedBy = mkForce [ "yami-dev.target" ]; partOf = [ "yami-dev.target" ]; after = [ "postgresql.service" ]; requires = [ "postgresql.service" ]; }; # ── Docker ───────────────────────────────────────────────────────────────── virtualisation.docker = { enable = true; enableOnBoot = false; }; systemd.services.docker = { wantedBy = mkForce [ "yami-dev.target" ]; partOf = [ "yami-dev.target" ]; }; users.users.${cfg.user}.extraGroups = [ "docker" ]; # ── Gitea Actions Runner ─────────────────────────────────────────────────── services.gitea-actions-runner.instances.default = { enable = true; name = "home-runner"; url = "http://localhost:3000"; tokenFile = "/var/lib/gitea-runner/token"; labels = [ "ubuntu-latest:docker://node:20" "native:host" ]; }; systemd.services."gitea-runner-default" = { wantedBy = mkForce [ "yami-dev.target" ]; partOf = [ "yami-dev.target" ]; after = [ "gitea.service" "docker.service" ]; requires = [ "gitea.service" "docker.service" ]; }; # Runner uses DynamicUser, so there's no static gitea-runner user/group. # The token file is read by the root-run pre-start, so root owns it. systemd.tmpfiles.rules = [ "d /var/lib/gitea-runner 0700 root root -" ]; # ── Shell aliases ────────────────────────────────────────────────────────── environment.shellAliases = { yami-up = "sudo systemctl start yami-dev.target && echo 'Gitea → http://localhost:3000'"; yami-down = "sudo systemctl stop yami-dev.target && echo 'Yami dev stack stopped'"; yami-status = "systemctl status yami-dev.target gitea postgresql docker gitea-runner-default --no-pager"; yami-logs = "journalctl -u gitea -u gitea-runner-default -f"; }; # ── Firewall ─────────────────────────────────────────────────────────────── # Merged with existing rules (Steam etc.); does not overwrite them. networking.firewall.allowedTCPPorts = [ 3000 2222 ]; }; }