{ config, lib, pkgs, ... }: with lib; let cfg = config.services.docker-host-incus; containerName = "docker-host"; in { options.services.docker-host-incus = { enable = mkEnableOption "Incus Docker host container"; image = mkOption { type = types.str; default = "images:ubuntu/24.04"; description = "Incus image to use for the Docker host container"; }; network = mkOption { type = types.str; default = "internalbr0"; description = "Incus bridge network to attach the container to"; }; ip = mkOption { type = types.str; default = "10.0.0.2"; description = "Static IP for the Docker host container"; }; gateway = mkOption { type = types.str; default = "10.0.0.1"; description = "Gateway for the container network"; }; dns = mkOption { type = types.str; default = "1.1.1.1"; description = "DNS server for the container"; }; exposeDockerTcp = mkOption { type = types.bool; default = false; description = "Expose Docker TCP API on port 2375 (internal bridge only)"; }; }; config = mkIf cfg.enable { # Ensure incus + jq are available on the host environment.systemPackages = with pkgs; [ incus-lts jq ]; # ── Systemd oneshot that creates/starts the Docker host container ── systemd.services.incus-docker-host = { description = "Incus Docker Host Container (${containerName})"; after = [ "incus.service" ]; requires = [ "incus.service" ]; wantedBy = [ "multi-user.target" ]; path = with pkgs; [ incus-lts jq ]; script = '' set -euo pipefail create_container() { echo "Creating ${containerName} from ${cfg.image}..." incus init "${cfg.image}" "${containerName}" \ --config security.privileged=true \ --config boot.autostart=true # Attach to the internal bridge incus config device add "${containerName}" eth0 nic \ nictype=bridged \ parent="${cfg.network}" # Inject cloud-init to install Docker incus config set "${containerName}" user.user-data - <<'CLOUD_EOF' #cloud-config package_update: true packages: - docker.io - docker-compose-v2 - curl runcmd: - systemctl enable docker.service - systemctl start docker.service - sleep 2 - docker info || echo "Docker not ready yet, will start on boot" CLOUD_EOF # Set a static lease ${optionalString (cfg.ip != "auto") '' incus config device set "${containerName}" eth0 ipv4.address "${cfg.ip}" ''} incus start "${containerName}" echo "✓ ${containerName} created and started" } # ── Main logic ── if ! incus list --format json 2>/dev/null | jq -e '.[] | select(.name == "${containerName}")' >/dev/null 2>&1; then create_container else STATUS=$(incus list --format json 2>/dev/null | jq -r '.[] | select(.name == "${containerName}") | .status') if [ "$STATUS" != "Running" ]; then echo "${containerName} is $STATUS — starting..." incus start "${containerName}" else echo "${containerName} already running" fi fi ''; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; }; }; }