Huge update. Reorganized folders. Added post-install logic. Have to do testing to check if everything works.

This commit is contained in:
Raphaël Numbus
2025-12-31 13:18:49 +01:00
parent 9559b232dc
commit 984d5454ac
44 changed files with 1052 additions and 581 deletions
Vendored
BIN
View File
Binary file not shown.
+2 -3
View File
@@ -1,5 +1,4 @@
agents/ agents/
ai-production/
extra-files/ extra-files/
test* final-nix-config/
deploy.conf test*
-16
View File
@@ -1,16 +0,0 @@
#TARGET SETTINGS
TARGET_HOST="192.168.1.10"
SSH_PUBLIC_KEY="ssh-ed25519 AAAAoefzefpoipoeCEZJCPEACPAcjapjcpajepcjAPJECJPEJAPJAZ yours@yourdomain.com"
# TRAEFIK SETTINGS
DOMAIN_NAME="yourdomain.com"
EMAIL_ADDRESS="your-mail@yourdomain.com"
CF_DNS_API_TOKEN="yourToken"
#SMTP SETTINGS
SENDER_EMAIL_ADDRESS="youraddress@gmail.com"
SENDER_EMAIL_ADDRESS_PASSWORD="emrp raps vzoi vnoe"
SENDER_EMAIL_DOMAIN="smtp.yourdomain.com"
SENDER_EMAIL_PORT="587"
#NETWORK SETTINGS
HOME_ROUTER_SUBNET="192.168.1.0/24"
HOME_ROUTER_IP="192.168.1.1"
HOME_SERVER_IP="192.168.1.5"
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p gum openssl sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto
gum confirm "Welcome to the Numbus-Server administration interface. Would you like to change a setting ?" \
|| { echo " ❌ Aborting as requested."; exit 1; }
ACTIONS_LIST=("Update networking settings" "Update backup settings" "Add/Remove services" \
"Add/Remove/Replace boot disk" "Add/Remove/Replace data disk(s)" "Add/Remove devices (i.e. coral TPUs)" \
"Test email notifications" "Check disk(s) health" "Export current configuration to a git server")
-66
View File
@@ -1,66 +0,0 @@
ssh_public_keys: $SSH_PUBLIC_KEY
sender_email_address_password: $SENDER_EMAIL_ADDRESS_PASSWORD
docker:
nextcloud: |
DOMAIN_NAME=$DOMAIN_NAME
NEXTCLOUD_ENABLE_DRI_DEVICE=$TARGET_GRAPHICS
frigate: |
DOMAIN_NAME=$DOMAIN_NAME
FRIGATE_MQTT_USER=$HOME_ASSISTANT_MQTT_USER
FRIGATE_MQTT_PASSWORD=$HOME_ASSISTANT_MQTT_PASSWORD
traefik: |
DOMAIN_NAME=$DOMAIN_NAME
CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN
hass: |
DOMAIN_NAME=$DOMAIN_NAME
HOME_ASSISTANT_MQTT_USER=$HOME_ASSISTANT_MQTT_USER
HOME_ASSISTANT_MQTT_PASSWORD=$HOME_ASSISTANT_MQTT_PASSWORD
passbolt: |
DOMAIN_NAME=$DOMAIN_NAME
TZ=Europe/Paris
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
SENDER_EMAIL_PORT=$SENDER_EMAIL_PORT
EMAIL_ADDRESS=$EMAIL_ADDRESS
pihole: |
DOMAIN_NAME=$DOMAIN_NAME
TZ=Europe/Paris
HOME_ROUTER_SUBNET=$HOME_ROUTER_SUBNET
HOME_ROUTER_IP=$HOME_ROUTER_IP
HOME_SERVER_IP=$HOME_SERVER_IP
FTLCONF_webserver_api_password=$FTLCONF_WEBSERVER_PASSWORD
immich: |
DOMAIN_NAME=$DOMAIN_NAME
TZ=Europe/Paris
UPLOAD_LOCATION=/mnt/data-storage/docker-data/immich
IMMICH_VERSION=release
IMMICH_TRUSTED_PROXIES=172.16.50.253
REDIS_HOSTNAME=immich-redis
DB_HOSTNAME=immich-database
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
content-disk-2: $CONTENT_DISK_2_KEY
content-disk-3: $CONTENT_DISK_3_KEY
content-disk-4: $CONTENT_DISK_4_KEY
content-disk-5: $CONTENT_DISK_5_KEY
content-disk-6: $CONTENT_DISK_6_KEY
parity-disk-1: $PARITY_DISK_1_KEY
parity-disk-2: $PARITY_DISK_2_KEY
parity-disk-3: $PARITY_DISK_3_KEY
+16
View File
@@ -0,0 +1,16 @@
#TARGET SETTINGS
TARGET_HOST="192.168.1.10"
TARGET_SSH_PUBLIC_KEY="ssh-ed25519 AAAAoefzefpoipoeCEZJCPEACPAcjapjcpajepcjAPJECJPEJAPJAZ yours@yourdomain.com"
# TRAEFIK SETTINGS
DOMAIN_NAME="yourdomain.com"
DOMAIN_EMAIL_ADDRESS="your-mail@yourdomain.com"
DOMAIN_CF_DNS_API_TOKEN="yourToken"
#SMTP SETTINGS
SENDER_EMAIL_ADDRESS="youraddress@gmail.com"
SENDER_EMAIL_ADDRESS_PASSWORD="emrp raps vzoi vnoe"
SENDER_EMAIL_DOMAIN="smtp.yourdomain.com"
SENDER_EMAIL_PORT="587"
#NETWORK SETTINGS
NETWORK_HOME_ROUTER_SUBNET="192.168.1.0/24"
NETWORK_HOME_ROUTER_IP="192.168.1.1"
NETWORK_HOME_SERVER_IP="192.168.1.5"
+421 -336
View File
@@ -1,266 +1,321 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
#!nix-shell -i bash -p gum xkcdpass sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto #!nix-shell -i bash -p gum fastfetch xkcdpass sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto
NECESSARY_VARIABLES_LIST=("TARGET_HOST" "SSH_PUBLIC_KEY" "DOMAIN_NAME" "EMAIL_ADDRESS" \
"CF_DNS_API_TOKEN" "SENDER_EMAIL_ADDRESS" "SENDER_EMAIL_ADDRESS_PASSWORD" \
### --> Default settings
export GUM_SPIN_SPINNER="minidot"
export GUM_SPIN_SPINNER_BOLD=true
export GUM_SPIN_SHOW_ERROR=true
export GUM_SPIN_TITLE_BOLD=true
NECESSARY_VARIABLES_LIST=("TARGET_HOST" "REMOTE_PASS" "SSH_PUBLIC_KEY" "DOMAIN_NAME" \
"EMAIL_ADDRESS" "CF_DNS_API_TOKEN" "SENDER_EMAIL_ADDRESS" "SENDER_EMAIL_ADDRESS_PASSWORD" \
"SENDER_EMAIL_DOMAIN" "SENDER_EMAIL_PORT" "HOME_ROUTER_SUBNET" "HOME_ROUTER_IP" \ "SENDER_EMAIL_DOMAIN" "SENDER_EMAIL_PORT" "HOME_ROUTER_SUBNET" "HOME_ROUTER_IP" \
"HOME_SERVER_IP") "HOME_SERVER_IP")
### Default settings <--
INSTALLED_REMOTE_PASS="changeMe!"
necessary_credentials() {
#TARGET SETTINGS
echo -e "\n\n ➡️ Please provide the IP address of the target host :"
export TARGET_HOST="$(gum input --placeholder "192.168.1.100")"
echo -e "\n\n ➡️ Please provide the public SSH key of an authorized device :"
export SSH_PUBLIC_KEY="$(gum input --placeholder "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGhcYDmjMo5YApLkk/3P3HZCnOSzm0uYewNAbxL8Fci8 user@your-pc")"
# TRAEFIK SETTINGS user_input() {
echo -e "\n\n ➡️ Please provide the domain name (FQDN) your home server will use :" local VAR_NAME="${1}"
export DOMAIN_NAME="$(gum input --placeholder "yourdomain.com")" local HEADER="${2}"
echo -e "\n\n ➡️ Please provide a valid email address (will be used for ACME, and your services) :" local PLACEHOLDER="${3}"
export EMAIL_ADDRESS="$(gum input --placeholder "myemail@gmail.com")" local REGEX="${4}"
echo -e "\n\n ➡️ Please provide a cloudflare API token with DNS zone permission :" local ERROR_MSG="${5}"
export CF_DNS_API_TOKEN="$(gum input --placeholder "bA7hdvCOuXGytlNKohi3ZGtlVpf5CHpLuCMiJrE")" local SENSITIVE="${6:-false}"
# SMTP SETTINGS while true; do
echo -e "\n\n ➡️ Some services will be able to send you emails. For that you need an email that supports sending emails.\n Please provide a valid sender email address :" [[ "$SENSITIVE" -eq "true" ]] && INPUT_VALUE=$(gum input --placeholder "${PLACEHOLDER}" --header "${HEADER}")
export SENDER_EMAIL_ADDRESS="$(gum input --placeholder "myemail@gmail.com")" [[ "$SENSITIVE" -eq "false" ]] && INPUT_VALUE=$(gum input --password --placeholder "${PLACEHOLDER}" --header "${HEADER}")
echo -e "\n\n ➡️ Please provide the password of this email address :"
export SENDER_EMAIL_ADDRESS_PASSWORD="$(gum input --placeholder "abcd efgh ijkl mnop")"
echo -e "\n\n ➡️ Please provide the SMTP server endpoint :"
export SENDER_EMAIL_DOMAIN="$(gum input --placeholder "smtp.gmail.com")"
echo -e "\n\n ➡️ Please provide the smtp TLS port (for gmail : 587) :"
export SENDER_EMAIL_PORT="$(gum input --placeholder "587")"
# NETWORK SETTINGS if [[ -z "${INPUT_VALUE}" ]]; then
echo -e "\n\n ➡️ Please provide your home network subnet :" echo "❌ Error: Input cannot be empty. Please provide the necessary information."
export HOME_ROUTER_SUBNET="$(gum input --placeholder "192.168.1.1/24")" continue
echo -e "\n\n ➡️ Please provide the ip address of your router :" fi
export HOME_ROUTER_IP="$(gum input --placeholder "192.168.1.1")"
echo -e "\n\n ➡️ Please choose the ip address that your server will use (i.e. any address in the 192.168.1.1/24 range that is not in use.) :" if [[ -n "${REGEX}" ]]; then
export HOME_SERVER_IP="$(gum input --placeholder "192.168.1.5")" if [[ ! "${INPUT_VALUE}" =~ ${REGEX} ]]; then
echo "❌ Error: ${ERROR_MSG}"
continue
fi
fi
export "${VAR_NAME}"="${INPUT_VALUE}"
break
done
} }
necessary_credentials_with_config() {
echo -e "\n\n ➡️ Please choose your configuration file :"
CONFIG_PATH="$(gum file)"
source "$CONFIG_PATH"
MISSING=0 necessary_credentials() {
# Regex Definitions
local IP_REGEX='^([0-9]{1,3}\.){3}[0-9]{1,3}$'
local SUBNET_REGEX='^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$'
local DOMAIN_REGEX='^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
local EMAIL_REGEX='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
local PORT_REGEX='^[0-9]{1,5}$'
local SSH_KEY_REGEX='^ssh-[a-z0-9]+ [A-Za-z0-9+/]+.*'
#TARGET SETTINGS
user_input "TARGET_HOST" "➡️ Please provide the IP address of the target host :" "192.168.1.100" "${IP_REGEX}" "Invalid IP address format."
user_input "REMOTE_PASS" "➡️ Please enter the password for '${TARGET_USER}@${TARGET_HOST}' :" "${TARGET_HOST}'s password" "" "" "true"
user_input "SSH_PUBLIC_KEY" "➡️ Please provide the public SSH key of an authorized device :" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGhcYDmjMo5YApLkk/3P3HZCnOSzm0uYewNAbxL8Fci8 user@your-pc" "${SSH_KEY_REGEX}" "Invalid SSH key format (must start with ssh-...)."
# TRAEFIK SETTINGS
user_input "DOMAIN_NAME" "➡️ Please provide the domain name (FQDN) your home server will use :" "yourdomain.com" "${DOMAIN_REGEX}" "Invalid domain name format."
user_input "EMAIL_ADDRESS" "➡️ Please provide a valid email address (will be used for ACME, and your services) :" "myemail@gmail.com" "${EMAIL_REGEX}" "Invalid email address format."
user_input "CF_DNS_API_TOKEN" "➡️ Please provide a cloudflare API token with DNS zone permission :" "bA7hdvCOuXGytlNKohi3ZGtlVpf5CHpLuCMiJrE" "" ""
# SMTP SETTINGS
echo -e "\n\n➡️ Some services will be able to send you emails. For that you need an email that supports sending emails (like Gmail for example)."
user_input "SENDER_EMAIL_ADDRESS" "➡️ Please provide a valid sender email address :" "myemail@gmail.com" "${EMAIL_REGEX}" "Invalid email address format."
user_input "SENDER_EMAIL_ADDRESS_PASSWORD" "➡️ Please provide the password of this email address :" "abcd efgh ijkl mnop" "" ""
user_input "SENDER_EMAIL_DOMAIN" "➡️ Please provide the SMTP server endpoint :" "smtp.gmail.com" "${DOMAIN_REGEX}" "Invalid domain name format."
user_input "SENDER_EMAIL_PORT" "➡️ Please provide the smtp TLS port (for gmail : 587) :" "587" "${PORT_REGEX}" "Invalid port number."
# NETWORK SETTINGS
user_input "HOME_ROUTER_SUBNET" "➡️ Please provide your home network subnet :" "192.168.1.1/24" "${SUBNET_REGEX}" "Invalid subnet format (e.g. 192.168.1.1/24)."
user_input "HOME_ROUTER_IP" "➡️ Please provide the ip address of your router :" "192.168.1.1" "${IP_REGEX}" "Invalid IP address format."
user_input "HOME_SERVER_IP" "➡️ Please choose the ip address that your server will use (i.e. any address in the 192.168.1.1/24 range that is not in use.) :" "192.168.1.5" "${IP_REGEX}" "Invalid IP address format."
}
necessary_credentials_with_config() {
echo -e "\n\n➡️ Please choose your configuration file :"
local CONFIG_PATH="$(gum file)"
source "${CONFIG_PATH}"
local MISSING=0
for VAR in "${NECESSARY_VARIABLES_LIST[@]}"; do for VAR in "${NECESSARY_VARIABLES_LIST[@]}"; do
if [[ -v $VAR && -n ${!VAR} ]]; then if [[ -v "${VAR}" && -n "${!VAR}" ]]; then
echo -e "\n ✅ $VAR imported successfully from the config file" gum spin --title "✅ "${VAR}" imported successfully from the config file" -- sleep 0.5
export $VAR
else else
echo "\n ❌ $VAR is missing or empty" gum spin --title "❌ "${VAR}" is missing or empty" -- sleep 0.5
MISSING=1 MISSING=1
fi fi
done done
if [[ "$MISSING" -eq "1" ]]; then if [[ "${MISSING}" -eq "1" ]]; then
echo -e "\n❌ Please check your configuration file to include all necessary variables"
exit 1 exit 1
fi fi
} }
generate_folder_tree() {
mkdir -p final-nix-config/
mkdir -p final-nix-config/home/
mkdir -p final-nix-config/home/numbus-admin/
mkdir -p final-nix-config/home/numbus-admin/.ssh/
mkdir -p final-nix-config/mnt/
mkdir -p final-nix-config/mnt/config/
mkdir -p final-nix-config/mnt/data/
mkdir -p final-nix-config/mnt/config/traefik/
mkdir -p final-nix-config/mnt/config/traefik/rules/
mkdir -p final-nix-config/mnt/config/traefik/certs/
mkdir -p final-nix-config/etc/
mkdir -p final-nix-config/etc/nixos/
mkdir -p final-nix-config/etc/secrets/
mkdir -p final-nix-config/etc/numbus-server/
mkdir -p final-nix-config/etc/nixos/misc/
mkdir -p final-nix-config/etc/nixos/pcie-coral/
mkdir -p final-nix-config/etc/nixos/podman/
mkdir -p final-nix-config/etc/nixos/secrets/
mkdir -p final-nix-config/var/
mkdir -p final-nix-config/var/lib/
mkdir -p final-nix-config/var/lib/sops-nix
}
setup_ssh() { setup_ssh() {
echo -e "\n\n ✅ Generating new SSH for numbus-admin..." echo -e "\n\n✅ Generating new SSH key for numbus-admin..."
mkdir -p extra-files/home/numbus-admin/.ssh/ chmod 700 final-nix-config/home/numbus-admin/.ssh/
chmod 700 extra-files/home/numbus-admin/.ssh/ ssh-keygen -t "ed25519" -C "numbus-admin@numbus-server" -f "final-nix-config/home/numbus-admin/.ssh/id_ed25519" -N "" -q
ssh-keygen -t "ed25519" -C "numbus-admin@numbus-server" -f "extra-files/home/numbus-admin/.ssh/id_ed25519" -N "" -q
LIVE_REMOTE_PASS=$(gum input --password --placeholder "Enter password for 'nixos@$TARGET_HOST'") echo -e "\n\n➡️ Copying SSH key to target host '${TARGET_USER}@${TARGET_HOST}'..."
if [ -z "$LIVE_REMOTE_PASS" ]; then if sshpass -p "${REMOTE_PASS}" ssh-copy-id -o StrictHostKeyChecking=no -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${TARGET_HOST}"; then
echo " ❌ Password is required to proceed. Aborting." echo "✅ SSH key copied successfully."
exit 1
fi
echo -e "\n\n ➡️ Copying SSH key to target host 'nixos@$TARGET_HOST'..."
if sshpass -p "$LIVE_REMOTE_PASS" ssh-copy-id -o StrictHostKeyChecking=no -i "extra-files/home/numbus-admin/.ssh/id_ed25519" "nixos@$TARGET_HOST"; then
echo " ✅ SSH key copied successfully."
else else
echo " ❌ Failed to copy SSH key. Please check the host IP and password." echo "❌ Failed to copy SSH key. Please check the host IP and password."
exit 1 exit 1
fi fi
export LIVE_REMOTE_PASS
} }
ssh_to_live_host() {
ARG="$1"
ssh -i "extra-files/home/numbus-admin/.ssh/id_ed25519" "nixos@$TARGET_HOST" $ARG
}
ssh_to_installed_host() {
ARG="$1" ssh_to_host() {
ssh -i "extra-files/home/numbus-admin/.ssh/id_ed25519" "numbus-admin@$TARGET_HOST" $ARG local COMMAND="${1}"
ssh -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${TARGET_HOST}" "${COMMAND}"
} }
hardware_detection() { hardware_detection() {
echo -e "\n\n 🔎 Detecting graphics card on target host..." ### --> Get hardware information
VGA_INFO=$(ssh_to_live_host "lspci -nn | grep -i 'vga'") local TMPFILE="/tmp/nixos-installation-hardware-detection-temp-file"
if echo "$VGA_INFO" | grep -iq "intel" 2>/dev/null; then
echo -e " ✅ Intel graphics card detected." ssh_to_host 'bash -s' << SSHEND
export TARGET_GRAPHICS="true" for brand in Intel AMD NVIDIA; do
elif echo "$VGA_INFO" | grep -iq "amd" 2>/dev/null; then if [[ lspci -nn | grep -i "vga" | grep -iq "\${brand}" ]]; then
echo -e " ✅ AMD graphics card detected." TARGET_GRAPHICS="true"
export TARGET_GRAPHICS="true" TARGET_GRAPHICS_BRAND+=("\${brand}")
elif echo "$VGA_INFO" | grep -iq "nvidia" 2>/dev/null; then
echo -e " ✅ NVIDIA graphics card detected."
export TARGET_GRAPHICS="true"
else else
echo -e " ⚠️ No dedicated graphics card detected." TARGET_GRAPHICS="false"
export TARGET_GRAPHICS="false"
fi fi
echo -e "\n\n 🔎 Detecting transconding acceleration on target host..." done
if ssh_to_live_host "ls /dev/dri/ | grep -iq 'renderD128'" 2>/dev/null; then
echo -e " ✅ Transcoding capable card detected." [[ ls /dev/dri/ | grep -iq "renderD128" ]] && TARGET_GRAPHICS_RENDERER="true" || TARGET_GRAPHICS_RENDERER="false"
TARGET_GRAPHICS_RENDERER="true" [[ lsusb | grep -iq "google" ]] && TARGET_USB_CORAL="true" || TARGET_USB_CORAL="false"
[[ lspci -nn | grep -iq "089a" ]] && TARGET_PCIE_CORAL="true" || TARGET_PCIE_CORAL="false"
[[ ls /dev/serial/by-id/ | grep -i "zigbee" ]] && TARGET_ZIGBEE_DEVICE=\$(ls /dev/serial/by-id/ | grep -i "zigbee" | head -n 1) || TARGET_ZIGBEE_DEVICE=""
for var in TARGET_GRAPHICS TARGET_GRAPHICS_BRAND TARGET_GRAPHICS_RENDERER TARGET_USB_CORAL TARGET_PCIE_CORAL TARGET_ZIGBEE_DEVICE; do
echo "export \${var}=\${!var}" >> "${TMPFILE}"
done
SSHEND
### Get hardware information <--
scp -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${TARGET_HOST}":"${TMPFILE}" "${TMPFILE}" &> /dev/null
source "${TMPFILE}" && rm "${TMPFILE}"
### --> Generate hardware-configuration.nix
echo -e "\n\n 🔎 Generating hardware-configuration.nix from target host..."
if ssh_to_host "sudo nixos-generate-config --no-filesystems --show-hardware-config" > final-nix-config/etc/nixos/hardware-configuration.nix; then
echo -e "✅ Hardware configuration generated"
else else
echo -e " ⚠️ No transcoding capable card detected." echo -e "❌ Failed to generate hardware configuration"
TARGET_GRAPHICS_RENDERER="false" exit 1
fi
echo -e "\n\n 🔎 Detecting USB Google Coral TPU on target host..."
if ssh_to_live_host "lsusb | grep -iq 'google'" 2>/dev/null; then
echo -e " ✅ USB Google Coral TPU detected."
TARGET_USB_CORAL="true"
else
echo -e " ⚠️ No USB Google Coral TPU detected."
TARGET_USB_CORAL="false"
fi
echo -e "\n\n 🔎 Detecting Zigbee coordinator on target host..."
if ssh_to_live_host "ls /dev/serial/by-id/ | grep -i 'zigbee'" 2>/dev/null; then
echo -e " ✅ Zigbee device found in /dev/serial/by-id/."
TARGET_ZIGBEE_DEVICE=$(ssh_to_live_host "ls /dev/serial/by-id/ | grep -i 'zigbee'")
else
echo -e " ⚠️ No Zigbee device found."
TARGET_ZIGBEE_DEVICE=""
fi fi
### Generate hardware-configuration.nix <--
} }
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:"
AVAILABLE_SERVICES=( "frigate" "gitea" "home-assistant" "immich" "it-tools" \ local AVAILABLE_SERVICES=( "frigate" "gitea" "home-assistant" "immich" "it-tools" \
"nextcloud" "passbolt" "pi-hole" ) "nextcloud" "passbolt" "pi-hole" )
AVAILABLE_SERVICES_NUMBER=${#AVAILABLE_SERVICES[@]}
SERVICES_DESCRIPTION=( "Pi-Hole : Block ads on all your devices" \ local SERVICES_DESCRIPTION=( "Pi-Hole : Block ads on all your devices" \
"Immich : Pictures and videos backup with local machine-learning" \ "Immich : Pictures and videos backup with local machine-learning" \
"Nextcloud : No fuss Office 365 replacement" \ "Nextcloud : No fuss Office 365 replacement" \
"Passbolt: Security-first password manager with collaboration features" \ "Passbolt: Security-first password manager with collaboration features" \
"Home-Assistant : Manage your smart home and security cameras" \ "Home-Assistant : Manage your smart home and security cameras" \
"Frigate [Home Assistant required] : Secure your house with security cameras" \ "Frigate [Home Assistant required] : Secure your house with security cameras" \
"Gitea : Your own git platform" \ "Gitea : Your own git platform" \
"IT-tools : A set of useful tools when doing IT" \ "IT-tools : A set of useful tools when doing IT"
) )
SELECTED_SERVICES_DESCRIPTION=$(gum choose --no-limit --header "Homelab services:" "${SERVICES_DESCRIPTION[@]}") local SELECTED_SERVICES_DESCRIPTION=$(gum choose --no-limit --header "Homelab services:" "${SERVICES_DESCRIPTION[@]}")
for i in $(seq 0 $((${#AVAILABLE_SERVICES[@]} - 1))); do for i in ${!AVAILABLE_SERVICES[@]}; do
if printf '%s' "$SELECTED_SERVICES_DESCRIPTION" | grep -iq "${AVAILABLE_SERVICES[$i]}"; then if printf '%s' "$SELECTED_SERVICES_DESCRIPTION" | grep -iq "${AVAILABLE_SERVICES[$i]}"; then
SELECTED_SERVICES+=(${AVAILABLE_SERVICES[$i]}) export SELECTED_SERVICES+=(${AVAILABLE_SERVICES[$i]})
fi fi
done done
for service in ${SELECTED_SERVICES[@]}; do
mkdir -p final-nix-config/mnt/config/"${service}"
mkdir -p final-nix-config/mnt/data/"${service}"
done
} }
files_generation() { files_generation() {
echo -e "\n ✅ Writing configuration files for the selected homelab services..." # Helper to generate standard DB credentials
# Traefik generate_db_creds() {
mkdir -p extra-files/mnt/config-storage/traefik/config/conf/ local SERVICE_UPPER="${1}"
envsubst < config-files/docker/config/traefik/traefik.yaml > extra-files/mnt/config-storage/traefik/config/traefik.yaml export "${SERVICE_UPPER}_DB_NAME"="$(xkcdpass -d "-" -n 2)"
export "${SERVICE_UPPER}_DB_USERNAME"="$(xkcdpass -d "-" -n 2)"
export "${SERVICE_UPPER}_DB_PASSWORD"="$(xkcdpass -d "-")"
}
for service in "${SELECTED_SERVICES[@]}"; do echo -e "\n✅ Copying the configuration to the new machine..."
# Frigate cp -avu templates/final-nix-config/etc/nixos/configuration.nix final-nix-config/etc/nixos/
if [[ "$service" -eq "frigate" ]]; then cp -avu templates/nix-config/flake.nix final-nix-config/etc/nixos/
echo -e "\n ✅ Adapting the docker configuration to your hardware..."
FRIGATE_DEVICES_BLOCK=""
if [[ "$TARGET_GRAPHICS_RENDERER" -eq "true" ]]; then
FRIGATE_DEVICES_BLOCK+=" - /dev/dri:/dev/dri\n"
fi
if [[ "$TARGET_USB_CORAL" -eq "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|" ./config-files/docker/compose/frigate.nix
else
sed -i.bak "/# --- frigate devices --- #/d" ./config-files/docker/compose/frigate.nix
fi
# Home-Assistant
elif [[ "$service" -eq "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/home-assistant.nix
else
sed -i.bak "/# --- hass devices --- #/d" ./config-files/docker/compose/home-assistant.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" -eq "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" -eq "pi-hole" ]]; then
export FTLCONF_WEBSERVER_PASSWORD="$(xkcdpass -d "-")"
# Immich
elif [[ "$service" -eq "immich" ]]; then
IMMICH_DEVICES_BLOCK=""
if [[ "$TARGET_GRAPHICS_RENDERER" -eq "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|" ./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" -eq "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" -eq "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 ./nix-config/docker/${service}.nix
done
echo -e "\n ✅ Generating sops-nix keys..." echo -e "\n✅ Generating sops-nix keys..."
mkdir -p extra-files/etc/secrets/disks/ ssh-to-age -private-key -i final-nix-config/home/numbus-admin/.ssh/id_ed25519 > final-nix-config/var/lib/sops-nix/key.txt
mkdir -p extra-files/var/lib/sops-nix/ export SOPS_PUBLIC_KEY=$(age-keygen -y final-nix-config/var/lib/sops-nix/key.txt)
mkdir -p extra-files/etc/nixos/secrets/
ssh-to-age -private-key -i extra-files/home/numbus-admin/.ssh/id_ed25519 > extra-files/var/lib/sops-nix/key.txt echo -e "\n✅ Generating sops-nix configuration files..."
export SOPS_PUBLIC_KEY=$(age-keygen -y extra-files/var/lib/sops-nix/key.txt) envsubst < templates/nix-config/sops-nix/.sops.yaml > final-nix-config/etc/nixos/.sops.yaml
echo -e "\n ✅ Generating sops-nix configuration files..." echo -e "\n✅ Encrypting secrets in the correct file..."
envsubst < config-files/sops-nix/.sops.yaml > extra-files/etc/nixos/.sops.yaml envsubst < "templates/nix-config/sops-nix/secrets.yaml" \
| sops encrypt --filename-override secrets.yaml \
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 \ --input-type yaml --output-type yaml \
--age $SOPS_PUBLIC_KEY \ --age $SOPS_PUBLIC_KEY \
--output extra-files/etc/nixos/secrets/secrets.yaml --output final-nix-config/etc/nixos/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/misc/networking.nix sed -i "s|HOME_SERVER_IP|${HOME_SERVER_IP}|g" final-nix-config/etc/nixos/misc/networking.nix
sed -i s+HOME_ROUTER_IP+$HOME_ROUTER_IP+g ./nix-config/misc/networking.nix sed -i "s|HOME_ROUTER_IP|${HOME_ROUTER_IP}|g" final-nix-config/etc/nixos/misc/networking.nix
echo -e "\n ✅ Copying the configuration to the new machine..." echo -e "\n✅ Writing configuration files for the selected homelab services..."
cp -ravu ./nix-config/* extra-files/etc/nixos/ envsubst < templates/podman-config/traefik/traefik.yaml > final-nix-config/mnt/config/traefik/traefik.yaml
for service in "${SELECTED_SERVICES[@]}"; do
cp templates/nix-config/podman/${service}.nix final-nix-config/etc/nixos/podman/${service}.nix
case "${service}" in
frigate)
local FRIGATE_DEVICES_BLOCK=""
[[ "$TARGET_GRAPHICS_RENDERER" -eq "true" ]] && local FRIGATE_DEVICES_BLOCK+=" - /dev/dri:/dev/dri\n"
[[ "$TARGET_USB_CORAL" -eq "true" ]] && local FRIGATE_DEVICES_BLOCK+=" - /dev/bus/usb:/dev/bus/usb\n"
if [[ "$TARGET_PCIE_CORAL" -eq "true" ]]; then
local FRIGATE_DEVICES_BLOCK+=" - /dev/apex_0:/dev/apex_0\n"
sed -i "s|# ./pcie-coral/coral.nix| ./pcie-coral/coral.nix|" final-nix-config/etc/nixos/configuration.nix
cp -avu templates/nix-config/pcie-coral/* final-nix-config/etc/nixos/pcie-coral/
fi
if [[ -n "$FRIGATE_DEVICES_BLOCK" ]]; then
local REPLACEMENT="devices:\n${FRIGATE_DEVICES_BLOCK%\\n}"
sed -i "s|# --- frigate devices --- #|$REPLACEMENT|" final-nix-config/etc/nixos/podman/frigate.nix
fi
;;
home-assistant)
if [[ -n "$TARGET_ZIGBEE_DEVICE" ]]; then
local REPLACEMENT="devices:\n - /dev/serial/by-id/${TARGET_ZIGBEE_DEVICE}:/dev/ttyUSB0"
sed -i "s|# --- hass devices --- #|$REPLACEMENT|" final-nix-config/etc/nixos/podman/home-assistant.nix
fi
export HOME_ASSISTANT_MQTT_USER="$(xkcdpass -d "-" -n 2)"
export HOME_ASSISTANT_MQTT_PASSWORD="$(xkcdpass -d "-")"
mkdir -p final-nix-config/mnt/config/mqtt/
envsubst < templates/podman-config/hass/mosquitto.conf > final-nix-config/mnt/config/mqtt/mosquitto.conf
touch final-nix-config/mnt/config/mqtt/password.txt
chmod 0700 final-nix-config/mnt/config/mqtt/password.txt
mosquitto_passwd -b final-nix-config/mnt/config/mqtt/password.txt "$HOME_ASSISTANT_MQTT_USER" "$HOME_ASSISTANT_MQTT_PASSWORD"
;;
passbolt)
generate_db_creds "PASSBOLT"
envsubst < templates/podman-config/traefik/headers.yaml > final-nix-config/mnt/config/traefik/rules/headers.yaml
envsubst < templates/podman-config/traefik/tls.yaml > final-nix-config/mnt/config/traefik/rules/tls.yaml
;;
pi-hole)
export FTLCONF_WEBSERVER_PASSWORD="$(xkcdpass -d "-")"
;;
immich)
local IMMICH_DEVICES_BLOCK=""
if [[ "$TARGET_GRAPHICS_RENDERER" -eq "true" ]]; then
local IMMICH_DEVICES_BLOCK+=" - /dev/dri:/dev/dri\n"
fi
if [[ -n "$IMMICH_DEVICES_BLOCK" ]]; then
local REPLACEMENT="devices:\n${IMMICH_DEVICES_BLOCK%\\n}"
sed -i "s|# --- immich devices --- #|$REPLACEMENT|" final-nix-config/etc/nixos/podman/immich.nix
fi
generate_db_creds "IMMICH"
;;
gitea)
generate_db_creds "GITEA"
;;
nextcloud)
envsubst < templates/podman-config/traefik/nextcloud.yaml > final-nix-config/mnt/config/traefik/rules/nextcloud.yaml
;;
esac
done
} }
disk_config_generation() { disk_config_generation() {
@@ -271,15 +326,15 @@ disk_config_generation() {
!! ALL DATA WILL BE WIPED ON THE DISKS YOU CHOOSE !! !! ALL DATA WILL BE WIPED ON THE DISKS YOU CHOOSE !!
Please press CTRL+C to abort. Please press CTRL+C to abort.
" "
gum confirm "Do you understand and wish to proceed?" || { echo -e "\n\n ❌ Aborting as requested."; exit 1; } gum confirm "Do you understand and wish to proceed?" || { echo -e "\n\n❌ Aborting as requested."; exit 1; }
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 <--
TMPFILE="/tmp/nixos-deployment-temp-file"
### --> Get disk information ### --> Get disk information
DISK_DETAILS=$(ssh_to_live_host 'bash -s' <<EOF local TMPFILE="/tmp/nixos-installation-disk-detection-temp-file"
ssh_to_host 'bash -s' << EOF
HDD=1 HDD=1
DISK_DEVPATH=() DISK_DEVPATH=()
@@ -303,7 +358,7 @@ for DISK in \$(lsblk -x SIZE -d -n -e 7,11 -o NAME); do
else DISK_TYPE+=("Other") else DISK_TYPE+=("Other")
fi fi
# Disk health # Disk health
if [[ \$(echo "$LIVE_REMOTE_PASS" | sudo -S smartctl -H /dev/\$DISK 2>/dev/null | grep 'self-assessment' | awk '{print \$6}') -eq "PASSED" ]]; then if [[ \$(echo "$REMOTE_PASS" | sudo -S smartctl -H /dev/\$DISK 2>/dev/null | grep 'self-assessment' | awk '{print \$6}') -eq "PASSED" ]]; then
DISK_HEALTH+=("PASSED") DISK_HEALTH+=("PASSED")
else else
DISK_HEALTH+=("N/A") DISK_HEALTH+=("N/A")
@@ -314,79 +369,69 @@ for DISK in \$(lsblk -x SIZE -d -n -e 7,11 -o NAME); do
DISK_SIZE+=("\$(lsblk -x SIZE -d -n -e 7,11 -o SIZE /dev/\$DISK)") DISK_SIZE+=("\$(lsblk -x SIZE -d -n -e 7,11 -o SIZE /dev/\$DISK)")
done done
echo "DISK_DEVPATH=(\${DISK_DEVPATH[@]})" > $TMPFILE echo "DISK_DEVPATH=(\${DISK_DEVPATH[@]})" > "${TMPFILE}"
echo "DISK_NAME=(\${DISK_NAME[@]})" >> $TMPFILE echo "DISK_NAME=(\${DISK_NAME[@]})" >> "${TMPFILE}"
echo "DISK_TYPE=(\${DISK_TYPE[@]})" >> $TMPFILE echo "DISK_TYPE=(\${DISK_TYPE[@]})" >> "${TMPFILE}"
echo "DISK_HEALTH=(\${DISK_HEALTH[@]})" >> $TMPFILE echo "DISK_HEALTH=(\${DISK_HEALTH[@]})" >> "${TMPFILE}"
echo "DISK_ID=(\${DISK_ID[@]})" >> $TMPFILE echo "DISK_ID=(\${DISK_ID[@]})" >> "${TMPFILE}"
echo "DISK_SIZE=(\${DISK_SIZE[@]})" >> $TMPFILE echo "DISK_SIZE=(\${DISK_SIZE[@]})" >> "${TMPFILE}"
EOF EOF
)
scp -i "extra-files/home/numbus-admin/.ssh/id_ed25519" nixos@$TARGET_HOST:$TMPFILE $TMPFILE &> /dev/null scp -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${TARGET_HOST}":"${TMPFILE}" "${TMPFILE}" &> /dev/null
source $TMPFILE && rm $TMPFILE source "${TMPFILE}" && rm "${TMPFILE}"
### --> Disk selection ### --> Disk selection
if [[ "${#DISK_NAME[@]}" -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") local HEADER=$(printf " %-12s %-12s %-12s %-12s %s" "Device" "Type" "Size" "SMART" "Path")
for i in ${!DISK_NAME[@]}; do for i in ${!DISK_NAME[@]}; do
GUM_PRINTED_ELEMENT=$(printf "%-12s %-12s %-12s %-12s %s" \ local 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}]}")
GUM_PRINTED_ELEMENTS+=("$GUM_PRINTED_ELEMENT") local GUM_PRINTED_ELEMENTS+=("$GUM_PRINTED_ELEMENT")
done done
gum style --foreground 212 " ➡️ Please choose one (stripe) or two (mirror) disks for your NixOS boot installation :" gum style --foreground 212 "➡️ Please choose one (stripe) or two (mirror) disks for your NixOS boot installation :"
SELECTED_BOOT_DISK=$(gum choose --limit 2 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}") local SELECTED_BOOT_DISK=$(gum choose --limit 2 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}")
for i in ${!DISK_NAME[@]}; do for i in ${!DISK_NAME[@]}; do
if printf '%s' "$SELECTED_BOOT_DISK" | grep -iqw "${DISK_NAME[${i}]}"; then if printf '%s' "$SELECTED_BOOT_DISK" | grep -iqw "${DISK_NAME[${i}]}"; then
if [[ -n "${DISK_ID[${i}]}" ]]; then export BOOT_DISKS_ID+=("${DISK_ID[${i}]:-${DISK_DEVPATH[${i}]}}")
export BOOT_DISKS_ID+=("${DISK_NAME[${i}]}")
else
export BOOT_DISKS_ID+=("${DISK_ID[${i}]}")
fi
unset "GUM_PRINTED_ELEMENTS[${i}]" unset "GUM_PRINTED_ELEMENTS[${i}]"
fi fi
done done
if [[ "${#BOOT_DISKS_ID[@]}" -eq 0 ]]; then if [[ "${#BOOT_DISKS_ID[@]}" -eq 0 ]]; then
echo -e "\n\n ❌ No boot disk selected. Aborting." echo -e "\n\n❌ No boot disk selected. Aborting."
exit 1 exit 1
elif [[ "${#BOOT_DISKS_ID[@]}" -eq 1 ]]; then elif [[ "${#BOOT_DISKS_ID[@]}" -eq 1 ]]; then
echo -e "\n\n ⚠️ One boot disk selected, continuing with striped boot disk configuration." echo -e "\n\n ⚠️ One boot disk selected, continuing with striped boot disk configuration."
echo -e " Consider using 2 boot disks instead to get data protection features on the boot disks." echo -e " Consider using 2 boot disks instead to get data protection features on the boot disks."
export BOOT_DISK_1_ID=${BOOT_DISKS_ID[0]} export BOOT_DISK_1_ID=${BOOT_DISKS_ID[0]}
elif [[ "${#BOOT_DISKS_ID[@]}" -eq 2 ]]; then elif [[ "${#BOOT_DISKS_ID[@]}" -eq 2 ]]; then
echo -e "\n\n ✅ Two boot disks selected, continuing with mirrored boot disks configuration." echo -e "\n\n✅ Two boot disks selected, continuing with mirrored boot disks configuration."
echo -e "\n\n ⚠️ If the two disks are different sizes, the resulting usable space size will be \ echo -e "\n\n ⚠️ If the two disks are different sizes, the resulting usable space size will be \
the one of the smallest disk." the one of the smallest disk."
export BOOT_DISK_1_ID=${BOOT_DISKS_ID[0]} export BOOT_DISK_1_ID=${BOOT_DISKS_ID[0]}
export BOOT_DISK_2_ID=${BOOT_DISKS_ID[1]} export BOOT_DISK_2_ID=${BOOT_DISKS_ID[1]}
else else
echo -e "\n\n ❌ Unexpected bug. Please contact the developer. Aborting." echo -e "\n\n❌ Unexpected bug. Please contact the developer. Aborting."
exit 1 exit 1
fi fi
gum style --foreground 212 " ➡️ Please choose data and parity disks (up to 9 total) :" gum style --foreground 212 "➡️ Please choose data and parity disks (up to 9 total) :"
SELECTED_DATA_DISK=$(gum choose --limit 9 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}") local SELECTED_DATA_DISK=$(gum choose --limit 9 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}")
for i in ${!DISK_NAME[@]}; do for i in ${!DISK_NAME[@]}; 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 [[ -n ${DISK_ID[${i}]} ]]; then export DATA_DISKS_ID+=("${DISK_ID[${i}]:-${DISK_DEVPATH[${i}]}}")
export DATA_DISKS_ID+=("${DISK_NAME[${i}]}") export DATA_DISKS_TYPE+=("${DISK_TYPE[${i}]}")
export DATA_DISKS_TYPE+=("${DISK_TYPE[${i}]}")
else
export DATA_DISKS_ID+=("${DISK_ID[${i}]}")
export DATA_DISKS_TYPE+=("${DISK_TYPE[${i}]}")
fi
fi fi
done done
@@ -395,7 +440,7 @@ the one of the smallest disk."
### Disk selection <-- ### Disk selection <--
### --> Selection recap ### --> Selection recap
RECAP_CONTENT=$(cat <<EOF RECAP_CONTENT=$(cat << EOF
### Disk Configuration Summary ### Disk Configuration Summary
Please review the selected disk layout before proceeding. Please review the selected disk layout before proceeding.
@@ -415,80 +460,77 @@ EOF
) )
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "$RECAP_CONTENT")" gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "$RECAP_CONTENT")"
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/templates/boot-${#BOOT_DISKS_ID[@]}.nix" local TEMPLATE_FILE="templates/nix-config/disks/boot-${#BOOT_DISKS_ID[@]}.nix"
(envsubst < "$TEMPLATE_FILE") > ./nix-config/disks/disko.nix (envsubst < "$TEMPLATE_FILE") > final-nix-config/etc/nixos/disks/disko.nix
echo -e "\n ✅ Generated boot disk configuration."
# Striped configuration # Striped configuration
if [[ "$CONTENT_DISK_NUMBER" -eq 1 && "$PARITY_DISK_NUMBER" -eq 0 ]]; then if [[ "$CONTENT_DISK_NUMBER" -eq 1 && "$PARITY_DISK_NUMBER" -eq 0 ]]; then
export j="1" export j="1"
export CONTENT_DISK_ID="${DATA_DISKS_ID[0]}" export CONTENT_DISK_ID="${DATA_DISKS_ID[0]}"
(envsubst < "config-files/disks/templates/content.nix") >> ./nix-config/disks/disko.nix (envsubst < "templates/nix-config/disks/content.nix") >> final-nix-config/etc/nixos/disks/disko.nix
sed -i "s|/mnt/content-1|/mnt/data-storage|" ./nix-config/disks/disko.nix sed -i "s|/mnt/content-1|/mnt/data-storage|" final-nix-config/etc/nixos/disks/disko.nix
# Mirror configuration # Mirror configuration
elif [[ "$CONTENT_DISK_NUMBER" -eq 1 && "$PARITY_DISK_NUMBER" -eq 1 ]]; then elif [[ "$CONTENT_DISK_NUMBER" -eq 1 && "$PARITY_DISK_NUMBER" -eq 1 ]]; then
export CONTENT_DISK_ID="${DATA_DISKS_ID[0]}" export CONTENT_DISK_ID="${DATA_DISKS_ID[0]}"
export PARITY_DISK_ID="${DATA_DISKS_ID[1]}" export PARITY_DISK_ID="${DATA_DISKS_ID[1]}"
(envsubst < "config-files/disks/templates/mirror.nix") >> ./nix-config/disks/disko.nix (envsubst < "templates/nix-config/disks/mirror.nix") >> final-nix-config/etc/nixos/disks/disko.nix
# SnapRAID configuration # SnapRAID configuration
elif [[ "$CONTENT_DISK_NUMBER" -gt 1 ]]; then elif [[ "$CONTENT_DISK_NUMBER" -gt 1 ]]; then
# Enable SnapRAID # Enable SnapRAID
sed -i "s|# ./disks/snapraid.nix| ./disks/snapraid.nix|" ./nix-config/configuration.nix cp -avu templates/nix-config/disks/snapraid.nix final-nix-config/etc/nixos/disks/
sed -i '$ d' ./config-files/disks/snapraid.nix cp -avu templates/nix-config/disks/pcr-check.nix final-nix-config/etc/nixos/disks/
cat <<EOF >> ./config-files/disks/snapraid.nix sed -i "s|# ./disks/snapraid.nix| ./disks/snapraid.nix|" final-nix-config/etc/nixos/configuration.nix
sed -i '$ d' final-nix-config/etc/nixos/disks/snapraid.nix
cat << EOF >> final-nix-config/etc/nixos/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
j=0 j=0
for i in $(seq 0 $(($CONTENT_DISK_NUMBER - 1))); do for i in $(seq 0 $(($CONTENT_DISK_NUMBER - 1))); do
export ((j++)) export ((j++))
LOOP_DISK="${DATA_DISKS_ID[${i}]}" export CONTENT_DISK_ID="${DATA_DISKS_ID[${i}]}"
export CONTENT_DISK_ID=${!LOOP_DISK} (envsubst < "templates/nix-config/disks/content.nix") >> final-nix-config/etc/nixos/disks/disko.nix
(envsubst < "config-files/disks/templates/content.nix") >> ./nix-config/disks/disko.nix cat << EOF >> final-nix-config/etc/nixos/disks/snapraid.nix
cat <<EOF >> ./config-files/disks/snapraid.nix
"crypted-content-disk-${j}" = { "crypted-content-disk-${j}" = {
device = "${!LOOP_DISK}"; device = "${CONTENT_DISK_ID}";
keyFile = "/etc/secrets/disks/content-disk-${j}"; keyFile = "/etc/secrets/disks/content-disk-${j}";
}; };
EOF EOF
done done
echo -e "\n ✅ Generated $CONTENT_DISK_NUMBER data disk configuration(s)." echo -e "\n✅ Generated $CONTENT_DISK_NUMBER data disk configuration(s)."
j=0 j=0
for i in $(seq $PARITY_DISK_NUMBER $((${#DATA_DISKS_ID[@]} - 1))); do for i in $(seq $PARITY_DISK_NUMBER $((${#DATA_DISKS_ID[@]} - 1))); do
export ((j++)) export ((j++))
LOOP_DISK="${DATA_DISKS_ID[${i}]}" export PARITY_DISK_ID="${DATA_DISKS_ID[${i}]}"
export PARITY_DISK_ID=${!LOOP_DISK} (envsubst < "templates/nix-config/disks/parity.nix") >> final-nix-config/etc/nixos/disks/disko.nix
(envsubst < "config-files/disks/templates/parity.nix") >> ./nix-config/disks/disko.nix cat << EOF >> final-nix-config/etc/nixos/disks/snapraid.nix
cat <<EOF >> ./config-files/disks/snapraid.nix
"crypted-parity-disk-${j}" = { "crypted-parity-disk-${j}" = {
device = "${!LOOP_DISK}"; device = "${PARITY_DISK_ID}";
keyFile = "/etc/secrets/disks/parity-disk-${j}}"; keyFile = "/etc/secrets/disks/parity-disk-${j}}";
}; };
EOF EOF
done done
echo -e "\n ✅ Generated $PARITY_DISK_NUMBER parity disk configuration(s)." echo -e "\n✅ Generated $PARITY_DISK_NUMBER parity disk configuration(s)."
# Close the snapraid.nix block # Close the snapraid.nix block
cat <<'EOF' >> ./config-files/disks/snapraid.nix cat <<'EOF' >> final-nix-config/etc/nixos/disks/snapraid.nix
# Automatic data disks unlock <-- # Automatic data disks unlock <--
}; };
} }
EOF EOF
cp -avu ./config-files/disks/snapraid.nix ./nix-config/disks/
fi fi
# Close the disko.nix block # Close the disko.nix block
cat <<'EOF' >> ./nix-config/disks/disko.nix cat <<'EOF' >> final-nix-config/etc/nixos/disks/disko.nix
}; };
}; };
} }
EOF EOF
echo -e "\n ✅ Final disko configuration created." echo -e "\n✅ Final disko configuration created."
if [[ -n "${DATA_DISKS_ID[@]}" ]]; then if [[ -n "${DATA_DISKS_ID[@]}" ]]; then
for i in ${!DATA_DISKS_ID[@]}; do for i in ${!DATA_DISKS_ID[@]}; do
@@ -497,41 +539,56 @@ EOF
fi fi
done done
if [[ -n "${DISK_ID_LIST[@]}" ]]; then if [[ -n "${DISK_ID_LIST[@]}" ]]; then
sed -i "s|DISK_ID_LIST|${DISK_ID_LIST[@]}|" ./config-files/disks/spindown.nix cp -avu templates/nix-config/disks/spindown.nix final-nix-config/etc/nixos/disks/
cp -avu ./config-files/disks/spindown.nix ./nix-config/disks/spindown.nix sed -i "s|DISK_ID_LIST|${DISK_ID_LIST[@]}|" final-nix-config/etc/nixos/disks/spindown.nix
echo -e "\n ✅ Disk spindown configuration created." echo -e "\n✅ Disk spindown configuration created."
fi fi
fi fi
### Config generation <-- ### Config generation <--
### --> Generate unlock keys ### --> Generate unlock keys
for i in ${#BOOT_DISKS_ID[@]}; do for i in $(seq 1 "${#BOOT_DISKS_ID[@]}"); do
declare "/etc/secrets/disks/boot-disk-${i}=$(xkcdpass -d "-")" PASS="$(xkcdpass -d "-")"
done echo -n "$PASS" > "final-nix-config/etc/secrets/disks/boot-disk-${i}"
for i in $CONTENT_DISK_NUMBER; do chmod 600 "final-nix-config/etc/secrets/disks/boot-disk-${i}"
declare "/etc/secrets/disks/content-disk-${i}=$(xkcdpass -d "-")"
done
for i in $PARITY_DISK_NUMBER; do
declare "/etc/secrets/disks/parity-disk-${i}=$(xkcdpass -d "-")"
done done
if [[ "$CONTENT_DISK_NUMBER" -gt 0 ]]; then
for i in $(seq 1 "$CONTENT_DISK_NUMBER"); do
PASS="$(xkcdpass -d "-")"
echo -n "$PASS" > "final-nix-config/etc/secrets/disks/content-disk-${i}"
chmod 600 "final-nix-config/etc/secrets/disks/content-disk-${i}"
done
fi
if [[ "$PARITY_DISK_NUMBER" -gt 0 ]]; then
for i in $(seq 1 "$PARITY_DISK_NUMBER"); do
PASS="$(xkcdpass -d "-")"
echo -n "$PASS" > "final-nix-config/etc/secrets/disks/parity-disk-${i}"
chmod 600 "final-nix-config/etc/secrets/disks/parity-disk-${i}"
done
fi
### Generate unlock keys <-- ### Generate unlock keys <--
} }
deploy() { export_configuration() {
echo -e "\n\n 🔄 Deploying to the remote server..." cp deploy.conf final-nix-config/etc/numbus-server/numbus-server.conf
nix run github:nix-community/nixos-anywhere -- \
--generate-hardware-config nixos-generate-config ./nix-config/hardware-configuration.nix \
--flake ./nix-config#numbus-server \
--extra-files extra-files \
--chown "/home/numbusing a us-admin/" 1000:1000 \
--target-host nixos@$TARGET_HOST
echo -e "\n\n ✅ Installation successfull !" local CONFIG_EXPORT_DIR="final-nix-config/etc/numbus-server/"
sleep 1 local CONFIG_EXPORT_FILE="${CONFIG_EXPORT_DIR}/numbus-server.conf"
cp -ravu templates/post-install/numbus-server.sh "$CONFIG_EXPORT_DIR"
echo "# SERVICE SETTINGS" >> $CONFIG_EXPORT_FILE
echo "SELECTED_SERVICES=(${SELECTED_SERVICES[@]})" >> $CONFIG_EXPORT_FILE
echo "# DISK SETTINGS" >> $CONFIG_EXPORT_FILE
echo "BOOT_DISK_ID_LIST=(${BOOT_DISKS_ID[@]})" >> $CONFIG_EXPORT_FILE
echo "DATA_DISKS_ID_LIST=(${DATA_DISKS_ID[@]})" >> $CONFIG_EXPORT_FILE
echo "SPINDOWN_DISKS_ID_LIST=(${DISK_ID_LIST[@]})" >> $CONFIG_EXPORT_FILE
echo "CONTENT_DISK_NUMBER=$CONTENT_DISK_NUMBER" >> $CONFIG_EXPORT_FILE
echo "PARITY_DISK_NUMBER=$PARITY_DISK_NUMBER" >> $CONFIG_EXPORT_FILE
} }
sum_up() { sum_up() {
RECAP_CONTENT=$(cat <<EOF RECAP_CONTENT=$(cat << EOF
### Generated Secrets Summary ### Generated Secrets Summary
Please save these secrets in a secure location (e.g., a password manager). Please save these secrets in a secure location (e.g., a password manager).
@@ -548,31 +605,49 @@ Please save these secrets in a secure location (e.g., a password manager).
* **Immich DB Password:** \`$IMMICH_DB_PASSWORD\` * **Immich DB Password:** \`$IMMICH_DB_PASSWORD\`
**Disk Encryption Keys:** **Disk Encryption Keys:**
$(for i in {1..2}; do key_var="BOOT_DISK_${i}_KEY"; [[ -n "${!key_var}" ]] && echo "* **Boot Disk $i Key:** \`${!key_var}\`"; done) $(for i in $(seq 1 "${#BOOT_DISKS_ID[@]}"); do f="final-nix-config/etc/secrets/disks/boot-disk-${i}"; [[ -f "$f" ]] && echo "* **Boot Disk $i Key:** \`$(cat "$f")\`"; done)
$(for i in {1..6}; do key_var="CONTENT_DISK_${i}_KEY"; [[ -n "${!key_var}" ]] && echo "* **Content Disk $i Key:** \`${!key_var}\`"; done) $(if [[ "$CONTENT_DISK_NUMBER" -gt 0 ]]; then for i in $(seq 1 "$CONTENT_DISK_NUMBER"); do f="final-nix-config/etc/secrets/disks/content-disk-${i}"; [[ -f "$f" ]] && echo "* **Content Disk $i Key:** \`$(cat "$f")\`"; done; fi)
$(for i in {1..3}; do key_var="PARITY_DISK_${i}_KEY"; [[ -n "${!key_var}" ]] && echo "* **Parity Disk $i Key:** \`${!key_var}\`"; done) $(if [[ "$PARITY_DISK_NUMBER" -gt 0 ]]; then for i in $(seq 1 "$PARITY_DISK_NUMBER"); do f="final-nix-config/etc/secrets/disks/parity-disk-${i}"; [[ -f "$f" ]] && echo "* **Parity Disk $i Key:** \`$(cat "$f")\`"; done; fi)
EOF EOF
) )
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "$RECAP_CONTENT")" gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "$RECAP_CONTENT")"
gum confirm "Do you want to deploy NixOS on the target host?" || { echo -e "\n\n❌ Aborting as requested"; exit 1; }
} }
deploy() {
echo -e "\n\n🔄 Deploying to the remote server..."
nix run github:nix-community/nixos-anywhere -- \
--flake ./final-nix-config/etc/nixos#numbus-server \
--extra-files final-nix-config \
--chown "/home/numbus-admin/" 1000:1000 \
--target-host ${TARGET_USER}@${TARGET_HOST}
echo -e "\n\n✅ Installation successfull !"
sleep 1
}
postrun_action() { postrun_action() {
echo -e "\n\n Now the remote machine will reboot. You will need to input the boot disk(s) passphrase. 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." 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 120 gum spin --title "Rebooting the remote..." -- sleep 120
gum confirm " ➡️ Select 'yes' once the machine rebooted and you unlocked the disks." || { echo -e "\n\n ❌ Aborting as requested."; exit 1; } gum confirm "➡️ Select 'yes' once the machine rebooted and you unlocked the disks." || { echo -e "\n\n❌ Aborting as requested."; exit 1; }
gum spin --spinner dot --title "\n\n 🔄 Waiting for the server to boot up..." --auto <<EOF gum spin --title "\n\n🔄 Waiting for the server to boot up..." --auto << EOF
while FOUND="false"; do while FOUND="false"; do
if ping -c1 -W1 $HOME_SERVER_IP >/dev/null 2>&1; then if ping -c1 -W1 $HOME_SERVER_IP >/dev/null 2>&1; then
FOUND="true" FOUND="true"
exit 0 exit 0
(i++) (i++)
if [[ "\${i}" -gt 150 ]]; then if [[ "\${i}" -gt 150 ]]; then
echo -e "\n\n ❌ Could not connect to the server after 150 retries. \ echo -e "\n\n❌ Could not connect to the server after 150 retries. \
This is most likely due to a networking issue. Please double check your network settings. Aborting." This is most likely due to a networking issue. Please double check your network settings. Aborting."
exit 1 exit 1
fi fi
@@ -580,17 +655,17 @@ This is most likely due to a networking issue. Please double check your network
done done
EOF EOF
ssh_to_installed_host 'bash -s' <<EOF ssh_to_host 'bash -s' << EOF
sed -i "s|# ./disks/pcr-check.nix| ./disks/pcr-check.nix|" /etc/nixos/configuration.nix sed -i "s|# ./disks/pcr-check.nix| ./disks/pcr-check.nix|" /etc/nixos/configuration.nix
if [[ ${#BOOT_DISKS_ID[@]} -eq 1 ]]; then if [[ ${#BOOT_DISKS_ID[@]} -eq 1 ]]; then
echo $INSTALLED_REMOTE_PASS | sudo -S systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/mapper/crypted-boot-1 echo $REMOTE_PASS | sudo -S systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/mapper/crypted-boot-1
elif [[ ${#BOOT_DISKS_ID[@]} -eq 2 ]]; then elif [[ ${#BOOT_DISKS_ID[@]} -eq 2 ]]; then
echo $INSTALLED_REMOTE_PASS | sudo -S systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/mapper/crypted-boot-1 echo $REMOTE_PASS | sudo -S systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/mapper/crypted-boot-1
echo $INSTALLED_REMOTE_PASS | sudo -S systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/mapper/crypted-boot-2 echo $REMOTE_PASS | sudo -S systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/mapper/crypted-boot-2
fi fi
PCR_HASH=\$(echo $INSTALLED_REMOTE_PASS | sudo -S systemd-analyze pcrs 15 --json=short) PCR_HASH=\$(echo $REMOTE_PASS | sudo -S systemd-analyze pcrs 15 --json=short)
sed -i "s|# systemIdentity.enable = true;| systemIdentity.enable = true;|" /etc/nixos/configuration.nix sed -i "s|# systemIdentity.enable = true;| systemIdentity.enable = true;|" /etc/nixos/configuration.nix
sed -i "s|# systemIdentity.pcr15 = "PCR_HASH";| systemIdentity.pcr15 = "PCR_HASH";|" /etc/nixos/configuration.nix sed -i "s|# systemIdentity.pcr15 = "PCR_HASH";| systemIdentity.pcr15 = "PCR_HASH";|" /etc/nixos/configuration.nix
@@ -603,11 +678,13 @@ You will almost never user it. Consider using a very strong password : you can w
securely on a hidden sheet of paper or add it to your password manager (local with Passbolt \ securely on a hidden sheet of paper or add it to your password manager (local with Passbolt \
any other online password manager provider.)." any other online password manager provider.)."
gum confirm " ➡️ I understand, 'yes' to proceed." || { echo -e "\n\n ❌ Aborting as requested."; exit 1; } gum confirm "➡️ I understand, 'yes' to proceed." || { echo -e "\n\n❌ Aborting as requested."; exit 1; }
echo $INSTALLED_REMOTE_PASS | sudo -S passwd numbus-admin echo $REMOTE_PASS | sudo -S passwd numbus-admin
} }
congrats() { congrats() {
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 " gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "
⚠️ $(gum style --foreground 212 'CONGRATULATIONS !!:') You now have a working home server. \ ⚠️ $(gum style --foreground 212 'CONGRATULATIONS !!:') You now have a working home server. \
@@ -621,79 +698,87 @@ it simple and use defaults) and take care to note down all the passwords. Change
} }
nixos_update() { nixos_update() {
echo -e "\n\n 🔄 Updating NixOS on the remote server..." echo -e "\n\n🔄 Updating NixOS on the remote server..."
echo "coming soon !" echo "coming soon !"
} }
set -euo pipefail set -euo pipefail
cat <<EOF fastfetch --logo nixos --structure ''
██████ █████ ███ ███████ █████████
░░██████ ░░███ ░░░ ███░░░░░███ ███░░░░░███
░███░███ ░███ ████ █████ █████ ███ ░░███░███ ░░░
░███░░███░███ ░░███ ░░███ ░░███ ░███ ░███░░█████████
░███ ░░██████ ░███ ░░░█████░ ░███ ░███ ░░░░░░░░███
░███ ░░█████ ░███ ███░░░███ ░░███ ███ ███ ░███
█████ ░░█████ █████ █████ █████ ░░░███████░ ░░█████████
░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░ ░░░░░░░░░
cat << EOF
██████ █████ █████
▒▒██████ ▒▒███ ▒▒███
▒███▒███ ▒███ █████ ████ █████████████ ▒███████ █████ ████ █████
▒███▒▒███▒███ ▒▒███ ▒███ ▒▒███▒▒███▒▒███ ▒███▒▒███▒▒███ ▒███ ███▒▒
▒███ ▒▒██████ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒▒█████
▒███ ▒▒█████ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒▒▒▒███
█████ ▒▒█████ ▒▒████████ █████▒███ █████ ████████ ▒▒████████ ██████
▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒
█████████
███▒▒▒▒▒███
▒███ ▒▒▒ ██████ ████████ █████ █████ ██████ ████████
▒▒█████████ ███▒▒███▒▒███▒▒███▒▒███ ▒▒███ ███▒▒███▒▒███▒▒███
▒▒▒▒▒▒▒▒███▒███████ ▒███ ▒▒▒ ▒███ ▒███ ▒███████ ▒███ ▒▒▒
███ ▒███▒███▒▒▒ ▒███ ▒▒███ ███ ▒███▒▒▒ ▒███
▒▒█████████ ▒▒██████ █████ ▒▒█████ ▒▒██████ █████
▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒
█████████ █████
███░░░░░███ ░░███
░███ ░███ ████████ █████ ████ █████ ███ █████ ░███████ ██████ ████████ ██████
░███████████ ░░███░░███ ░░███ ░███ ░░███ ░███░░███ ░███░░███ ███░░███░░███░░███ ███░░███
░███░░░░░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███████ ░███ ░░░ ░███████
░███ ░███ ░███ ░███ ░███ ░███ ░░███████████ ░███ ░███ ░███░░░ ░███ ░███░░░
█████ █████ ████ █████ ░░███████ ░░████░████ ████ █████░░██████ █████ ░░██████
░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░███ ░░░░ ░░░░ ░░░░ ░░░░░ ░░░░░░ ░░░░░ ░░░░░░
███ ░███
░░██████
░░░░░░
EOF EOF
sleep 1 sleep 1
# Choose the action # Choose the action
ACTION_ANSWER=$(gum choose "[1] 🌐 Deploy NixOS on a remote machine" "[2] 💽 Deploy NixOS on a remote machine with a file configuration" "[3] 🛠️ Update a NixOS remote machine") ACTION_ANSWER=$(gum choose "[1] 🌐 Deploy NixOS on a remote machine" "[2] 💽 Deploy NixOS on a remote machine with a file configuration" "[3] 🛠️ Update a NixOS remote machine")
echo $ACTION_ANSWER
TARGET_USER="nixos"
if [[ "$ACTION_ANSWER" -eq "[1] 🌐 Deploy NixOS on a remote machine" ]]; then if [[ "$ACTION_ANSWER" -eq "[1] 🌐 Deploy NixOS on a remote machine" ]]; then
echo -e "\n ➡️ Proceeding with deployment…" echo -e "\n➡️ Proceeding with deployment…"
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "➡️ On the target host : start the computer and boot into the NixOS iso. gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "➡️ On the target host : start the computer and boot into the NixOS iso.
Launch a console and set up a new user password." Launch a console and set up a new user password"
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; }
necessary_credentials necessary_credentials
setup_ssh setup_ssh
hardware_detection hardware_detection
services_selection services_selection
files_generation files_generation
disk_config_generation disk_config_generation
export_configuration
sum_up
deploy deploy
TARGET_USER="numbus-admin"
REMOTE_PASS="changeMe!"
postrun_action postrun_action
congrats congrats
elif [[ "$ACTION_ANSWER" -eq "[2] 💽 Deploy NixOS on a remote machine with a file configuration" ]]; then elif [[ "$ACTION_ANSWER" -eq "[2] 💽 Deploy NixOS on a remote machine with a file configuration" ]]; then
echo -e "\n ➡️ Proceeding with deployment using a config file…" echo -e "\n➡️ Proceeding with deployment using a config file…"
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "➡️ On the target host : start the computer and boot into the NixOS iso. gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "➡️ On the target host : start the computer and boot into the NixOS iso.
Launch a console and set up a new user password." Launch a console and set up a new user password"
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; }
necessary_credentials_with_config necessary_credentials_with_config
setup_ssh setup_ssh
gum spin --title "Generating folder tree..." -- generate_folder_tree
hardware_detection hardware_detection
services_selection services_selection
files_generation files_generation
disk_config_generation disk_config_generation
deploy export_configuration
sum_up sum_up
deploy
TARGET_USER="numbus-admin"
REMOTE_PASS="changeMe!"
postrun_action postrun_action
congrats congrats
elif [[ "$ACTION_ANSWER" -eq "[3] 🛠️ Update a NixOS remote machine" ]]; then elif [[ "$ACTION_ANSWER" -eq "[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
echo "Aborting - you did not type '1, 2 or 3'." echo "Aborting - you did not type '1, 2 or 3'"
exit 1 exit 1
fi fi
View File
View File
BIN
View File
Binary file not shown.
@@ -8,6 +8,7 @@
./disks/disko.nix ./disks/disko.nix
# ./disks/snapraid.nix # ./disks/snapraid.nix
# ./disks/pcr-check.nix # ./disks/pcr-check.nix
# ./pcie-coral/coral.nix
]; ];
# Hardware settings # Hardware settings
@@ -22,13 +23,15 @@
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."sender_email_address_password" = {};
sops.secrets."docker/frigate" = { owner = "numbus-admin"; path = "/etc/docker-compose/frigate/.env"; }; sops.secrets."podman/frigate" = { owner = "numbus-admin"; path = "/etc/podman/frigate/.env"; };
sops.secrets."docker/traefik" = { owner = "numbus-admin"; path = "/etc/docker-compose/traefik/.env"; }; sops.secrets."podman/gitea" = { owner = "numbus-admin"; path = "/etc/podman/gitea/.env"; };
sops.secrets."docker/nextcloud" = { owner = "numbus-admin"; path = "/etc/docker-compose/nextcloud/.env"; }; sops.secrets."podman/home_assistant" = { owner = "numbus-admin"; path = "/etc/podman/home-assistant/.env"; };
sops.secrets."docker/passbolt" = { owner = "numbus-admin"; path = "/etc/docker-compose/passbolt/.env"; }; sops.secrets."podman/immich" = { owner = "numbus-admin"; path = "/etc/podman/immich/.env"; };
sops.secrets."docker/hass" = { owner = "numbus-admin"; path = "/etc/docker-compose/hass/.env"; }; sops.secrets."podman/it_tools" = { owner = "numbus-admin"; path = "/etc/podman/immich/.env"; };
sops.secrets."docker/pihole" = { owner = "numbus-admin"; path = "/etc/docker-compose/pihole/.env"; }; sops.secrets."podman/nextcloud" = { owner = "numbus-admin"; path = "/etc/podman/nextcloud/.env"; };
sops.secrets."docker/immich" = { owner = "numbus-admin"; path = "/etc/docker-compose/immich/.env"; }; sops.secrets."podman/passbolt" = { owner = "numbus-admin"; path = "/etc/podman/passbolt/.env"; };
sops.secrets."podman/pi_hole" = { owner = "numbus-admin"; path = "/etc/podman/pi-hole/.env"; };
sops.secrets."podman/traefik" = { owner = "numbus-admin"; path = "/etc/podman/traefik/.env"; };
# Bootloader options # Bootloader options
boot.initrd.systemd.enable = true; boot.initrd.systemd.enable = true;
@@ -65,9 +68,7 @@
}; };
# Enable SSH # Enable SSH
services.openssh = { services.openssh.enable = true;
enable = true;
};
# Allow unfree packages # Allow unfree packages
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
@@ -87,6 +88,9 @@
smartmontools smartmontools
cpufrequtils cpufrequtils
intel-gpu-tools intel-gpu-tools
podman
podman-compose
podman-tui
]; ];
# Power savings # Power savings
@@ -104,17 +108,15 @@
]; ];
}; };
# Enable docker # Enable Podman
virtualisation.docker.enable = true; virtualisation.podman.enable = true;
virtualisation.docker.daemon.settings = { virtualisation.podman.defaultNetwork.settings.dns_enabled = true;
data-root = "/mnt/config-storage/docker-volumes/";
};
# User account # User account
users.users.numbus-admin = { users.users.numbus-admin = {
isNormalUser = true; isNormalUser = true;
description = "Numbus Admin"; description = "Numbus Admin";
extraGroups = [ "networkmanager" "wheel" "docker" ]; extraGroups = [ "networkmanager" "wheel" ];
uid = 1000; uid = 1000;
initialPassword = "changeMe!"; initialPassword = "changeMe!";
}; };
@@ -139,8 +141,16 @@
randomizedDelaySec = "45min"; randomizedDelaySec = "45min";
}; };
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d";
};
# Enable NixOS flakes # Enable NixOS flakes
nix.settings.experimental-features = [ "nix-command" "flakes" ]; nix.settings.experimental-features = [ "nix-command" "flakes" ];
# Enable auto nix-store optimization
nix.settings.auto-optimise-store = true;
system.stateVersion = "25.05"; system.stateVersion = "25.05";
} }
@@ -16,10 +16,19 @@
networking.networkmanager.enable = true; networking.networkmanager.enable = true;
networking.nftables.enable = true; networking.nftables.enable = true;
networking.firewall.enable = true; networking.firewall.enable = true;
# networking.firewall.extraCommands = "
# iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080 networking.nftables.tables.nat = {
# iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8443 family = "ip";
# "; content = ''
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
tcp dport 80 redirect to :8080
tcp dport 443 redirect to :8443
tcp dport 53 redirect to :5353
udp dport 53 redirect to :5353
}
'';
};
# Open ports in the firewall # Open ports in the firewall
networking.firewall.allowPing = true; networking.firewall.allowPing = true;
+12
View File
@@ -0,0 +1,12 @@
{ config, pkgs, ... }:
let
libedgetpu = pkgs.callPackage ./libedgetpu.nix {};
gasket = config.boot.kernelPackages.callPackage ./gasket.nix {};
in
{
services.udev.packages = [ libedgetpu ];
users.groups.plugdev = {};
boot.extraModulePackages = [ gasket ];
}
@@ -0,0 +1,35 @@
{ stdenv, lib, fetchFromGitHub, kernel }:
stdenv.mkDerivation rec {
pname = "gasket";
version = "1.0-18";
src = fetchFromGitHub {
owner = "google";
repo = "gasket-driver";
rev = "97aeba584efd18983850c36dcf7384b0185284b3";
sha256 = "pJwrrI7jVKFts4+bl2xmPIAD01VKFta2SRuElerQnTo=";
};
makeFlags = [
"-C"
"${kernel.dev}/lib/modules/${kernel.modDirVersion}/build"
"M=$(PWD)"
];
buildFlags = [ "modules" ];
installFlags = [ "INSTALL_MOD_PATH=${placeholder "out"}" ];
installTargets = [ "modules_install" ];
sourceRoot = "source/src";
hardeningDisable = [ "pic" "format" ];
nativeBuildInputs = kernel.moduleBuildDependencies;
meta = with lib; {
description = "The Coral Gasket Driver allows usage of the Coral EdgeTPU on Linux systems.";
homepage = "https://github.com/google/gasket-driver";
license = licenses.gpl2;
maintainers = [ lib.maintainers.kylehendricks ];
platforms = platforms.linux;
};
}
@@ -0,0 +1,59 @@
{ stdenv, lib, fetchFromGitHub, libusb1, abseil-cpp, flatbuffers, xxd }:
let
flatbuffers_1_12 = flatbuffers.overrideAttrs (oldAttrs: rec {
version = "1.12.0";
NIX_CFLAGS_COMPILE = "-Wno-error=class-memaccess -Wno-error=maybe-uninitialized";
cmakeFlags = (oldAttrs.cmakeFlags or []) ++ ["-DFLATBUFFERS_BUILD_SHAREDLIB=ON"];
NIX_CXXSTDLIB_COMPILE = "-std=c++17";
configureFlags = (oldAttrs.configureFlags or []) ++ ["--enable-shared"];
src = fetchFromGitHub {
owner = "google";
repo = "flatbuffers";
rev = "v${version}";
sha256 = "sha256-L1B5Y/c897Jg9fGwT2J3+vaXsZ+lfXnskp8Gto1p/Tg=";
};
});
in stdenv.mkDerivation rec {
pname = "libedgetpu";
version = "grouper";
src = fetchFromGitHub {
owner = "google-coral";
repo = pname;
rev = "release-${version}";
sha256 = "sha256-73hwItimf88Iqnb40lk4ul/PzmCNIfdt6Afi+xjNiBE=";
};
makeFlags = ["-f" "makefile_build/Makefile" "libedgetpu" ];
buildInputs = [
libusb1
abseil-cpp
flatbuffers_1_12
];
nativeBuildInputs = [
xxd
];
NIX_CXXSTDLIB_COMPILE = "-std=c++17";
TFROOT = "${fetchFromGitHub {
owner = "tensorflow";
repo = "tensorflow";
rev = "v2.7.4";
sha256 = "sha256-liDbUAdaVllB0b74aBeqNxkYNu/zPy7k3CevzRF5dk0=";
}}";
enableParallelBuilding = false;
installPhase = ''
mkdir -p $out/lib
cp out/direct/k8/libedgetpu.so.1.0 $out/lib
ln -s $out/lib/libedgetpu.so.1.0 $out/lib/libedgetpu.so.1
mkdir -p $out/lib/udev/rules.d
cp debian/edgetpu-accelerator.rules $out/lib/udev/rules.d/99-edgetpu-accelerator.rules
'';
}
@@ -2,14 +2,14 @@
let let
container_name = "frigate"; container_name = "frigate";
compose-dir = "docker-compose/frigate"; compose_file = "podman/frigate/compose.yaml";
config-dir = "/mnt/config-storage/docker-data/frigate"; config_dir = "/mnt/config/frigate";
data-dir = "/mnt/data-storage/docker-data/frigate"; data_dir = "/mnt/data/frigate";
in in
{ {
config = { config = {
environment.etc."${compose-dir}/compose.yaml".text = environment.etc."${compose_file}".text =
/* /*
yaml yaml
*/ */
@@ -17,16 +17,16 @@ in
services: services:
frigate: frigate:
image: ghcr.io/blakeblackshear/frigate:stable image: ghcr.io/blakeblackshear/frigate:stable
container_name: frigate container_name: ${container_name}
shm_size: "512MB" shm_size: "512MB"
networks: networks:
hass_frontend: hass_frontend:
hass_backend: hass_backend:
volumes: volumes:
- ${config-dir}/config:/config - ${config_dir}:/config
- ${data-dir}/clips:/media/frigate/clips - ${data_dir}/clips:/media/frigate/clips
- ${data-dir}/recordings:/media/frigate/recordings - ${data_dir}/recordings:/media/frigate/recordings
- ${data-dir}/exports:/media/frigate/exports - ${data_dir}/exports:/media/frigate/exports
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- type: tmpfs - type: tmpfs
target: /tmp/cache target: /tmp/cache
@@ -41,7 +41,7 @@ in
- traefik.http.services.frigate.loadbalancer.server.port=8971 - traefik.http.services.frigate.loadbalancer.server.port=8971
- traefik.http.services.frigate.loadbalancer.server.scheme=http - traefik.http.services.frigate.loadbalancer.server.scheme=http
- traefik.http.routers.frigate-https.entrypoints=websecure - traefik.http.routers.frigate-https.entrypoints=websecure
- traefik.http.routers.frigate-https.rule=Host(`cctv.$DOMAIN_NAME`) - traefik.http.routers.frigate-https.rule=Host(`${container_name}.$DOMAIN_NAME`)
- traefik.http.routers.frigate-https.tls=true - traefik.http.routers.frigate-https.tls=true
- traefik.http.routers.frigate-https.tls.certresolver=cloudflare - traefik.http.routers.frigate-https.tls.certresolver=cloudflare
restart: unless-stopped restart: unless-stopped
@@ -54,20 +54,20 @@ in
''; '';
systemd.services.frigate = { systemd.services.frigate = {
description = "Docker container : ${container_name}"; description = "Podman container : ${container_name}";
after = [ "network.target" "docker.service" "docker.socket" "traefik.service" ]; after = [ "network.target" "traefik.service" ];
requires = [ "docker.service" ]; requires = [ "network.target" ];
wantedBy = ["multi-user.target"]; wantedBy = ["multi-user.target"];
path = [ pkgs.docker ]; path = [ pkgs.podman-compose ];
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
# Pull the latest image before running # Pull the latest image before running
ExecStartPre = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml pull"; ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up # Bring the service up
ExecStart = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml up --remove-orphans"; ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully # Take it down gracefully
ExecStop = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml down"; ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -2,26 +2,27 @@
let let
container_name = "gitea"; container_name = "gitea";
compose-dir = "docker-compose/gitea"; compose_file = "podman/gitea/compose.yaml";
config-dir = "/mnt/config-storage/docker-data/gitea"; config_dir = "/mnt/config/gitea";
data_dir = "/mnt/data/gitea";
in in
{ {
config = { config = {
environment.etc."${compose-dir}/compose.yaml".text = environment.etc."${compose_file}".text =
/*DB_NAM /*
yaml yaml
*/ */
'' ''
services: services:
gitea: gitea:
image: gitea/gitea:latest image: gitea/gitea:latest
container_name: gitea container_name: ${container_name}
networks: networks:
gitea_frontend: gitea_frontend:
gitea_backend: gitea_backend:
volumes: volumes:
- ${config_dir}/data:/data - ${data_dir}:/data
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
environment: environment:
@@ -39,7 +40,7 @@ in
- traefik.http.services.gitea.loadbalancer.server.port=3000 - traefik.http.services.gitea.loadbalancer.server.port=3000
- traefik.http.services.gitea.loadbalancer.server.scheme=http - traefik.http.services.gitea.loadbalancer.server.scheme=http
- traefik.http.routers.gitea-https.entrypoints=websecure - traefik.http.routers.gitea-https.entrypoints=websecure
- traefik.http.routers.gitea-https.rule=Host(`gitea.$DOMAIN_NAME`) - traefik.http.routers.gitea-https.rule=Host(`${container_name}.$DOMAIN_NAME`)
- traefik.http.routers.gitea-https.tls=true - traefik.http.routers.gitea-https.tls=true
- traefik.http.routers.gitea-https.tls.certresolver=cloudflare - traefik.http.routers.gitea-https.tls.certresolver=cloudflare
depends_on: depends_on:
@@ -56,12 +57,9 @@ in
networks: networks:
gitea_backend: gitea_backend:
volumes: volumes:
- gitea-database:/var/lib/postgresql/data - ${config_dir}:/var/lib/postgresql/data
restart: unless-stopped restart: unless-stopped
volumes:
gitea-database:
networks: networks:
gitea_frontend: gitea_frontend:
external: true external: true
@@ -70,20 +68,20 @@ in
''; '';
systemd.services.gitea = { systemd.services.gitea = {
description = "Docker container : ${container_name}"; description = "Podman container : ${container_name}";
after = [ "network.target" "docker.service" "docker.socket" "traefik.service" ]; after = [ "network.target" "traefik.service" ];
requires = [ "docker.service" ]; requires = [ "network.target" ];
wantedBy = ["multi-user.target"]; wantedBy = ["multi-user.target"];
path = [ pkgs.docker ]; path = [ pkgs.podman-compose ];
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
# Pull the latest image before running # Pull the latest image before running
ExecStartPre = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml pull"; ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up # Bring the service up
ExecStart = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml up --remove-orphans"; ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully # Take it down gracefully
ExecStop = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml down"; ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -2,13 +2,14 @@
let let
container_name = "home-assistant"; container_name = "home-assistant";
compose-dir = "docker-compose/hass"; compose_file = "podman/home-assistant/compose.yaml";
config-dir = "/mnt/config-storage/docker-data/hass"; config_dir_1 = "/mnt/config/home-assistant";
config_dir_2 = "/mnt/config/mqtt";
in in
{ {
config = { config = {
environment.etc."${compose-dir}/compose.yaml".text = environment.etc."${compose_file}".text =
/* /*
yaml yaml
*/ */
@@ -16,12 +17,12 @@ in
services: services:
home-assistant: home-assistant:
image: ghcr.io/home-assistant/home-assistant:latest image: ghcr.io/home-assistant/home-assistant:latest
container_name: home-assistant container_name: ${container_name}
networks: networks:
hass_frontend: hass_frontend:
hass_backend: hass_backend:
volumes: volumes:
- ${config-dir}/config:/config - ${config_dir_1}:/config
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro - /run/dbus:/run/dbus:ro
# --- hass devices --- # # --- hass devices --- #
@@ -30,19 +31,19 @@ in
- traefik.http.services.home-assistant.loadbalancer.server.port=8123 - traefik.http.services.home-assistant.loadbalancer.server.port=8123
- traefik.http.services.home-assistant.loadbalancer.server.scheme=http - traefik.http.services.home-assistant.loadbalancer.server.scheme=http
- traefik.http.routers.home-assistant-https.entrypoints=websecure - traefik.http.routers.home-assistant-https.entrypoints=websecure
- traefik.http.routers.home-assistant-https.rule=Host(`hass.$DOMAIN_NAME`) - traefik.http.routers.home-assistant-https.rule=Host(`${container_name}.$DOMAIN_NAME`)
- traefik.http.routers.home-assistant-https.tls=true - traefik.http.routers.home-assistant-https.tls=true
- traefik.http.routers.home-assistant-https.tls.certresolver=cloudflare - traefik.http.routers.home-assistant-https.tls.certresolver=cloudflare
restart: unless-stopped restart: unless-stopped
frigate-mqtt: frigate-mqtt:
image: eclipse-mosquitto image: eclipse-mosquitto
container_name: frigate-mqtt container_name: mqtt
user: 1000:1000 user: 1000:1000
networks: networks:
hass_backend: hass_backend:
volumes: volumes:
- ${config-dir}/mqtt:/mosquitto - ${config_dir_2}:/mosquitto
restart: unless-stopped restart: unless-stopped
networks: networks:
@@ -53,20 +54,20 @@ in
''; '';
systemd.services.hass = { systemd.services.hass = {
description = "Docker container : ${container_name}"; description = "Podman container : ${container_name}";
after = [ "network.target" "docker.service" "docker.socket" "traefik.service" ]; after = [ "network.target" "traefik.service" ];
requires = [ "docker.service" ]; requires = [ "network.target" ];
wantedBy = ["multi-user.target"]; wantedBy = ["multi-user.target"];
path = [ pkgs.docker ]; path = [ pkgs.podman-compose ];
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
# Pull the latest image before running # Pull the latest image before running
ExecStartPre = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml pull"; ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up # Bring the service up
ExecStart = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml up --remove-orphans"; ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully # Take it down gracefully
ExecStop = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml down"; ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -2,13 +2,14 @@
let let
container_name = "immich"; container_name = "immich";
compose-dir = "docker-compose/immich"; compose_file = "podman/immich/compose.yaml";
config-dir = "/mnt/config-storage/docker-data/immich"; config_dir = "/mnt/config/immich";
data_dir = "/mnt/data/immich";
in in
{ {
config = { config = {
environment.etc."${compose-dir}/compose.yaml".text = environment.etc."${compose_file}".text =
/* /*
yaml yaml
*/ */
@@ -16,7 +17,7 @@ in
services: services:
immich-server: immich-server:
image: ghcr.io/immich-app/immich-server:$IMMICH_VERSION image: ghcr.io/immich-app/immich-server:$IMMICH_VERSION
container_name: immich-server container_name: ${container_name}-server
networks: networks:
immich_frontend: immich_frontend:
immich_backend: immich_backend:
@@ -29,7 +30,7 @@ in
- traefik.http.services.immich.loadbalancer.server.port=2283 - traefik.http.services.immich.loadbalancer.server.port=2283
- traefik.http.services.immich.loadbalancer.server.scheme=http - traefik.http.services.immich.loadbalancer.server.scheme=http
- traefik.http.routers.immich-https.entrypoints=websecure - traefik.http.routers.immich-https.entrypoints=websecure
- traefik.http.routers.immich-https.rule=Host(`immich.$DOMAIN_NAME`) - traefik.http.routers.immich-https.rule=Host(`${container_name}.$DOMAIN_NAME`)
- traefik.http.routers.immich-https.tls=true - traefik.http.routers.immich-https.tls=true
- traefik.http.routers.immich-https.tls.certresolver=cloudflare - traefik.http.routers.immich-https.tls.certresolver=cloudflare
env_file: env_file:
@@ -42,12 +43,12 @@ in
disable: false disable: false
immich-machine-learning: immich-machine-learning:
container_name: immich-machine-learning container_name: ${container_name}-machine-learning
image: ghcr.io/immich-app/immich-machine-learning:$IMMICH_VERSION image: ghcr.io/immich-app/immich-machine-learning:$IMMICH_VERSION
networks: networks:
immich_backend: immich_backend:
volumes: volumes:
- ${config-dir}/models:/cache - ${config_dir}/models:/cache
env_file: env_file:
- .env - .env
restart: always restart: always
@@ -55,7 +56,7 @@ in
disable: false disable: false
immich-redis: immich-redis:
container_name: immich-redis container_name: ${container_name}-redis
image: docker.io/valkey/valkey:8-bookworm@sha256:a137a2b60aca1a75130022d6bb96af423fefae4eb55faf395732db3544803280 image: docker.io/valkey/valkey:8-bookworm@sha256:a137a2b60aca1a75130022d6bb96af423fefae4eb55faf395732db3544803280
networks: networks:
immich_backend: immich_backend:
@@ -64,7 +65,7 @@ in
restart: always restart: always
immich-database: immich-database:
container_name: immich-database container_name: ${container_name}-database
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a
networks: networks:
immich_backend: immich_backend:
@@ -89,20 +90,20 @@ in
''; '';
systemd.services.immich = { systemd.services.immich = {
description = "Docker container : ${container_name}"; description = "Podman container : ${container_name}";
after = [ "network.target" "docker.service" "docker.socket" "traefik.service" ]; after = [ "network.target" "traefik.service" ];
requires = [ "docker.service" ]; requires = [ "network.target" ];
wantedBy = ["multi-user.target"]; wantedBy = ["multi-user.target"];
path = [ pkgs.docker ]; path = [ pkgs.podman-compose ];
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
# Pull the latest image before running # Pull the latest image before running
ExecStartPre = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml pull"; ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up # Bring the service up
ExecStart = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml up --remove-orphans"; ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully # Take it down gracefully
ExecStop = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml down"; ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -2,19 +2,19 @@
let let
container_name = "it-tools"; container_name = "it-tools";
compose-dir = "docker-compose/it-tools"; compose_file = "podman/it-tools/compose.yaml";
in in
{ {
config = { config = {
environment.etc."${compose-dir}/compose.yaml".text = environment.etc."${compose_file}".text =
/* /*
yaml yaml
*/ */
'' ''
services: services:
it-tools: it-tools:
container_name: it-tools container_name: ${container_name}
image: corentinth/it-tools image: corentinth/it-tools
networks: networks:
it-tools: it-tools:
@@ -23,7 +23,7 @@ in
- traefik.http.services.it-tools.loadbalancer.server.port=80 - traefik.http.services.it-tools.loadbalancer.server.port=80
- traefik.http.services.it-tools.loadbalancer.server.scheme=http - traefik.http.services.it-tools.loadbalancer.server.scheme=http
- traefik.http.routers.it-tools-https.entrypoints=websecure - 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.rule=Host(`${container_name}.$DOMAIN_NAME`)
- traefik.http.routers.it-tools-https.tls=true - traefik.http.routers.it-tools-https.tls=true
- traefik.http.routers.it-tools-https.tls.certresolver=cloudflare - traefik.http.routers.it-tools-https.tls.certresolver=cloudflare
restart: unless-stopped restart: unless-stopped
@@ -33,20 +33,20 @@ in
''; '';
systemd.services.it-tools = { systemd.services.it-tools = {
description = "Docker container : ${container_name}"; description = "Podman container : ${container_name}";
after = [ "network.target" "docker.service" "docker.socket" "traefik.service" ]; after = [ "network.target" "traefik.service" ];
requires = [ "docker.service" ]; requires = [ "network.target" ];
wantedBy = ["multi-user.target"]; wantedBy = ["multi-user.target"];
path = [ pkgs.docker ]; path = [ pkgs.podman-compose ];
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
# Pull the latest image before running # Pull the latest image before running
ExecStartPre = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml pull"; ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up # Bring the service up
ExecStart = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml up --remove-orphans"; ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully # Take it down gracefully
ExecStop = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml down"; ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -2,13 +2,13 @@
let let
container_name = "nextcloud"; container_name = "nextcloud";
compose-dir = "docker-compose/nextcloud"; compose_file = "podman/nextcloud/compose.yaml";
data-dir = "/mnt/data-storage/docker-data/nextcloud"; data_dir = "/mnt/data/nextcloud";
in in
{ {
config = { config = {
environment.etc."${compose-dir}/compose.yaml".text = environment.etc."${compose_file}".text =
/* /*
yaml yaml
*/ */
@@ -16,7 +16,7 @@ in
services: services:
nextcloud-aio-mastercontainer: nextcloud-aio-mastercontainer:
image: nextcloud/all-in-one:latest image: nextcloud/all-in-one:latest
container_name: nextcloud-aio-mastercontainer container_name: ${container_name}-aio-mastercontainer
networks: networks:
nextcloud-aio: nextcloud-aio:
volumes: volumes:
@@ -27,7 +27,7 @@ in
NEXTCLOUD_TRUSTED_DOMAINS: nextcloud.$DOMAIN_NAME nextcloud-aio.$DOMAIN_NAME NEXTCLOUD_TRUSTED_DOMAINS: nextcloud.$DOMAIN_NAME nextcloud-aio.$DOMAIN_NAME
TRUSTED_PROXIES: 172.16.1.253 TRUSTED_PROXIES: 172.16.1.253
APACHE_IP_BINDING: 127.0.0.1 APACHE_IP_BINDING: 127.0.0.1
NEXTCLOUD_DATADIR: ${data-dir} NEXTCLOUD_DATADIR: ${data_dir}
NEXTCLOUD_ENABLE_DRI_DEVICE: $NEXTCLOUD_ENABLE_DRI_DEVICE NEXTCLOUD_ENABLE_DRI_DEVICE: $NEXTCLOUD_ENABLE_DRI_DEVICE
NEXTCLOUD_UPLOAD_LIMIT: 16G NEXTCLOUD_UPLOAD_LIMIT: 16G
NEXTCLOUD_MAX_TIME: 3600 NEXTCLOUD_MAX_TIME: 3600
@@ -40,7 +40,7 @@ in
- traefik.http.services.nextcloud-aio.loadbalancer.server.port=8080 - traefik.http.services.nextcloud-aio.loadbalancer.server.port=8080
- traefik.http.services.nextcloud-aio.loadbalancer.server.scheme=https - traefik.http.services.nextcloud-aio.loadbalancer.server.scheme=https
- traefik.http.routers.nextcloud-aio-https.entrypoints=websecure - traefik.http.routers.nextcloud-aio-https.entrypoints=websecure
- traefik.http.routers.nextcloud-aio-https.rule=Host(`nextcloud-aio.$DOMAIN_NAME`) - traefik.http.routers.nextcloud-aio-https.rule=Host(`${container_name}-aio.$DOMAIN_NAME`)
- traefik.http.routers.nextcloud-aio-https.tls=true - traefik.http.routers.nextcloud-aio-https.tls=true
- traefik.http.routers.nextcloud-aio-https.tls.certresolver=cloudflare - traefik.http.routers.nextcloud-aio-https.tls.certresolver=cloudflare
init: true init: true
@@ -56,20 +56,20 @@ in
''; '';
systemd.services.nextcloud = { systemd.services.nextcloud = {
description = "Docker container : ${container_name}"; description = "Podman container : ${container_name}";
after = [ "network.target" "docker.service" "docker.socket" "traefik.service" ]; after = [ "network.target" "traefik.service" ];
requires = [ "docker.service" ]; requires = [ "network.target" ];
wantedBy = ["multi-user.target"]; wantedBy = ["multi-user.target"];
path = [ pkgs.docker ]; path = [ pkgs.podman-compose ];
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
# Pull the latest image before running # Pull the latest image before running
ExecStartPre = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml pull"; ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up # Bring the service up
ExecStart = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml up --remove-orphans"; ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully # Take it down gracefully
ExecStop = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml down"; ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -2,12 +2,12 @@
let let
container_name = "passbolt"; container_name = "passbolt";
compose-dir = "docker-compose/passbolt"; compose_file = "podman/passbolt/compose.yaml";
in in
{ {
config = { config = {
environment.etc."${compose-dir}/compose.yaml".text = environment.etc."${compose_file}".text =
/* /*
yaml yaml
*/ */
@@ -15,7 +15,7 @@ in
services: services:
passbolt: passbolt:
image: passbolt/passbolt:latest-ce-non-root image: passbolt/passbolt:latest-ce-non-root
container_name: passbolt container_name: ${container_name}
networks: networks:
passbolt_frontend: passbolt_frontend:
passbolt_backend: passbolt_backend:
@@ -60,7 +60,7 @@ in
passbolt-database: passbolt-database:
image: mariadb:11.3 image: mariadb:11.3
container_name: passbolt-database container_name: ${container_name}-database
networks: networks:
passbolt_backend: passbolt_backend:
volumes: volumes:
@@ -85,20 +85,20 @@ in
''; '';
systemd.services.passbolt = { systemd.services.passbolt = {
description = "Docker container : ${container_name}"; description = "Podman container : ${container_name}";
after = [ "network.target" "docker.service" "docker.socket" "traefik.service" ]; after = [ "network.target" "traefik.service" ];
requires = [ "docker.service" ]; requires = [ "network.target" ];
wantedBy = ["multi-user.target"]; wantedBy = ["multi-user.target"];
path = [ pkgs.docker ]; path = [ pkgs.podman-compose ];
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
# Pull the latest image before running # Pull the latest image before running
ExecStartPre = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml pull"; ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up # Bring the service up
ExecStart = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml up --remove-orphans"; ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully # Take it down gracefully
ExecStop = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml down"; ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -2,13 +2,13 @@
let let
container_name = "pihole"; container_name = "pihole";
compose-dir = "docker-compose/pihole"; compose_file = "podman/pihole/compose.yaml";
config-dir = "/mnt/config-storage/docker-data/pihole"; config_dir = "/mnt/config/pihole";
in in
{ {
config = { config = {
environment.etc."${compose-dir}/compose.yaml".text = environment.etc."${compose_file}".text =
/* /*
yaml yaml
*/ */
@@ -16,7 +16,7 @@ in
services: services:
pihole: pihole:
image: pihole/pihole:latest image: pihole/pihole:latest
container_name: pihole container_name: ${container_name}
networks: networks:
pihole: pihole:
ports: ports:
@@ -42,7 +42,7 @@ in
PIHOLE_UID: 1000 PIHOLE_UID: 1000
PIHOLE_GID: 1000 PIHOLE_GID: 1000
volumes: volumes:
- ${config-dir}/config:/etc/pihole - ${config_dir}:/etc/pihole
cap_add: cap_add:
- SYS_TIME - SYS_TIME
- SYS_NICE - SYS_NICE
@@ -51,7 +51,7 @@ in
- traefik.http.services.pihole.loadbalancer.server.port=443 - traefik.http.services.pihole.loadbalancer.server.port=443
- traefik.http.services.pihole.loadbalancer.server.scheme=https - traefik.http.services.pihole.loadbalancer.server.scheme=https
- traefik.http.routers.pihole-https.entrypoints=websecure - traefik.http.routers.pihole-https.entrypoints=websecure
- traefik.http.routers.pihole-https.rule=Host(`dns.$DOMAIN_NAME`) - traefik.http.routers.pihole-https.rule=Host(`${container_name}.$DOMAIN_NAME`)
- traefik.http.routers.pihole-https.tls=true - traefik.http.routers.pihole-https.tls=true
- traefik.http.routers.pihole-https.tls.certresolver=cloudflare - traefik.http.routers.pihole-https.tls.certresolver=cloudflare
restart: unless-stopped restart: unless-stopped
@@ -62,20 +62,20 @@ in
''; '';
systemd.services.pihole = { systemd.services.pihole = {
description = "Docker container : ${container_name}"; description = "Podman container : ${container_name}";
after = [ "network.target" "docker.service" "docker.socket" "traefik.service" ]; after = [ "network.target" "traefik.service" ];
requires = [ "docker.service" ]; requires = [ "network.target" ];
wantedBy = ["multi-user.target"]; wantedBy = ["multi-user.target"];
path = [ pkgs.docker ]; path = [ pkgs.podman-compose ];
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
# Pull the latest image before running # Pull the latest image before running
ExecStartPre = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml pull"; ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up # Bring the service up
ExecStart = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml up --remove-orphans"; ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully # Take it down gracefully
ExecStop = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml down"; ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -2,13 +2,13 @@
let let
container_name = "traefik"; container_name = "traefik";
compose-dir = "docker-compose/traefik"; compose_file = "podman/traefik/compose.yaml";
config-dir = "/mnt/config-storage/docker-data/traefik"; config_dir = "/mnt/config/traefik";
in in
{ {
config = { config = {
environment.etc."${compose-dir}/compose.yaml".text = environment.etc."${compose_file}".text =
/* /*
yaml yaml
*/ */
@@ -16,7 +16,7 @@ in
services: services:
traefik: traefik:
image: docker.io/library/traefik:latest image: docker.io/library/traefik:latest
container_name: traefik container_name: ${container_name}
networks: networks:
nextcloud-aio: nextcloud-aio:
ipv4_address: 172.16.1.253 ipv4_address: 172.16.1.253
@@ -28,14 +28,18 @@ in
ipv4_address: 172.16.40.253 ipv4_address: 172.16.40.253
immich_frontend: immich_frontend:
ipv4_address: 172.16.50.253 ipv4_address: 172.16.50.253
gitea_frontend:
ipv4_address: 172.16.60.253
it-tools:
ipv4_address: 172.16.7.253
ports: ports:
- 80:80 - 80:80
- 443:443 - 443:443
volumes: volumes:
- /run/docker.sock:/run/docker.sock:ro - /run/user/1000/podman/podman.sock:/run/docker.sock:ro
- ${config-dir}/config/conf/:/etc/traefik/conf/:ro - ${config_dir}/rules/:/etc/traefik/conf/:ro
- ${config-dir}/config/traefik.yaml:/etc/traefik/traefik.yaml:ro - ${config_dir}/traefik.yaml:/etc/traefik/traefik.yaml:ro
- ${config-dir}/certs/:/var/traefik/certs/:rw - ${config_dir}/certs/:/var/traefik/certs/:rw
environment: environment:
- CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN - CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN
labels: labels:
@@ -43,7 +47,7 @@ in
- traefik.http.services.traefik.loadbalancer.server.port=8080 - traefik.http.services.traefik.loadbalancer.server.port=8080
- traefik.http.services.traefik.loadbalancer.server.scheme=http - traefik.http.services.traefik.loadbalancer.server.scheme=http
- traefik.http.routers.traefik-https.entrypoints=websecure - traefik.http.routers.traefik-https.entrypoints=websecure
- traefik.http.routers.traefik-https.rule=Host(`reverse.$DOMAIN_NAME`) - traefik.http.routers.traefik-https.rule=Host(`${container_name}.$DOMAIN_NAME`)
- traefik.http.routers.traefik-https.tls=true - traefik.http.routers.traefik-https.tls=true
- traefik.http.routers.traefik-https.tls.certresolver=cloudflare - traefik.http.routers.traefik-https.tls.certresolver=cloudflare
restart: always restart: always
@@ -104,23 +108,44 @@ in
config: config:
- subnet: "172.16.50.0/24" - subnet: "172.16.50.0/24"
gateway: "172.16.50.254" gateway: "172.16.50.254"
gitea_backend:
name: gitea_backend
driver: bridge
ipam:
config:
- subnet: "172.16.6.0/24"
gateway: "172.16.6.254"
gitea_frontend:
name: gitea_frontend
driver: bridge
ipam:
config:
- subnet: "172.16.60.0/24"
gateway: "172.16.60.254"
it-tools:
name: it-tools
driver: bridge
ipam:
config:
- subnet: "172.16.7.0/24"
gateway: "172.16.7.254"
''; '';
systemd.services.traefik = { systemd.services.traefik = {
description = "Docker container : ${container_name}"; description = "Podman container : ${container_name}";
after = [ "network.target" "docker.service" "docker.socket" ]; after = [ "network.target" "docker.socket" ];
requires = [ "docker.service" ]; requires = [ "network.target" ];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
path = [ pkgs.docker ]; path = [ pkgs.podman-compose ];
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
# Pull the latest image before running # Pull the latest image before running
ExecStartPre = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml pull"; ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
# Bring the service up # Bring the service up
ExecStart = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml up --remove-orphans"; ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully # Take it down gracefully
ExecStop = "${pkgs.docker}/bin/docker compose -f /etc/${compose-dir}/compose.yaml down"; ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure"; Restart = "on-failure";
}; };
@@ -0,0 +1,68 @@
ssh_public_keys: "${SSH_PUBLIC_KEY}"
sender_email_address_password: "${SENDER_EMAIL_ADDRESS_PASSWORD}"
podman:
frigate: |
DOMAIN_NAME="${DOMAIN_NAME}"
FRIGATE_MQTT_USER="${HOME_ASSISTANT_MQTT_USER}"
FRIGATE_MQTT_PASSWORD="${HOME_ASSISTANT_MQTT_PASSWORD}"
gitea: |
DOMAIN_NAME="${DOMAIN_NAME}"
DB_NAME="${GITEA_DB_NAME}"
DB_USERNAME="${GITEA_DB_USERNAME}"
DB_PASSWORD="${GITEA_DB_PASSWORD}"
POSTGRES_HOST="gitea-database"
POSTGRES_PORT="5432"
home_assistant: |
DOMAIN_NAME="${DOMAIN_NAME}"
HOME_ASSISTANT_MQTT_USER="${HOME_ASSISTANT_MQTT_USER}"
HOME_ASSISTANT_MQTT_PASSWORD="${HOME_ASSISTANT_MQTT_PASSWORD}"
immich: |
DOMAIN_NAME="${DOMAIN_NAME}"
DB_DATABASE_NAME="${IMMICH_DB_NAME}"
DB_USERNAME="${IMMICH_DB_USERNAME}"
DB_PASSWORD="${IMMICH_DB_PASSWORD}"
IMMICH_VERSION="release"
IMMICH_TRUSTED_PROXIES="172.16.50.253"
REDIS_HOSTNAME="immich-redis"
DB_HOSTNAME="immich-database"
UPLOAD_LOCATION="/mnt/data/immich"
DB_DATA_LOCATION="/mnt/config/immich/database"
TZ="Europe/Paris"
it_tools: |
DOMAIN_NAME="${DOMAIN_NAME}"
nextcloud: |
DOMAIN_NAME="${DOMAIN_NAME}"
NEXTCLOUD_ENABLE_DRI_DEVICE="${TARGET_GRAPHICS}"
passbolt: |
DOMAIN_NAME="${DOMAIN_NAME}"
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}"
SENDER_EMAIL_PORT="${SENDER_EMAIL_PORT}"
EMAIL_ADDRESS="${EMAIL_ADDRESS}"
TZ="Europe/Paris"
pi_hole: |
DOMAIN_NAME="${DOMAIN_NAME}"
HOME_ROUTER_SUBNET="${HOME_ROUTER_SUBNET}"
HOME_ROUTER_IP="${HOME_ROUTER_IP}"
HOME_SERVER_IP="${HOME_SERVER_IP}"
FTLCONF_webserver_api_password=$FTLCONF_WEBSERVER_PASSWORD
TZ="Europe/Paris"
traefik: |
DOMAIN_NAME="${DOMAIN_NAME}"
CF_DNS_API_TOKEN="${CF_DNS_API_TOKEN}"
disks:
content-disk-1: "${CONTENT_DISK_1_KEY:-Undefined}"
content-disk-2: "${CONTENT_DISK_2_KEY:-Undefined}"
content-disk-3: "${CONTENT_DISK_3_KEY:-Undefined}"
content-disk-4: "${CONTENT_DISK_4_KEY:-Undefined}"
content-disk-5: "${CONTENT_DISK_5_KEY:-Undefined}"
content-disk-6: "${CONTENT_DISK_6_KEY:-Undefined}"
parity-disk-1: "${PARITY_DISK_1_KEY:-Undefined}"
parity-disk-2: "${PARITY_DISK_2_KEY:-Undefined}"
parity-disk-3: "${PARITY_DISK_3_KEY:-Undefined}"
@@ -48,7 +48,7 @@ serversTransport:
providers: providers:
docker: docker:
exposedByDefault: false exposedByDefault: false
network: nextcloud-aio, passbolt_frontend, pihole, hass_frontend, immich_frontend network: nextcloud-aio, passbolt_frontend, pihole, hass_frontend, immich_frontend, it-tools, gitrea_frontend
file: file:
directory: "/etc/traefik/conf/" directory: "/etc/traefik/conf/"
watch: true watch: true
+24
View File
@@ -0,0 +1,24 @@
#TARGET SETTINGS
TARGET_HOST="192.168.1.10"
TARGET_SSH_PUBLIC_KEY="ssh-ed25519 AAAAoefzefpoipoeCEZJCPEACPAcjapjcpajepcjAPJECJPEJAPJAZ yours@yourdomain.com"
# TRAEFIK SETTINGS
DOMAIN_NAME="yourdomain.com"
DOMAIN_EMAIL_ADDRESS="your-mail@yourdomain.com"
DOMAIN_CF_DNS_API_TOKEN="yourToken"
#SMTP SETTINGS
SENDER_EMAIL_ADDRESS="youraddress@gmail.com"
SENDER_EMAIL_ADDRESS_PASSWORD="emrp raps vzoi vnoe"
SENDER_EMAIL_DOMAIN="smtp.yourdomain.com"
SENDER_EMAIL_PORT="587"
#NETWORK SETTINGS
NETWORK_HOME_ROUTER_SUBNET="192.168.1.0/24"
NETWORK_HOME_ROUTER_IP="192.168.1.1"
NETWORK_HOME_SERVER_IP="192.168.1.5"
# SERVICE SETTINGS
SELECTED_SERVICES=("frigate" "home-assistant")
# DISK SETTINGS
BOOT_DISK_ID_LIST=("/dev/disk/by-id/nvme001-dfzpjvp")
DATA_DISKS_ID_LIST=("/dev/disk/by-id/sata-barracuda-veojapoj")
SPINDOWN_DISKS_ID_LIST=("/dev/disk/by-id/sata-barracuda-veojapoj")
CONTENT_DISK_NUMBER=2
PARITY_DISK_NUMBER=2
+220
View File
@@ -0,0 +1,220 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p gum fastfetch xkcdpass sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto
networking() {
SELECTED_SUBACTION=$(gum choose --header "Choose a setting to change:" "${NETWORKING_SETTINGS_LIST[@]}")
if [[ "$SELECTED_SUBACTION" = " 🏠 Change the home router subnet" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " 🌐 Change the home router IP address" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " 🖥️ Change the server's IP address" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " 🌍 Change the domain name" ]]; then
echo "Some future logic here"
else
echo -e "\n\n ❌ Unexpected bug. Please contact the developer. Aborting."
exit 1
fi
}
backup() {
SELECTED_SUBACTION=$(gum choose --header "Choose a setting to change:" "${BACKUP_SETTINGS_LIST[@]}")
if [[ "$SELECTED_SUBACTION" = " 🕣 Change the backup time" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " 💾 Backup all data to a ssh remote" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " 💿 Backup all data to a local folder" ]]; then
echo "Some future logic here"
else
echo -e "\n\n ❌ Unexpected bug. Please contact the developer. Aborting."
exit 1
fi
}
services() {
echo "Some future logic here"
}
devices() {
echo "Some future logic here"
}
disks() {
SELECTED_SUBACTION=$(gum choose --header "Choose a setting to change:" "${DISKS_SETTINGS_LIST[@]}")
if [[ "$SELECTED_SUBACTION" = " Add or remove a boot disk" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " Add or remove data disk(s)" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " 🔄 Replace a boot disk" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " 🔄 Replace a data disk" ]]; then
echo "Some future logic here"
else
echo -e "\n\n ❌ Unexpected bug. Please contact the developer. Aborting."
exit 1
fi
}
health() {
SELECTED_SUBACTION=$(gum choose --header "Choose a setting to change:" "${SERVER_HEALTH_SETTINGS_LIST[@]}")
if [[ "$SELECTED_SUBACTION" = " 🩺 Check disk(s) health" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " ⛑️ Check service(s) health" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " 🌐 Check connectivity" ]]; then
echo "Some future logic here"
else
echo -e "\n\n ❌ Unexpected bug. Please contact the developer. Aborting."
exit 1
fi
}
email() {
SELECTED_SUBACTION=$(gum choose --header "Choose a setting to change:" "${EMAIL_SETTINGS_LIST[@]}")
if [[ "$SELECTED_SUBACTION" = " ⚙️ Change the server's email SMTP settings" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " 📧 Change your personal email" ]]; then
echo "Some future logic here"
else
echo -e "\n\n ❌ Unexpected bug. Please contact the developer. Aborting."
exit 1
fi
}
passwords() {
SELECTED_SUBACTION=$(gum choose --header "Choose a setting to change:" "${PASSWORD_SETTINGS_LIST[@]}")
if [[ "$SELECTED_SUBACTION" = " 👁️ Display the server's configuration credentials" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " 🔧 Change the numbus-admin's password" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " 🔑 Add or remove SSH keys" ]]; then
echo "Some future logic here"
elif [[ "$SELECTED_SUBACTION" = " ⚙️ Change Cloudflare API token" ]]; then
echo "Some future logic here"
else
echo -e "\n\n ❌ Unexpected bug. Please contact the developer. Aborting."
exit 1
fi
}
configuration_export() {
echo "Some future logic here"
}
### --> Main logic
set -euo pipefail
fastfetch --logo nixos --structure ''
cat <<EOF
██████ █████ █████
▒▒██████ ▒▒███ ▒▒███
▒███▒███ ▒███ █████ ████ █████████████ ▒███████ █████ ████ █████
▒███▒▒███▒███ ▒▒███ ▒███ ▒▒███▒▒███▒▒███ ▒███▒▒███▒▒███ ▒███ ███▒▒
▒███ ▒▒██████ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒▒█████
▒███ ▒▒█████ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒▒▒▒███
█████ ▒▒█████ ▒▒████████ █████▒███ █████ ████████ ▒▒████████ ██████
▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒
█████████
███▒▒▒▒▒███
▒███ ▒▒▒ ██████ ████████ █████ █████ ██████ ████████
▒▒█████████ ███▒▒███▒▒███▒▒███▒▒███ ▒▒███ ███▒▒███▒▒███▒▒███
▒▒▒▒▒▒▒▒███▒███████ ▒███ ▒▒▒ ▒███ ▒███ ▒███████ ▒███ ▒▒▒
███ ▒███▒███▒▒▒ ▒███ ▒▒███ ███ ▒███▒▒▒ ▒███
▒▒█████████ ▒▒██████ █████ ▒▒█████ ▒▒██████ █████
▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒
EOF
sleep 1
source /etc/numbus-server/numbus-server.conf
gum confirm "\n\n 🚀 Welcome to the Numbus-Server administration interface. Here,
you can tweak the settings of your homelab. Do you want to continue?" \
|| { echo " ❌ Aborting as requested."; exit 1; }
ACTIONS_LIST=(
" 🛜 Update networking settings" \
" 💾 Update backup settings" \
" 📱 Add or remove services" \ # No further options
" 🧩 Add or remove devices" \ # No further options
" 💿 Change disks configuration" \
" ⛑️ Check server health" \
" 📧 Change email settings" \
" 🔐 Passwords and keys management" \
" ↥ Export current configuration to a git server" \ # No further options
)
NETWORKING_SETTINGS_LIST=(
" 🏠 Change the home router subnet" \
" 🌐 Change the home router IP address" \
" 🖥️ Change the server's IP address" \
" 🌍 Change the domain name" \
)
BACKUP_SETTINGS_LIST=(
" 🕣 Change the backup time" \
" 💾 Backup all data to a ssh remote" \
" 💿 Backup all data to a local folder" \
)
DISKS_SETTINGS_LIST=(
" Add or remove a boot disk" \
" Add or remove data disk(s)" \
" 🔄 Replace a boot disk" \
" 🔄 Replace a data disk" \
)
SERVER_HEALTH_SETTINGS_LIST=(
" 🩺 Check disk(s) health"
" ⛑️ Check service(s) health"
" 🌐 Check connectivity"
)
EMAIL_SETTINGS_LIST=(
" ⚙️ Change the server's email SMTP settings"
" 📧 Change your personal email"
)
PASSWORD_SETTINGS_LIST=(
" 👁️ Display the server's configuration credentials"
" 🔧 Change the numbus-admin's password"
" 🔑 Add or remove SSH keys"
" ⚙️ Change Cloudflare API token"
)
SELECTED_ACTION=$(gum choose --header "Choose a setting to change:" "${ACTIONS_LIST[@]}")
if [[ "$SELECTED_ACTION" = " 🛜 Update networking settings" ]]; then
networking
elif [[ "$SELECTED_ACTION" = " 💾 Update backup settings" ]]; then
backup
elif [[ "$SELECTED_ACTION" = " 📱 Add or remove services" ]]; then
services
elif [[ "$SELECTED_ACTION" = " 🧩 Add or remove devices" ]]; then
devices
elif [[ "$SELECTED_ACTION" = " 💿 Change disks configuration" ]]; then
disks
elif [[ "$SELECTED_ACTION" = " ⛑️ Check server health" ]]; then
health
elif [[ "$SELECTED_ACTION" = " 📧 Change email settings" ]]; then
email
elif [[ "$SELECTED_ACTION" = " 🔐 Passwords and keys management" ]]; then
passwords
elif [[ "$SELECTED_ACTION" = " ↥ Export current configuration to a git server" ]]; then
configuration_export
else
echo -e "\n\n ❌ Unexpected bug. Please contact the developer. Aborting."
exit 1
fi
### Main logic <--