Big update. Folder reorganization. Disk selection logic finished. Improved services selection (not done yet).
This commit is contained in:
@@ -1 +0,0 @@
|
||||
deploy.conf
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
agents/
|
||||
ai-production/
|
||||
extra-files/
|
||||
test.sh
|
||||
test2.sh
|
||||
test*
|
||||
deploy.conf
|
||||
@@ -1,7 +1,8 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
# --> SnapRAID disks research
|
||||
let
|
||||
|
||||
### --> SnapRAID disks research
|
||||
contentDiskMounts = lib.attrsets.attrNames (
|
||||
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; })
|
||||
{ i = 0; }
|
||||
contentDiskMounts;
|
||||
### SnapRAID disks research <--
|
||||
|
||||
### --> Spindown disks
|
||||
hardDrives = [ ${DISK_ID_LIST[@]} ];
|
||||
### Spindown disks <--
|
||||
in
|
||||
# SnapRAID disks research <--
|
||||
|
||||
|
||||
|
||||
# --> MergerFS setup
|
||||
|
||||
### --> MergerFS setup
|
||||
{
|
||||
fileSystems."/mnt/data-storage" = {
|
||||
device = "mergerfs";
|
||||
@@ -32,19 +38,35 @@ in
|
||||
"srcmounts=${lib.strings.concatStringsSep ":" contentDiskMounts}"
|
||||
];
|
||||
};
|
||||
# MergerFS setup <--
|
||||
### MergerFS setup <--
|
||||
|
||||
|
||||
|
||||
# --> SnapRAID setup
|
||||
### --> SnapRAID setup
|
||||
services.snapraid = {
|
||||
enable = true;
|
||||
contentFiles = map (disk: "${disk}/snapraid.content") contentDiskMounts;
|
||||
parityFiles = map (disk: "${disk}/snapraid.parity") parityDiskMounts;
|
||||
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 <--
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,7 @@ TARGET_HOST="192.168.1.10"
|
||||
SSH_PUBLIC_KEY="ssh-ed25519 AAAAoefzefpoipoeCEZJCPEACPAcjapjcpajepcjAPJECJPEJAPJAZ yours@yourdomain.com"
|
||||
# TRAEFIK SETTINGS
|
||||
DOMAIN_NAME="yourdomain.com"
|
||||
EMAIL_ADDRESS="no-reply@yourdomain.com"
|
||||
EMAIL_ADDRESS="your-mail@yourdomain.com"
|
||||
CF_DNS_API_TOKEN="yourToken"
|
||||
#SMTP SETTINGS
|
||||
SENDER_EMAIL_ADDRESS="youraddress@gmail.com"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
ssh_public_keys: $SSH_PUBLIC_KEY
|
||||
sender_email_address_password: $SENDER_EMAIL_ADDRESS_PASSWORD
|
||||
|
||||
docker:
|
||||
nextcloud: |
|
||||
@@ -18,9 +19,9 @@ docker:
|
||||
passbolt: |
|
||||
DOMAIN_NAME=$DOMAIN_NAME
|
||||
TZ=Europe/Paris
|
||||
PASSBOLT_MYSQL_DATABASE=$PASSBOLT_MYSQL_DATABASE
|
||||
PASSBOLT_MYSQL_USER=$PASSBOLT_MYSQL_USER
|
||||
PASSBOLT_MYSQL_PASSWORD=$PASSBOLT_MYSQL_PASSWORD
|
||||
PASSBOLT_MYSQL_DATABASE=$PASSBOLT_DB_NAME
|
||||
PASSBOLT_MYSQL_USER=$PASSBOLT_DB_USERNAME
|
||||
PASSBOLT_MYSQL_PASSWORD=$PASSBOLT_DB_PASSWORD
|
||||
SENDER_EMAIL_ADDRESS=$SENDER_EMAIL_ADDRESS
|
||||
SENDER_EMAIL_ADDRESS_PASSWORD=$SENDER_EMAIL_ADDRESS_PASSWORD
|
||||
SENDER_EMAIL_DOMAIN=$SENDER_EMAIL_DOMAIN
|
||||
@@ -41,10 +42,17 @@ docker:
|
||||
IMMICH_TRUSTED_PROXIES=172.16.50.253
|
||||
REDIS_HOSTNAME=immich-redis
|
||||
DB_HOSTNAME=immich-database
|
||||
DB_DATABASE_NAME=$IMMICH_DB_DATABASE_NAME
|
||||
DB_DATABASE_NAME=$IMMICH_DB_NAME
|
||||
DB_USERNAME=$IMMICH_DB_USERNAME
|
||||
DB_PASSWORD=$IMMICH_DB_PASSWORD
|
||||
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:
|
||||
content-disk-1: $CONTENT_DISK_1_KEY
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/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() {
|
||||
#TARGET SETTINGS
|
||||
@@ -127,148 +127,126 @@ hardware_detection() {
|
||||
services_selection() {
|
||||
echo -e "\n\n ➡️ You will now select the services you want installed on your server:"
|
||||
|
||||
declare -A SERVICE_MAP
|
||||
SERVICE_MAP["Pi-Hole: Block ads on all your devices"]="pihole"
|
||||
SERVICE_MAP["Home Assistant: Manage your smart home or security cameras"]="hass"
|
||||
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"
|
||||
AVAILABLE_SERVICES=( "frigate" "gitea" "home-assistant" "immich" "it-tools" \
|
||||
"nextcloud" "passbolt" "pi-hole" )
|
||||
AVAILABLE_SERVICES_NUMBER=${#AVAILABLE_SERVICES[@]}
|
||||
|
||||
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=()
|
||||
if [[ -n "$SELECTED_DESCRIPTIONS_STRING" ]]; then
|
||||
while IFS= read -r line; do
|
||||
SERVICES+=("${SERVICE_MAP[$line]}")
|
||||
done <<< "$SELECTED_DESCRIPTIONS_STRING"
|
||||
fi
|
||||
for i in $(seq 0 $((${#AVAILABLE_SERVICES[@]} - 1))); do
|
||||
if printf '%s' "$SELECTED_SERVICES_DESCRIPTION" | grep -iq "${AVAILABLE_SERVICES[$i]}"; then
|
||||
SELECTED_SERVICES+=(${AVAILABLE_SERVICES[$i]})
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
files_generation() {
|
||||
echo -e "\n\n ✅ Generating 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/var/lib/sops-nix/
|
||||
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
|
||||
export SOPS_PUBLIC_KEY=$(age-keygen -y extra-files/var/lib/sops-nix/key.txt)
|
||||
|
||||
echo -e "\n ✅ Generating sops-nix configuration files..."
|
||||
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..."
|
||||
envsubst < "config-files/sops-nix/secrets.yaml" | sops encrypt --filename-override secrets.yaml \
|
||||
--input-type yaml --output-type yaml \
|
||||
--age $SOPS_PUBLIC_KEY \
|
||||
--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..."
|
||||
sed -i s+HOME_SERVER_IP+$HOME_SERVER_IP+g ./nix-config/configuration.nix
|
||||
sed -i s+HOME_ROUTER_IP+$HOME_ROUTER_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/misc/networking.nix
|
||||
|
||||
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"
|
||||
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
|
||||
echo -e "\n ✅ Copying the configuration to the new machine..."
|
||||
cp -ravu ./nix-config/* extra-files/etc/nixos/
|
||||
}
|
||||
|
||||
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)"
|
||||
### Disk wiping warning <--
|
||||
|
||||
|
||||
|
||||
### --> Get disk information
|
||||
DISK_DETAILS=$(ssh darky "
|
||||
# Declare arrays and variables
|
||||
@@ -340,19 +316,17 @@ echo \"\${DISK_SIZE[@]}\"
|
||||
read -r -a DISK_SIZE <<<"${LINES[5]}"
|
||||
### Get disk information <--
|
||||
|
||||
|
||||
|
||||
### --> Disk selection
|
||||
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."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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" \
|
||||
"${DISK_NAME[$i]}" "${DISK_TYPE[$i]}" "${DISK_SIZE[$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[@]}")
|
||||
|
||||
j=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_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++))
|
||||
export declare "BOOT_DISK_${NUMBER_OF_BOOT_DISKS}_ID=${DISK_ID[$i]}"
|
||||
unset "GUM_PRINTED_ELEMENTS[${i}]"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -387,7 +359,7 @@ echo \"\${DISK_SIZE[@]}\"
|
||||
|
||||
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
|
||||
((NUMBER_OF_DATA_DISKS++))
|
||||
fi
|
||||
@@ -398,23 +370,26 @@ echo \"\${DISK_SIZE[@]}\"
|
||||
elif [[ "$NUMBER_OF_DATA_DISKS" == "1" ]]; then
|
||||
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."
|
||||
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
|
||||
NUMBER_OF_CONTENT_DISKS="1"
|
||||
NUMBER_OF_PARITY_DISKS="0"
|
||||
export CONTENT_DISK_1_ID="${DISK_ID[$i]}"
|
||||
export DISK_1_ID_LIST+=(${CONTENT_DISK_1_ID})
|
||||
fi
|
||||
done
|
||||
elif [[ "$NUMBER_OF_DATA_DISKS" == "2" ]]; then
|
||||
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 [[ "$NUMBER_OF_CONTENT_DISKS" == "0" ]]; then
|
||||
NUMBER_OF_CONTENT_DISKS="1"
|
||||
export CONTENT_DISK_1_ID="${DISK_ID[$i]}"
|
||||
export DISK_ID_LIST+=(${CONTENT_DISK_1_ID})
|
||||
else
|
||||
NUMBER_OF_PARITY_DISKS="1"
|
||||
export PARITY_DISK_1_ID="${DISK_ID[$i]}"
|
||||
export DISK_ID_LIST+=(${PARITY_DISK_1_ID})
|
||||
fi
|
||||
fi
|
||||
done
|
||||
@@ -425,14 +400,16 @@ echo \"\${DISK_SIZE[@]}\"
|
||||
k="$NUMBER_OF_CONTENT_DISKS"
|
||||
l="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 [[ "$k" -gt 0 ]]; then
|
||||
declare "CONTENT_DISK_${l}_ID=${DISK_ID[$i]}"
|
||||
export DISK_ID_LIST+=(${CONTENT_DISK_${l}_ID})
|
||||
k=$((k - 1))
|
||||
((l++))
|
||||
elif [[ "$j" -gt 0 ]]; then
|
||||
declare "PARITY_DISK_${m}_ID=${DISK_ID[$i]}"
|
||||
export DISK_ID_LIST+=(${PARITY_DISK_${m}_ID})
|
||||
j=$((j - 1))
|
||||
((m++))
|
||||
fi
|
||||
@@ -441,8 +418,6 @@ echo \"\${DISK_SIZE[@]}\"
|
||||
fi
|
||||
### Disk selection <--
|
||||
|
||||
|
||||
|
||||
### --> Selection recap
|
||||
RECAP_CONTENT=$(cat <<EOF
|
||||
### Disk Configuration Summary
|
||||
@@ -467,31 +442,31 @@ EOF
|
||||
gum confirm "Proceed with this disk configuration?" || { echo -e "\n\n ❌ Aborting as requested."; exit 1; }
|
||||
### Selection recap <--
|
||||
|
||||
|
||||
|
||||
### --> Config generation
|
||||
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
|
||||
echo -e "\n ✅ Generated boot disk configuration."
|
||||
|
||||
# Mirror configuration
|
||||
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
|
||||
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
|
||||
export i
|
||||
LOOP_DISK="CONTENT_DISK_${i}_ID"
|
||||
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
|
||||
echo -e "\n ✅ Generated $NUMBER_OF_CONTENT_DISKS data disk configuration(s)."
|
||||
for i in $(seq 1 $NUMBER_OF_PARITY_DISKS); do
|
||||
export i
|
||||
LOOP_DISK="PARITY_DISK_${i}_ID"
|
||||
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
|
||||
echo -e "\n ✅ Generated $NUMBER_OF_PARITY_DISKS parity disk configuration(s)."
|
||||
fi
|
||||
@@ -504,14 +479,12 @@ EOF
|
||||
echo -e "\n ✅ Final disko configuration created."
|
||||
### Config generation <--
|
||||
|
||||
|
||||
|
||||
### --> Generate automatic unlock configuration
|
||||
if [[ "$NUMBER_OF_CONTENT_DISKS" -gt 1 && "$NUMBER_OF_PARITY_DISKS" -gt 0 ]]; then
|
||||
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)
|
||||
boot.initrd.luks.devices = {
|
||||
EOF
|
||||
@@ -519,7 +492,7 @@ EOF
|
||||
if [[ "$NUMBER_OF_CONTENT_DISKS" -gt 0 ]]; then
|
||||
for i in $(seq 1 $NUMBER_OF_CONTENT_DISKS); do
|
||||
LOOP_DISK="CONTENT_DISK_$i"
|
||||
cat <<EOF >> ./nix-config/disks/snapraid.nix
|
||||
cat <<EOF >> ./config-files/disks/snapraid.nix
|
||||
"crypted-content-disk-${i}" = {
|
||||
device = "${!LOOP_DISK}";
|
||||
keyFile = "/etc/secrets/disks/content-disk-${i}";
|
||||
@@ -531,7 +504,7 @@ EOF
|
||||
if [[ "$NUMBER_OF_PARITY_DISKS" -gt 0 ]]; then
|
||||
for i in $(seq 1 $NUMBER_OF_PARITY_DISKS); do
|
||||
LOOP_DISK="PARITY_DISK_$i"
|
||||
cat <<EOF >> ./nix-config/disks/snapraid.nix
|
||||
cat <<EOF >> ./config-files/disks/snapraid.nix
|
||||
"crypted-parity-disk-${i}" = {
|
||||
device = "${!LOOP_DISK}";
|
||||
keyFile = "/etc/secrets/disks/parity-disk-${i}";
|
||||
@@ -540,13 +513,27 @@ EOF
|
||||
done
|
||||
fi
|
||||
|
||||
cat <<'EOF' >> ./nix-config/disks/snapraid.nix
|
||||
cat <<'EOF' >> ./config-files/disks/snapraid.nix
|
||||
# Automatic data disks unlock <--
|
||||
};
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
cp -avu ./config-files/disks/snapraid.nix ./nixos-config/disks/
|
||||
### 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() {
|
||||
@@ -555,7 +542,7 @@ deploy() {
|
||||
--generate-hardware-config nixos-generate-config ./nix-config/hardware-configuration.nix \
|
||||
--flake ./nix-config#numbus-server \
|
||||
--extra-files extra-files \
|
||||
--chown "/home/numbus-admin/" 1000:1000 \
|
||||
--chown "/home/numbusing a us-admin/" 1000:1000 \
|
||||
--target-host nixos@$TARGET_HOST
|
||||
|
||||
echo -e "\n\n ✅ Installation successfull !"
|
||||
@@ -590,7 +577,12 @@ EOF
|
||||
}
|
||||
|
||||
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 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
|
||||
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
|
||||
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; }
|
||||
nixos_update
|
||||
else
|
||||
|
||||
@@ -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.
|
||||
@@ -21,6 +21,7 @@
|
||||
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
|
||||
sops.age.generateKey = true;
|
||||
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/traefik" = { owner = "numbus-admin"; path = "/etc/docker-compose/traefik/.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/";
|
||||
};
|
||||
|
||||
# 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
|
||||
networking.hostName = "numbus-server";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
inputs = {
|
||||
# Core Nixpkgs
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||
# Disk‑partitioning helper
|
||||
disko.url = "github:nix-community/disko";
|
||||
disko.inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
||||
@@ -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 <--
|
||||
|
||||
}
|
||||
@@ -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 ];
|
||||
}
|
||||
Reference in New Issue
Block a user