245 lines
7.6 KiB
Nix
245 lines
7.6 KiB
Nix
{ self }:
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
let
|
|
cfg = config.services.quixosHost;
|
|
quixosPackages = self.packages.${pkgs.stdenv.hostPlatform.system};
|
|
packageTreeGitAskPass = pkgs.writeShellScript "quixos-package-tree-git-askpass" ''
|
|
case "$1" in
|
|
*Username*)
|
|
printf '%s\n' "''${QUIXOS_PACKAGE_TREE_GIT_USERNAME:-oauth2}"
|
|
;;
|
|
*Password*)
|
|
printf '%s\n' "''${QUIXOS_PACKAGE_TREE_GIT_PASSWORD:-}"
|
|
;;
|
|
*)
|
|
printf '\n'
|
|
;;
|
|
esac
|
|
'';
|
|
in
|
|
{
|
|
options.services.quixosHost = {
|
|
enable = lib.mkEnableOption "Quixos host";
|
|
|
|
domain = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Public domain served by Caddy.";
|
|
};
|
|
|
|
packagesPath = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "/persist/srv/quixos/packages";
|
|
description = "Mutable package-tree checkout path.";
|
|
};
|
|
|
|
dataRoot = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "/persist/var/lib/quixos";
|
|
description = "Persistent Quixos runtime state root.";
|
|
};
|
|
|
|
secretsEnvironmentFile = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "/persist/secrets/quixos.env";
|
|
description = "Environment file containing Quixos secrets.";
|
|
};
|
|
|
|
packageTreeGitEnvironmentFile = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "/persist/secrets/quixos-git.env";
|
|
description = "Optional environment file containing Git credentials for the package tree.";
|
|
};
|
|
|
|
packageTreeRepo = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "https://gitea-external.egads.tutti.syntaxblitz.net/quixos-packages/packages.git";
|
|
description = "Git URL for the mutable package-tree super-repo.";
|
|
};
|
|
|
|
packageTreeRef = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "master";
|
|
description = "Package-tree ref to clone on first boot.";
|
|
};
|
|
|
|
forcePackageTreeRefOnBoot = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Whether each boot should force the package tree back to packageTreeRef.";
|
|
};
|
|
|
|
persistDevice = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Persistent block device path mounted at /persist.";
|
|
};
|
|
|
|
zfsPool = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "quixos-persist";
|
|
description = "ZFS pool name on the persistent block device.";
|
|
};
|
|
|
|
hostId = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "71756978";
|
|
description = "Eight hex digit hostId required by ZFS.";
|
|
};
|
|
|
|
enableSsh = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Enable inbound SSH in the NixOS host config. AWS ingress is managed by OpenTofu.";
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
|
|
|
networking.hostId = cfg.hostId;
|
|
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
|
boot.supportedFilesystems = [ "zfs" ];
|
|
boot.zfs.extraPools = [ cfg.zfsPool ];
|
|
boot.zfs.forceImportRoot = false;
|
|
services.zfs.autoScrub = {
|
|
enable = true;
|
|
pools = [ cfg.zfsPool ];
|
|
};
|
|
|
|
fileSystems."/nix" = {
|
|
device = "${cfg.zfsPool}/nix";
|
|
fsType = "zfs";
|
|
neededForBoot = true;
|
|
};
|
|
|
|
fileSystems."/persist" = {
|
|
device = "${cfg.zfsPool}/persist";
|
|
fsType = "zfs";
|
|
neededForBoot = true;
|
|
};
|
|
|
|
users.groups.quixos = {};
|
|
users.users.quixos = {
|
|
isSystemUser = true;
|
|
group = "quixos";
|
|
home = cfg.dataRoot;
|
|
createHome = true;
|
|
};
|
|
users.users.caddy.extraGroups = [ "quixos" ];
|
|
|
|
environment.systemPackages = [
|
|
pkgs.git
|
|
pkgs.nix
|
|
pkgs.amazon-ssm-agent
|
|
quixosPackages.qx-control
|
|
];
|
|
|
|
services.amazon-ssm-agent.enable = true;
|
|
|
|
services.openssh = {
|
|
enable = lib.mkForce cfg.enableSsh;
|
|
hostKeys = [
|
|
{
|
|
path = "/persist/etc/ssh/ssh_host_ed25519_key";
|
|
type = "ed25519";
|
|
}
|
|
{
|
|
path = "/persist/etc/ssh/ssh_host_rsa_key";
|
|
type = "rsa";
|
|
bits = 4096;
|
|
}
|
|
];
|
|
};
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"d /persist/etc/ssh 0700 root root -"
|
|
"d /persist/secrets 0700 root root -"
|
|
"d /persist/srv/quixos 0755 quixos quixos -"
|
|
"d ${cfg.dataRoot} 0750 quixos quixos -"
|
|
"d /persist/var/lib/acme 0750 caddy caddy -"
|
|
"L /var/lib/acme - - - - /persist/var/lib/acme"
|
|
];
|
|
|
|
systemd.services.quixos-package-tree = {
|
|
description = "Prepare Quixos package tree";
|
|
wantedBy = [ "multi-user.target" ];
|
|
before = [ "quixos-orchestrator.service" ];
|
|
after = [ "network-online.target" "persist.mount" ];
|
|
wants = [ "network-online.target" ];
|
|
path = [ pkgs.git ];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
User = "quixos";
|
|
Group = "quixos";
|
|
EnvironmentFile = "-${cfg.packageTreeGitEnvironmentFile}";
|
|
RemainAfterExit = true;
|
|
};
|
|
environment = {
|
|
GIT_ASKPASS = packageTreeGitAskPass;
|
|
GIT_TERMINAL_PROMPT = "0";
|
|
};
|
|
script = ''
|
|
set -eu
|
|
mkdir -p "$(dirname ${lib.escapeShellArg cfg.packagesPath})"
|
|
if [ ! -d ${lib.escapeShellArg cfg.packagesPath}/.git ]; then
|
|
git clone ${lib.escapeShellArg cfg.packageTreeRepo} ${lib.escapeShellArg cfg.packagesPath}
|
|
git -C ${lib.escapeShellArg cfg.packagesPath} checkout --detach ${lib.escapeShellArg cfg.packageTreeRef}
|
|
git -C ${lib.escapeShellArg cfg.packagesPath} submodule update --init --recursive
|
|
elif ${if cfg.forcePackageTreeRefOnBoot then "true" else "false"}; then
|
|
git -C ${lib.escapeShellArg cfg.packagesPath} fetch --tags --prune origin
|
|
git -C ${lib.escapeShellArg cfg.packagesPath} checkout --detach ${lib.escapeShellArg cfg.packageTreeRef}
|
|
git -C ${lib.escapeShellArg cfg.packagesPath} submodule sync --recursive
|
|
git -C ${lib.escapeShellArg cfg.packagesPath} submodule update --init --recursive
|
|
fi
|
|
'';
|
|
};
|
|
|
|
systemd.services.quixos-orchestrator = {
|
|
description = "Quixos orchestrator";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network-online.target" "quixos-package-tree.service" ];
|
|
wants = [ "network-online.target" "quixos-package-tree.service" ];
|
|
serviceConfig = {
|
|
User = "quixos";
|
|
Group = "quixos";
|
|
RuntimeDirectory = "quixos";
|
|
RuntimeDirectoryMode = "0770";
|
|
WorkingDirectory = "/persist/srv/quixos";
|
|
EnvironmentFile = cfg.secretsEnvironmentFile;
|
|
Restart = "always";
|
|
RestartSec = "5s";
|
|
ExecStart = "${quixosPackages.quixos-orchestrator}/bin/quixos-orchestrator --packages-path ${cfg.packagesPath}";
|
|
};
|
|
environment = {
|
|
XDG_DATA_HOME = cfg.dataRoot;
|
|
QUIXOS_WORKSPACE_ROOT = "/persist/srv/quixos";
|
|
QUIXOS_CONTROL_SOCKET = "/run/quixos/control.sock";
|
|
QUIXOS_DEVICE_ASSIGNMENTS_FILE = "${cfg.packagesPath}/device-assignments.json";
|
|
QUIXOS_REMOTE_DOM_RELAY_SOCKET = "/run/quixos/relay.sock";
|
|
QUIXOS_REMOTE_DOM_RELAY_PUBLIC_WS_URL = "wss://${cfg.domain}/relay/";
|
|
QUIXOS_REMOTE_DOM_RELAY_PUBLIC_HTTP_ORIGIN = "https://${cfg.domain}/relay";
|
|
};
|
|
};
|
|
|
|
services.caddy = {
|
|
enable = true;
|
|
virtualHosts.${cfg.domain}.extraConfig = ''
|
|
handle_path /control/* {
|
|
reverse_proxy unix//run/quixos/control.sock
|
|
}
|
|
|
|
handle_path /relay/* {
|
|
reverse_proxy unix//run/quixos/relay.sock {
|
|
header_up X-Forwarded-Prefix /relay
|
|
}
|
|
}
|
|
|
|
handle {
|
|
root * ${quixosPackages.project-visualizer-static}
|
|
file_server
|
|
}
|
|
'';
|
|
};
|
|
};
|
|
}
|