Big update. Folder reorganization. Disk selection logic finished. Improved services selection (not done yet).

This commit is contained in:
Raphael Numbus
2025-12-14 13:58:01 +01:00
parent 0e0ed4d3a3
commit f777e608b8
32 changed files with 435 additions and 261 deletions
-1
View File
@@ -1 +0,0 @@
deploy.conf
+1 -2
View File
@@ -1,6 +1,5 @@
agents/ agents/
ai-production/ ai-production/
extra-files/ extra-files/
test.sh test*
test2.sh
deploy.conf deploy.conf
@@ -1,7 +1,8 @@
{ config, lib, ... }: { config, lib, ... }:
# --> SnapRAID disks research
let let
### --> SnapRAID disks research
contentDiskMounts = lib.attrsets.attrNames ( contentDiskMounts = lib.attrsets.attrNames (
lib.attrsets.filterAttrs (name: value: lib.strings.hasPrefix "/mnt/content-" name) config.fileSystems lib.attrsets.filterAttrs (name: value: lib.strings.hasPrefix "/mnt/content-" name) config.fileSystems
); );
@@ -12,12 +13,17 @@ let
(acc: path: acc // { "d${toString (acc.i + 1)}" = path; i = acc.i + 1; }) (acc: path: acc // { "d${toString (acc.i + 1)}" = path; i = acc.i + 1; })
{ i = 0; } { i = 0; }
contentDiskMounts; contentDiskMounts;
### SnapRAID disks research <--
### --> Spindown disks
hardDrives = [ ${DISK_ID_LIST[@]} ];
### Spindown disks <--
in in
# SnapRAID disks research <--
# --> MergerFS setup
### --> MergerFS setup
{ {
fileSystems."/mnt/data-storage" = { fileSystems."/mnt/data-storage" = {
device = "mergerfs"; device = "mergerfs";
@@ -32,19 +38,35 @@ in
"srcmounts=${lib.strings.concatStringsSep ":" contentDiskMounts}" "srcmounts=${lib.strings.concatStringsSep ":" contentDiskMounts}"
]; ];
}; };
# MergerFS setup <-- ### MergerFS setup <--
# --> SnapRAID setup ### --> SnapRAID setup
services.snapraid = { services.snapraid = {
enable = true; enable = true;
contentFiles = map (disk: "${disk}/snapraid.content") contentDiskMounts; contentFiles = map (disk: "${disk}/snapraid.content") contentDiskMounts;
parityFiles = map (disk: "${disk}/snapraid.parity") parityDiskMounts; parityFiles = map (disk: "${disk}/snapraid.parity") parityDiskMounts;
dataDisks = builtins.removeAttrs snapraidDataDisks [ "i" ]; dataDisks = builtins.removeAttrs snapraidDataDisks [ "i" ];
}; };
# SnapRAID setup <-- ### SnapRAID setup <--
### --> Disk spindown
systemd.services.hd-idle = {
description = "External HD spin down daemon";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
ExecStart =
let
idleTime = toString 1800;
hardDriveParameter = lib.strings.concatMapStringsSep " " (x: "-a ${x} -i ${idleTime}") hardDrives;
in
"${pkgs.hd-idle}/bin/hd-idle -i 0 ${hardDriveParameter}";
};
};
### Disk spindown <--
} }
+92
View File
@@ -0,0 +1,92 @@
{ config, pkgs, ... }:
let
container_name = "gitea";
compose-dir = "docker-compose/gitea";
config-dir = "/mnt/config-storage/docker-data/gitea";
in
{
config = {
environment.etc."${compose-dir}/compose.yaml".text =
/*DB_NAM
yaml
*/
''
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
networks:
gitea_frontend:
gitea_backend:
volumes:
- ${config_dir}/data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=$POSTGRES_HOST:$POSTGRES_PORT
- GITEA__database__NAME=$DB_NAME
- GITEA__database__USER=$DB_USERNAME
- GITEA__database__PASSWD=$DB_PASSWORD
- GITEA__server__SSH_PORT=2424
- GITEA__server__ROOT_URL=gitea.$DOMAIN_NAME
labels:
- traefik.enable=true
- traefik.http.services.gitea.loadbalancer.server.port=3000
- traefik.http.services.gitea.loadbalancer.server.scheme=http
- traefik.http.routers.gitea-https.entrypoints=websecure
- traefik.http.routers.gitea-https.rule=Host(`gitea.$DOMAIN_NAME`)
- traefik.http.routers.gitea-https.tls=true
- traefik.http.routers.gitea-https.tls.certresolver=cloudflare
depends_on:
- gitea-database
restart: unless-stopped
gitea-database:
image: docker.io/library/postgres:17.5
container_name: gitea-database
environment:
- POSTGRES_USER=$DB_USERNAME
- POSTGRES_PASSWORD=$DB_PASSWORD
- POSTGRES_DB=$DB_NAME
networks:
gitea_backend:
volumes:
- gitea-database:/var/lib/postgresql/data
restart: unless-stopped
volumes:
gitea-database:
networks:
gitea_frontend:
external: true
gitea_backend:
external: true
'';
systemd.services.gitea = {
description = "Docker container : ${container_name}";
after = [ "network.target" "docker.service" "docker.socket" "traefik.service" ];
requires = [ "docker.service" ];
wantedBy = ["multi-user.target"];
path = [ pkgs.docker ];
serviceConfig = {
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml pull";
# Bring the service up
ExecStart = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml down";
Restart = "on-failure";
};
};
};
}
+55
View File
@@ -0,0 +1,55 @@
{ config, pkgs, ... }:
let
container_name = "it-tools";
compose-dir = "docker-compose/it-tools";
in
{
config = {
environment.etc."${compose-dir}/compose.yaml".text =
/*
yaml
*/
''
services:
it-tools:
container_name: it-tools
image: corentinth/it-tools
networks:
it-tools:
labels:
- traefik.enable=true
- traefik.http.services.it-tools.loadbalancer.server.port=80
- traefik.http.services.it-tools.loadbalancer.server.scheme=http
- traefik.http.routers.it-tools-https.entrypoints=websecure
- traefik.http.routers.it-tools-https.rule=Host(`it-tools.$DOMAIN_NAME`)
- traefik.http.routers.it-tools-https.tls=true
- traefik.http.routers.it-tools-https.tls.certresolver=cloudflare
restart: unless-stopped
networks:
it-tools:
external: true
'';
systemd.services.it-tools = {
description = "Docker container : ${container_name}";
after = [ "network.target" "docker.service" "docker.socket" "traefik.service" ];
requires = [ "docker.service" ];
wantedBy = ["multi-user.target"];
path = [ pkgs.docker ];
serviceConfig = {
Type = "exec";
# Pull the latest image before running
ExecStartPre = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml pull";
# Bring the service up
ExecStart = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml down";
Restart = "on-failure";
};
};
};
}
+1 -1
View File
@@ -3,7 +3,7 @@ TARGET_HOST="192.168.1.10"
SSH_PUBLIC_KEY="ssh-ed25519 AAAAoefzefpoipoeCEZJCPEACPAcjapjcpajepcjAPJECJPEJAPJAZ yours@yourdomain.com" SSH_PUBLIC_KEY="ssh-ed25519 AAAAoefzefpoipoeCEZJCPEACPAcjapjcpajepcjAPJECJPEJAPJAZ yours@yourdomain.com"
# TRAEFIK SETTINGS # TRAEFIK SETTINGS
DOMAIN_NAME="yourdomain.com" DOMAIN_NAME="yourdomain.com"
EMAIL_ADDRESS="no-reply@yourdomain.com" EMAIL_ADDRESS="your-mail@yourdomain.com"
CF_DNS_API_TOKEN="yourToken" CF_DNS_API_TOKEN="yourToken"
#SMTP SETTINGS #SMTP SETTINGS
SENDER_EMAIL_ADDRESS="youraddress@gmail.com" SENDER_EMAIL_ADDRESS="youraddress@gmail.com"
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p gum openssl sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto
+12 -4
View File
@@ -1,4 +1,5 @@
ssh_public_keys: $SSH_PUBLIC_KEY ssh_public_keys: $SSH_PUBLIC_KEY
sender_email_address_password: $SENDER_EMAIL_ADDRESS_PASSWORD
docker: docker:
nextcloud: | nextcloud: |
@@ -18,9 +19,9 @@ docker:
passbolt: | passbolt: |
DOMAIN_NAME=$DOMAIN_NAME DOMAIN_NAME=$DOMAIN_NAME
TZ=Europe/Paris TZ=Europe/Paris
PASSBOLT_MYSQL_DATABASE=$PASSBOLT_MYSQL_DATABASE PASSBOLT_MYSQL_DATABASE=$PASSBOLT_DB_NAME
PASSBOLT_MYSQL_USER=$PASSBOLT_MYSQL_USER PASSBOLT_MYSQL_USER=$PASSBOLT_DB_USERNAME
PASSBOLT_MYSQL_PASSWORD=$PASSBOLT_MYSQL_PASSWORD PASSBOLT_MYSQL_PASSWORD=$PASSBOLT_DB_PASSWORD
SENDER_EMAIL_ADDRESS=$SENDER_EMAIL_ADDRESS SENDER_EMAIL_ADDRESS=$SENDER_EMAIL_ADDRESS
SENDER_EMAIL_ADDRESS_PASSWORD=$SENDER_EMAIL_ADDRESS_PASSWORD SENDER_EMAIL_ADDRESS_PASSWORD=$SENDER_EMAIL_ADDRESS_PASSWORD
SENDER_EMAIL_DOMAIN=$SENDER_EMAIL_DOMAIN SENDER_EMAIL_DOMAIN=$SENDER_EMAIL_DOMAIN
@@ -41,10 +42,17 @@ docker:
IMMICH_TRUSTED_PROXIES=172.16.50.253 IMMICH_TRUSTED_PROXIES=172.16.50.253
REDIS_HOSTNAME=immich-redis REDIS_HOSTNAME=immich-redis
DB_HOSTNAME=immich-database DB_HOSTNAME=immich-database
DB_DATABASE_NAME=$IMMICH_DB_DATABASE_NAME DB_DATABASE_NAME=$IMMICH_DB_NAME
DB_USERNAME=$IMMICH_DB_USERNAME DB_USERNAME=$IMMICH_DB_USERNAME
DB_PASSWORD=$IMMICH_DB_PASSWORD DB_PASSWORD=$IMMICH_DB_PASSWORD
DB_DATA_LOCATION=/mnt/config-storage/docker-data/immich/database DB_DATA_LOCATION=/mnt/config-storage/docker-data/immich/database
gitea: |
DOMAIN_NAME=$DOMAIN_NAME
POSTGRES_HOST=gitea-database
POSTGRES_PORT=5432
DB_NAME=$GITEA_DB_NAME
DB_USERNAME=$GITEA_DB_USERNAME
DB_PASSWORD=$GITEA_DB_PASSWORD
disks: disks:
content-disk-1: $CONTENT_DISK_1_KEY content-disk-1: $CONTENT_DISK_1_KEY
+144 -153
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
#!nix-shell -i bash -p gum openssl sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto #!nix-shell -i bash -p gum xkcdpass openssl sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto
necessary_credentials() { necessary_credentials() {
#TARGET SETTINGS #TARGET SETTINGS
@@ -127,148 +127,126 @@ hardware_detection() {
services_selection() { services_selection() {
echo -e "\n\n ➡️ You will now select the services you want installed on your server:" echo -e "\n\n ➡️ You will now select the services you want installed on your server:"
declare -A SERVICE_MAP AVAILABLE_SERVICES=( "frigate" "gitea" "home-assistant" "immich" "it-tools" \
SERVICE_MAP["Pi-Hole: Block ads on all your devices"]="pihole" "nextcloud" "passbolt" "pi-hole" )
SERVICE_MAP["Home Assistant: Manage your smart home or security cameras"]="hass" AVAILABLE_SERVICES_NUMBER=${#AVAILABLE_SERVICES[@]}
SERVICE_MAP["Passbolt: Secure password manager with collaboration features"]="passbolt"
SERVICE_MAP["Frigate [Home Assistant required]: Secure your house with security cameras"]="frigate"
SERVICE_MAP["Nextcloud: No fuss Office 365 replacement"]="nextcloud"
SERVICE_MAP["Immich: Pictures and videos backup with local machine-learning"]="immich"
mapfile -t SERVICE_DESCRIPTIONS < <(for key in "${!SERVICE_MAP[@]}"; do echo "$key"; done | sort) SERVICES_DESCRIPTION=( "Pi-Hole : Block ads on all your devices" \
"Immich : Pictures and videos backup with local machine-learning" \
"Nextcloud : No fuss Office 365 replacement" \
"Passbolt: Security-first password manager with collaboration features" \
"Home-Assistant : Manage your smart home and security cameras" \
"Frigate [Home Assistant required] : Secure your house with security cameras" \
"Gitea : Your own git platform" \
"IT-tools : A set of useful tools when doing IT" \
)
SELECTED_DESCRIPTIONS_STRING=$(gum choose --no-limit --header "Homelab services:" "${SERVICE_DESCRIPTIONS[@]}") SELECTED_SERVICES_DESCRIPTION=$(gum choose --no-limit --header "Homelab services:" "${SERVICES_DESCRIPTION[@]}")
SERVICES=() for i in $(seq 0 $((${#AVAILABLE_SERVICES[@]} - 1))); do
if [[ -n "$SELECTED_DESCRIPTIONS_STRING" ]]; then if printf '%s' "$SELECTED_SERVICES_DESCRIPTION" | grep -iq "${AVAILABLE_SERVICES[$i]}"; then
while IFS= read -r line; do SELECTED_SERVICES+=(${AVAILABLE_SERVICES[$i]})
SERVICES+=("${SERVICE_MAP[$line]}") fi
done <<< "$SELECTED_DESCRIPTIONS_STRING" done
fi
} }
files_generation() { files_generation() {
echo -e "\n\nGenerating necessary folder tree..." echo -e "\n ✅ Writing configuration files for the selected homelab services..."
# Traefik
mkdir -p extra-files/mnt/config-storage/traefik/config/conf/
cp ./config-files/docker/compose/traefik.nix ./config-files/docker/compose/traefik.nix
envsubst < config-files/docker/config/traefik/traefik.yaml > extra-files/mnt/config-storage/traefik/config/traefik.yaml
for service in "${SELECTED_SERVICES[@]}"; do
# Frigate
if [[ "$service" == "frigate" ]]; then
echo -e "\n ✅ Adapting the docker configuration to your hardware..."
FRIGATE_DEVICES_BLOCK=""
if [[ "$TARGET_GRAPHICS_RENDERER" == "true" ]]; then
FRIGATE_DEVICES_BLOCK+=" - /dev/dri:/dev/dri\n"
if [[ "$TARGET_USB_CORAL" == "true" ]]; then
FRIGATE_DEVICES_BLOCK+=" - /dev/bus/usb:/dev/bus/usb\n"
if [[ -n "$FRIGATE_DEVICES_BLOCK" ]]; then
REPLACEMENT="devices:\n${FRIGATE_DEVICES_BLOCK%\\n}"
sed -i.bak "s|# --- frigate devices --- #|$REPLACEMENT|" ./config-files/docker/compose/frigate.nix
else
sed -i.bak "/# --- frigate devices --- #/d" ./config-files/docker/compose/frigate.nix
fi
# Home-Assistant
elif [[ "$service" == "home-assistant" ]]; then
if [[ -n "$TARGET_ZIGBEE_DEVICE" ]]; then
REPLACEMENT="devices:\n - /dev/serial/by-id/${TARGET_ZIGBEE_DEVICE}:/dev/ttyUSB0"
sed -i.bak "s|# --- hass devices --- #|$REPLACEMENT|" ./config-files/docker/compose/hass.nix
else
sed -i.bak "/# --- hass devices --- #/d" ./config-files/docker/compose/hass.nix
fi
export HOME_ASSISTANT_MQTT_USER="$(xkcdpass -d "-" -n 2)"
export HOME_ASSISTANT_MQTT_PASSWORD="$(xkcdpass -d "-")"
mkdir -p extra-files/mnt/config-storage/hass/mqtt/config/
mkdir -p extra-files/mnt/config-storage/hass/mqtt/data/
envsubst < config-files/docker/config/hass/mosquitto.conf > extra-files/mnt/config-storage/hass/mqtt/config/mosquitto.conf
touch extra-files/mnt/config-storage/hass/mqtt/config/password.txt
chmod 0700 extra-files/mnt/config-storage/hass/mqtt/config/password.txt
mosquitto_passwd -b extra-files/mnt/config-storage/hass/mqtt/config/password.txt $HOME_ASSISTANT_MQTT_USER $HOME_ASSISTANT_MQTT_PASSWORD
# Passbolt
elif [[ "$service" == "passbolt" ]]; then
export PASSBOLT_DB_NAME="$(xkcdpass -d "-" -n 2)"
export PASSBOLT_DB_USERNAME="$(xkcdpass -d "-" -n 2)"
export PASSBOLT_DB_PASSWORD="$(xkcdpass -d "-")"
envsubst < config-files/docker/config/traefik/headers.yaml > extra-files/mnt/config-storage/traefik/config/conf/headers.yaml
envsubst < config-files/docker/config/traefik/tls.yaml > extra-files/mnt/config-storage/traefik/config/conf/tls.yaml
# Pi-Hole
elif [[ "$service" == "pi-hole" ]]; then
export FTLCONF_WEBSERVER_PASSWORD="$(xkcdpass -d "-")"
# Immich
elif [[ "$service" == "immich" ]]; then
IMMICH_DEVICES_BLOCK=""
if [[ "$TARGET_GRAPHICS_RENDERER" == "true" ]]; then
IMMICH_DEVICES_BLOCK+=" - /dev/dri:/dev/dri\n"
if [[ -n "$IMMICH_DEVICES_BLOCK" ]]; then
REPLACEMENT="devices:\n${IMMICH_DEVICES_BLOCK%\\n}"
sed -i.bak "s|# --- immich devices --- #|$REPLACEMENT|" ./config-files/docker/compose/immich.nix
else
sed -i.bak "/# --- immich devices --- #/d" ./config-files/docker/compose/immich.nix
fi
export IMMICH_DB_NAME="$(xkcdpass -d "-" -n 2)"
export IMMICH_DB_USERNAME="$(xkcdpass -d "-" -n 2)"
export IMMICH_DB_PASSWORD="$(xkcdpass -d "-")"
mkdir -p extra-files/mnt/data-storage/immich/
elif [[ "$service" == "gitea" ]]; then
export GITEA_DB_NAME="$(xkcdpass -d "-" -n 2)"
export GITEA_DB_USERNAME="$(xkcdpass -d "-" -n 2)"
export GITEA_DB_PASSWORD="$(xkcdpass -d "-")"
elif [[ "$service" == "nextcloud" ]]; then
envsubst < config-files/docker/config/traefik/nextcloud.yaml > extra-files/mnt/config-storage/traefik/config/conf/nextcloud.yaml
mkdir -p extra-files/mnt/data-storage/nextcloud/
fi
cp ./config-files/docker/compose/${service}.nix ./config-files/docker/compose/${service}.nix
done
echo -e "\n ✅ Generating sops-nix keys..."
mkdir -p extra-files/etc/secrets/disks/ mkdir -p extra-files/etc/secrets/disks/
mkdir -p extra-files/var/lib/sops-nix/ mkdir -p extra-files/var/lib/sops-nix/
mkdir -p extra-files/etc/nixos/secrets/ mkdir -p extra-files/etc/nixos/secrets/
mkdir -p extra-files/mnt/config-storage/traefik/config/conf/
mkdir -p extra-files/mnt/config-storage/hass/mqtt/config/
mkdir -p extra-files/mnt/config-storage/hass/mqtt/data/
mkdir -p extra-files/mnt/data-storage/nextcloud/
mkdir -p extra-files/mnt/data-storage/immich/
echo -e "\n ✅ Generating sops-nix keys..."
ssh-to-age -private-key -i extra-files/home/numbus-admin/.ssh/id_ed25519 > extra-files/var/lib/sops-nix/key.txt ssh-to-age -private-key -i extra-files/home/numbus-admin/.ssh/id_ed25519 > extra-files/var/lib/sops-nix/key.txt
export SOPS_PUBLIC_KEY=$(age-keygen -y extra-files/var/lib/sops-nix/key.txt) export SOPS_PUBLIC_KEY=$(age-keygen -y extra-files/var/lib/sops-nix/key.txt)
echo -e "\n ✅ Generating sops-nix configuration files..." echo -e "\n ✅ Generating sops-nix configuration files..."
envsubst < config-files/sops-nix/.sops.yaml > extra-files/etc/nixos/.sops.yaml envsubst < config-files/sops-nix/.sops.yaml > extra-files/etc/nixos/.sops.yaml
echo -e "\n ✅ Generating secure random database passwords..."
export HOME_ASSISTANT_MQTT_USER="$(openssl rand -hex 10)"
export HOME_ASSISTANT_MQTT_PASSWORD="$(openssl rand -base64 32 | tr -d '\=+/')"
export PASSBOLT_MYSQL_DATABASE="$(openssl rand -hex 10)"
export PASSBOLT_MYSQL_USER="$(openssl rand -hex 10)"
export PASSBOLT_MYSQL_PASSWORD="$(openssl rand -base64 32 | tr -d '\=+/')"
export FTLCONF_WEBSERVER_PASSWORD="$(openssl rand -base64 32 | tr -d '\=+/')"
export IMMICH_DB_DATABASE_NAME="$(openssl rand -hex 10)"
export IMMICH_DB_USERNAME="$(openssl rand -hex 10)"
export IMMICH_DB_PASSWORD="$(openssl rand -base64 32 | tr -d '\=+/')"
export CONTENT_DISK_1_KEY="$(openssl rand -base64 10 | tr -d '\=+/')"
export CONTENT_DISK_2_KEY="$(openssl rand -base64 10 | tr -d '\=+/')"
export CONTENT_DISK_3_KEY="$(openssl rand -base64 10 | tr -d '\=+/')"
export CONTENT_DISK_4_KEY="$(openssl rand -base64 10 | tr -d '\=+/')"
export CONTENT_DISK_5_KEY="$(openssl rand -base64 10 | tr -d '\=+/')"
export CONTENT_DISK_6_KEY="$(openssl rand -base64 10 | tr -d '\=+/')"
export PARITY_DISK_1_KEY="$(openssl rand -base64 10 | tr -d '\=+/ ')"
export PARITY_DISK_2_KEY="$(openssl rand -base64 10 | tr -d '\=+/ ')"
export PARITY_DISK_3_KEY="$(openssl rand -base64 10 | tr -d '\=+/ ')"
export BOOT_DISK_1_KEY="$(openssl rand -base64 10 | tr -d '\=+/ ')"
export BOOT_DISK_2_KEY="$(openssl rand -base64 10 | tr -d '\=+/ ')"
echo -e "\n ✅ Generating disk keyfiles in /etc/secrets/disks/..."
for i in {1..6}; do var="CONTENT_DISK_${i}_KEY"; [[ -n "${!var}" ]] && echo -n "${!var}" > "extra-files/etc/secrets/disks/content-disk-$i"; done
for i in {1..3}; do var="PARITY_DISK_${i}_KEY"; [[ -n "${!var}" ]] && echo -n "${!var}" > "extra-files/etc/secrets/disks/parity-disk-$i"; done
for i in {1..2}; do var="BOOT_DISK_${i}_KEY"; [[ -n "${!var}" ]] && echo -n "${!var}" > "extra-files/etc/secrets/disks/boot-disk-$i"; done
echo "$REMOTE_PASS" | ssh_to_host """
sudo -S mkdir -p /etc/secrets/disks/
echo -n $CONTENT_DISK_1_KEY | sudo -S tee /etc/secrets/disks/content-disk-1 > /dev/null
echo -n $CONTENT_DISK_2_KEY | sudo -S tee /etc/secrets/disks/content-disk-2 > /dev/null
echo -n $CONTENT_DISK_3_KEY | sudo -S tee /etc/secrets/disks/content-disk-3 > /dev/null
echo -n $CONTENT_DISK_4_KEY | sudo -S tee /etc/secrets/disks/content-disk-4 > /dev/null
echo -n $CONTENT_DISK_5_KEY | sudo -S tee /etc/secrets/disks/content-disk-5 > /dev/null
echo -n $CONTENT_DISK_6_KEY | sudo -S tee /etc/secrets/disks/content-disk-6 > /dev/null
echo -n $PARITY_DISK_1_KEY | sudo -S tee /etc/secrets/disks/parity-disk-1 > /dev/null
echo -n $PARITY_DISK_2_KEY | sudo -S tee /etc/secrets/disks/parity-disk-2 > /dev/null
echo -n $PARITY_DISK_3_KEY | sudo -S tee /etc/secrets/disks/parity-disk-3 > /dev/null
echo -n $BOOT_DISK_1_KEY | sudo -S tee /etc/secrets/disks/boot-disk-1 > /dev/null
echo -n $BOOT_DISK_2_KEY | sudo -S tee /etc/secrets/disks/boot-disk-2 > /dev/null
"""
echo -e "\n ✅ Encrypting secrets in the correct file..." echo -e "\n ✅ Encrypting secrets in the correct file..."
envsubst < "config-files/sops-nix/secrets.yaml" | sops encrypt --filename-override secrets.yaml \ envsubst < "config-files/sops-nix/secrets.yaml" | sops encrypt --filename-override secrets.yaml \
--input-type yaml --output-type yaml \ --input-type yaml --output-type yaml \
--age $SOPS_PUBLIC_KEY \ --age $SOPS_PUBLIC_KEY \
--output extra-files/etc/nixos/secrets/secrets.yaml --output extra-files/etc/nixos/secrets/secrets.yaml
cp -avu extra-files/etc/nixos/secrets/secrets.yaml ./nix-config/secrets/secrets.yaml
echo -e "\n ✅ Writing correct ips to configuration.nix..." echo -e "\n ✅ Writing correct ips to configuration.nix..."
sed -i s+HOME_SERVER_IP+$HOME_SERVER_IP+g ./nix-config/configuration.nix sed -i s+HOME_SERVER_IP+$HOME_SERVER_IP+g ./nix-config/misc/networking.nix
sed -i s+HOME_ROUTER_IP+$HOME_ROUTER_IP+g ./nix-config/configuration.nix sed -i s+HOME_ROUTER_IP+$HOME_ROUTER_IP+g ./nix-config/misc/networking.nix
echo -e "\n ✅ Adapting the docker configuration to your hardware..." echo -e "\n ✅ Copying the configuration to the new machine..."
FRIGATE_DEVICES_BLOCK="" cp -ravu ./nix-config/* extra-files/etc/nixos/
if [[ "$TARGET_GRAPHICS_RENDERER" == "true" ]]; then
FRIGATE_DEVICES_BLOCK+=" - /dev/dri:/dev/dri\n"
fi
if [[ "$TARGET_USB_CORAL" == "true" ]]; then
FRIGATE_DEVICES_BLOCK+=" - /dev/bus/usb:/dev/bus/usb\n"
fi
if [[ -n "$FRIGATE_DEVICES_BLOCK" ]]; then
REPLACEMENT="devices:\n${FRIGATE_DEVICES_BLOCK%\\n}"
sed -i.bak "s|# --- frigate devices --- #|$REPLACEMENT|" docker/frigate.original
else
sed -i.bak "/# --- frigate devices --- #/d" docker/frigate.original
fi
IMMICH_DEVICES_BLOCK=""
if [[ "$TARGET_GRAPHICS_RENDERER" == "true" ]]; then
IMMICH_DEVICES_BLOCK+=" - /dev/dri:/dev/dri\n"
fi
if [[ -n "$IMMICH_DEVICES_BLOCK" ]]; then
REPLACEMENT="devices:\n${IMMICH_DEVICES_BLOCK%\\n}"
sed -i.bak "s|# --- immich devices --- #|$REPLACEMENT|" docker/immich.original
else
sed -i.bak "/# --- immich devices --- #/d" docker/immich.original
fi
if [[ -n "$TARGET_ZIGBEE_DEVICE" ]]; then
REPLACEMENT="devices:\n - /dev/serial/by-id/${TARGET_ZIGBEE_DEVICE}:/dev/ttyUSB0"
sed -i.bak "s|# --- hass devices --- #|$REPLACEMENT|" docker/hass.original
else
sed -i.bak "/# --- hass devices --- #/d" docker/hass.original
fi
echo -e "\n ✅ Copying configuration files for the selected homelab services..."
cp docker/traefik.original docker/traefik.nix
for service in "${SERVICES[@]}"; do
cp docker/${service}.original docker/${service}.nix
done
echo -e "\n ✅ Writing docker configuration files..."
envsubst < config-files/docker/traefik/headers.yaml > extra-files/mnt/config-storage/traefik/config/conf/headers.yaml
envsubst < config-files/docker/traefik/nextcloud.yaml > extra-files/mnt/config-storage/traefik/config/conf/nextcloud.yaml
envsubst < config-files/docker/traefik/tls.yaml > extra-files/mnt/config-storage/traefik/config/conf/tls.yaml
envsubst < config-files/docker/traefik/traefik.yaml > extra-files/mnt/config-storage/traefik/config/traefik.yaml
envsubst < config-files/docker/hass/mosquitto.conf > extra-files/mnt/config-storage/hass/mqtt/config/mosquitto.conf
touch extra-files/mnt/config-storage/hass/mqtt/config/password.txt
chmod 0700 extra-files/mnt/config-storage/hass/mqtt/config/password.txt
mosquitto_passwd -b extra-files/mnt/config-storage/hass/mqtt/config/password.txt $HOME_ASSISTANT_MQTT_USER $HOME_ASSISTANT_MQTT_PASSWORD
} }
disk_config_generation() { disk_config_generation() {
@@ -284,8 +262,6 @@ disk_config_generation() {
echo -e "\n\n 🔎 Fetching and analyzing disks from target host... (This may take a moment)" echo -e "\n\n 🔎 Fetching and analyzing disks from target host... (This may take a moment)"
### Disk wiping warning <-- ### Disk wiping warning <--
### --> Get disk information ### --> Get disk information
DISK_DETAILS=$(ssh darky " DISK_DETAILS=$(ssh darky "
# Declare arrays and variables # Declare arrays and variables
@@ -340,19 +316,17 @@ echo \"\${DISK_SIZE[@]}\"
read -r -a DISK_SIZE <<<"${LINES[5]}" read -r -a DISK_SIZE <<<"${LINES[5]}"
### Get disk information <-- ### Get disk information <--
### --> Disk selection ### --> Disk selection
TOTAL_NUMBER_OF_DISKS=${#DISK_NAME[@]} TOTAL_NUMBER_OF_DISKS=${#DISK_NAME[@]}
if [ "$TOTAL_NUMBER_OF_DISKS" -eq 0 ]; then if [ "${#DISK_NAME[@]}" -eq 0 ]; then
echo -e "\n\n ❌ No disks found on the target host. Aborting." echo -e "\n\n ❌ No disks found on the target host. Aborting."
exit 1 exit 1
fi fi
HEADER=$(printf " %-12s %-12s %-12s %-12s %s" "Device" "Type" "Size" "SMART" "Path") HEADER=$(printf " %-12s %-12s %-12s %-12s %s" "Device" "Type" "Size" "SMART" "Path")
for i in $(seq 0 $((TOTAL_NUMBER_OF_DISKS-1))); do for i in $(seq 0 $((${#DISK_NAME[@]} - 1))); do
GUM_PRINTED_ELEMENT=$(printf "%-12s %-12s %-12s %-12s %s" \ GUM_PRINTED_ELEMENT=$(printf "%-12s %-12s %-12s %-12s %s" \
"${DISK_NAME[$i]}" "${DISK_TYPE[$i]}" "${DISK_SIZE[$i]}" \ "${DISK_NAME[$i]}" "${DISK_TYPE[$i]}" "${DISK_SIZE[$i]}" \
"${DISK_HEALTH[$i]}" "${DISK_DEVPATH[$i]}") "${DISK_HEALTH[$i]}" "${DISK_DEVPATH[$i]}")
@@ -363,13 +337,11 @@ echo \"\${DISK_SIZE[@]}\"
SELECTED_BOOT_DISK=$(gum choose --limit 2 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}") SELECTED_BOOT_DISK=$(gum choose --limit 2 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}")
j=0 for i in $(seq 0 $((${#DISK_NAME[@]} - 1))); do
for i in $(seq 0 $((TOTAL_NUMBER_OF_DISKS - 1))); do
if printf '%s' "$SELECTED_BOOT_DISK" | grep -iq "${DISK_NAME[$i]}"; then if printf '%s' "$SELECTED_BOOT_DISK" | grep -iq "${DISK_NAME[$i]}"; then
((j++))
export declare "BOOT_DISK_${j}_ID=${DISK_ID[$i]}"
unset "GUM_PRINTED_ELEMENTS[${i}]"
((NUMBER_OF_BOOT_DISKS++)) ((NUMBER_OF_BOOT_DISKS++))
export declare "BOOT_DISK_${NUMBER_OF_BOOT_DISKS}_ID=${DISK_ID[$i]}"
unset "GUM_PRINTED_ELEMENTS[${i}]"
fi fi
done done
@@ -387,7 +359,7 @@ echo \"\${DISK_SIZE[@]}\"
SELECTED_DATA_DISK=$(gum choose --limit 9 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}") SELECTED_DATA_DISK=$(gum choose --limit 9 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}")
for i in $(seq 0 $((TOTAL_NUMBER_OF_DISKS - 1))); do for i in $(seq 0 $((${#DISK_NAME[@]} - 1))); do
if printf '%s' "$SELECTED_DATA_DISK" | grep -iq "${DISK_NAME[$i]}"; then if printf '%s' "$SELECTED_DATA_DISK" | grep -iq "${DISK_NAME[$i]}"; then
((NUMBER_OF_DATA_DISKS++)) ((NUMBER_OF_DATA_DISKS++))
fi fi
@@ -398,23 +370,26 @@ echo \"\${DISK_SIZE[@]}\"
elif [[ "$NUMBER_OF_DATA_DISKS" == "1" ]]; then elif [[ "$NUMBER_OF_DATA_DISKS" == "1" ]]; then
echo -e "\n\n ⚠️ One data disk selected, continuing with striped boot disk configuration." echo -e "\n\n ⚠️ One data disk selected, continuing with striped boot disk configuration."
echo -e " Consider using AT LEAST 2 data disks instead to get data protection features on the data disks." echo -e " Consider using AT LEAST 2 data disks instead to get data protection features on the data disks."
for i in $(seq 0 $((TOTAL_NUMBER_OF_DISKS - 1))); do for i in $(seq 0 $((${#DISK_NAME[@]} - 1))); do
if printf '%s' "$SELECTED_DATA_DISK" | grep -iq "${DISK_NAME[$i]}"; then if printf '%s' "$SELECTED_DATA_DISK" | grep -iq "${DISK_NAME[$i]}"; then
NUMBER_OF_CONTENT_DISKS="1" NUMBER_OF_CONTENT_DISKS="1"
NUMBER_OF_PARITY_DISKS="0" NUMBER_OF_PARITY_DISKS="0"
export CONTENT_DISK_1_ID="${DISK_ID[$i]}" export CONTENT_DISK_1_ID="${DISK_ID[$i]}"
export DISK_1_ID_LIST+=(${CONTENT_DISK_1_ID})
fi fi
done done
elif [[ "$NUMBER_OF_DATA_DISKS" == "2" ]]; then elif [[ "$NUMBER_OF_DATA_DISKS" == "2" ]]; then
NUMBER_OF_CONTENT_DISKS="0" NUMBER_OF_CONTENT_DISKS="0"
for i in $(seq 0 $((TOTAL_NUMBER_OF_DISKS - 1))); do for i in $(seq 0 $((${#DISK_NAME[@]} - 1))); do
if printf '%s' "$SELECTED_DATA_DISK" | grep -iq "${DISK_NAME[$i]}"; then if printf '%s' "$SELECTED_DATA_DISK" | grep -iq "${DISK_NAME[$i]}"; then
if [[ "$NUMBER_OF_CONTENT_DISKS" == "0" ]]; then if [[ "$NUMBER_OF_CONTENT_DISKS" == "0" ]]; then
NUMBER_OF_CONTENT_DISKS="1" NUMBER_OF_CONTENT_DISKS="1"
export CONTENT_DISK_1_ID="${DISK_ID[$i]}" export CONTENT_DISK_1_ID="${DISK_ID[$i]}"
export DISK_ID_LIST+=(${CONTENT_DISK_1_ID})
else else
NUMBER_OF_PARITY_DISKS="1" NUMBER_OF_PARITY_DISKS="1"
export PARITY_DISK_1_ID="${DISK_ID[$i]}" export PARITY_DISK_1_ID="${DISK_ID[$i]}"
export DISK_ID_LIST+=(${PARITY_DISK_1_ID})
fi fi
fi fi
done done
@@ -425,14 +400,16 @@ echo \"\${DISK_SIZE[@]}\"
k="$NUMBER_OF_CONTENT_DISKS" k="$NUMBER_OF_CONTENT_DISKS"
l="1" l="1"
m="1" m="1"
for i in $(seq 0 $((TOTAL_NUMBER_OF_DISKS - 1))); do for i in $(seq 0 $((${#DISK_NAME[@]} - 1))); do
if printf '%s' "$SELECTED_DATA_DISK" | grep -iq "${DISK_NAME[$i]}"; then if printf '%s' "$SELECTED_DATA_DISK" | grep -iq "${DISK_NAME[$i]}"; then
if [[ "$k" -gt 0 ]]; then if [[ "$k" -gt 0 ]]; then
declare "CONTENT_DISK_${l}_ID=${DISK_ID[$i]}" declare "CONTENT_DISK_${l}_ID=${DISK_ID[$i]}"
export DISK_ID_LIST+=(${CONTENT_DISK_${l}_ID})
k=$((k - 1)) k=$((k - 1))
((l++)) ((l++))
elif [[ "$j" -gt 0 ]]; then elif [[ "$j" -gt 0 ]]; then
declare "PARITY_DISK_${m}_ID=${DISK_ID[$i]}" declare "PARITY_DISK_${m}_ID=${DISK_ID[$i]}"
export DISK_ID_LIST+=(${PARITY_DISK_${m}_ID})
j=$((j - 1)) j=$((j - 1))
((m++)) ((m++))
fi fi
@@ -441,8 +418,6 @@ echo \"\${DISK_SIZE[@]}\"
fi fi
### Disk selection <-- ### Disk selection <--
### --> Selection recap ### --> Selection recap
RECAP_CONTENT=$(cat <<EOF RECAP_CONTENT=$(cat <<EOF
### Disk Configuration Summary ### Disk Configuration Summary
@@ -467,31 +442,31 @@ EOF
gum confirm "Proceed with this disk configuration?" || { echo -e "\n\n ❌ Aborting as requested."; exit 1; } gum confirm "Proceed with this disk configuration?" || { echo -e "\n\n ❌ Aborting as requested."; exit 1; }
### Selection recap <-- ### Selection recap <--
### --> Config generation ### --> Config generation
echo -e "\n\n ✅ Generating disko configuration from templates..." echo -e "\n\n ✅ Generating disko configuration from templates..."
TEMPLATE_FILE="config-files/disks/boot-${NUMBER_OF_BOOT_DISKS}.nix" TEMPLATE_FILE="config-files/disks/templates/boot-${NUMBER_OF_BOOT_DISKS}.nix"
(envsubst < "$TEMPLATE_FILE") > ./nix-config/disks/disko.nix (envsubst < "$TEMPLATE_FILE") > ./nix-config/disks/disko.nix
echo -e "\n ✅ Generated boot disk configuration." echo -e "\n ✅ Generated boot disk configuration."
# Mirror configuration # Mirror configuration
if [[ "$NUMBER_OF_CONTENT_DISKS" == 1 && "$NUMBER_OF_PARITY_DISKS" == 1 ]]; then if [[ "$NUMBER_OF_CONTENT_DISKS" == 1 && "$NUMBER_OF_PARITY_DISKS" == 1 ]]; then
(envsubst < "config-files/disks/mirror.nix") >> ./nix-config/disks/disko.nix (envsubst < "config-files/disks/templates/mirror.nix") >> ./nix-config/disks/disko.nix
# SnapRAID configuration # SnapRAID configuration
elif [[ "$NUMBER_OF_CONTENT_DISKS" -gt 0 ]]; then elif [[ "$NUMBER_OF_CONTENT_DISKS" -gt 0 ]]; then
(envsubst < "nix-config/disks/snapraid.nix") >> ./nix-config/disks/snapraid.nix
sed -i "s|# ./disks/snapraid.nix| ./disks/snapraid.nix|" ./nix-config/configuration.nix
for i in $(seq 1 $NUMBER_OF_CONTENT_DISKS); do for i in $(seq 1 $NUMBER_OF_CONTENT_DISKS); do
export i export i
LOOP_DISK="CONTENT_DISK_${i}_ID" LOOP_DISK="CONTENT_DISK_${i}_ID"
export CONTENT_DISK_ID=${!LOOP_DISK} export CONTENT_DISK_ID=${!LOOP_DISK}
(envsubst < "config-files/disks/content.nix") >> ./nix-config/disks/disko.nix (envsubst < "config-files/disks/templates/content.nix") >> ./nix-config/disks/disko.nix
done done
echo -e "\n ✅ Generated $NUMBER_OF_CONTENT_DISKS data disk configuration(s)." echo -e "\n ✅ Generated $NUMBER_OF_CONTENT_DISKS data disk configuration(s)."
for i in $(seq 1 $NUMBER_OF_PARITY_DISKS); do for i in $(seq 1 $NUMBER_OF_PARITY_DISKS); do
export i export i
LOOP_DISK="PARITY_DISK_${i}_ID" LOOP_DISK="PARITY_DISK_${i}_ID"
export PARITY_DISK_ID=${!LOOP_DISK} export PARITY_DISK_ID=${!LOOP_DISK}
(envsubst < "config-files/disks/parity.nix") >> ./nix-config/disks/disko.nix (envsubst < "config-files/disks/templates/parity.nix") >> ./nix-config/disks/disko.nix
done done
echo -e "\n ✅ Generated $NUMBER_OF_PARITY_DISKS parity disk configuration(s)." echo -e "\n ✅ Generated $NUMBER_OF_PARITY_DISKS parity disk configuration(s)."
fi fi
@@ -504,14 +479,12 @@ EOF
echo -e "\n ✅ Final disko configuration created." echo -e "\n ✅ Final disko configuration created."
### Config generation <-- ### Config generation <--
### --> Generate automatic unlock configuration ### --> Generate automatic unlock configuration
if [[ "$NUMBER_OF_CONTENT_DISKS" -gt 1 && "$NUMBER_OF_PARITY_DISKS" -gt 0 ]]; then if [[ "$NUMBER_OF_CONTENT_DISKS" -gt 1 && "$NUMBER_OF_PARITY_DISKS" -gt 0 ]]; then
echo -e "\n ✅ Generating automatic disk unlocking configuration..." echo -e "\n ✅ Generating automatic disk unlocking configuration..."
sed -i '$ d' ./nix-config/disks/snapraid.nix sed -i '$ d' ./config-files/disks/snapraid.nix
cat <<EOF >> ./nix-config/disks/snapraid.nix cat <<EOF >> ./config-files/disks/snapraid.nix
# --> Automatic data disks unlock, generated by deploy.sh on $(date) # --> Automatic data disks unlock, generated by deploy.sh on $(date)
boot.initrd.luks.devices = { boot.initrd.luks.devices = {
EOF EOF
@@ -519,7 +492,7 @@ EOF
if [[ "$NUMBER_OF_CONTENT_DISKS" -gt 0 ]]; then if [[ "$NUMBER_OF_CONTENT_DISKS" -gt 0 ]]; then
for i in $(seq 1 $NUMBER_OF_CONTENT_DISKS); do for i in $(seq 1 $NUMBER_OF_CONTENT_DISKS); do
LOOP_DISK="CONTENT_DISK_$i" LOOP_DISK="CONTENT_DISK_$i"
cat <<EOF >> ./nix-config/disks/snapraid.nix cat <<EOF >> ./config-files/disks/snapraid.nix
"crypted-content-disk-${i}" = { "crypted-content-disk-${i}" = {
device = "${!LOOP_DISK}"; device = "${!LOOP_DISK}";
keyFile = "/etc/secrets/disks/content-disk-${i}"; keyFile = "/etc/secrets/disks/content-disk-${i}";
@@ -531,7 +504,7 @@ EOF
if [[ "$NUMBER_OF_PARITY_DISKS" -gt 0 ]]; then if [[ "$NUMBER_OF_PARITY_DISKS" -gt 0 ]]; then
for i in $(seq 1 $NUMBER_OF_PARITY_DISKS); do for i in $(seq 1 $NUMBER_OF_PARITY_DISKS); do
LOOP_DISK="PARITY_DISK_$i" LOOP_DISK="PARITY_DISK_$i"
cat <<EOF >> ./nix-config/disks/snapraid.nix cat <<EOF >> ./config-files/disks/snapraid.nix
"crypted-parity-disk-${i}" = { "crypted-parity-disk-${i}" = {
device = "${!LOOP_DISK}"; device = "${!LOOP_DISK}";
keyFile = "/etc/secrets/disks/parity-disk-${i}"; keyFile = "/etc/secrets/disks/parity-disk-${i}";
@@ -540,13 +513,27 @@ EOF
done done
fi fi
cat <<'EOF' >> ./nix-config/disks/snapraid.nix cat <<'EOF' >> ./config-files/disks/snapraid.nix
# Automatic data disks unlock <-- # Automatic data disks unlock <--
}; };
} }
EOF EOF
fi fi
cp -avu ./config-files/disks/snapraid.nix ./nixos-config/disks/
### Generate automatic unlock configuration <-- ### Generate automatic unlock configuration <--
### --> Generate unlock keys
for i in $NUMBER_OF_BOOT_DISKS; do
declare "/etc/secrets/disks/boot-disk-${i}=$(xkcdpass -d "-")"
done
for i in $NUMBER_OF_CONTENT_DISKS; do
declare "/etc/secrets/disks/content-disk-${i}=$(xkcdpass -d "-")"
done
for i in $NUMBER_OF_PARITY_DISKS; do
declare "/etc/secrets/disks/parity-disk-${i}=$(xkcdpass -d "-")"
done
### Generate unlock keys <--
} }
deploy() { deploy() {
@@ -555,7 +542,7 @@ deploy() {
--generate-hardware-config nixos-generate-config ./nix-config/hardware-configuration.nix \ --generate-hardware-config nixos-generate-config ./nix-config/hardware-configuration.nix \
--flake ./nix-config#numbus-server \ --flake ./nix-config#numbus-server \
--extra-files extra-files \ --extra-files extra-files \
--chown "/home/numbus-admin/" 1000:1000 \ --chown "/home/numbusing a us-admin/" 1000:1000 \
--target-host nixos@$TARGET_HOST --target-host nixos@$TARGET_HOST
echo -e "\n\n ✅ Installation successfull !" echo -e "\n\n ✅ Installation successfull !"
@@ -590,7 +577,12 @@ EOF
} }
postrun_action() { postrun_action() {
echo "" echo -e "\n\n Now the remote machine will reboot. You will need to input the boot disk(s) passphrase.
This will be the only time you will have to do so, it will be automatic in the future."
gum spin --spinner dot --title "Rebooting the remote..." -- sleep 60
ssh
# Add TPM2 boot disk decryption # Add TPM2 boot disk decryption
# Add pcr-check.nix # Add pcr-check.nix
} }
@@ -663,8 +655,7 @@ elif [[ "$ACTION_ANSWER" == "[2] 💽 Deploy NixOS on a remote machine with a fi
elif [[ "$ACTION_ANSWER" == "[3] 🛠️ Update a NixOS remote machine" ]]; then elif [[ "$ACTION_ANSWER" == "[3] 🛠️ Update a NixOS remote machine" ]]; then
echo -e "\n ➡️ Proceeding with update…" echo -e "\n ➡️ Proceeding with update…"
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "➡️ On the target host : make sure the NixOS installation you want gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "➡️ On the target host : make sure the NixOS installation you want
to update is up-and-running, accessible with SSH. to update is up-and-running, accessible with SSH."
"
gum confirm "Do you understand and wish to proceed?" || { echo " ❌ Aborting as requested."; exit 1; } gum confirm "Do you understand and wish to proceed?" || { echo " ❌ Aborting as requested."; exit 1; }
nixos_update nixos_update
else else
-76
View File
@@ -1,76 +0,0 @@
I am working on a homelab deployer tool. The description of this homelab deployer tool is available in the README.md file, you shall read it to better understand your job. Your job as a NixOS expert will be to help me change the current broken configuration into my wanted configuration.
# Here is how I want my setup to be
### Disks selection
My script allows the selection of disks.
The disks are separated in two categories : boot and data disks. Data disks include content disks and parity disks.
First, the user chooses the boot disks : he can choose one boot disk or two boot disks in a mirror setup. User has to choose at least one boot disk.
Then, the user chooses data disks. He can choose how many data disks he desires up to 9, or no disks at all. Then the scripts automatically assigns data disks to the content and parity disks according to 3 conditions : <br>
- if there is only one data disk selected, it must be a content disk <br>
- if more than one data disk is selected, the larger (or equal if all disks are the same size) disks are necessarily parity disks <br>
- if more than one data disk is selected, the content/parity repartition must be 1 parity disk for up to 2 content disks. <br>
### RAID configuration
If there is only one boot disk selected, the boot disk will be striped. If there are 2 boot disks selected, it will be a mirror.
The data disks mountpoints are dynamically and logically set : <br>
- /mnt/content-1 <br>
- /mnt/content-2 <br>
- /mnt/content-3 <br>
- /mnt/content-4 <br>
- /mnt/content-5 <br>
- /mnt/content-6 <br>
- /mnt/parity-1 <br>
- /mnt/parity-2 <br>
- /mnt/parity-3 <br>
SnapRAID is used to get a RAID configuration working with the content and parity drives even if their size is not the same. MergerFS is used to obtain one clean path to the data storage. The script uses a combination of code to find the UUIDs of the disks to reference them in the final disk-config.nix configuration file.
### Disks unlocking
The boot disks are unlocked manually by providing the passphrase on boot.
The data disks are unlocked automatically on boot using a keyfile that is located on the root partition. This means that I need to unlock the boot disk(s), and once it is unlocked the keyfiles for the data disks are decrypted and ready to be used.
The keyfile are dynamically and logically ordered and referenced on the root partition : <br>
- /etc/secrets/disks/content-disk-1 <br>
- /etc/secrets/disks/content-disk-2 <br>
- /etc/secrets/disks/content-disk-3 <br>
- /etc/secrets/disks/content-disk-4 <br>
- /etc/secrets/disks/content-disk-5 <br>
- /etc/secrets/disks/content-disk-6 <br>
- /etc/secrets/disks/parity-disk-1 <br>
- /etc/secrets/disks/parity-disk-2 <br>
- /etc/secrets/disks/parity-disk-3 <br>
The LUKS partition on the disks are dynamically and logically ordered : <br>
- crypted-content-disk-1 <br>
- crypted-content-disk-2 <br>
- crypted-content-disk-3 <br>
- crypted-content-disk-4 <br>
- crypted-content-disk-5 <br>
- crypted-content-disk-6 <br>
- crypted-parity-disk-1 <br>
- crypted-parity-disk-2 <br>
- crypted-parity-disk-3 <br>
To automatically unlock the data disks, you need to set a crypttab entry. Since we are using NixOS, we will set the boot.initrd.luks.devices option.
This option needs to following : volume-name (i.e. crypted-content-1 for example), encrypted-device (i.e. the UUID path), key-file (i.e. the keyfile path). Here is a static example for a disk.
```
boot.initrd.luks.devices = {
"my-device-mapper" = {
device = "/dev/disk/by-uuid/YOUR-UUID-HERE";
keyFile = "/path/to/your-keyfile";
};
};
```
Volume names are logically ordered : crypted-data-1, crypted-data-2, crypted-data-3, [...], crypted-data-6, or crypted-parity-1, [...], crypted-parity-3.
Encrypted-device is the path to the device, using the /dev/by-id/YOUR-UUID-HERE UUID path. This is the tricky part since the disks are dynamically selected. The configuration needs to find the correct UUIDs, the same ones as those selected in the script.
Key-file is the path to the keyfile which are logically ordered : /etc/secrets/disks/data-disk-1, /etc/secrets/disks/data-disk-2, /etc/secrets/disks/data-disk-3, [...], /etc/secrets/disks/data-disk-6 or /etc/secrets/disks/parity-disk-1, [...], /etc/secrets/disks/parity-disk-3.
+1 -17
View File
@@ -21,6 +21,7 @@
sops.age.keyFile = "/var/lib/sops-nix/key.txt"; sops.age.keyFile = "/var/lib/sops-nix/key.txt";
sops.age.generateKey = true; sops.age.generateKey = true;
sops.secrets."ssh_public_keys" = { owner = "numbus-admin"; path = "/etc/ssh/authorized_keys.d/numbus-admin"; }; sops.secrets."ssh_public_keys" = { owner = "numbus-admin"; path = "/etc/ssh/authorized_keys.d/numbus-admin"; };
sops.secrets."sender_email_address_password" = {};
sops.secrets."docker/frigate" = { owner = "numbus-admin"; path = "/etc/docker-compose/frigate/.env"; }; sops.secrets."docker/frigate" = { owner = "numbus-admin"; path = "/etc/docker-compose/frigate/.env"; };
sops.secrets."docker/traefik" = { owner = "numbus-admin"; path = "/etc/docker-compose/traefik/.env"; }; sops.secrets."docker/traefik" = { owner = "numbus-admin"; path = "/etc/docker-compose/traefik/.env"; };
sops.secrets."docker/nextcloud" = { owner = "numbus-admin"; path = "/etc/docker-compose/nextcloud/.env"; }; sops.secrets."docker/nextcloud" = { owner = "numbus-admin"; path = "/etc/docker-compose/nextcloud/.env"; };
@@ -109,23 +110,6 @@
data-root = "/mnt/config-storage/docker-volumes/"; data-root = "/mnt/config-storage/docker-volumes/";
}; };
# Enable networking and firewall
networking.interfaces.eth0.ipv4.addresses = [
{
address = "HOME_SERVER_IP";
prefixLength = 24;
}
];
networking.defaultGateway = "HOME_ROUTER_IP";
networking.nameservers = [ "HOME_SERVER_IP" "9.9.9.9" ];
networking.networkmanager.enable = true;
networking.firewall.enable = true;
# Open ports in the firewall
networking.firewall.allowPing = false;
networking.firewall.allowedTCPPorts = [ 53 80 443 ];
networking.firewall.allowedUDPPorts = [ 53 ];
# Hostname # Hostname
networking.hostName = "numbus-server"; networking.hostName = "numbus-server";
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
inputs = { inputs = {
# Core Nixpkgs # Core Nixpkgs
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
# Diskpartitioning helper # Diskpartitioning helper
disko.url = "github:nix-community/disko"; disko.url = "github:nix-community/disko";
disko.inputs.nixpkgs.follows = "nixpkgs"; disko.inputs.nixpkgs.follows = "nixpkgs";
+72
View File
@@ -0,0 +1,72 @@
{ config, pkgs, lib, ... }:
let
cfg = config.email;
in
### --> Mail notifications configuration
{
options.email = {
enable = lib.mkEnableOption "Email sending functionality";
fromAddress = lib.mkOption {
description = "The 'from' address";
type = lib.types.str;
default = "no-reply@${DOMAIN_NAME}";
};
toAddress = lib.mkOption {
description = "The 'to' address";
type = lib.types.str;
default = "${EMAIL_ADDRESS}";
};
smtpServer = lib.mkOption {
description = "The SMTP server address";
type = lib.types.str;
default = "${SENDER_EMAIL_DOMAIN}";
};
smtpUsername = lib.mkOption {
description = "The SMTP username";
type = lib.types.str;
default = "${SENDER_EMAIL_ADDRESS}";
};
smtpPasswordPath = lib.mkOption {
description = "Path to the secret containing SMTP password";
type = lib.types.path;
default = config.sops.secrets.sender_email_address_password.path;
};
};
config = lib.mkIf cfg.enable {
programs.msmtp = {
enable = true;
accounts.default = {
auth = true;
host = config.email.smtpServer;
from = config.email.fromAddress;
user = config.email.smtpUsername;
tls = true;
passwordeval = "${pkgs.coreutils}/bin/cat ${config.email.smtpPasswordPath}";
};
};
};
### Mail notifications configuration <--
### --> SMART disk heath
services.smartd = {
enable = true;
defaults.autodetected = "-a -o on -S on -s (S/../.././02|L/../../6/03) -n standby,q";
notifications = {
wall = {
enable = true;
};
mail = {
enable = true;
sender = config.email.fromAddress;
recipient = config.email.toAddress;
};
};
};
### SMART disk heath <--
}
+25
View File
@@ -0,0 +1,25 @@
{ config, ... }:
{
# Enable networking and firewall
networking.interfaces.eth0.ipv4.addresses = [
{
address = "HOME_SERVER_IP";
prefixLength = 24;
}
];
networking.defaultGateway = "HOME_ROUTER_IP";
networking.nameservers = [ "HOME_SERVER_IP" "9.9.9.9" ];
networking.networkmanager.enable = true;
# networking.nftables.enable = false;
networking.firewall.enable = true;
# networking.firewall.extraCommands = "
# iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
# iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8443
# ";
# Open ports in the firewall
networking.firewall.allowPing = true;
networking.firewall.allowedTCPPorts = [ 53 80 443 ];
networking.firewall.allowedUDPPorts = [ 53 ];
}