Initial commit

This commit is contained in:
HotaruBlaze
2026-05-31 09:33:14 +00:00
commit 6bce73c167
12 changed files with 698 additions and 0 deletions

244
Taskfile.yml Normal file
View File

@@ -0,0 +1,244 @@
# ------------------------------------------------------------
# Taskfile Dockerbased Nix development & Colmena deployment
# ------------------------------------------------------------
version: "3"
vars:
# Base directory of the repository pass it when you invoke `task`,
# e.g. `task -v ROOT_DIR=$(pwd) …`. You can also export it in your shell.
ROOT_DIR: "{{ .ROOT_DIR }}"
# Convenience paths (expanded by `task` before being passed to Docker)
FLAKE_DIR: "{{ .ROOT_DIR }}"
AGE_KEY_PATH: "{{ .ROOT_DIR }}/age.key"
SECRETS_YAML_PATH: "{{ .ROOT_DIR }}/hosts/blade/secrets.yaml"
# -----------------------------------------------------------------
# Docker helpers (the Nix container always gets the repo mounted at
# /workspace). `DOCKER_NIX_CMD` is used for *any* command that runs
# inside the container.
# -----------------------------------------------------------------
DOCKER_COMPOSE_CMD: "docker compose -f {{ .ROOT_DIR }}/docker-compose.yml"
DOCKER_NIX_CMD: >-
{{ .DOCKER_COMPOSE_CMD }} run --rm
-v "{{ .ROOT_DIR }}:/workspace"
nixos
# -----------------------------------------------------------------
# Docker management
# -----------------------------------------------------------------
tasks:
docker-build:
desc: Build the Docker container for Nix
cmds:
- "{{ .DOCKER_COMPOSE_CMD }} build"
dir: "{{ .ROOT_DIR }}"
# ---------------------------------------------------------------
# NOTE: we now use DOCKER_NIX_CMD (which already has the bindmount)
# ---------------------------------------------------------------
docker-shell:
desc: Enter a shell in the Docker container (repo is mounted)
cmds:
- "{{ .DOCKER_NIX_CMD }} bash"
dir: "{{ .ROOT_DIR }}"
docker-up:
desc: Start the Docker container (detached)
cmds:
- "{{ .DOCKER_COMPOSE_CMD }} up -d"
dir: "{{ .ROOT_DIR }}"
docker-down:
desc: Stop the Docker container
cmds:
- "{{ .DOCKER_COMPOSE_CMD }} down"
dir: "{{ .ROOT_DIR }}"
# -----------------------------------------------------------------
# Agekey handling (SOPS)
# -----------------------------------------------------------------
generate-age-key:
desc: Generate a new age key for a host (default - all hosts without a key)
vars:
# If HOST is supplied we generate only for that host; otherwise we
# iterate over every directory under `hosts/` that lacks an age.key.
TARGET_HOSTS: |
{{- if .HOST -}}
{{ .HOST }}
{{- else -}}
{{- range $dir := (files (printf "%s/hosts/*" .ROOT_DIR)) -}}
{{- $name := base $dir -}}
{{- if not (fileExists (printf "%s/age.key" $dir)) -}}
{{ $name }}
{{- end -}}
{{- end -}}
{{- end }}
cmds:
- |
{{- $list := splitList "\n" .TARGET_HOSTS -}}
{{- range $h := $list }}
echo "🔑 Generating age.key for host '{{ $h }}' ..."
{{ .DOCKER_NIX_CMD }} -v "{{ .ROOT_DIR }}/hosts/{{ $h }}:/workspace/hosts/{{ $h }}" \
nixos age-keygen -o /workspace/hosts/{{ $h }}/age.key
{{- end }}
dir: "{{ .ROOT_DIR }}"
# -----------------------------------------------------------------
# Encrypt / Decrypt (loop over hosts)
# -----------------------------------------------------------------
encrypt-secrets:
desc: |
Encrypt `secrets.yaml` for every host that has an age.key.
Pass HOST=<name> to limit to a single host.
vars:
# Spaceseparated list of hosts that have both age.key & secrets.yaml
TARGET_HOSTS: |
{{- if .HOST -}}
{{ .HOST }}
{{- else -}}
{{- $list := slice -}}
{{- range $dir := (files (printf "%s/hosts/*" .ROOT_DIR)) -}}
{{- $name := base $dir -}}
{{- $age := printf "%s/age.key" $dir -}}
{{- $sec := printf "%s/secrets.yaml" $dir -}}
{{- if (and (fileExists $age) (fileExists $sec)) -}}
{{- $list = append $list $name -}}
{{- end -}}
{{- end -}}
{{- join " " $list -}}
{{- end }}
preconditions:
- sh: test -n "{{ .TARGET_HOSTS }}"
msg: "No hosts with both age.key and secrets.yaml were found."
cmds:
- |
for host in {{ .TARGET_HOSTS }}; do
echo "🔐 Encrypting secrets for host '$host' ..."
{{ .DOCKER_NIX_CMD }} -v "{{ .ROOT_DIR }}/hosts/$host:/workspace/hosts/$host" \
nixos sh -c "\
pub=\$(grep 'public key' /workspace/hosts/$host/age.key | awk '{print \$NF}') && \
sops --encrypt --age=\$pub --in-place /workspace/hosts/$host/secrets.yaml"
done
dir: "{{ .ROOT_DIR }}"
decrypt-secrets:
desc: |
Decrypt `secrets.yaml` for every host that has an age.key.
Decrypted output is written to `secrets.yaml.decrypted`.
Pass HOST=<name> to limit to a single host.
vars:
TARGET_HOSTS: |
{{- if .HOST -}}
{{ .HOST }}
{{- else -}}
{{- $list := slice -}}
{{- range $dir := (files (printf "%s/hosts/*" .ROOT_DIR)) -}}
{{- $name := base $dir -}}
{{- $age := printf "%s/age.key" $dir -}}
{{- $sec := printf "%s/secrets.yaml" $dir -}}
{{- if (and (fileExists $age) (fileExists $sec)) -}}
{{- $list = append $list $name -}}
{{- end -}}
{{- end -}}
{{- join " " $list -}}
{{- end }}
preconditions:
- sh: test -n "{{ .TARGET_HOSTS }}"
msg: "No hosts with both age.key and secrets.yaml were found."
cmds:
- |
for host in {{ .TARGET_HOSTS }}; do
echo "🔓 Decrypting secrets for host '$host' ..."
{{ .DOCKER_NIX_CMD }} -v "{{ .ROOT_DIR }}/hosts/$host:/workspace/hosts/$host" \
nixos sh -c "\
pub=\$(grep '# public key' /workspace/hosts/$host/age.key | awk '{print \$NF}') && \
sops --decrypt --age=\$pub /workspace/hosts/$host/secrets.yaml \
> /workspace/hosts/$host/secrets.yaml.decrypted"
done
dir: "{{ .ROOT_DIR }}"
# -----------------------------------------------------------------
# Nix flake & build
# -----------------------------------------------------------------
flake-check:
desc: Run `nix flake check` on the repository
cmds:
- "{{ .DOCKER_NIX_CMD }} nix flake check --flake /workspace"
dir: "{{ .ROOT_DIR }}"
build-blade:
desc: Build the NixOS configuration for the *blade* host
cmds:
- "{{ .DOCKER_NIX_CMD }} nix build /workspace#nixosConfigurations.blade"
dir: "{{ .ROOT_DIR }}"
# -----------------------------------------------------------------
# Colmena deployment
# -----------------------------------------------------------------
deploy:
desc: Deploy to an arbitrary node (usage - task deploy NODE=blade)
cmds:
- "{{ .DOCKER_NIX_CMD }} colmena apply --on {{ .NODE }}"
dir: "{{ .ROOT_DIR }}"
preconditions:
- sh: test -n "{{ .NODE }}"
msg: "NODE variable is required. Example: task deploy NODE=blade"
- sh: test -f "{{ .ROOT_DIR }}/age.key"
- sh: test -f "{{ .ROOT_DIR }}/hosts/{{ .NODE }}/secrets.yaml"
deploy-blade:
desc: Deploy the configuration to the *blade* host
cmds:
- "{{ .DOCKER_NIX_CMD }} colmena apply --on blade"
dir: "{{ .ROOT_DIR }}"
preconditions:
- sh: test -f "{{ .ROOT_DIR }}/age.key"
- sh: test -f "{{ .ROOT_DIR }}/hosts/blade/secrets.yaml"
# -----------------------------------------------------------------
# Helper: add a new node
# -----------------------------------------------------------------
add-node:
desc: Create a new host directory with skeleton Nix files
prompt: "Enter the name of the new node (e.g., node2):"
cmds:
- mkdir -p "{{ .FLAKE_DIR }}/hosts/{{ .CLI_ARGS }}"
- |
cat > "{{ .FLAKE_DIR }}/hosts/{{ .CLI_ARGS }}/configuration.nix" <<'EOF'
{ config, lib, pkgs, ... }:
{
imports = [
./hardware-configuration.nix
../../common.nix
];
networking.hostName = "{{ .CLI_ARGS }}";
}
EOF
- |
cat > "{{ .FLAKE_DIR }}/hosts/{{ .CLI_ARGS }}/hardware-configuration.nix" <<'EOF'
# Add hardwarespecific configurations here
{ config, lib, pkgs, modulesPath, ... }:
{
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
}
EOF
- |
cat > "{{ .FLAKE_DIR }}/hosts/{{ .CLI_ARGS }}/secrets.yaml" <<'EOF'
# Add nodespecific secrets here
EOF
- |
cat > "{{ .FLAKE_DIR }}/hosts/{{ .CLI_ARGS }}/default.nix" <<'EOF'
{ ... }: { imports = [ ./configuration.nix ./hardware-configuration.nix ../../common.nix ]; }
EOF
dir: "{{ .ROOT_DIR }}"
# -----------------------------------------------------------------
# Housekeeping
# -----------------------------------------------------------------
clean:
desc: Remove temporary/decrypted secret files for all hosts
cmds:
- |
find "{{ .ROOT_DIR }}/hosts" -name 'secrets.yaml.decrypted' -delete
silent: true