Patterns and Conventions Found
Null-guard convention in this codebase: if (x is null) throw new ArgumentNullException(nameof(x)); inline at all public method entries. osisa.Validation and Precondition.NotNull are NOT present in NETSetup.csproj and NOT used anywhere in the existing code. See EnsureCustomerVms.cs:110-112, ProvisionNixosVm.cs:145-149.
Static orchestrator + delegate-seam pattern: all provisioning orchestrators are static classes with a production Run(…) method that wires real deps and a testable RunCore(…) method that receives delegates. Source: ProvisionNixosVm.cs:144 (Run) and ProvisionNixosVm.cs:311 (RunCore). Mirror exactly.
OS constant routing: NetsetupOS.cs defines OperatingSystem.From("NixNextcloud", OperatingSystemFamily.Linux_GNU) etc. NixOsExtensions.IsNixBacked at src/NETSetup/Extensions/NixOsExtensions.cs:23 returns true for any OS name starting with "Nix". New OS names NixMail and NixDns satisfy IsNixBacked automatically. PmgLxc does NOT satisfy it (intentional: routed by separate EnsureCustomerLxcs).
pct execution: pct is a Proxmox host-local binary at /usr/sbin/pct. On production (running on Proxmox host), use LinuxCommandBase.Builder("/usr/sbin/pct") same as pvesm at UpdateMethods.cs:756. In tests, use proxmox.Execute(PveCommand.From("pct list")) over SSH.
FileOperator write pattern: see WriteAbsoluteFile private method at UpdateMethods.cs:772. Use PhysicalFileOperatorWorkaround.Create(dir, logger.As<PhysicalFileOperator>()).WriteAllTextAsync(name, content, default).Wait() for absolute-path writes. Use IFileOperatorExtensions.EnsureDirectory(dir) for directory creation (see ProvisionNixosVm.cs:279).
File Map (New and Modified)
| Full path | New/Mod | What |
|---|---|---|
|
Modified |
Add NixMail, NixDns, PmgLxc OS constants after NixDocker (line 23) |
|
New |
Sealed record: Host string, Port int (default 587), User string, PassFile AbsolutePath |
|
New |
Sealed record: Provider string, CredentialsFile AbsolutePath, CredentialsAttrs IReadOnlyDictionary<string,string>? |
|
Modified |
Add |
|
New |
Builder for /usr/sbin/pct argv; create/start/stop/destroy/exec/set/status subcommands; AddNamed for all param-value pairs |
|
New |
NixHostTemplate subclass for NixMail OS; GenerateDefaultNix emits postfix+dovecot+roundcube NixOS options |
|
New |
NixHostTemplate subclass for NixDns OS; GenerateDefaultNix emits coredns+step-ca+lego+ddns-updater NixOS options; consumes DnsProviderOptions |
|
Modified |
Add NixMail/NixDns branches at line 56; add ForMachine(Machine, NETSetupConfig) overload; wire domain, relay, dns provider onto templates |
|
Modified |
EmitConfigBundle signature: add NETSetupConfig param; pass to ForMachine overload at line 284 |
|
New |
Static orchestrator: ensure Debian-12 LXC template, pct create, pct start, pct exec install PMG, configure relay |
|
New |
Mirror of EnsureCustomerVms; classifies PmgLxc machines; dispatches to ProvisionPmgLxc; per-LXC fault isolation |
|
Modified |
EnsureProxmoxPostInstall: append EnsurePbsInstalled after EnsureLocalStorageEnabled (line 744). RunProxmoxUpdate: add EnsureCustomerLxcs.Run after EnsureCustomerVms.Run (after line 425) |
|
Already exists (backend-nix) |
Single mail module emitting postfix+dovecot+roundcube; sub-options under host.mail.* |
|
Already exists (backend-nix) |
Single dns module emitting coredns+step-ca+acme(lego)+ddns-updater; sub-options under host.dns.* |
|
Already exists (backend-nix) |
Host template enabling host.mail.*; consumed by NixMailTemplate emit |
|
Already exists (backend-nix) |
Host template enabling host.dns.*; consumed by NixDnsTemplate emit |
|
Modified |
Add mail@lib (102@lib), dns@lib (103@lib), pmg@lib (200@lib) devices after LibWin11 entry (line 43) |
|
New |
Unit tests: GenerateDefaultNix with Relay=null (no relayhost), with Relay set (relayhost emitted) |
|
New |
Unit tests: GenerateDefaultNix emits coredns, step-ca, lego, ddns-updater blocks with correct domain + provider |
|
New |
Unit tests: pct create arg verification, skip path when lxcExists=true, exec script content check |
|
New |
Unit tests: Classify returns Pmg for PmgLxc OS, Skip for others; fault isolation; aggregate throw |
|
New |
E2E test class [Ignore("Local Tests")], Liberator() method; phases: PBS check, LXC created, mail VM services, DNS VM services, PMG running, webmail HTTPS probe |
New Types
LxcCommand and Builder Family
File: src/NETSetup/NETCommand/LxcCommand.cs
Namespace: NETSetup.NETCommand
LxcCommand is NOT a subclass of LinuxCommandBase or CliWrapCommandBase. It is a composite command object whose Builder produces an argv list for the /usr/sbin/pct binary. The ExecuteLocal() method assembles a LinuxCommandBase.Builder("/usr/sbin/pct") from the stored argv, identical to how pvesm is driven at UpdateMethods.cs:756. The ToArgString() method produces a shell-safe single string for passing to proxmox.Execute(PveCommand.From(…)) in tests.
public sealed class LxcCommand
{
internal LxcCommand(string subcommand, IReadOnlyList<string> argv)
public string Subcommand { get; }
public IReadOnlyList<string> Argv { get; }
public string ToArgString()
public ICommandResult ExecuteLocal(TimeSpan? timeout = null)
}
public sealed class Builder
{
public static Builder Create()
// Subcommand selection (one must be called):
public Builder WithCreate(int ctid, string templatePath)
public Builder WithStart(int ctid)
public Builder WithStop(int ctid)
public Builder WithDestroy(int ctid)
public Builder WithExec(int ctid, string shellCmd)
public Builder WithSet(int ctid)
public Builder WithStatus(int ctid)
// Option methods (AddNamed enforced - never raw Add for param+value):
public Builder WithHostname(string hostname)
public Builder WithMemory(int mb)
public Builder WithCores(int n)
public Builder WithRootfs(string storage, int diskGb)
public Builder WithNet(int id, string bridge, string ip)
public Builder WithFeatures(string features)
public Builder WithUnprivileged(bool yes)
public Builder WithOsType(string type)
public Builder WithSshKey(AbsolutePath keyFile)
public Builder WithPassword(string pw)
public LxcCommand Build()
}
Forbidden in Builder: _argv.Add($"--hostname {hostname}"). Always two tokens: _argv.Add("--hostname"); _argv.Add(hostname).
ProvisionPmgLxc
File: src/NETSetup/Stages/5-NETSetupLinux/ProvisionPmgLxc.cs
Namespace: NETSetup.Stages._5_NETSetupLinux
Mirrors ProvisionNixosVm (static class, Run + RunCore, delegate seams).
public sealed class ProvisionPmgLxcResult
{
public bool SkippedExisting { get; init; }
public int ContainerId { get; init; }
}
public static class ProvisionPmgLxc
{
public const string DebianTemplateName = "debian-12-standard_12.7-1_amd64.tar.zst";
public static readonly AbsolutePath LxcTemplateCacheDir =
(AbsolutePath)"/var/lib/vz/template/cache";
public static ProvisionPmgLxcResult Run(
Host proxmoxHost, Machine machine, NETSetupConfig config, ISystemHost sysHost)
public static ProvisionPmgLxcResult RunCore(
Machine machine,
string customerDomain,
Func<bool> lxcExists,
Action<int, string> ensureTemplate,
Action<int, string> createLxc,
Action<int> startLxc,
Func<int, string, string> execInLxc,
ILogger logger)
}
RunCore sequence:
-
ExtractCtid(machine) - mirror
ProvisionNixosVm.ExtractVmIdatProvisionNixosVm.cs:362. Parsemachine.SerialNumber.HostedIdas int. ThrowNETSetupExceptionif not numeric. -
lxcExists() check. If true, log skip and return SkippedExisting=true.
-
ensureTemplate(ctid, LxcTemplateCacheDir / DebianTemplateName). Production: check
(LxcTemplateCacheDir / DebianTemplateName).FileExists(). If missing, runpveam update && pveam download local debian-12-standardviaLinuxCommandBase.Builder("/usr/sbin/pveam"). -
createLxc(ctid, templatePath). Production:
LxcCommand.Builder.Create().WithCreate(ctid, templatePath).WithHostname(machine.MachineName.Value).WithMemory(2048).WithCores(2).WithRootfs(diskStorage, 8).WithNet(0, "vmbr0", "dhcp").WithFeatures("nesting=1").WithUnprivileged(true).WithOsType("debian").Build().ExecuteLocal(TimeSpan.FromMinutes(5)). -
startLxc(ctid). Then poll
pct status <ctid>until "running" (60 s, 5 s sleep). -
execInLxc(ctid, installScript). Script: wait-for-network ping loop, add PMG apt key+repo,
apt-get update,apt-get install -y proxmox-mailgateway, write basic relay config to/etc/pmg/main.cfpointing tomail.<customerDomain>:26. Timeout 10 min. -
Return ProvisionPmgLxcResult with ContainerId.
Error handling: each ICommandResult.IsSuccess checked; throw NETSetupException($"step X failed: {result.StandardError}") on failure.
EnsureCustomerLxcs
File: src/NETSetup/Helpers/EnsureCustomerLxcs.cs
Namespace: NETSetup.Helpers
Direct mirror of EnsureCustomerVms.cs.
public enum LxcProvisionKind { Skip, Pmg }
public sealed class LxcProvisionOutcome
{
public string LxcName { get; init; } = string.Empty;
public LxcProvisionKind Kind { get; init; }
public bool Success { get; init; }
public string? Error { get; init; }
}
public sealed class EnsureCustomerLxcsResult
{
public IReadOnlyList<LxcProvisionOutcome> Outcomes { get; init; } = Array.Empty<LxcProvisionOutcome>();
}
public static class EnsureCustomerLxcs
{
public static LxcProvisionKind Classify(Machine machine)
public static IReadOnlyList<Machine> GetOrderedLxcsForHost(NETSetupConfig config, SerialNumber hostSerial)
public static void Run(ISystemHost host, NETSetupConfig config, IServiceProvider services)
public static EnsureCustomerLxcsResult RunCore(
IReadOnlyList<Machine> orderedLxcs,
Action<Machine> provisionPmg,
ILogger logger)
}
Per-LXC try/catch, accumulate failures, throw aggregate NETSetupException at end (mirror EnsureCustomerVms.cs:225).
EnsurePbsInstalled
Private static method in UpdateMethods.cs, called from EnsureProxmoxPostInstall after EnsureLocalStorageEnabled(host) at line 744.
Signature: private static void EnsurePbsInstalled(ISystemHost host)
Sequence:
-
Idempotency:
dpkg -l proxmox-backup-server | grep '^ii'. If matched, log and return. -
Install:
RunAptWithRetry(host, "apt install proxmox-backup-server", () ⇒ new LinuxCommandBase.Builder("/usr/bin/env").Add("DEBIAN_FRONTEND=noninteractive").Add("apt-get").Add("install").Add("-y").Add("--no-install-recommends").Add("proxmox-backup-server").Build()). -
Enable service:
systemctl enable --now proxmox-backup. Log warning on failure (non-fatal). -
Datastore: check
((AbsolutePath)"/var/lib/proxmox-backup/datastore").DirectoryExists(). If false:proxmox-backup-manager datastore create datastore /var/lib/proxmox-backup/datastore. Throw on failure. -
Log "PBS installed and datastore configured."
MailRelayOptions
File: src/NETSetup/Entities/Mail/MailRelayOptions.cs
Namespace: NETSetup.Entities.Mail
// osisa copyright header
using Nuke.Common.IO;
namespace NETSetup.Entities.Mail;
public sealed record MailRelayOptions
{
public string Host { get; init; } = string.Empty;
public int Port { get; init; } = 587;
public string User { get; init; } = string.Empty;
public AbsolutePath PassFile { get; init; } = (AbsolutePath)"/run/secrets/relay-pass";
}
PassFile is a path to a SOPS-encrypted secret on the mail VM; NETSetup never reads its content.
DnsProviderOptions
File: src/NETSetup/Entities/Dns/DnsProviderOptions.cs
Namespace: NETSetup.Entities.Dns
// osisa copyright header
using System.Collections.Generic;
using Nuke.Common.IO;
namespace NETSetup.Entities.Dns;
public sealed record DnsProviderOptions
{
// Provider name. MUST match a string supported by both lego (security.acme dnsProvider)
// AND services.ddns-updater. Examples: "cloudflare", "duckdns", "porkbun", "namecheap",
// "gandi", "hetzner", "godaddy", "ovh", "dyn", "noip".
public string Provider { get; init; } = string.Empty;
// Env-format file (KEY=value lines) decrypted by SOPS at runtime. Provider-specific
// env-var contract per lego docs. Examples:
// cloudflare: CF_DNS_API_TOKEN=...
// duckdns: DUCKDNS_TOKEN=...
// namecheap: NAMECHEAP_API_USER=... NAMECHEAP_API_KEY=...
public AbsolutePath CredentialsFile { get; init; } =
(AbsolutePath)"/run/secrets/dns-provider-credentials";
// Provider-specific extras not covered by env file (account IDs, zone IDs, etc.).
// Passed verbatim into the ddns-updater settings attrset.
public IReadOnlyDictionary<string, string>? CredentialsAttrs { get; init; }
}
MailHostTemplate (NixMailTemplate)
File: src/NETSetup/Config/Nix/Templates/NixMailTemplate.cs
Namespace: NETSetup.Config.Nix.Templates
public sealed class NixMailTemplate : NixHostTemplate
{
public string CustomerDomain { get; set; } = string.Empty;
public MailRelayOptions? Relay { get; set; }
public string PmgLanIp { get; set; } = string.Empty;
public override string GenerateDefaultNix() { ... }
}
GenerateDefaultNix() emits:
-
All base system options (packages, nix, gc, shell, user, timezone, qemuGuest, network, disk, security, ssh, authorizedKeys, hostname) - copy from NixNextcloudTemplate
-
host.mail.enable = true; -
host.mail.domain = "<CustomerDomain>"; -
host.mail.relay = …(conditional:nullwhen Relay is null, else{ host = …; port = …; user = …; passFile = …; }) -
networking.firewall.allowedTCPPorts = [ 25 26 80 143 443 587 993 ]; -
system.stateVersion = "25.11";
Conditional relay block:
var relayBlock = this.Relay is null
? "null"
: $$"""
{
host = "{{this.Relay.Host}}";
port = {{this.Relay.Port}};
user = "{{this.Relay.User}}";
passFile = "{{this.Relay.PassFile}}";
}
""";
Guard at method entry: if (string.IsNullOrWhiteSpace(this.CustomerDomain)) throw new NETSetupException("NixMailTemplate.CustomerDomain is required.");
DnsHostTemplate (NixDnsTemplate)
File: src/NETSetup/Config/Nix/Templates/NixDnsTemplate.cs
Namespace: NETSetup.Config.Nix.Templates
public sealed class NixDnsTemplate : NixHostTemplate
{
public string CustomerDomain { get; set; } = string.Empty;
public DnsProviderOptions? DnsProvider { get; set; }
public string MailLanIp { get; set; } = string.Empty;
public string PmgLanIp { get; set; } = string.Empty;
public bool AcmeStaging { get; set; } = true;
public string AcmeContactEmail { get; set; } = string.Empty;
public override string GenerateDefaultNix() { ... }
}
GenerateDefaultNix() emits (matching host.dns.* option shape from nixos/modules/services/dns.nix):
-
All base system options
-
host.dns.enable = true; -
host.dns.zone = "<CustomerDomain>"; -
host.dns.records = { mail = "<MailLanIp>"; pmg = "<PmgLanIp>"; mx = "<PmgLanIp>"; };(use empty string when LAN IPs unset; backend-nix coredns module must handle empty) -
host.dns.provider = "<DnsProvider.Provider>"; -
host.dns.credentialsFile = "<DnsProvider.CredentialsFile>"; -
host.dns.credentialsAttrs = { … };from DnsProvider.CredentialsAttrs ({}when null) -
host.dns.acmeStaging = <AcmeStaging>; -
host.dns.acmeContactEmail = "<AcmeContactEmail>"; -
host.dns.publicHostnames = [ "mail.<CustomerDomain>" "pmg.<CustomerDomain>" ]; -
system.stateVersion = "25.11";
Guard: throw NETSetupException if CustomerDomain or DnsProvider null.
Modified Types
NetsetupOS.cs
Add three constants after NixDocker (line 23):
public static readonly OperatingSystem NixMail = OperatingSystem.From("NixMail", OperatingSystemFamily.Linux_GNU);
public static readonly OperatingSystem NixDns = OperatingSystem.From("NixDns", OperatingSystemFamily.Linux_GNU);
public static readonly OperatingSystem PmgLxc = OperatingSystem.From("PmgLxc", OperatingSystemFamily.Linux_GNU);
NixMail/NixDns names start with "Nix" so IsNixBacked returns true. PmgLxc does not, so EnsureCustomerVms.Classify returns Skip; only EnsureCustomerLxcs handles it.
NixTemplateFactory.cs
-
Add two branches at line 56 before
else if (os == OS.NixOS):
else if (os == NetsetupOS.NixMail)
{
template = new NixMailTemplate();
}
else if (os == NetsetupOS.NixDns)
{
template = new NixDnsTemplate();
}
-
Add new public overload:
public static NixHostTemplate ForMachine(Machine machine, NETSetupConfig config)
{
if (machine is null) throw new ArgumentNullException(nameof(machine));
if (config is null) throw new ArgumentNullException(nameof(config));
var template = ForMachine(machine);
var customerDomain = config.CustomerDomain
?? config.DirectoryService?.Name?.Value
?? string.Empty;
if (template is NixMailTemplate mailTemplate)
{
mailTemplate.CustomerDomain = customerDomain;
mailTemplate.Relay = config.MailRelay;
}
else if (template is NixDnsTemplate dnsTemplate)
{
dnsTemplate.CustomerDomain = customerDomain;
dnsTemplate.DnsProvider = config.DnsProvider;
dnsTemplate.AcmeContactEmail = $"admin@{customerDomain}";
}
return template;
}
-
Update
ProvisionNixosVm.EmitConfigBundleat line 274/284 to acceptNETSetupConfig configand pass toForMachine(machine, config). ThreadconfigthroughRun→RunCore→emitConfigBundledelegate.
NETSetupConfig.cs
Add three properties:
public string? CustomerDomain { get; set; }
public MailRelayOptions? MailRelay { get; set; }
public DnsProviderOptions? DnsProvider { get; set; }
Add usings: using NETSetup.Entities.Mail; and using NETSetup.Entities.Dns;
TestCompany.cs
Insert after LibWin11 (line 43):
.AddDevice("mail@lib", DeviceTypes.Server)
.WithSerialNumber("102@lib")
.WithDescription("Mail")
.AddDevice("dns@lib", DeviceTypes.Server)
.WithSerialNumber("103@lib")
.WithDescription("Dns")
.AddDevice("pmg@lib", DeviceTypes.Server)
.WithSerialNumber("200@lib")
.WithDescription("PMG")
Backend agent must also update DeviceTranslator.cs to map "Mail" → NetsetupOS.NixMail, "Dns" → NetsetupOS.NixDns, "PMG" → NetsetupOS.PmgLxc. Search for "NixNextcloud" or "Nextcloud" in DeviceTranslator.cs to find the table.
The TestCompany.Create() builder must also set .WithCustomerDomain("test01.iam.free") and .WithDnsProvider(new DnsProviderOptions { Provider = "cloudflare", CredentialsFile = "/run/secrets/dns-provider-credentials" }) somewhere — if Company builder does not yet support these, set them on the resulting NETSetupConfig after Build() (e.g. in the test fixture wrapper). Backend agent: choose path based on existing builder shape.
EnsureProxmoxPostInstall (UpdateMethods.cs)
Insert at line 744 (after EnsureLocalStorageEnabled(host);):
EnsurePbsInstalled(host);
RunProxmoxUpdate (UpdateMethods.cs)
After EnsureCustomerVms.Run block (lines 416-425), before RunBtrfsScrub at line 427:
host.Logger.LogInformation("Ensuring customer LXCs (PMG)...");
try
{
EnsureCustomerLxcs.Run(host, config, services);
}
catch (Exception ex)
{
host.Logger.LogError(ex, "EnsureCustomerLxcs failed during RunProxmoxUpdate.");
throw;
}
Ordering: PMG relay points to mail VM; mail VM is provisioned by EnsureCustomerVms (NixMail). So LXCs MUST run AFTER VMs.
DI Wiring
No new DI registrations. EnsureCustomerLxcs.Run uses the same IServiceProvider already wired by the CLI host. MailRelayOptions and DnsProviderOptions are POCOs on NETSetupConfig; no injection.
Conventions Checklist
-
❏ Null guards:
if (x is null) throw new ArgumentNullException(nameof(x));(NOT Precondition.NotNull, NOT in codebase). -
❏
is null/is not nulleverywhere. Never== null/!= null. -
❏ Always braces on if/else/for/foreach/while/using.
-
❏ File-scoped namespaces.
-
❏ One class per file. Generic types:
[T]in filename. -
❏ osisa copyright header on every .cs file. Copy from
ProvisionNixosVm.cs:1-5. -
❏ No System.IO: use AbsolutePath / IFileOperatorExtensions.EnsureDirectory / PhysicalFileOperatorWorkaround.
-
❏ No raw ProcessStartInfo. All process via
LinuxCommandBase.BuilderorLxcCommand.Builder. -
❏
LxcCommand.Builder: neverAdd($"--flag {value}"). Always two argv tokens. -
❏ No new
.Result/.Wait()introductions; mirror existing sync-over-async pattern. -
❏
osisa.ValidationNOT added.ArgumentNullExceptiondirectly. -
❏ ASCII only in all new files.
Decisions Made
-
PmgLxcOS constant for LXC routing (not Description string match). -
MailRelayOptions+DnsProviderOptionsinNETSetup.Entities.{Mail,Dns}(not osisa.Enterprise.Entities; cannot extend external package without netbase PR). -
CustomerDomainas nullable override onNETSetupConfig(falls back toDirectoryService.Name.Value). -
ForMachine(Machine, NETSetupConfig)overload required;EmitConfigBundlesignature must addNETSetupConfigparam. -
EnsureCustomerLxcs.RunafterEnsureCustomerVms.Run(mail VM must exist before PMG relay config references it). -
LxcCommand.Builderproduces argv list;ExecuteLocal()usesLinuxCommandBase; noProcessStartInfo. -
SSH-push cert distribution from DNS VM to mail/PMG (NixOS-side concern; no C# code).
-
VMID/CTID assignments: mail=102@lib, dns=103@lib, pmg=200@lib.
-
Precondition.NotNullNOT used; rawArgumentNullException. -
DDNS + ACME provider pluggable per-customer via
DnsProviderOptions. Cloudflare = example, NOT hardcoded.
Open Questions for Backend Agent
-
proxmox-backup-manager datastore createexact CLI arg syntax; confirm on live PBS. -
LXC network bridge name on Liberator (assumed vmbr0; confirm via
pvesh get /nodes/pve/network). -
PMG apt repo URL for Proxmox 9 / trixie if Liberator is trixie-based.
-
coredns module behavior for empty MailLanIp/PmgLanIp; module must handle empty gracefully.
-
PMG relay config file path and syntax (
/etc/pmg/main.cfvspmgconfig set). -
lego vs ddns-updater provider name string parity; verify per provider before shipping.
-
ddns-updater credential transport: env-file vs inline JSON. Backend-Nix flagged: cloudflare-style providers work via env file; others may need SOPS-templated JSON. Out-of-scope for v1 if cloudflare is the test provider.
Build Order for Backend Agent
-
Add
MailRelayOptions+DnsProviderOptionsrecords. -
Add
CustomerDomain,MailRelay,DnsProviderproperties toNETSetupConfig. -
Add
NetsetupOS.NixMail,NixDns,PmgLxcconstants. -
Update
DeviceTranslator.csDescription-to-OS map. -
Add
LxcCommand+LxcCommand.Builder. -
Add
NixMailTemplate.csandNixDnsTemplate.cs. -
Add
ForMachine(Machine, NETSetupConfig)overload + NixMail/NixDns branches. -
Update
ProvisionNixosVm.EmitConfigBundlesignature; thread config through Run/RunCore. -
Add
ProvisionPmgLxc.cs. -
Add
EnsureCustomerLxcs.cs. -
Add
EnsurePbsInstalledprivate method toUpdateMethods.cs. Wire intoEnsureProxmoxPostInstall. -
Wire
EnsureCustomerLxcs.RunintoRunProxmoxUpdate. -
Update
TestCompany.cs: add 3 hosted devices + customer domain + dns provider. -
Run
dotnet build src/NETSetup.slnand fix any build errors.
Notes on Pre-existing NixOS Modules
Backend-Nix already created the following nix files (do not recreate):
-
src/NETSetup/nixos/modules/services/mail.nix— options:host.mail.{enable,domain,tlsCertPath,tlsKeyPath,user,relay,dnsServer,pmgSourceCidr} -
src/NETSetup/nixos/modules/services/dns.nix— options:host.dns.{enable,zone,records,provider,credentialsFile,credentialsAttrs,acmeStaging,acmeContactEmail,publicHostnames} -
src/NETSetup/nixos/hosts/default-mail/default.nix -
src/NETSetup/nixos/hosts/default-dns/default.nix
The C# templates NixMailTemplate.GenerateDefaultNix and NixDnsTemplate.GenerateDefaultNix MUST emit option names that match these existing modules exactly. Verify by reading the actual nix files before writing the C# string interpolation.