|
|
|
@@ -7,6 +7,9 @@ let
|
|
|
|
|
quixosPackages = self.packages.${pkgs.stdenv.hostPlatform.system};
|
|
|
|
|
localPersistMount = "${utils.escapeSystemdPath "/persist-local"}.mount";
|
|
|
|
|
sharedPersistMount = "${utils.escapeSystemdPath "/persist"}.mount";
|
|
|
|
|
devHomeMount = "${utils.escapeSystemdPath cfg.devHome.mountPoint}.mount";
|
|
|
|
|
sourceCheckoutService = "quixos-source-checkout.service";
|
|
|
|
|
visualizerDevService = "quixos-project-visualizer-dev.service";
|
|
|
|
|
secretFiles = {
|
|
|
|
|
controlSecret = "${secretsCfg.runtimeDirectory}/control-secret";
|
|
|
|
|
stateContextSecret = "${secretsCfg.runtimeDirectory}/state-context-secret";
|
|
|
|
@@ -100,6 +103,102 @@ let
|
|
|
|
|
fi
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
qxdevStatus = pkgs.writeShellApplication {
|
|
|
|
|
name = "qxdev-status";
|
|
|
|
|
runtimeInputs = [ pkgs.git pkgs.systemd ];
|
|
|
|
|
text = ''
|
|
|
|
|
source_path=${lib.escapeShellArg cfg.sourceCheckout.path}
|
|
|
|
|
echo "source: $source_path"
|
|
|
|
|
if [ -d "$source_path/.git" ]; then
|
|
|
|
|
git -C "$source_path" status --short --branch
|
|
|
|
|
else
|
|
|
|
|
echo "source checkout is missing"
|
|
|
|
|
fi
|
|
|
|
|
echo
|
|
|
|
|
systemctl status --no-pager \
|
|
|
|
|
quixos-orchestrator.service \
|
|
|
|
|
${visualizerDevService} \
|
|
|
|
|
caddy.service || true
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
qxdevBootstrap = pkgs.writeShellApplication {
|
|
|
|
|
name = "qxdev-bootstrap";
|
|
|
|
|
runtimeInputs = [ pkgs.bash pkgs.git pkgs.sudo pkgs.systemd pkgs.yarn-berry_4 ];
|
|
|
|
|
text = ''
|
|
|
|
|
${lib.optionalString (!cfg.sourceCheckout.enable) ''
|
|
|
|
|
echo "Quixos source checkout mode is not enabled on this host." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
''}
|
|
|
|
|
source_path=${lib.escapeShellArg cfg.sourceCheckout.path}
|
|
|
|
|
source_user=${lib.escapeShellArg cfg.devHome.user}
|
|
|
|
|
|
|
|
|
|
sudo systemctl start ${sourceCheckoutService}
|
|
|
|
|
|
|
|
|
|
if [ ! -d "$source_path/.git" ]; then
|
|
|
|
|
echo "source checkout is missing: $source_path" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
sudo -u "$source_user" -H ${pkgs.bash}/bin/bash -lc ${lib.escapeShellArg ''
|
|
|
|
|
set -eu
|
|
|
|
|
cd "$1/orchestrator"
|
|
|
|
|
${pkgs.yarn-berry_4}/bin/yarn install --immutable
|
|
|
|
|
${pkgs.yarn-berry_4}/bin/yarn build
|
|
|
|
|
|
|
|
|
|
cd "$1/project-visualizer"
|
|
|
|
|
${pkgs.yarn-berry_4}/bin/yarn install --immutable
|
|
|
|
|
''} qxdev-bootstrap "$source_path"
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
qxdevRestart = pkgs.writeShellApplication {
|
|
|
|
|
name = "qxdev-restart";
|
|
|
|
|
runtimeInputs = [ pkgs.bash pkgs.sudo pkgs.systemd pkgs.yarn-berry_4 ];
|
|
|
|
|
text = ''
|
|
|
|
|
${lib.optionalString (!cfg.sourceCheckout.enable) ''
|
|
|
|
|
echo "Quixos source checkout mode is not enabled on this host." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
''}
|
|
|
|
|
source_path=${lib.escapeShellArg cfg.sourceCheckout.path}
|
|
|
|
|
source_user=${lib.escapeShellArg cfg.devHome.user}
|
|
|
|
|
|
|
|
|
|
${lib.optionalString cfg.orchestrator.source.enable ''
|
|
|
|
|
sudo -u "$source_user" -H ${pkgs.bash}/bin/bash -lc ${lib.escapeShellArg ''
|
|
|
|
|
set -eu
|
|
|
|
|
cd "$1/orchestrator"
|
|
|
|
|
${pkgs.yarn-berry_4}/bin/yarn build
|
|
|
|
|
''} qxdev-restart "$source_path"
|
|
|
|
|
''}
|
|
|
|
|
|
|
|
|
|
sudo systemctl restart quixos-orchestrator.service
|
|
|
|
|
${lib.optionalString cfg.visualizer.vite.enable ''
|
|
|
|
|
sudo systemctl restart ${visualizerDevService}
|
|
|
|
|
''}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
qxdevLogs = pkgs.writeShellApplication {
|
|
|
|
|
name = "qxdev-logs";
|
|
|
|
|
runtimeInputs = [ pkgs.systemd ];
|
|
|
|
|
text = ''
|
|
|
|
|
exec journalctl -fu \
|
|
|
|
|
quixos-orchestrator.service \
|
|
|
|
|
${visualizerDevService} \
|
|
|
|
|
caddy.service
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
qxdevOrchLogs = pkgs.writeShellApplication {
|
|
|
|
|
name = "qxdev-orch-logs";
|
|
|
|
|
runtimeInputs = [ pkgs.systemd ];
|
|
|
|
|
text = ''
|
|
|
|
|
exec journalctl -fu quixos-orchestrator.service
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
qxdevVizLogs = pkgs.writeShellApplication {
|
|
|
|
|
name = "qxdev-viz-logs";
|
|
|
|
|
runtimeInputs = [ pkgs.systemd ];
|
|
|
|
|
text = ''
|
|
|
|
|
exec journalctl -fu ${visualizerDevService}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
quixosCodexDir = "/persist/var/lib/quixos/codex";
|
|
|
|
|
quixosCodexRule = ''prefix_rule(pattern=[\x22qx-control\x22],decision=\x22allow\x22)'';
|
|
|
|
|
in
|
|
|
|
@@ -206,6 +305,114 @@ in
|
|
|
|
|
description = "Enable inbound SSH in the NixOS host config. AWS ingress is managed by OpenTofu.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
devHome = {
|
|
|
|
|
enable = lib.mkOption {
|
|
|
|
|
type = lib.types.bool;
|
|
|
|
|
default = false;
|
|
|
|
|
description = "Create a persistent development user home on its own ZFS dataset.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
user = lib.mkOption {
|
|
|
|
|
type = lib.types.str;
|
|
|
|
|
default = "quixos-dev";
|
|
|
|
|
description = "User that owns the development source checkout.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mountPoint = lib.mkOption {
|
|
|
|
|
type = lib.types.str;
|
|
|
|
|
default = "/persist-local/home/quixos-dev";
|
|
|
|
|
description = "Mount point for the development home dataset.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dataset = lib.mkOption {
|
|
|
|
|
type = lib.types.str;
|
|
|
|
|
default = "${cfg.persistZfsPool}/dev-home";
|
|
|
|
|
description = "ZFS dataset mounted as the development user home.";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
sourceCheckout = {
|
|
|
|
|
enable = lib.mkOption {
|
|
|
|
|
type = lib.types.bool;
|
|
|
|
|
default = false;
|
|
|
|
|
description = "Ensure a mutable Quixos source checkout exists for source-backed development services.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
path = lib.mkOption {
|
|
|
|
|
type = lib.types.str;
|
|
|
|
|
default = "${cfg.devHome.mountPoint}/quixos";
|
|
|
|
|
description = "Mutable Quixos source checkout path.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
repo = lib.mkOption {
|
|
|
|
|
type = lib.types.str;
|
|
|
|
|
default = "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos.git";
|
|
|
|
|
description = "Quixos source repository cloned when the source checkout is missing.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ref = lib.mkOption {
|
|
|
|
|
type = lib.types.str;
|
|
|
|
|
default = "master";
|
|
|
|
|
description = "Initial ref checked out when the source checkout is first cloned.";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
orchestrator = {
|
|
|
|
|
nix.enable = lib.mkOption {
|
|
|
|
|
type = lib.types.bool;
|
|
|
|
|
default = true;
|
|
|
|
|
description = "Run the Nix-built orchestrator from the immutable Quixos flake.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
source = {
|
|
|
|
|
enable = lib.mkOption {
|
|
|
|
|
type = lib.types.bool;
|
|
|
|
|
default = false;
|
|
|
|
|
description = "Run the orchestrator from a mutable source checkout.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
path = lib.mkOption {
|
|
|
|
|
type = lib.types.str;
|
|
|
|
|
default = cfg.sourceCheckout.path;
|
|
|
|
|
description = "Quixos source checkout used for the source-backed orchestrator.";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
visualizer = {
|
|
|
|
|
static.enable = lib.mkOption {
|
|
|
|
|
type = lib.types.bool;
|
|
|
|
|
default = true;
|
|
|
|
|
description = "Serve the Nix-built static project visualizer from Caddy.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
vite = {
|
|
|
|
|
enable = lib.mkOption {
|
|
|
|
|
type = lib.types.bool;
|
|
|
|
|
default = false;
|
|
|
|
|
description = "Run the project visualizer through a Vite dev server and proxy it through Caddy.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
path = lib.mkOption {
|
|
|
|
|
type = lib.types.str;
|
|
|
|
|
default = "${cfg.sourceCheckout.path}/project-visualizer";
|
|
|
|
|
description = "Project visualizer source directory for Vite dev mode.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
host = lib.mkOption {
|
|
|
|
|
type = lib.types.str;
|
|
|
|
|
default = "127.0.0.1";
|
|
|
|
|
description = "Vite bind host.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
port = lib.mkOption {
|
|
|
|
|
type = lib.types.port;
|
|
|
|
|
default = 4173;
|
|
|
|
|
description = "Vite bind port.";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
secrets = {
|
|
|
|
|
provider = lib.mkOption {
|
|
|
|
|
type = lib.types.enum [ "files" "aws-ssm" ];
|
|
|
|
@@ -284,6 +491,32 @@ in
|
|
|
|
|
);
|
|
|
|
|
message = "services.quixosHost.secrets.awsSsm.*Parameter options must all be set when provider is aws-ssm.";
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
assertion = (if cfg.orchestrator.nix.enable then 1 else 0)
|
|
|
|
|
+ (if cfg.orchestrator.source.enable then 1 else 0) == 1;
|
|
|
|
|
message = "Exactly one of services.quixosHost.orchestrator.nix.enable or services.quixosHost.orchestrator.source.enable must be true.";
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
assertion = (if cfg.visualizer.static.enable then 1 else 0)
|
|
|
|
|
+ (if cfg.visualizer.vite.enable then 1 else 0) == 1;
|
|
|
|
|
message = "Exactly one of services.quixosHost.visualizer.static.enable or services.quixosHost.visualizer.vite.enable must be true.";
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
assertion = (!cfg.sourceCheckout.enable) || cfg.devHome.enable;
|
|
|
|
|
message = "services.quixosHost.sourceCheckout.enable requires services.quixosHost.devHome.enable.";
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
assertion = (!cfg.orchestrator.source.enable) || cfg.sourceCheckout.enable;
|
|
|
|
|
message = "services.quixosHost.orchestrator.source.enable requires services.quixosHost.sourceCheckout.enable.";
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
assertion = (!cfg.visualizer.vite.enable) || cfg.sourceCheckout.enable;
|
|
|
|
|
message = "services.quixosHost.visualizer.vite.enable requires services.quixosHost.sourceCheckout.enable.";
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
assertion = cfg.sourceCheckout.path != cfg.packagesPath;
|
|
|
|
|
message = "services.quixosHost.sourceCheckout.path must be separate from services.quixosHost.packagesPath.";
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
|
|
|
@@ -298,32 +531,44 @@ in
|
|
|
|
|
pools = [ cfg.nixZfsPool cfg.persistZfsPool cfg.sharedPersistZfsPool ];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fileSystems."/nix" = {
|
|
|
|
|
device = "${cfg.nixZfsPool}/nix";
|
|
|
|
|
fsType = "zfs";
|
|
|
|
|
neededForBoot = true;
|
|
|
|
|
};
|
|
|
|
|
fileSystems = {
|
|
|
|
|
"/nix" = {
|
|
|
|
|
device = "${cfg.nixZfsPool}/nix";
|
|
|
|
|
fsType = "zfs";
|
|
|
|
|
neededForBoot = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fileSystems."/persist-local" = {
|
|
|
|
|
device = "${cfg.persistZfsPool}/persist";
|
|
|
|
|
fsType = "zfs";
|
|
|
|
|
options =
|
|
|
|
|
[ "defaults" ]
|
|
|
|
|
++ lib.optionals (cfg.persistDevice != null) [
|
|
|
|
|
"x-systemd.requires=quixos-persist-init.service"
|
|
|
|
|
"x-systemd.after=quixos-persist-init.service"
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
"/persist-local" = {
|
|
|
|
|
device = "${cfg.persistZfsPool}/persist";
|
|
|
|
|
fsType = "zfs";
|
|
|
|
|
options =
|
|
|
|
|
[ "defaults" ]
|
|
|
|
|
++ lib.optionals (cfg.persistDevice != null) [
|
|
|
|
|
"x-systemd.requires=quixos-persist-init.service"
|
|
|
|
|
"x-systemd.after=quixos-persist-init.service"
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fileSystems."/persist" = {
|
|
|
|
|
device = "${cfg.sharedPersistZfsPool}/persist";
|
|
|
|
|
fsType = "zfs";
|
|
|
|
|
options =
|
|
|
|
|
[ "defaults" ]
|
|
|
|
|
++ lib.optionals (cfg.sharedPersistDevice != null) [
|
|
|
|
|
"x-systemd.requires=quixos-shared-persist-init.service"
|
|
|
|
|
"x-systemd.after=quixos-shared-persist-init.service"
|
|
|
|
|
"/persist" = {
|
|
|
|
|
device = "${cfg.sharedPersistZfsPool}/persist";
|
|
|
|
|
fsType = "zfs";
|
|
|
|
|
options =
|
|
|
|
|
[ "defaults" ]
|
|
|
|
|
++ lib.optionals (cfg.sharedPersistDevice != null) [
|
|
|
|
|
"x-systemd.requires=quixos-shared-persist-init.service"
|
|
|
|
|
"x-systemd.after=quixos-shared-persist-init.service"
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
} // lib.optionalAttrs cfg.devHome.enable {
|
|
|
|
|
${cfg.devHome.mountPoint} = {
|
|
|
|
|
device = cfg.devHome.dataset;
|
|
|
|
|
fsType = "zfs";
|
|
|
|
|
options = [
|
|
|
|
|
"defaults"
|
|
|
|
|
"x-systemd.requires=quixos-dev-home-init.service"
|
|
|
|
|
"x-systemd.after=quixos-dev-home-init.service"
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
systemd.services.quixos-persist-init = lib.mkIf (cfg.persistDevice != null) {
|
|
|
|
@@ -471,17 +716,58 @@ in
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
systemd.services.quixos-dev-home-init = lib.mkIf cfg.devHome.enable {
|
|
|
|
|
description = "Initialize Quixos development home ZFS dataset";
|
|
|
|
|
requiredBy = [ devHomeMount ];
|
|
|
|
|
before = [ devHomeMount "local-fs.target" ];
|
|
|
|
|
after = [ localPersistMount ];
|
|
|
|
|
requires = [ localPersistMount ];
|
|
|
|
|
path = [ pkgs.coreutils pkgs.zfs ];
|
|
|
|
|
unitConfig = {
|
|
|
|
|
DefaultDependencies = false;
|
|
|
|
|
};
|
|
|
|
|
serviceConfig = {
|
|
|
|
|
Type = "oneshot";
|
|
|
|
|
RemainAfterExit = true;
|
|
|
|
|
};
|
|
|
|
|
script = ''
|
|
|
|
|
set -eu
|
|
|
|
|
|
|
|
|
|
dataset=${lib.escapeShellArg cfg.devHome.dataset}
|
|
|
|
|
mount_point=${lib.escapeShellArg cfg.devHome.mountPoint}
|
|
|
|
|
|
|
|
|
|
install -d -m 0755 "$(dirname "$mount_point")"
|
|
|
|
|
|
|
|
|
|
if ! zfs list -H "$dataset" >/dev/null 2>&1; then
|
|
|
|
|
zfs create -o mountpoint=legacy "$dataset"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
install -d -m 0755 -o ${lib.escapeShellArg cfg.devHome.user} -g users "$mount_point"
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
users.groups.quixos = {};
|
|
|
|
|
users.groups.quixos-control = {};
|
|
|
|
|
users.users.quixos = {
|
|
|
|
|
isSystemUser = true;
|
|
|
|
|
group = "quixos";
|
|
|
|
|
extraGroups = [ "quixos-control" ];
|
|
|
|
|
home = cfg.dataRoot;
|
|
|
|
|
createHome = true;
|
|
|
|
|
shell = pkgs.bashInteractive;
|
|
|
|
|
users.users = {
|
|
|
|
|
quixos = {
|
|
|
|
|
isSystemUser = true;
|
|
|
|
|
group = "quixos";
|
|
|
|
|
extraGroups = [ "quixos-control" ];
|
|
|
|
|
home = cfg.dataRoot;
|
|
|
|
|
createHome = true;
|
|
|
|
|
shell = pkgs.bashInteractive;
|
|
|
|
|
};
|
|
|
|
|
caddy.extraGroups = [ "quixos" "quixos-control" ];
|
|
|
|
|
} // lib.optionalAttrs cfg.devHome.enable {
|
|
|
|
|
${cfg.devHome.user} = {
|
|
|
|
|
isNormalUser = true;
|
|
|
|
|
group = "users";
|
|
|
|
|
extraGroups = [ "wheel" "quixos" "quixos-control" ];
|
|
|
|
|
home = cfg.devHome.mountPoint;
|
|
|
|
|
createHome = true;
|
|
|
|
|
shell = pkgs.bashInteractive;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
users.users.caddy.extraGroups = [ "quixos" "quixos-control" ];
|
|
|
|
|
|
|
|
|
|
environment.systemPackages = [
|
|
|
|
|
pkgs.codex
|
|
|
|
@@ -491,6 +777,12 @@ in
|
|
|
|
|
pkgs.amazon-ssm-agent
|
|
|
|
|
qxControl
|
|
|
|
|
quixosTrackMaster
|
|
|
|
|
qxdevStatus
|
|
|
|
|
qxdevBootstrap
|
|
|
|
|
qxdevRestart
|
|
|
|
|
qxdevLogs
|
|
|
|
|
qxdevOrchLogs
|
|
|
|
|
qxdevVizLogs
|
|
|
|
|
];
|
|
|
|
|
programs.bash.shellAliases = {
|
|
|
|
|
nixrb = "sudo quixos-track-master /etc/nixos/flake.nix && sudo nix --refresh flake update quixos --flake /etc/nixos && sudo nixos-rebuild switch --flake /etc/nixos#quixos-host";
|
|
|
|
@@ -533,6 +825,7 @@ in
|
|
|
|
|
"z /persist 0755 root root -"
|
|
|
|
|
"z /persist-local 0755 root root -"
|
|
|
|
|
"d /persist/etc/ssh 0700 root root -"
|
|
|
|
|
"d /persist-local/home 0755 root root -"
|
|
|
|
|
"d /persist-local/srv 0755 root root -"
|
|
|
|
|
"d /persist-local/srv/quixos 0755 quixos quixos -"
|
|
|
|
|
"d /persist-local/srv/quixos/packages 0755 quixos quixos -"
|
|
|
|
@@ -557,6 +850,10 @@ in
|
|
|
|
|
"quixos-orchestrator.service"
|
|
|
|
|
"quixos-codex-app-server.service"
|
|
|
|
|
"caddy.service"
|
|
|
|
|
] ++ lib.optionals cfg.sourceCheckout.enable [
|
|
|
|
|
sourceCheckoutService
|
|
|
|
|
] ++ lib.optionals cfg.visualizer.vite.enable [
|
|
|
|
|
visualizerDevService
|
|
|
|
|
] ++ lib.optionals cfg.enableSsh [
|
|
|
|
|
"sshd-keygen.service"
|
|
|
|
|
"sshd.service"
|
|
|
|
@@ -567,12 +864,16 @@ in
|
|
|
|
|
"quixos-orchestrator.service"
|
|
|
|
|
"quixos-codex-app-server.service"
|
|
|
|
|
"caddy.service"
|
|
|
|
|
] ++ lib.optionals cfg.sourceCheckout.enable [
|
|
|
|
|
sourceCheckoutService
|
|
|
|
|
] ++ lib.optionals cfg.visualizer.vite.enable [
|
|
|
|
|
visualizerDevService
|
|
|
|
|
] ++ lib.optionals cfg.enableSsh [
|
|
|
|
|
"sshd-keygen.service"
|
|
|
|
|
"sshd.service"
|
|
|
|
|
];
|
|
|
|
|
after = [ localPersistMount sharedPersistMount ];
|
|
|
|
|
requires = [ localPersistMount sharedPersistMount ];
|
|
|
|
|
after = [ localPersistMount sharedPersistMount ] ++ lib.optionals cfg.devHome.enable [ devHomeMount ];
|
|
|
|
|
requires = [ localPersistMount sharedPersistMount ] ++ lib.optionals cfg.devHome.enable [ devHomeMount ];
|
|
|
|
|
path = [ config.systemd.package ];
|
|
|
|
|
serviceConfig = {
|
|
|
|
|
Type = "oneshot";
|
|
|
|
@@ -584,6 +885,47 @@ in
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
systemd.services.quixos-source-checkout = lib.mkIf cfg.sourceCheckout.enable {
|
|
|
|
|
description = "Prepare Quixos development source checkout";
|
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
|
before =
|
|
|
|
|
lib.optionals cfg.orchestrator.source.enable [ "quixos-orchestrator.service" ]
|
|
|
|
|
++ lib.optionals cfg.visualizer.vite.enable [ visualizerDevService ];
|
|
|
|
|
after = [ "network-online.target" "quixos-persist-layout.service" ];
|
|
|
|
|
wants = [ "network-online.target" ];
|
|
|
|
|
requires = [ "quixos-persist-layout.service" ];
|
|
|
|
|
path = [ pkgs.coreutils pkgs.git ];
|
|
|
|
|
serviceConfig = {
|
|
|
|
|
Type = "oneshot";
|
|
|
|
|
User = cfg.devHome.user;
|
|
|
|
|
Group = "users";
|
|
|
|
|
RemainAfterExit = true;
|
|
|
|
|
};
|
|
|
|
|
script = ''
|
|
|
|
|
set -eu
|
|
|
|
|
|
|
|
|
|
source_path=${lib.escapeShellArg cfg.sourceCheckout.path}
|
|
|
|
|
source_parent="$(dirname "$source_path")"
|
|
|
|
|
|
|
|
|
|
install -d -m 0755 "$source_parent"
|
|
|
|
|
|
|
|
|
|
if [ -d "$source_path/.git" ]; then
|
|
|
|
|
echo "Using existing source checkout: $source_path"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ -e "$source_path" ]; then
|
|
|
|
|
echo "Source checkout path exists but is not a Git worktree: $source_path" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
git clone ${lib.escapeShellArg cfg.sourceCheckout.repo} "$source_path"
|
|
|
|
|
git -C "$source_path" fetch --tags origin '+refs/heads/*:refs/remotes/origin/*'
|
|
|
|
|
git -C "$source_path" checkout ${lib.escapeShellArg cfg.sourceCheckout.ref} \
|
|
|
|
|
|| git -C "$source_path" checkout refs/remotes/origin/${lib.escapeShellArg cfg.sourceCheckout.ref}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
systemd.services.sshd-keygen = lib.mkIf cfg.enableSsh {
|
|
|
|
|
after = [ "quixos-persist-layout.service" ];
|
|
|
|
|
requires = [ "quixos-persist-layout.service" ];
|
|
|
|
@@ -692,21 +1034,35 @@ in
|
|
|
|
|
description = "Quixos orchestrator";
|
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
|
upheldBy = [ "multi-user.target" ];
|
|
|
|
|
restartTriggers = [
|
|
|
|
|
quixosPackages.quixos-orchestrator
|
|
|
|
|
qxControl
|
|
|
|
|
];
|
|
|
|
|
after = [ "network-online.target" "nix-daemon.socket" "quixos-persist-layout.service" "quixos-secrets.service" "quixos-package-tree.service" ];
|
|
|
|
|
wants = [ "network-online.target" "nix-daemon.socket" "quixos-secrets.service" "quixos-package-tree.service" ];
|
|
|
|
|
requires = [ "quixos-persist-layout.service" "quixos-secrets.service" "quixos-package-tree.service" ];
|
|
|
|
|
path = [ qxControl ];
|
|
|
|
|
restartTriggers =
|
|
|
|
|
[ qxControl ]
|
|
|
|
|
++ lib.optionals cfg.orchestrator.nix.enable [ quixosPackages.quixos-orchestrator ];
|
|
|
|
|
after =
|
|
|
|
|
[ "network-online.target" "nix-daemon.socket" "quixos-persist-layout.service" "quixos-secrets.service" "quixos-package-tree.service" ]
|
|
|
|
|
++ lib.optionals cfg.orchestrator.source.enable [ sourceCheckoutService ];
|
|
|
|
|
wants =
|
|
|
|
|
[ "network-online.target" "nix-daemon.socket" "quixos-secrets.service" "quixos-package-tree.service" ]
|
|
|
|
|
++ lib.optionals cfg.orchestrator.source.enable [ sourceCheckoutService ];
|
|
|
|
|
requires =
|
|
|
|
|
[ "quixos-persist-layout.service" "quixos-secrets.service" "quixos-package-tree.service" ]
|
|
|
|
|
++ lib.optionals cfg.orchestrator.source.enable [ sourceCheckoutService ];
|
|
|
|
|
path =
|
|
|
|
|
[ qxControl ]
|
|
|
|
|
++ lib.optionals cfg.orchestrator.source.enable [
|
|
|
|
|
pkgs.git
|
|
|
|
|
pkgs.nodejs_24
|
|
|
|
|
pkgs.yarn-berry_4
|
|
|
|
|
];
|
|
|
|
|
serviceConfig = {
|
|
|
|
|
User = "quixos";
|
|
|
|
|
Group = "quixos-control";
|
|
|
|
|
SupplementaryGroups = [ "quixos" ];
|
|
|
|
|
RuntimeDirectory = "quixos";
|
|
|
|
|
RuntimeDirectoryMode = "0770";
|
|
|
|
|
WorkingDirectory = "/persist-local/srv/quixos";
|
|
|
|
|
WorkingDirectory =
|
|
|
|
|
if cfg.orchestrator.source.enable
|
|
|
|
|
then "${cfg.orchestrator.source.path}/orchestrator"
|
|
|
|
|
else "/persist-local/srv/quixos";
|
|
|
|
|
Restart = "always";
|
|
|
|
|
RestartSec = "5s";
|
|
|
|
|
};
|
|
|
|
@@ -716,7 +1072,16 @@ in
|
|
|
|
|
export QUIXOS_CONTROL_SECRET
|
|
|
|
|
QUIXOS_STATE_CONTEXT_SECRET="$(cat ${lib.escapeShellArg secretFiles.stateContextSecret})"
|
|
|
|
|
export QUIXOS_STATE_CONTEXT_SECRET
|
|
|
|
|
exec ${quixosPackages.quixos-orchestrator}/bin/quixos-orchestrator --packages-path ${lib.escapeShellArg cfg.packagesPath}
|
|
|
|
|
${if cfg.orchestrator.source.enable then ''
|
|
|
|
|
if [ ! -f dist/server.js ]; then
|
|
|
|
|
echo "Missing orchestrator build output at $(pwd)/dist/server.js" >&2
|
|
|
|
|
echo "Run qxdev-bootstrap or qxdev-restart." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
exec ${pkgs.yarn-berry_4}/bin/yarn node --enable-source-maps dist/server.js --packages-path ${lib.escapeShellArg cfg.packagesPath}
|
|
|
|
|
'' else ''
|
|
|
|
|
exec ${quixosPackages.quixos-orchestrator}/bin/quixos-orchestrator --packages-path ${lib.escapeShellArg cfg.packagesPath}
|
|
|
|
|
''}
|
|
|
|
|
'';
|
|
|
|
|
environment = {
|
|
|
|
|
GIT_ASKPASS = packageTreeGitAskPass;
|
|
|
|
@@ -727,9 +1092,15 @@ in
|
|
|
|
|
GIT_COMMITTER_EMAIL = "quixos-runtime@localhost";
|
|
|
|
|
XDG_DATA_HOME = cfg.dataRoot;
|
|
|
|
|
QUIXOS_WORKSPACE_ROOT = "/persist-local/srv/quixos";
|
|
|
|
|
QUIXOS_APP_ROOT = "${self}";
|
|
|
|
|
QUIXOS_APP_ROOT =
|
|
|
|
|
if cfg.orchestrator.source.enable
|
|
|
|
|
then cfg.orchestrator.source.path
|
|
|
|
|
else "${self}";
|
|
|
|
|
QUIXOS_PACKAGES_PATH = cfg.packagesPath;
|
|
|
|
|
QUIXOS_PACKAGE_TEMPLATES_DIR = "${self}/package-templates";
|
|
|
|
|
QUIXOS_PACKAGE_TEMPLATES_DIR =
|
|
|
|
|
if cfg.orchestrator.source.enable
|
|
|
|
|
then "${cfg.orchestrator.source.path}/package-templates"
|
|
|
|
|
else "${self}/package-templates";
|
|
|
|
|
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";
|
|
|
|
@@ -738,6 +1109,32 @@ in
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
systemd.services.quixos-project-visualizer-dev = lib.mkIf cfg.visualizer.vite.enable {
|
|
|
|
|
description = "Quixos project visualizer Vite dev server";
|
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
|
upheldBy = [ "multi-user.target" ];
|
|
|
|
|
after = [ "network-online.target" "quixos-persist-layout.service" sourceCheckoutService ];
|
|
|
|
|
wants = [ "network-online.target" sourceCheckoutService ];
|
|
|
|
|
requires = [ "quixos-persist-layout.service" sourceCheckoutService ];
|
|
|
|
|
path = [
|
|
|
|
|
pkgs.nodejs_24
|
|
|
|
|
pkgs.yarn-berry_4
|
|
|
|
|
];
|
|
|
|
|
serviceConfig = {
|
|
|
|
|
User = cfg.devHome.user;
|
|
|
|
|
Group = "users";
|
|
|
|
|
WorkingDirectory = cfg.visualizer.vite.path;
|
|
|
|
|
Restart = "always";
|
|
|
|
|
RestartSec = "5s";
|
|
|
|
|
};
|
|
|
|
|
environment = {
|
|
|
|
|
QUIXOS_VITE_ALLOWED_HOSTS = cfg.domain;
|
|
|
|
|
};
|
|
|
|
|
script = ''
|
|
|
|
|
exec ${pkgs.yarn-berry_4}/bin/yarn dev --host ${lib.escapeShellArg cfg.visualizer.vite.host} --port ${toString cfg.visualizer.vite.port}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
systemd.services.quixos-codex-app-server = {
|
|
|
|
|
description = "Codex app server";
|
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
@@ -793,18 +1190,23 @@ in
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handle {
|
|
|
|
|
root * ${quixosPackages.project-visualizer-static}
|
|
|
|
|
file_server
|
|
|
|
|
${if cfg.visualizer.vite.enable then ''
|
|
|
|
|
reverse_proxy ${cfg.visualizer.vite.host}:${toString cfg.visualizer.vite.port}
|
|
|
|
|
'' else ''
|
|
|
|
|
root * ${quixosPackages.project-visualizer-static}
|
|
|
|
|
file_server
|
|
|
|
|
''}
|
|
|
|
|
}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
systemd.services.caddy = {
|
|
|
|
|
upheldBy = [ "multi-user.target" ];
|
|
|
|
|
reloadTriggers = [
|
|
|
|
|
reloadTriggers = lib.optionals cfg.visualizer.static.enable [
|
|
|
|
|
quixosPackages.project-visualizer-static
|
|
|
|
|
];
|
|
|
|
|
after = [ "quixos-persist-layout.service" ];
|
|
|
|
|
after = [ "quixos-persist-layout.service" ] ++ lib.optionals cfg.visualizer.vite.enable [ visualizerDevService ];
|
|
|
|
|
wants = lib.optionals cfg.visualizer.vite.enable [ visualizerDevService ];
|
|
|
|
|
requires = [ "quixos-persist-layout.service" ];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|