feat: add Yami dev stack module (on-demand gitea+postgres+docker+runner) #3

Merged
Chexit merged 1 commits from claude/add-yami-dev-stack-zHOMn into master 2026-04-18 23:58:52 +00:00
2 changed files with 162 additions and 1 deletions
Showing only changes of commit 27cb88d8a0 - Show all commits
+3 -1
View File
@@ -7,6 +7,7 @@
./desktop.nix ./desktop.nix
./nvidia.nix ./nvidia.nix
./shell.nix ./shell.nix
# ./yami-dev-stack.nix # uncomment to enable Yami dev stack, then set services.yami-dev-stack below
]; ];
nix = { nix = {
@@ -94,7 +95,8 @@
programs.gamemode.enable = true; programs.gamemode.enable = true;
services.tailscale.enable = true; services.tailscale.enable = true;
virtualisation.docker.enable = true; # Docker is now managed by yami-dev-stack.nix when that module is enabled.
# services.yami-dev-stack = { enable = true; user = "chexit"; };
system.stateVersion = "25.11"; system.stateVersion = "25.11";
} }
+159
View File
@@ -0,0 +1,159 @@
{ 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";
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";
copilot-pull-request-reviewer[bot] commented 2026-04-18 23:56:30 +00:00 (Migrated from github.com)
Review

Binding the Gitea HTTP server to 0.0.0.0 and opening firewall ports makes this dev stack reachable from the network by default. For a local-only on-demand dev stack, prefer binding to 127.0.0.1 (or make the bind address / openFirewall behavior configurable via module options) to reduce accidental exposure.

          HTTP_ADDR        = "127.0.0.1";
Binding the Gitea HTTP server to `0.0.0.0` and opening firewall ports makes this dev stack reachable from the network by default. For a local-only on-demand dev stack, prefer binding to `127.0.0.1` (or make the bind address / openFirewall behavior configurable via module options) to reduce accidental exposure. ```suggestion HTTP_ADDR = "127.0.0.1"; ```
HTTP_PORT = 3000;
SSH_PORT = 2222;
copilot-pull-request-reviewer[bot] commented 2026-04-18 23:56:30 +00:00 (Migrated from github.com)
Review

Gitea built-in SSH server typically listens on SSH_LISTEN_PORT (default 22); setting only SSH_PORT = 2222 usually affects clone URL display, not the actual bind port. With START_SSH_SERVER = true this may try to bind to 22 and fail (or require extra privileges). Set SSH_LISTEN_PORT = 2222 (and optionally SSH_LISTEN_HOST) alongside SSH_PORT to ensure it actually listens on 2222.

          SSH_PORT         = 2222;
          SSH_LISTEN_PORT  = 2222;
Gitea built-in SSH server typically listens on `SSH_LISTEN_PORT` (default 22); setting only `SSH_PORT = 2222` usually affects clone URL display, not the actual bind port. With `START_SSH_SERVER = true` this may try to bind to 22 and fail (or require extra privileges). Set `SSH_LISTEN_PORT = 2222` (and optionally `SSH_LISTEN_HOST`) alongside `SSH_PORT` to ensure it actually listens on 2222. ```suggestion SSH_PORT = 2222; SSH_LISTEN_PORT = 2222; ```
START_SSH_SERVER = true;
LFS_START_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" ];
};
copilot-pull-request-reviewer[bot] commented 2026-04-18 23:56:29 +00:00 (Migrated from github.com)
Review

For “manual-only” Docker, overriding only systemd.services.docker.wantedBy may not be sufficient if docker.socket is still enabled (socket activation can start dockerd without yami-dev.target). Verify whether virtualisation.docker.enableOnBoot = false disables the socket; if not, also adjust systemd.sockets.docker.wantedBy (or explicitly disable the socket) so Docker can’t start outside yami-dev.target.


    systemd.sockets.docker = {
      wantedBy = mkForce [ "yami-dev.target" ];
      partOf   = [ "yami-dev.target" ];
    };
For “manual-only” Docker, overriding only `systemd.services.docker.wantedBy` may not be sufficient if `docker.socket` is still enabled (socket activation can start dockerd without `yami-dev.target`). Verify whether `virtualisation.docker.enableOnBoot = false` disables the socket; if not, also adjust `systemd.sockets.docker.wantedBy` (or explicitly disable the socket) so Docker can’t start outside `yami-dev.target`. ```suggestion systemd.sockets.docker = { wantedBy = mkForce [ "yami-dev.target" ]; partOf = [ "yami-dev.target" ]; }; ```
users.users.${cfg.user}.extraGroups = [ "docker" ];
copilot-pull-request-reviewer[bot] commented 2026-04-18 23:56:29 +00:00 (Migrated from github.com)
Review

users.users.${cfg.user}.extraGroups = [ "docker" ]; will create/modify that user entry from this module. If cfg.user isn’t already defined elsewhere, this can lead to an invalid/incomplete user definition or unexpected user creation. Consider adding an assertion that the user exists (builtins.hasAttr cfg.user config.users.users) and use mkAfter to append the group instead of redefining the list.

    assertions = [
      {
        assertion = builtins.hasAttr cfg.user config.users.users;
        message = "services.yami-dev-stack.user must refer to an existing user defined in users.users";
      }
    ];

    users.users.${cfg.user}.extraGroups = mkAfter [ "docker" ];
`users.users.${cfg.user}.extraGroups = [ "docker" ];` will create/modify that user entry from this module. If `cfg.user` isn’t already defined elsewhere, this can lead to an invalid/incomplete user definition or unexpected user creation. Consider adding an assertion that the user exists (`builtins.hasAttr cfg.user config.users.users`) and use `mkAfter` to append the group instead of redefining the list. ```suggestion assertions = [ { assertion = builtins.hasAttr cfg.user config.users.users; message = "services.yami-dev-stack.user must refer to an existing user defined in users.users"; } ]; users.users.${cfg.user}.extraGroups = mkAfter [ "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 = [
copilot-pull-request-reviewer[bot] commented 2026-04-18 23:56:30 +00:00 (Migrated from github.com)
Review

services.gitea-actions-runner will fail to start if tokenFile doesn’t exist yet, which makes yami-up likely to report a failed unit on first run. Consider adding a systemd ConditionPathExists= (or similar gating) for gitea-runner-default so the rest of the target can start cleanly before the token is provisioned.

`services.gitea-actions-runner` will fail to start if `tokenFile` doesn’t exist yet, which makes `yami-up` likely to report a failed unit on first run. Consider adding a systemd `ConditionPathExists=` (or similar gating) for `gitea-runner-default` so the rest of the target can start cleanly before the token is provisioned.
"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" ];
};
# Directory for the runner token file; owner matches the runner service user.
systemd.tmpfiles.rules = [
"d /var/lib/gitea-runner 0700 gitea-runner gitea-runner -"
];
# ── 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 ];
};
}