feat: p4 on hectic-lab
This commit is contained in:
@@ -0,0 +1,355 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.services.p4d;
|
||||||
|
|
||||||
|
boolString = value: if value then "1" else "0";
|
||||||
|
|
||||||
|
serviceUser = cfg.user;
|
||||||
|
serviceGroup = cfg.group;
|
||||||
|
packageExe = if cfg.package != null then "${cfg.package}/bin/p4d" else "/run/current-system/sw/bin/false";
|
||||||
|
clientExe = if cfg.clientPackage != null then "${cfg.clientPackage}/bin/p4" else "/run/current-system/sw/bin/false";
|
||||||
|
|
||||||
|
journalFile = "${cfg.journalDir}/journal";
|
||||||
|
logFile = "${cfg.dataDir}/logs/p4d.log";
|
||||||
|
sslPort = if cfg.ssl.enable then "ssl:${cfg.listenAddress}:${toString cfg.port}" else "${cfg.listenAddress}:${toString cfg.port}";
|
||||||
|
bootstrapPort = "${cfg.bootstrap.listenAddress}:${toString cfg.bootstrap.port}";
|
||||||
|
|
||||||
|
typemapLines = [
|
||||||
|
"binary+l //....uasset"
|
||||||
|
"binary+l //....umap"
|
||||||
|
"binary+l //....upk"
|
||||||
|
"binary+l //....udk"
|
||||||
|
"binary+l //....ubulk"
|
||||||
|
"binary+l //....uexp"
|
||||||
|
"binary+l //....utoc"
|
||||||
|
"binary+l //....ucas"
|
||||||
|
"binary+w //....exe"
|
||||||
|
"binary+w //....dll"
|
||||||
|
"binary+w //....lib"
|
||||||
|
"binary+w //....app"
|
||||||
|
"binary+w //....dylib"
|
||||||
|
"binary+w //....stub"
|
||||||
|
"binary+w //....ipa"
|
||||||
|
"binary+w //....pdb"
|
||||||
|
] ++ cfg.typemap.extraLines;
|
||||||
|
|
||||||
|
typemapSpec = pkgs.writeText "p4d-unreal-typemap.txt" (
|
||||||
|
lib.concatStringsSep "\n" ([ "Typemap:" ] ++ map (line: "\t${line}") typemapLines) + "\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
mainArgs = [
|
||||||
|
"-r" cfg.dataDir
|
||||||
|
"-p" sslPort
|
||||||
|
"-J" journalFile
|
||||||
|
"-L" logFile
|
||||||
|
] ++ lib.optional (!cfg.caseSensitive) "-C1";
|
||||||
|
|
||||||
|
bootstrapArgs = [
|
||||||
|
"-r" cfg.dataDir
|
||||||
|
"-p" bootstrapPort
|
||||||
|
"-J" journalFile
|
||||||
|
"-L" "${cfg.dataDir}/logs/bootstrap.log"
|
||||||
|
] ++ lib.optional (!cfg.caseSensitive) "-C1";
|
||||||
|
|
||||||
|
initScript = pkgs.writeShellScript "p4d-init" ''
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
export P4ROOT=${lib.escapeShellArg cfg.dataDir}
|
||||||
|
export P4JOURNAL=${lib.escapeShellArg journalFile}
|
||||||
|
export P4SSLDIR=${lib.escapeShellArg cfg.ssl.dir}
|
||||||
|
|
||||||
|
marker_file="$P4ROOT/.hectic-p4d-initialized"
|
||||||
|
typemap_target="$P4ROOT/unreal-engine.typemap"
|
||||||
|
server_id_file="$P4ROOT/server.id"
|
||||||
|
|
||||||
|
${pkgs.coreutils}/bin/install -d -m 0750 "$P4ROOT"
|
||||||
|
${pkgs.coreutils}/bin/install -d -m 0750 ${lib.escapeShellArg cfg.journalDir}
|
||||||
|
${pkgs.coreutils}/bin/install -d -m 0750 ${lib.escapeShellArg cfg.checkpointDir}
|
||||||
|
${pkgs.coreutils}/bin/install -d -m 0750 "$P4ROOT/logs"
|
||||||
|
${pkgs.coreutils}/bin/install -d -m 0700 ${lib.escapeShellArg cfg.ssl.dir}
|
||||||
|
|
||||||
|
if [ ! -f "$server_id_file" ]; then
|
||||||
|
umask 077
|
||||||
|
${pkgs.coreutils}/bin/printf '%s\n' ${lib.escapeShellArg cfg.serverId} > "$server_id_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${boolString cfg.typemap.enable}" = "1" ]; then
|
||||||
|
${pkgs.coreutils}/bin/install -m 0640 ${lib.escapeShellArg typemapSpec} "$typemap_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${boolString cfg.ssl.enable}" = "1" ] && {
|
||||||
|
[ ! -f "$P4SSLDIR/privatekey.txt" ] || [ ! -f "$P4SSLDIR/certificate.txt" ];
|
||||||
|
}; then
|
||||||
|
${packageExe} -r "$P4ROOT" -Gc
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$marker_file" ] && [ "${boolString cfg.unicode}" = "1" ] && [ ! -e "$P4ROOT/db.counters" ]; then
|
||||||
|
${packageExe} ${lib.escapeShellArgs ([ "-r" cfg.dataDir ] ++ lib.optional (!cfg.caseSensitive) "-C1" ++ [ "-xi" ])}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$marker_file" ] && [ "${boolString cfg.bootstrap.enable}" = "1" ]; then
|
||||||
|
temp_pid_file="$P4ROOT/bootstrap.pid"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [ -f "$temp_pid_file" ]; then
|
||||||
|
pid=$(cat "$temp_pid_file")
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
wait "$pid" 2>/dev/null || true
|
||||||
|
rm -f "$temp_pid_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
${packageExe} ${lib.escapeShellArgs bootstrapArgs} &
|
||||||
|
pid=$!
|
||||||
|
${pkgs.coreutils}/bin/printf '%s\n' "$pid" > "$temp_pid_file"
|
||||||
|
|
||||||
|
ready=0
|
||||||
|
tries=0
|
||||||
|
while [ "$tries" -lt 30 ]; do
|
||||||
|
if ${clientExe} -p ${lib.escapeShellArg bootstrapPort} info >/dev/null 2>&1; then
|
||||||
|
ready=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
tries=$((tries + 1))
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$ready" -ne 1 ]; then
|
||||||
|
printf 'p4d bootstrap server failed to become ready on %s\n' ${lib.escapeShellArg bootstrapPort} >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
${lib.optionalString (cfg.securityLevel != null) ''
|
||||||
|
${clientExe} -p ${lib.escapeShellArg bootstrapPort} configure set security=${toString cfg.securityLevel}
|
||||||
|
''}
|
||||||
|
|
||||||
|
if [ "${boolString cfg.typemap.enable}" = "1" ]; then
|
||||||
|
${clientExe} -p ${lib.escapeShellArg bootstrapPort} typemap -i < "$typemap_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
${clientExe} -p ${lib.escapeShellArg bootstrapPort} admin stop || true
|
||||||
|
wait "$pid" 2>/dev/null || true
|
||||||
|
rm -f "$temp_pid_file"
|
||||||
|
trap - EXIT INT TERM
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$marker_file" ]; then
|
||||||
|
: > "$marker_file"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
options.hectic.services.p4d = {
|
||||||
|
enable = lib.mkEnableOption "Perforce Helix Core p4d server";
|
||||||
|
|
||||||
|
package = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr package;
|
||||||
|
default = null;
|
||||||
|
defaultText = lib.literalExpression "pkgs.p4d";
|
||||||
|
description = ''
|
||||||
|
Package providing the `p4d` executable. Left null by default because
|
||||||
|
nixpkgs marks Perforce packages unfree.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
clientPackage = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr package;
|
||||||
|
default = null;
|
||||||
|
defaultText = lib.literalExpression "pkgs.p4";
|
||||||
|
description = ''
|
||||||
|
Package providing the `p4` client executable used for optional local
|
||||||
|
bootstrap tasks like typemap installation and security configuration.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
user = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "p4d";
|
||||||
|
description = "System user running the p4d service.";
|
||||||
|
};
|
||||||
|
|
||||||
|
group = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "p4d";
|
||||||
|
description = "System group running the p4d service.";
|
||||||
|
};
|
||||||
|
|
||||||
|
dataDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/var/lib/p4d";
|
||||||
|
description = "Persistent P4ROOT directory for metadata and archives.";
|
||||||
|
};
|
||||||
|
|
||||||
|
journalDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/var/lib/p4d/journal";
|
||||||
|
description = "Directory storing the live journal file.";
|
||||||
|
};
|
||||||
|
|
||||||
|
checkpointDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/var/lib/p4d/checkpoints";
|
||||||
|
description = "Directory reserved for checkpoints and offline backups.";
|
||||||
|
};
|
||||||
|
|
||||||
|
listenAddress = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "0.0.0.0";
|
||||||
|
description = "Address p4d listens on for normal client traffic.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 1666;
|
||||||
|
description = "TCP port exposed by p4d.";
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewall = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether to open the p4d TCP port in the firewall.";
|
||||||
|
};
|
||||||
|
|
||||||
|
serverId = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "master";
|
||||||
|
description = "Perforce server identifier written to P4ROOT/server.id.";
|
||||||
|
};
|
||||||
|
|
||||||
|
unicode = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Initialize Unicode mode on first boot. Irreversible after init.";
|
||||||
|
};
|
||||||
|
|
||||||
|
caseSensitive = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether the server remains case-sensitive. Linux default stays true.
|
||||||
|
Set false only when tooling requires case-insensitive behavior.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
securityLevel = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr (ints.between 0 5);
|
||||||
|
default = 4;
|
||||||
|
description = ''
|
||||||
|
Optional `p4 configure set security=<level>` value applied during local
|
||||||
|
bootstrap. Set null to skip automatic security tuning.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
ssl = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether p4d listens with SSL enabled.";
|
||||||
|
};
|
||||||
|
|
||||||
|
dir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/var/lib/p4d/ssl";
|
||||||
|
description = "Directory holding p4d SSL certificate and key material.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
bootstrap = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Run one-time local bootstrap tasks on first start: optional security
|
||||||
|
level configuration and Unreal typemap installation.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
listenAddress = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
description = "Loopback address used by the local temporary bootstrap server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 1667;
|
||||||
|
description = "Loopback-only port used by the temporary bootstrap server.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
typemap = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Generate the Unreal-focused typemap spec and optionally install it
|
||||||
|
during bootstrap.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraLines = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Extra lines appended to the generated Perforce typemap spec.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.package != null;
|
||||||
|
message = "hectic.services.p4d.package must be set, for example pkgs.p4d.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = !cfg.bootstrap.enable || cfg.clientPackage != null;
|
||||||
|
message = "hectic.services.p4d.clientPackage must be set when bootstrap.enable is true.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
users.groups.${serviceGroup} = { };
|
||||||
|
users.users.${serviceUser} = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = serviceGroup;
|
||||||
|
home = cfg.dataDir;
|
||||||
|
createHome = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d ${cfg.dataDir} 0750 ${serviceUser} ${serviceGroup} - -"
|
||||||
|
"d ${cfg.journalDir} 0750 ${serviceUser} ${serviceGroup} - -"
|
||||||
|
"d ${cfg.checkpointDir} 0750 ${serviceUser} ${serviceGroup} - -"
|
||||||
|
"d ${cfg.dataDir}/logs 0750 ${serviceUser} ${serviceGroup} - -"
|
||||||
|
"d ${cfg.ssl.dir} 0700 ${serviceUser} ${serviceGroup} - -"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.p4d = {
|
||||||
|
description = "Perforce Helix Core p4d server";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
preStart = builtins.readFile initScript;
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
User = serviceUser;
|
||||||
|
Group = serviceGroup;
|
||||||
|
WorkingDirectory = cfg.dataDir;
|
||||||
|
ExecStart = "${packageExe} ${lib.escapeShellArgs mainArgs}";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = "5s";
|
||||||
|
TimeoutStopSec = "30s";
|
||||||
|
KillSignal = "SIGTERM";
|
||||||
|
KillMode = "mixed";
|
||||||
|
UMask = "0077";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ in self.lib.nixpkgs-lib.nixosSystem {
|
|||||||
inherit system;
|
inherit system;
|
||||||
overlays = [ self.overlays.default ];
|
overlays = [ self.overlays.default ];
|
||||||
config.allowUnfreePredicate = pkg:
|
config.allowUnfreePredicate = pkg:
|
||||||
builtins.elem (inputs.nixpkgs.lib.getName pkg) [ "steamcmd" "steam-unwrapped" ];
|
builtins.elem (inputs.nixpkgs.lib.getName pkg) [ "steamcmd" "steam-unwrapped" "p4d" "p4" ];
|
||||||
};
|
};
|
||||||
modules = [
|
modules = [
|
||||||
{ networking.hostName = name; }
|
{ networking.hostName = name; }
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
config,
|
config,
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
modulesPath,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
with builtins;
|
with builtins;
|
||||||
@@ -99,6 +98,13 @@ in {
|
|||||||
workshopItems = [ ];
|
workshopItems = [ ];
|
||||||
mods = [ ];
|
mods = [ ];
|
||||||
};
|
};
|
||||||
|
services.p4d = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.p4d;
|
||||||
|
clientPackage = pkgs.p4;
|
||||||
|
openFirewall = true;
|
||||||
|
bootstrap.enable = false;
|
||||||
|
};
|
||||||
services.gitea-runner-controller = {
|
services.gitea-runner-controller = {
|
||||||
# NOTE(yukkop): ephemeral Hetzner VM runners (1 VM = 1 job).
|
# NOTE(yukkop): ephemeral Hetzner VM runners (1 VM = 1 job).
|
||||||
# Runbook: infra/gitea-runners/runbook.md "Ephemeral VM runner cutover".
|
# Runbook: infra/gitea-runners/runbook.md "Ephemeral VM runner cutover".
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
{ system, inputs, self, pkgs, flake }:
|
{ system, inputs, self, pkgs, flake }:
|
||||||
(import ./package { inherit system inputs self pkgs; })
|
(import ./package { inherit system inputs self pkgs; }) //
|
||||||
|
(import ./nixos { inherit system inputs self pkgs flake; })
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
{ system, inputs, self, pkgs, ... }:
|
||||||
|
let
|
||||||
|
lib = inputs.nixpkgs.lib;
|
||||||
|
|
||||||
|
mkSystem = module:
|
||||||
|
lib.nixosSystem {
|
||||||
|
inherit system;
|
||||||
|
modules = [
|
||||||
|
inputs.sops-nix.nixosModules.sops
|
||||||
|
self.nixosModules.hectic
|
||||||
|
module
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
fakeP4d = pkgs.writeShellScriptBin "p4d" "exit 0";
|
||||||
|
fakeP4 = pkgs.writeShellScriptBin "p4" "exit 0";
|
||||||
|
|
||||||
|
enabledCfg = (mkSystem {
|
||||||
|
hectic.services.p4d = {
|
||||||
|
enable = true;
|
||||||
|
package = fakeP4d;
|
||||||
|
};
|
||||||
|
}).config;
|
||||||
|
|
||||||
|
bootstrapCfg = (mkSystem {
|
||||||
|
hectic.services.p4d = {
|
||||||
|
enable = true;
|
||||||
|
package = fakeP4d;
|
||||||
|
clientPackage = fakeP4;
|
||||||
|
bootstrap.enable = true;
|
||||||
|
openFirewall = true;
|
||||||
|
caseSensitive = false;
|
||||||
|
};
|
||||||
|
}).config;
|
||||||
|
in {
|
||||||
|
p4d-module-basic =
|
||||||
|
assert enabledCfg.users.users.p4d.isSystemUser;
|
||||||
|
assert enabledCfg.users.users.p4d.group == "p4d";
|
||||||
|
assert enabledCfg.systemd.services.p4d.serviceConfig.User == "p4d";
|
||||||
|
assert enabledCfg.systemd.services.p4d.serviceConfig.Group == "p4d";
|
||||||
|
assert enabledCfg.systemd.services.p4d.serviceConfig.ExecStart == "${fakeP4d}/bin/p4d -r /var/lib/p4d -p ssl:0.0.0.0:1666 -J /var/lib/p4d/journal/journal -L /var/lib/p4d/logs/p4d.log";
|
||||||
|
pkgs.runCommand "p4d-module-basic" {} ''
|
||||||
|
touch "$out"
|
||||||
|
'';
|
||||||
|
|
||||||
|
p4d-module-bootstrap =
|
||||||
|
assert lib.any (rule: rule == "d /var/lib/p4d/ssl 0700 p4d p4d - -") bootstrapCfg.systemd.tmpfiles.rules;
|
||||||
|
assert bootstrapCfg.networking.firewall.allowedTCPPorts == [ 1666 ];
|
||||||
|
assert bootstrapCfg.systemd.services.p4d.preStart != "";
|
||||||
|
assert lib.hasInfix "configure set security=4" bootstrapCfg.systemd.services.p4d.preStart;
|
||||||
|
assert lib.hasInfix "typemap -i" bootstrapCfg.systemd.services.p4d.preStart;
|
||||||
|
assert lib.hasInfix "-C1" bootstrapCfg.systemd.services.p4d.serviceConfig.ExecStart;
|
||||||
|
pkgs.runCommand "p4d-module-bootstrap" {} ''
|
||||||
|
touch "$out"
|
||||||
|
'';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user