6775a88ca8
- Bind-mounts /var/lib/{gitea,postgresql} under configurable dataDir
- gitea-runner intentionally excluded: its DynamicUser + StateDirectory
conflicts with pre-existing mount points ("File exists")
- Runner token moved to /var/lib/gitea-runner-secret/ to avoid the same
symlink/StateDirectory conflict inside /var/lib/gitea-runner
- Enable the stack on this host with dataDir = /mnt/ssd_110/yami
209 lines
7.6 KiB
Nix
209 lines
7.6 KiB
Nix
{ 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";
|
|
};
|
|
|
|
dataDir = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
example = "/mnt/ssd_110/yami";
|
|
description = ''
|
|
If set, bind-mounts /var/lib/{gitea,postgresql,gitea-runner} under this
|
|
path so all stack data lives on a separate disk. Directories are created
|
|
automatically; existing data must be rsync'd manually before enabling.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable (mkMerge [ {
|
|
|
|
# ── 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";
|
|
# NOT under /var/lib/gitea-runner — that path is systemd-managed for the
|
|
# service (DynamicUser + StateDirectory means it becomes a symlink to
|
|
# /var/lib/private/gitea-runner). Pre-creating it ourselves breaks the
|
|
# service with "Failed to set up special execution directory: File exists".
|
|
tokenFile = "/var/lib/gitea-runner-secret/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.
|
|
# Token lives in a sibling dir (not /var/lib/gitea-runner — that's systemd's
|
|
# StateDirectory and pre-creating it breaks DynamicUser bind setup).
|
|
# The file MUST be in env-file format, not a raw token:
|
|
# TOKEN=<paste-registration-token-from-gitea-ui>
|
|
# Get the token from Site Administration → Actions → Runners → Create new Runner.
|
|
systemd.tmpfiles.rules = [
|
|
"d /var/lib/gitea-runner-secret 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 ];
|
|
}
|
|
|
|
# ── Optional: move all stack data to a separate disk via bind-mounts ─────────
|
|
(mkIf (cfg.dataDir != null) (
|
|
let
|
|
# gitea-runner intentionally NOT bind-mounted: its service uses
|
|
# DynamicUser + StateDirectory, which refuses to adopt an existing
|
|
# mount point ("Failed to set up special execution directory: File exists").
|
|
# Its data is tiny (token + .runner state) so it stays on the system disk.
|
|
subs = [ "gitea" "postgresql" ];
|
|
mkBind = name: {
|
|
name = "/var/lib/${name}";
|
|
value = {
|
|
device = "${cfg.dataDir}/${name}";
|
|
fsType = "none";
|
|
options = [ "bind" ];
|
|
};
|
|
};
|
|
in {
|
|
# Ensure target dirs exist on the data disk before mounts happen.
|
|
systemd.tmpfiles.rules = map (n: "d ${cfg.dataDir}/${n} 0755 root root -") subs
|
|
++ [ "d ${cfg.dataDir} 0755 root root -" ];
|
|
|
|
fileSystems = listToAttrs (map mkBind subs);
|
|
}
|
|
))
|
|
]);
|
|
}
|