Lots of changes. Needs more work.

This commit is contained in:
Raphaël Numbus
2026-02-23 16:35:24 +01:00
parent 30b6ce5f9c
commit bc8ad796de
51 changed files with 186 additions and 4083 deletions
+17 -12
View File
@@ -1,28 +1,33 @@
# SCRIPT SETTINGS
export DEBUG="true"
#TARGET SETTINGS
export TARGET_HOST="192.168.1.10"
export REMOTE_PASS="example"
export SSH_PUBLIC_KEY="ssh-ed25519 AAAAoefzefpoipoeCEZJCPEACPAcjapjcpajepcjAPJECJPEJAPJAZ yours@yourdomain.com"
#LIVE TARGET SETTINGS
export LIVE_TARGET_IP="192.168.1.10"
export LIVE_TARGET_PASSWD="example"
#SERVER SETTINGS
export TIMEZONE="Europe/Paris"
export SERVER_OWNER_NAME="yourName"
export SERVER_USER_EMAIL="user@your-domain.com"
export SERVER_ADMIN_EMAIL="admin@your-domain.com"
export AUTHORIZED_SSH_PUBLIC_KEY=( "ssh-ed25519 AAAAoefzefpoipoeCEZJCPEACPAcjapjcpajepcjAPJECJPEJAPJAZ yours@yourdomain.com" )
# TRAEFIK SETTINGS
export DOMAIN_NAME="yourdomain.com"
export EMAIL_ADDRESS="your-mail@yourdomain.com"
export CF_DNS_API_TOKEN="yourToken"
export CLOUDFLARE_DNS_API_TOKEN="yourToken"
# SMTP SETTINGS
export SENDER_EMAIL_ADDRESS="youraddress@gmail.com"
export SENDER_EMAIL_ADDRESS_PASSWORD="emrp raps vzoi vnoe"
export SENDER_EMAIL_DOMAIN="smtp.yourdomain.com"
export SENDER_EMAIL_PORT="587"
export SMTP_SERVER_USERNAME="your-address@gmail.com"
export SMTP_SERVER_PASSWORD="emrp raps vzoi vnoe"
export SMTP_SERVER_HOST="smtp.yourdomain.com"
export SMTP_SERVER_PORT="587"
#NETWORK SETTINGS
export HOME_ROUTER_SUBNET="192.168.1.0/24"
export HOME_ROUTER_IP="192.168.1.1"
export NETWORK_SUBNET="192.168.1.0/24"
export NETWORK_ROUTER_IP="192.168.1.1"
export HOME_SERVER_IP="192.168.1.5"
# SERVICES SETTINGS
export SELECTED_DNS_SERVICE="pi-hole" # or adguard
export SELECTED_SERVICES=( "frigate" "gitea" "home-assistant" "immich" "it-tools" \
"nextcloud" "passbolt" "pi-hole" "virtualization" )
+145 -400
View File
@@ -7,10 +7,12 @@ 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" "SERVER_OWNER_NAME" "SELECTED_SERVICES" \
"HOME_ROUTER_SUBNET" "HOME_ROUTER_IP" "HOME_SERVER_IP" )
NECESSARY_VARIABLES_LIST=( LIVE_TARGET_IP LIVE_TARGET_PASSWD \
SERVER_OWNER_NAME SERVER_USER_EMAIL SERVER_ADMIN_EMAIL AUTHORIZED_SSH_PUBLIC_KEY \
DOMAIN_NAME CLOUDFLARE_DNS_API_TOKEN \
SMTP_SERVER_USERNAME SMTP_SERVER_PASSWORD SMTP_SERVER_HOST SMTP_SERVER_PORT \
NETWORK_SUBNET NETWORK_ROUTER_IP HOME_SERVER_IP)
### Default settings <--
user_input() {
@@ -44,8 +46,8 @@ user_input() {
strictly_necessary_information() {
export IP_REGEX='^([0-9]{1,3}\.){3}[0-9]{1,3}$'
user_input "TARGET_HOST" " Please provide the IP address of the target host :" "For example : 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 "LIVE_TARGET_IP" " Please provide the IP address of the target host :" "For example : 192.168.1.100" "${IP_REGEX}" "Invalid IP address format."
user_input "LIVE_TARGET_PASSWD" " Please enter the password for '${TARGET_USER}@${LIVE_TARGET_IP}' :" "${LIVE_TARGET_IP}'s password" "" "" "true"
}
necessary_information() {
@@ -57,29 +59,34 @@ necessary_information() {
local SSH_KEY_REGEX='^ssh-[a-z0-9]+ [A-Za-z0-9+/]+.*'
echo -e "\n\n➡️ This script needs information about the target you want to install NixOS on\n"
#TARGET SETTINGS
user_input "TARGET_HOST" " Please provide the IP address of the target host :" "For example : 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 :" "For example : ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGhcYDmjMo5YApLkk/3P3HZCnOSzm0uYewNAbxL8Fci8 user@your-pc" "${SSH_KEY_REGEX}" "Invalid SSH key format (must start with ssh-...)." "true"
# LIVE TARGET SETTINGS
user_input "LIVE_TARGET_IP" " Please provide the IP address of the target host :" "For example : 192.168.1.100" "${IP_REGEX}" "Invalid IP address format."
user_input "LIVE_TARGET_PASSWD" " Please enter the password for '${TARGET_USER}@${LIVE_TARGET_IP}' :" "${LIVE_TARGET_IP}'s password" "" "" "true"
echo -e "\n\n➡️ Now provide some information about the server you are deploying\n"
# SERVER SETTINGS
user_input "TIMEZONE" " Please provide the wanted timezone :" "For example : Europe/Paris, Europe/Berlin" "" ""
user_input "SERVER_OWNER_NAME" " Please provide the name of the owner of this server :" "For example : Steve" "" ""
user_input "SERVER_USER_EMAIL" " Please provide a valid user email address (to stay informed about your server's health) :" "For example : myemail@gmail.com" "${EMAIL_REGEX}" "Invalid email address format."
user_input "SERVER_ADMIN_EMAIL" " Please provide a valid admin email address (will be used for ACME, and system failures notifications) :" "For example : myemail@gmail.com" "${EMAIL_REGEX}" "Invalid email address format."
user_input "AUTHORIZED_SSH_PUBLIC_KEY" " Please provide a list of SSH public keys of authorized devices :" "For example : ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGhcYDmjMo5YApLkk/3P3HZCnOSzm0uYewNAbxL8Fci8 user@your-pc" "${SSH_KEY_REGEX}" "Invalid SSH key format (must start with ssh-...)." "true"
echo -e "\n\n➡️ You will access your services via a domain name (e.g. cloud.mydomain.com) and containers need credentials to create those subdomains\n"
# TRAEFIK SETTINGS
user_input "DOMAIN_NAME" " Please provide the domain name (FQDN) your home server will use :" "For example : 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) :" "For example : 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 :" "For example : bA7hdvCOuXGytlNKohi3ZGtlVpf5CHpLuCMiJrE" "" "" "true"
user_input "SERVER_OWNER_NAME" " Please provide the name of the server owner :" "For example : Steve" "" ""
user_input "CLOUDFLARE_DNS_API_TOKEN" " Please provide a cloudflare API token with DNS zone permission :" "For example : bA7hdvCOuXGytlNKohi3ZGtlVpf5CHpLuCMiJrE" "" "" "true"
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)\n"
# SMTP SETTINGS
user_input "SENDER_EMAIL_ADDRESS" " Please provide a valid sender email address :" "For example : 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" "" "" "true"
user_input "SENDER_EMAIL_DOMAIN" " Please provide the SMTP server endpoint :" "For Gmail : smtp.gmail.com" "${DOMAIN_REGEX}" "Invalid domain name format."
user_input "SENDER_EMAIL_PORT" " Please provide the smtp TLS port :" "For Gmail : 587" "${PORT_REGEX}" "Invalid port number."
user_input "SMTP_SERVER_USERNAME" " Please provide a valid sender email address :" "For example : myemail@gmail.com" "${EMAIL_REGEX}" "Invalid email address format."
user_input "SMTP_SERVER_PASSWORD" " Please provide the password of this email address :" "abcd efgh ijkl mnop" "" "" "true"
user_input "SMTP_SERVER_HOST" " Please provide the SMTP server endpoint :" "For Gmail : smtp.gmail.com" "${DOMAIN_REGEX}" "Invalid domain name format."
user_input "SMTP_SERVER_PORT" " Please provide the smtp TLS port :" "For Gmail : 587" "${PORT_REGEX}" "Invalid port number."
echo -e "\n\n➡️ This server will connect to your local network and you will configure its IP address\n"
# NETWORK SETTINGS
user_input "HOME_ROUTER_SUBNET" " Please provide your home network subnet :" "For example 192.168.1.0/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 :" "Most likely 192.168.1.1 or 192.168.1.254" "${IP_REGEX}" "Invalid IP address format."
user_input "NETWORK_SUBNET" " Please provide your network subnet :" "For example 192.168.1.0/24" "${SUBNET_REGEX}" "Invalid subnet format (e.g. 192.168.1.1/24)."
user_input "NETWORK_ROUTER_IP" " Please provide the ip address of your router :" "Most likely 192.168.1.1 or 192.168.1.254" "${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.) :" "For example 192.168.1.5" "${IP_REGEX}" "Invalid IP address format."
}
@@ -113,11 +120,6 @@ necessary_information_config() {
fi
}
more_information_config() {
sshpass -p "${REMOTE_PASS}" scp ${TARGET_USER}@${TARGET_HOST}:/etc/numbus-server/numbus-server.conf .
source "numbus-server.conf"
}
setup_ssh() {
mkdir -p final-nix-config/
mkdir -p final-nix-config/etc/
@@ -130,8 +132,8 @@ setup_ssh() {
chmod 700 final-nix-config/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
echo -e "\n\n➡️ Copying SSH key to target host '${TARGET_USER}@${TARGET_HOST}'..."
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 -e "\n\n➡️ Copying SSH key to target host '${TARGET_USER}@${LIVE_TARGET_IP}'..."
if sshpass -p "${LIVE_TARGET_PASSWD}" ssh-copy-id -o StrictHostKeyChecking=no -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}"; then
echo -e "\n✅ SSH key copied successfully"
else
echo -e "\n❌ Failed to copy SSH key. Please check the host IP and password."
@@ -141,7 +143,18 @@ setup_ssh() {
ssh_to_host() {
local COMMAND="${1}"
ssh -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${TARGET_HOST}" "${COMMAND}"
ssh -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}" "${COMMAND}"
}
hierarchy_preparation() {
mkdir -p /final-nix-config/
mkdir -p /final-nix-config/etc
mkdir -p /final-nix-config/etc/nixos
mkdir -p /final-nix-config/etc/nixos/secrets
echo -e "\n ✅ Writing configuration..."
cp -${FILES_COPY_FLAGS} templates/nix-config/configuration.nix final-nix-config/etc/nixos/configuration.nix
export CONFIGURATION_PATH="final-nix-config/etc/nixos/configuration.nix"
}
hardware_detection() {
@@ -196,7 +209,7 @@ for DISK in \$(lsblk -x SIZE -d -n -e 7,11 -o NAME); do
fi
# Disk health
if [[ \$(echo "$REMOTE_PASS" | sudo -S smartctl -H /dev/\$DISK 2>/dev/null | grep 'self-assessment' | awk '{print \$6}') == "PASSED" ]]; then
if [[ \$(echo "$LIVE_TARGET_PASSWD" | sudo -S smartctl -H /dev/\$DISK 2>/dev/null | grep 'self-assessment' | awk '{print \$6}') == "PASSED" ]]; then
DISK_HEALTH+=("PASSED")
else
DISK_HEALTH+=("N/A")
@@ -232,7 +245,7 @@ done
SSHEND
### Get hardware information <--
scp -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${TARGET_HOST}":"${TMPFILE}" "${TMPFILE}" &> /dev/null
scp -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}":"${TMPFILE}" "${TMPFILE}" &> /dev/null
source "${TMPFILE}" && rm -rf "${TMPFILE}"
### --> Generate hardware-configuration.nix
@@ -248,34 +261,46 @@ SSHEND
services_selection() {
echo -e "\n\n ➡️ You will now select the services you want installed on your server:"
local AVAILABLE_DNS_SERVICES=( "pi-hole" "adguard" )
local AVAILABLE_SERVICES=( "frigate" "gitea" "home-assistant" "immich" "it-tools" \
"nextcloud" "passbolt" "virtualization" )
local SERVICES_DESCRIPTION=( "Pi-Hole [Always included] : Block ads on all your devices" \
"Immich : Pictures and videos backup with local machine-learning" \
local DNS_SERVICES_DESCRIPTION=( "Pi-Hole : Simple open-source DNS black hole" \
"AdGuard " : Feature rich DNS service )
local SERVICES_DESCRIPTION=( "Immich : Pictures and videos backup with local machine-learning" \
"Nextcloud : No fuss Office 365 replacement" \
"Passbolt: Security-first password manager with collaboration features" \
"Home-Assistant : Manage your smart home and security cameras" \
"Frigate [Home Assistant required] : Secure your house with security cameras" \
"Gitea : Your own git platform" \
"IT-tools : A set of useful tools when doing IT" \
"Virtualization : Run Virtual Machines (KVM/QEMU) with Libvirt"
)
"Virtualization : Run Virtual Machines (KVM/QEMU) with Libvirt" )
SELECTED_SERVICES=()
local SELECTED_SERVICES_DESCRIPTION=$(gum choose --no-limit --header "Homelab services:" "${SERVICES_DESCRIPTION[@]}")
SELECTED_DNS_SERVICE=""
local SELECTED_DNS_SERVICE_DESCRIPTION=$(gum choose --no-limit --header "Homelab services:" "${DNS_SERVICES_DESCRIPTION[@]}")
for i in ${!AVAILABLE_SERVICES[@]}; do
if printf '%s' "${SELECTED_SERVICES_DESCRIPTION}" | grep -iq "${AVAILABLE_SERVICES[${i}]}"; then
SELECTED_SERVICES+=("${AVAILABLE_SERVICES[${i}]}")
fi
done
for i in ${!AVAILABLE_DNS_SERVICES[@]}; do
if printf '%s' "${SELECTED_DNS_SERVICE_DESCRIPTION}" | grep -iq "${AVAILABLE_DNS_SERVICES[${i}]}"; then
SELECTED_DNS_SERVICE="${AVAILABLE_DNS_SERVICES[${i}]}"
fi
done
export SELECTED_SERVICES
export SELECTED_DNS_SERVICE
}
disks_selection() {
### --> Disk wiping warning
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "
⚠️ $(gum style --foreground 212 'WARNING:') You will choose the disks to install NixOS on.
!! PLEASE MAKE SURE YOU BACKED UP ANY IMPORTANT DATA !!
@@ -285,9 +310,7 @@ disks_selection() {
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)"
### Disk wiping warning <--
### --> Disk selection
if [[ "${#DISK_NAME[@]}" -eq 0 ]]; then
echo -e "\n ❌ No disks found on the target host. Aborting."
exit 1
@@ -307,46 +330,19 @@ disks_selection() {
local SELECTED_BOOT_DISK=$(gum choose --limit 2 --header "${HEADER}" "${GUM_PRINTED_ELEMENTS[@]}")
BOOT_DISKS_ID=()
BOOT_DISKS_NAME=()
for i in ${!DISK_NAME[@]}; do
if printf '%s' "$SELECTED_BOOT_DISK" | grep -iqw "${DISK_NAME[${i}]}"; then
BOOT_DISKS_ID+=("${DISK_ID[${i}]:-${DISK_DEVPATH[${i}]}}")
BOOT_DISKS_ID_LIST+=("${DISK_ID[${i}]:-${DISK_DEVPATH[${i}]}}")
BOOT_DISKS_NAME+=("${DISK_NAME[${i}]}")
unset "GUM_PRINTED_ELEMENTS[${i}]"
fi
done
if [[ "${#BOOT_DISKS_ID[@]}" -eq 0 ]]; then
echo -e "\n\n ❌ No boot disk selected. Aborting."
exit 1
elif [[ "${#BOOT_DISKS_ID[@]}" -eq 1 ]]; then
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."
export BOOT_DISK_1_ID="${BOOT_DISKS_ID[0]}"
export BOOT_DISK_1_NAME="${BOOT_DISKS_NAME[0]}"
export BOOT_DISK_2_NAME=""
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 ⚠️ If the two disks are different sizes, the resulting usable space size will be \
the one of the smallest disk."
export BOOT_DISK_1_ID="${BOOT_DISKS_ID[0]}"
export BOOT_DISK_2_ID="${BOOT_DISKS_ID[1]}"
export BOOT_DISK_1_NAME="${BOOT_DISKS_NAME[0]}"
export BOOT_DISK_2_NAME="${BOOT_DISKS_NAME[1]}"
else
echo -e "\n\n ❌ Unexpected bug. Please contact the developer. Aborting."
exit 1
fi
echo ""
gum style --foreground 212 "➡️ Please choose data and parity disks (up to 9 total) :"
local SELECTED_DATA_DISK=$(gum choose --limit 9 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}")
### Disk selection <--
DATA_DISKS_ID=()
DATA_DISKS_TYPE=()
for i in ${!DISK_NAME[@]}; do
if printf '%s' "$SELECTED_DATA_DISK" | grep -iq "${DISK_NAME[${i}]}"; then
DATA_DISKS_ID+=("${DISK_ID[${i}]:-${DISK_DEVPATH[${i}]}}")
@@ -357,280 +353,88 @@ disks_selection() {
if [[ "${#DATA_DISKS_ID[@]}" -eq 1 ]]; then
export PARITY_DISK_NUMBER=0
export CONTENT_DISK_NUMBER=1
export PARITY_DISK_NUMBER_LIST=()
export CONTENT_DISK_NUMBER_LIST=("${#DATA_DISKS_ID[0]}")
else
export PARITY_DISK_NUMBER=$(((${#DATA_DISKS_ID[@]} + 2) / 3))
export CONTENT_DISK_NUMBER=$((${#DATA_DISKS_ID[@]} - PARITY_DISK_NUMBER))
for i in $(seq 0 $(($CONTENT_DISK_NUMBER - 1))); do
CONTENT_DISK_NUMBER_LIST+=("${#DATA_DISKS_ID[${i}]}")
done
for i in $(seq $CONTENT_DISK_NUMBER $((${#DATA_DISKS_ID[@]} - 1))); do
PARITY_DISK_NUMBER_LIST+=("${#DATA_DISKS_ID[${i}]}")
done
fi
export DATA_DISKS_ID
export DATA_DISKS_TYPE
}
folder_tree_generation() {
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/secrets/
mkdir -p final-nix-config/etc/secrets/disks/
mkdir -p final-nix-config/etc/numbus-server/
mkdir -p final-nix-config/etc/nixos/misc/
mkdir -p final-nix-config/etc/nixos/disks/
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
}
services_generation() {
generate_db_creds() {
local SERVICE_UPPER="${1}"
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 "-")"
}
generate_network() {
local SERVICE="${1}"
local HAS_BACKEND="${2:-0}"
local NETWORK_NAME_OVERRIDE="${3:-}"
if [[ -z "${NETWORK_NAME_OVERRIDE}" ]]; then
NETWORK_ID=$((NETWORK_ID + 1))
PODMAN_NETWORKS+=" sudo -u numbus-admin podman network exists \"${SERVICE}_frontend\" || sudo -u numbus-admin podman network create --driver=\"bridge\" --subnet=\"10.89.${NETWORK_ID}.0/24\" --ip-range=\"10.89.${NETWORK_ID}.0/24\" --gateway=\"10.89.${NETWORK_ID}.254\" \"${SERVICE}_frontend\""$'\n'
TRAEFIK_NETWORKS+=" ${SERVICE}_frontend:"$'\n'
TRAEFIK_NETWORKS+=" ipv4_address: 10.89.${NETWORK_ID}.253"$'\n'
TRAEFIK_REF_NETWORKS+=" ${SERVICE}_frontend:"$'\n'
TRAEFIK_REF_NETWORKS+=" external: true"$'\n'
if [[ "${HAS_BACKEND}" == "1" ]]; then
NETWORK_ID=$((NETWORK_ID + 1))
PODMAN_NETWORKS+=" sudo -u numbus-admin podman network exists \"${SERVICE}_backend\" || sudo -u numbus-admin podman network create --driver=\"bridge\" --subnet=\"10.89.${NETWORK_ID}.0/24\" --ip-range=\"10.89.${NETWORK_ID}.0/24\" --gateway=\"10.89.${NETWORK_ID}.254\" \"${SERVICE}_backend\""$'\n'
SERVICES_NETWORK_IDS+=("$(( ${NETWORK_ID} - 1 )),${NETWORK_ID}:${SERVICE}")
else
SERVICES_NETWORK_IDS+=("${NETWORK_ID}:${SERVICE}")
fi
else
NETWORK_ID=$((NETWORK_ID + 1))
PODMAN_NETWORKS+=" sudo -u numbus-admin podman network exists \"${NETWORK_NAME_OVERRIDE}\" || sudo -u numbus-admin podman network create --driver=\"bridge\" --subnet=\"10.89.${NETWORK_ID}.0/24\" --ip-range=\"10.89.${NETWORK_ID}.0/24\" --gateway=\"10.89.${NETWORK_ID}.254\" \"${NETWORK_NAME_OVERRIDE}\""$'\n'
TRAEFIK_NETWORKS+=" ${NETWORK_NAME_OVERRIDE}:"$'\n'
TRAEFIK_NETWORKS+=" ipv4_address: 10.89.${NETWORK_ID}.253"$'\n'
TRAEFIK_REF_NETWORKS+=" ${NETWORK_NAME_OVERRIDE}:"$'\n'
TRAEFIK_REF_NETWORKS+=" external: true"$'\n'
SERVICES_NETWORK_IDS+=("${NETWORK_ID}:${SERVICE}")
fi
export NETWORK_ID
export PODMAN_NETWORKS
export TRAEFIK_NETWORKS
export TRAEFIK_REF_NETWORKS
export SERVICES_NETWORK_IDS
}
NETWORK_ID=0
PODMAN_NETWORKS=""
TRAEFIK_NETWORKS=""
TRAEFIK_REF_NETWORKS=""
SERVICES_NETWORK_IDS=()
echo -e "\n ✅ Writing configuration files for the selected homelab services..."
cp -${FILES_COPY_FLAGS} templates/nix-config/configuration.nix final-nix-config/etc/nixos/configuration.nix
cp -${FILES_COPY_FLAGS} templates/nix-config/podman/traefik.nix final-nix-config/etc/nixos/podman/traefik.nix
cp -${FILES_COPY_FLAGS} templates/nix-config/podman/pi-hole.nix final-nix-config/etc/nixos/podman/pi-hole.nix
envsubst < templates/podman-config/traefik/traefik.yaml > final-nix-config/mnt/config/traefik/traefik.yaml
generate_network "pi-hole"
export FTLCONF_WEBSERVER_PASSWORD="$(xkcdpass -d "-")"
for service in "${SELECTED_SERVICES[@]}"; do
# Copy podman container file
[[ "${service}" != "virtualization" ]] && cp -${FILES_COPY_FLAGS} templates/nix-config/podman/"${service}".nix final-nix-config/etc/nixos/podman/"${service}".nix
# Frigate config
if [[ "${service}" == "frigate" ]]; then
local FRIGATE_DEVICES_BLOCK=""
[[ "${TARGET_GRAPHICS_RENDERER}" == "true" ]] && FRIGATE_DEVICES_BLOCK+=" - /dev/dri:/dev/dri\n"
[[ "${TARGET_USB_CORAL}" == "true" ]] && FRIGATE_DEVICES_BLOCK+=" - /dev/bus/usb:/dev/bus/usb\n"
if [[ "${TARGET_PCIE_CORAL}" == "true" ]]; then
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 -${FILES_COPY_FLAGS} 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
# Gitea config
elif [[ "${service}" == "gitea" ]]; then
generate_network "${service}" "1"
generate_db_creds "GITEA"
# Home Assistant config
elif [[ "${service}" == "home-assistant" ]]; then
generate_network "${service}" "1"
if [[ -n "${TARGET_ZIGBEE_DEVICE}" ]]; then
local REPLACEMENT="devices:\n - /dev/serial/by-id/${TARGET_ZIGBEE_DEVICE}:/dev/ttyUSB0"
sed -i "s|# --- home-assistant 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/home-assistant/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"
# Immich config
elif [[ "${service}" == "immich" ]]; then
generate_network "${service}" "1"
generate_db_creds "IMMICH"
local IMMICH_DEVICES_BLOCK=""
if [[ "$TARGET_GRAPHICS_RENDERER" == "true" ]]; then
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
# Nextcloud config
elif [[ "${service}" == "nextcloud" ]]; then
generate_network "${service}" "1"
generate_db_creds "NEXTCLOUD"
export "NEXTCLOUD_REDIS_PASSWORD"="$(xkcdpass -d "-")"
# envsubst < templates/podman-config/traefik/nextcloud.yaml > final-nix-config/mnt/config/traefik/rules/nextcloud.yaml
# Passbolt config
elif [[ "${service}" == "passbolt" ]]; then
generate_network "${service}" "1"
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
# Virtualization config
elif [[ "${service}" == "virtualization" ]]; then
sed -i "s|# virtualisation.libvirtd.enable = true;| virtualisation.libvirtd.enable = true;|" final-nix-config/etc/nixos/configuration.nix
sed -i "s|# programs.virt-manager.enable = true;| programs.virt-manager.enable = true;|" final-nix-config/etc/nixos/configuration.nix
sed -i 's|extraGroups = \[ "wheel" \];|extraGroups = [ "wheel" "libvirtd" ];|' final-nix-config/etc/nixos/configuration.nix
# Other podman containers with no special configuration
else
generate_network "${service}"
fi
done
export PODMAN_NETWORKS
export TRAEFIK_NETWORKS
export TRAEFIK_REF_NETWORKS
}
disks_generation() {
# Boot disk(s)
echo -e "\n\n ✅ Generating disko configuration from templates..."
local TEMPLATE_FILE="templates/nix-config/disks/boot-${#BOOT_DISKS_ID[@]}.nix"
(envsubst < "$TEMPLATE_FILE") > final-nix-config/etc/nixos/disks/disko.nix
SNAPRAID_CONTENT_FILES=""
SNAPRAID_DATA_DISKS=""
SNAPRAID_PARITY_FILES=""
MOUNT_DEPENDENCIES_START=""
MOUNT_DEPENDENCIES_STOP=""
# Striped configuration
if [[ "$CONTENT_DISK_NUMBER" -eq 1 && "$PARITY_DISK_NUMBER" -eq 0 ]]; then
export j=1
export CONTENT_DISK_ID="${DATA_DISKS_ID[0]}"
if [[ "${DATA_DISKS_TYPE[0]}" == "HDD" ]]; then export ALLOW_DISCARDS="false"; else export ALLOW_DISCARDS="true"; fi
(envsubst < "templates/nix-config/disks/content.nix") >> final-nix-config/etc/nixos/disks/disko.nix
sed -i "s|/mnt/content-1|/mnt/data|" final-nix-config/etc/nixos/disks/disko.nix
# SnapRAID configuration
elif [[ ${CONTENT_DISK_NUMBER} -gt 0 && ${PARITY_DISK_NUMBER} -gt 0 ]]; then
sed -i "s|# ./disks/snapraid.nix| ./disks/snapraid.nix|" final-nix-config/etc/nixos/configuration.nix
j=0
for i in $(seq 0 $(($CONTENT_DISK_NUMBER - 1))); do
export j=$((j + 1))
export CONTENT_DISK_ID="${DATA_DISKS_ID[${i}]}"
if [[ "${DATA_DISKS_TYPE[${i}]}" == "HDD" ]]; then export ALLOW_DISCARDS="false"; else export ALLOW_DISCARDS="true"; fi
(envsubst < "templates/nix-config/disks/content.nix") >> final-nix-config/etc/nixos/disks/disko.nix
SNAPRAID_CONTENT_FILES+=" \"/mnt/content-${j}/snapraid.content\""$'\n'
SNAPRAID_DATA_DISKS+=" d${j} = \"/mnt/content-${j}\";"$'\n'
MOUNT_DEPENDENCIES_START+=" \${pkgs.cryptsetup}/bin/cryptsetup open ${CONTENT_DISK_ID}-part1 crypted-content-${j} --key-file /etc/secrets/disks/content-${j}"$'\n'
MOUNT_DEPENDENCIES_START+=" \${pkgs.coreutils}/bin/mkdir -p /mnt/content-${j}"$'\n'
MOUNT_DEPENDENCIES_START+=" \${pkgs.util-linux}/bin/mount /mnt/content-${j}"$'\n'
MOUNT_DEPENDENCIES_STOP+=" \${pkgs.util-linux}/bin/umount /mnt/content-${j}"$'\n'
MOUNT_DEPENDENCIES_STOP+=" \${pkgs.cryptsetup}/bin/cryptsetup close crypted-content-${j}"$'\n'
done
echo -e "\n ✅ Generated $CONTENT_DISK_NUMBER data disk configuration(s)."
j=0
for i in $(seq $CONTENT_DISK_NUMBER $((${#DATA_DISKS_ID[@]} - 1))); do
export j=$((j + 1))
export PARITY_DISK_ID="${DATA_DISKS_ID[${i}]}"
if [[ "${DATA_DISKS_TYPE[${i}]}" == "HDD" ]]; then export ALLOW_DISCARDS="false"; else export ALLOW_DISCARDS="true"; fi
(envsubst < "templates/nix-config/disks/parity.nix") >> final-nix-config/etc/nixos/disks/disko.nix
SNAPRAID_PARITY_FILES+=" \"/mnt/parity-${j}/snapraid.parity\""$'\n'
MOUNT_DEPENDENCIES_START+=" \${pkgs.cryptsetup}/bin/cryptsetup open ${PARITY_DISK_ID}-part1 crypted-parity-${j} --key-file /etc/secrets/disks/parity-${j}"$'\n'
MOUNT_DEPENDENCIES_START+=" \${pkgs.coreutils}/bin/mkdir -p /mnt/parity-${j}"$'\n'
MOUNT_DEPENDENCIES_START+=" \${pkgs.util-linux}/bin/mount /mnt/parity-${j}"$'\n'
MOUNT_DEPENDENCIES_STOP+=" \${pkgs.util-linux}/bin/umount /mnt/parity-${j}"$'\n'
MOUNT_DEPENDENCIES_STOP+=" \${pkgs.cryptsetup}/bin/cryptsetup close crypted-parity-${j}"$'\n'
done
echo -e "\n ✅ Generated $PARITY_DISK_NUMBER parity disk configuration(s)."
[[ ${CONTENT_DISK_NUMBER} -eq 1 && ${PARITY_DISK_NUMBER} -eq 1 ]] && SNAPRAID_CONTENT_FILES+=" \"/mnt/content-0/snapraid.content\""$'\n' && SNAPRAID_DATA_DISKS+=" d0 = \"/mnt/content-0\";"$'\n'
export SNAPRAID_CONTENT_FILES
export SNAPRAID_DATA_DISKS
export SNAPRAID_PARITY_FILES
export MOUNT_DEPENDENCIES_START
export MOUNT_DEPENDENCIES_STOP
fi
envsubst < templates/nix-config/disks/snapraid.nix > final-nix-config/etc/nixos/disks/snapraid.nix
# Close the disko.nix block
cat <<'EOF' >> final-nix-config/etc/nixos/disks/disko.nix
};
};
}
EOF
echo -e "\n ✅ Final disko configuration created."
SPINDOWN_DISKS_ID=()
if [[ "${#DATA_DISKS_ID[@]}" -gt 0 ]]; then
for i in ${!DATA_DISKS_ID[@]}; do
if [[ "${DATA_DISKS_TYPE[${i}]}" == "HDD" ]]; then
SPINDOWN_DISKS_ID+=("${DATA_DISKS_ID[${i}]}")
fi
done
if [[ "${#SPINDOWN_DISKS_ID[@]}" -gt 0 ]]; then
cp -${FILES_COPY_FLAGS} templates/nix-config/disks/spindown.nix final-nix-config/etc/nixos/disks/
local FORMATTED_DISKS=""
for disk in "${SPINDOWN_DISKS_ID[@]}"; do
FORMATTED_DISKS+=" \"$disk\"\n"
done
sed -i "s|DISK_LIST|${FORMATTED_DISKS}|" final-nix-config/etc/nixos/disks/spindown.nix
echo -e "\n ✅ Disk spindown configuration created."
fi
fi
### Config generation <--
export SPINDOWN_DISKS_ID
export PARITY_DISK_NUMBER_LIST
export CONTENT_DISK_NUMBER_LIST
}
server_config_generation() {
echo -e "\n # Server settings" >> ${CONFIGURATION_PATH}
echo -e " time.timeZone = \"${TIMEZONE}\";" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.owner = \"${SERVER_OWNER_NAME}\";" >> ${CONFIGURATION_PATH}
}
network_config_generation() {
echo -e "\n # Network settings" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.networking.ipAddress = \"${HOME_SERVER_IP}\";" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.networking.interface = \"${TARGET_INTERFACE}\";" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.networking.routerIpAddress = \"${NETWORK_ROUTER_IP}\";" >> ${CONFIGURATION_PATH}
}
services_config_generation() {
echo -e "\n # DNS settings" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.dns = \"${SELECTED_DNS_SERVICE}\";" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.services.${SELECTED_DNS_SERVICE} = true;" >> ${CONFIGURATION_PATH}
echo -e "\n # Services settings" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.services.domain = \"${DOMAIN_NAME}\";" >> ${CONFIGURATION_PATH}
for service in "${SELECTED_SERVICES[@]}"; do
echo -e " config.numbus.services.${service}.enable = true;" >> ${CONFIGURATION_PATH}
done
}
mail_config_generation() {
echo -e "\n # Mail settings" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.mail.enable = true;" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.mail.userAddress = \"${SERVER_USER_EMAIL}\";" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.mail.adminAddress = \"${SERVER_ADMIN_EMAIL}\";" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.mail.smtpUsername = \"${SMTP_SERVER_USERNAME}\";" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.mail.smtpPasswordPath = config.sops.secrets.smtpPassword.path;" >> ${CONFIGURATION_PATH}
if [[ "${SMTP_SERVER_HOST}" != "smtp.gmail.com" ]]; then
echo -e " config.numbus.mail.smtpServer = \"${SMTP_SERVER_HOST}\";" >> ${CONFIGURATION_PATH}
if [[ "${SMTP_SERVER_PORT}" != "587" ]]; then
echo -e " config.numbus.mail.smtpPort = ${SMTP_SERVER_PORT};" >> ${CONFIGURATION_PATH}
fi
}
disk_config_generation() {
echo -e "\n # Hardware settings" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.hardware.bootDisksList = [ ${BOOT_DISKS_ID_LIST[@]} ];" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.hardware.dataDisksList = [ ${CONTENT_DISK_NUMBER_LIST[@]} ];" >> ${CONFIGURATION_PATH}
echo -e " config.numbus.hardware.parityDisksList = [ ${PARITY_DISK_NUMBER_LIST[@]} ];" >> ${CONFIGURATION_PATH}
}
keys_generation() {
### --> Generate disk keys
for i in $(seq 1 "${#BOOT_DISKS_ID[@]}"); do
for i in $(seq 1 "${#BOOT_DISKS_ID_LIST[@]}"); do
PASS="$(xkcdpass)"
echo -n "$PASS" > "final-nix-config/etc/secrets/disks/boot-${i}"
chmod 600 "final-nix-config/etc/secrets/disks/boot-${i}"
ssh_to_host 'bash -s' << EOF
echo "$REMOTE_PASS" | sudo -S mkdir -p /etc/secrets/disks/
echo "$REMOTE_PASS" | sudo -S bash -c "printf '%s' '$PASS' > /etc/secrets/disks/boot-${i}"
echo "$LIVE_TARGET_PASSWD" | sudo -S mkdir -p /etc/secrets/disks/
echo "$LIVE_TARGET_PASSWD" | sudo -S bash -c "printf '%s' '$PASS' > /etc/secrets/disks/boot-${i}"
echo "$LIVE_TARGET_PASSWD" | sudo -S chmod 600 /etc/secrets/disks/boot-${i}"
EOF
done
for i in $(seq 1 "$CONTENT_DISK_NUMBER"); do
@@ -638,7 +442,8 @@ EOF
echo -n "$PASS" > "final-nix-config/etc/secrets/disks/content-${i}"
chmod 600 "final-nix-config/etc/secrets/disks/content-${i}"
ssh_to_host 'bash -s' << EOF
echo "$REMOTE_PASS" | sudo -S bash -c "printf '%s' '$PASS' > /etc/secrets/disks/content-${i}"
echo "$LIVE_TARGET_PASSWD" | sudo -S bash -c "printf '%s' '$PASS' > /etc/secrets/disks/content-${i}"
echo "$LIVE_TARGET_PASSWD" | sudo -S chmod 600 /etc/secrets/disks/content-${i}"
EOF
done
for i in $(seq 1 "$PARITY_DISK_NUMBER"); do
@@ -646,10 +451,10 @@ EOF
echo -n "$PASS" > "final-nix-config/etc/secrets/disks/parity-${i}"
chmod 600 "final-nix-config/etc/secrets/disks/parity-${i}"
ssh_to_host 'bash -s' << EOF
echo "$REMOTE_PASS" | sudo -S bash -c "printf '%s' '$PASS' > /etc/secrets/disks/parity-${i}"
echo "$LIVE_TARGET_PASSWD" | sudo -S bash -c "printf '%s' '$PASS' > /etc/secrets/disks/parity-${i}"
echo "$LIVE_TARGET_PASSWD" | sudo -S chmod 600 /etc/secrets/disks/parity-${i}"
EOF
done
### Generate disk keys <--
echo -e "\n ✅ Generating sops-nix keys..."
ssh-to-age -private-key -i final-nix-config/home/numbus-admin/.ssh/id_ed25519 > final-nix-config/var/lib/sops-nix/key.txt
@@ -666,41 +471,15 @@ EOF
--output final-nix-config/etc/nixos/secrets/secrets.yaml
}
nix_generation() {
echo -e "\n ✅ Copying the configuration to the new machine..."
cp -${FILES_COPY_FLAGS} templates/nix-config/flake.nix final-nix-config/etc/nixos/
cp -${FILES_COPY_FLAGS} templates/nix-config/misc/* final-nix-config/etc/nixos/misc/
echo "${SERVER_OWNER_NAME:-User}" > final-nix-config/etc/numbus-server/owner
echo -e "\n ✅ Writing correct ips to configuration.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" final-nix-config/etc/nixos/misc/networking.nix
sed -i "s|TARGET_INTERFACE|${TARGET_INTERFACE}|g" final-nix-config/etc/nixos/misc/networking.nix
sed -i "s|DOMAIN_NAME|${DOMAIN_NAME}|" final-nix-config/etc/nixos/misc/mail.nix
sed -i "s|EMAIL_ADDRESS|${EMAIL_ADDRESS}|" final-nix-config/etc/nixos/misc/mail.nix
sed -i "s|SENDER_MAIL_DOMAIN|${SENDER_EMAIL_DOMAIN}|" final-nix-config/etc/nixos/misc/mail.nix
sed -i "s|SENDER_MAIL_ADDRESS|${SENDER_EMAIL_ADDRESS}|" final-nix-config/etc/nixos/misc/mail.nix
sed -i "s*PODMAN_NETWORKS*${PODMAN_NETWORKS//$'\n'/\\n}*" final-nix-config/etc/nixos/misc/activation.nix
sed -i "s|TRAEFIK_NETWORKS|${TRAEFIK_NETWORKS//$'\n'/\\n}|" final-nix-config/etc/nixos/podman/traefik.nix
sed -i "s|TRAEFIK_REF_NETWORKS|${TRAEFIK_REF_NETWORKS//$'\n'/\\n}|" final-nix-config/etc/nixos/podman/traefik.nix
if [[ "${TARGET_TPM}" == "true" && ${TARGET_TPM_VERSION} -eq 2 ]]; then
sed -i "s|# ./disks/pcr-check.nix| ./disks/pcr-check.nix|" final-nix-config/etc/nixos/configuration.nix
sed -i "s|# boot.initrd.systemd.tpm2.enable = true;| boot.initrd.systemd.tpm2.enable = true;|" final-nix-config/etc/nixos/configuration.nix
sed -i "s|# systemIdentity.enable = true;| systemIdentity.enable = true;|" final-nix-config/etc/nixos/configuration.nix
cp -${FILES_COPY_FLAGS} templates/nix-config/disks/pcr-check.nix final-nix-config/etc/nixos/disks/
fi
}
sum_up() {
### --> Disk selection recap
DISK_RECAP_CONTENT=$(cat << EOF
### Disk Configuration Summary
Please review the selected disk layout before proceeding.
**Boot Disks (${#BOOT_DISKS_ID[@]}):**
* **Boot 1:** \`${BOOT_DISKS_ID[0]}\`
$( [[ -n "${BOOT_DISKS_ID[1]:-}" ]] && echo "* **Boot 2:** \`${BOOT_DISKS_ID[1]}\`" || echo "* **Boot 2:** *Not configured*" )
**Boot Disks (${#BOOT_DISKS_ID_LIST[@]}):**
* **Boot 1:** \`${BOOT_DISKS_ID_LIST[0]}\`
$( [[ -n "${BOOT_DISKS_ID_LIST[1]:-}" ]] && echo "* **Boot 2:** \`${BOOT_DISKS_ID_LIST[1]}\`" || echo "* **Boot 2:** *Not configured*" )
**Data Disks ($CONTENT_DISK_NUMBER):**
$( j=1 && for i in $(seq 0 $(($CONTENT_DISK_NUMBER - 1))); do echo "* **Data ${j}:** \`${DATA_DISKS_ID[${i}]}\`" && j=$((j + 1)); done )
@@ -714,40 +493,6 @@ EOF
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "${DISK_RECAP_CONTENT}")"
gum confirm "Proceed with this disk configuration?" || { echo -e "\n\n ❌ Aborting as requested."; exit 1; }
### Disk selection recap <--
### Keys recap <--
KEYS_RECAP_CONTENT=$(cat << EOF
### Generated Secrets Summary
Please save these secrets in a secure location (e.g., a password manager).
**Service Credentials:**
$( [[ -n ${HOME_ASSISTANT_MQTT_USER:-} ]] && echo "* **Home Assistant MQTT User:** \`${HOME_ASSISTANT_MQTT_USER}\`" && \
echo "* **Home Assistant MQTT Password:** \`$HOME_ASSISTANT_MQTT_PASSWORD\`" \
|| echo "* **Home assistant:** *Not configured*" )
$( [[ -n ${FTLCONF_WEBSERVER_PASSWORD:-} ]] && echo "* **Pi-hole Web Password:** \`${FTLCONF_WEBSERVER_PASSWORD}\`" \
|| echo "* **Pi-hole:** *Not configured*" )
$( [[ -n ${PASSBOLT_DB_NAME:-} ]] && echo "* **Passbolt DB Name:** \`${PASSBOLT_DB_NAME}\`" && \
echo "* **Passbolt DB User:** \`${PASSBOLT_DB_USERNAME}\`" && echo "* **Passbolt DB Password:** \`${PASSBOLT_DB_PASSWORD}\`" \
|| echo "* **Passbolt:** *Not configured*" )
$( [[ -n ${IMMICH_DB_NAME:-} ]] && echo "* **Immich DB Name:** \`${IMMICH_DB_NAME}\`" && \
echo "* **Immich DB User:** \`${IMMICH_DB_USERNAME}\`" && echo "* **Immich DB Password:** \`${IMMICH_DB_PASSWORD}\`" \
|| echo "* **Immich:** *Not configured*" )
$( [[ -n ${GITEA_DB_NAME:-} ]] && echo "* **Gitea DB Name:** \`${GITEA_DB_NAME}\`" && \
echo "* **Gitea DB User:** \`${GITEA_DB_USERNAME}\`" && echo "* **Gitea DB Password:** \`${GITEA_DB_PASSWORD}\`" \
|| echo "* **Gitea:** *Not configured*" )
**Disk Encryption Keys:**
$( for i in $(seq 1 "${#BOOT_DISKS_ID[@]}"); do f="final-nix-config/etc/secrets/disks/boot-${i}"; [[ -f "$f" ]] && echo "* **Boot Disk $i Key:** \`$(cat "$f")\`"; done )
$( for i in $(seq 1 "$CONTENT_DISK_NUMBER"); do f="final-nix-config/etc/secrets/disks/content-${i}"; [[ -f "$f" ]] && echo "* **Content Disk $i Key:** \`$(cat "$f")\`"; done )
$( for i in $(seq 1 "$PARITY_DISK_NUMBER"); do f="final-nix-config/etc/secrets/disks/parity-${i}"; [[ -f "$f" ]] && echo "* **Parity Disk $i Key:** \`$(cat "$f")\`"; done )
EOF
)
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "${KEYS_RECAP_CONTENT}")"
gum confirm "Do you want to deploy NixOS on the target host?" || { echo -e "\n\n ❌ Aborting as requested"; exit 1; }
### Keys recap <--
}
cloudflare_dns_setup() {
@@ -761,7 +506,7 @@ cloudflare_dns_setup() {
local CREATION_STATUS
CREATION_STATUS=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer ${CF_DNS_API_TOKEN}" \
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"${SUBDOMAIN}\",\"content\":\"${HOME_SERVER_IP}\",\"ttl\":1,\"proxied\":false}" | jq -r '.success')
@@ -784,12 +529,12 @@ cloudflare_dns_setup() {
|| { echo -e "\n ⚠️ DNS records for ${SUBDOMAIN} will not be updated"; return 0; }
RECORD_IDS=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?name=${SUBDOMAIN}&type=A" \
-H "Authorization: Bearer ${CF_DNS_API_TOKEN}" \
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.result[].id')
for id in ${RECORD_IDS}; do
curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${id}" \
-H "Authorization: Bearer ${CF_DNS_API_TOKEN}" \
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
-H "Content-Type: application/json" > /dev/null 2>&1
done
@@ -812,7 +557,7 @@ cloudflare_dns_setup() {
# Get Zone ID
ZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${DOMAIN_NAME}" \
-H "Authorization: Bearer ${CF_DNS_API_TOKEN}" \
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.result[0].id')
if [[ "${ZONE_ID}" == "null" || -z "${ZONE_ID}" ]]; then
@@ -823,7 +568,7 @@ cloudflare_dns_setup() {
# Check for existing records and create them if non-existent
for service_domain in "${SELECTED_SERVICES_DNS[@]}"; do
DNS_RECORDS=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?name=${service_domain}&type=A" \
-H "Authorization: Bearer ${CF_DNS_API_TOKEN}" \
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
-H "Content-Type: application/json")
RECORD_COUNT=$(echo "${DNS_RECORDS}" | jq '.result | length')
@@ -856,7 +601,7 @@ export_configuration() {
echo -e "\n# SERVER SETTINGS" >> $CONFIG_EXPORT_FILE
echo "export SERVER_OWNER_NAME=\"${SERVER_OWNER_NAME:-User}\"" >> $CONFIG_EXPORT_FILE
echo -e "\n# DISK SETTINGS" >> $CONFIG_EXPORT_FILE
echo "export BOOT_DISKS_ID=\"(${BOOT_DISKS_ID[@]})\"" >> $CONFIG_EXPORT_FILE
echo "export BOOT_DISKS_ID_LIST=\"(${BOOT_DISKS_ID_LIST[@]})\"" >> $CONFIG_EXPORT_FILE
echo "export DATA_DISKS_ID=\"(${DATA_DISKS_ID[@]})\"" >> $CONFIG_EXPORT_FILE
echo "export DATA_DISKS_TYPE=\"(${DATA_DISKS_TYPE[@]})\"" >> $CONFIG_EXPORT_FILE
echo "export SPINDOWN_DISKS_ID=\"(${SPINDOWN_DISKS_ID[@]})\"" >> $CONFIG_EXPORT_FILE
@@ -880,7 +625,7 @@ deploy() {
--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}
--target-host ${TARGET_USER}@${LIVE_TARGET_IP}
echo -e "\n\n✅ Installation successfull !"
sleep 1
@@ -888,8 +633,8 @@ deploy() {
postrun_action() {
TARGET_USER="numbus-admin"
TARGET_HOST="${HOME_SERVER_IP}"
REMOTE_PASS="changeMe!"
LIVE_TARGET_IP="${HOME_SERVER_IP}"
LIVE_TARGET_PASSWD="changeMe!"
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."
@@ -926,7 +671,7 @@ postrun_action() {
Do you want to enable automatic disk decryption on boot ?"
if gum confirm "➡️ I understand, 'yes' to proceed."; then
sshpass -p "${REMOTE_PASS}" ssh -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${TARGET_HOST}" 'bash -s' << EOF
sshpass -p "${LIVE_TARGET_PASSWD}" ssh -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}" 'bash -s' << EOF
echo "Enrolling boot disk key to TPM..."
BOOT_DISKS_NAME=(${BOOT_DISKS_NAME[@]})
@@ -943,14 +688,14 @@ for i in \${!BOOT_DISKS_NAME[@]}; do
DISK_PATH="/dev/\${BOOT_DISKS_NAME[\${i}]}2"
fi
[[ "\${DEBUG}" == "true" ]] && echo "Issuing enroll command for disk \${DISK_PATH}..."
echo ${REMOTE_PASS} | sudo -S systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 --unlock-key-file=/etc/secrets/disks/boot-\${j} \${DISK_PATH}
echo ${LIVE_TARGET_PASSWD} | sudo -S systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 --unlock-key-file=/etc/secrets/disks/boot-\${j} \${DISK_PATH}
j=\$((j + 1))
done
echo "Getting PCRS 15 hash..."
PCR_HASH=\$(echo ${REMOTE_PASS} | sudo -S systemd-analyze pcrs 15 --json=short)
PCR_HASH=\$(echo ${LIVE_TARGET_PASSWD} | sudo -S systemd-analyze pcrs 15 --json=short)
echo ${REMOTE_PASS} | sudo -S sed -i "s|PCR_HASH|\${PCR_HASH}|" /etc/nixos/configuration.nix
echo ${LIVE_TARGET_PASSWD} | sudo -S sed -i "s|PCR_HASH|\${PCR_HASH}|" /etc/nixos/configuration.nix
EOF
else
echo "Skipping TPM configuration."
@@ -967,13 +712,13 @@ securely on a hidden sheet of paper or add it to your password manager (locally
gum confirm "➡️ I understand, 'yes' to proceed." || { echo -e "\n\n❌ Aborting as requested."; exit 1; }
echo $REMOTE_PASS | sudo -S passwd numbus-admin
echo $LIVE_TARGET_PASSWD | sudo -S passwd numbus-admin
}
nix_update() {
echo -e "\n\n🔄 Updating NixOS on the remote server..."
nixos-rebuild --target-host numbus-admin@${TARGET_HOST} \
nixos-rebuild --target-host numbus-admin@${LIVE_TARGET_IP} \
--use-remote-sudo switch --flake final-nix-config/etc/nixos#numbus-server
}
-17
View File
@@ -1,17 +0,0 @@
{
description = "Numbus Server - Your Personal Cloud, Simplified";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
};
outputs = { self, nixpkgs, ... }: {
nixosModules = {
default = { config, pkgs, lib, ... }: {
imports = [
./modules/default.nix
];
};
};
};
}
-97
View File
@@ -1,97 +0,0 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.numbus.services.frigate;
container_name = "frigate";
compose_file = "podman/frigate/compose.yaml";
config_dir = "/mnt/config/frigate";
data_dir = "/mnt/data/frigate";
in
{
options.numbus.services.frigate = {
enable = mkEnableOption "Frigate NVR";
domain = mkOption {
type = types.str;
description = "The root domain name (e.g., example.com). Frigate will use frigate.example.com";
};
mqtt = {
user = mkOption {
type = types.str;
default = "frigate";
description = "MQTT User for Frigate";
};
# In the future, we will handle passwords via sops-nix secrets
};
devices = mkOption {
type = types.listOf types.str;
default = [];
example = [ "/dev/dri:/dev/dri" "/dev/bus/usb:/dev/bus/usb" ];
description = "List of devices to map into the container";
};
};
config = mkIf cfg.enable {
environment.etc."${compose_file}".text =
''
services:
frigate:
image: ghcr.io/blakeblackshear/frigate:stable
container_name: frigate
shm_size: "512MB"
networks:
home-assistant_frontend:
home-assistant_backend:
volumes:
- ${config_dir}:/config
- ${data_dir}/clips:/media/frigate/clips
- ${data_dir}/recordings:/media/frigate/recordings
- ${data_dir}/exports:/media/frigate/exports
- /etc/localtime:/etc/localtime:ro
- type: tmpfs
target: /tmp/cache
tmpfs:
size: 2000000000
environment:
FRIGATE_MQTT_USER: ${cfg.mqtt.user}
# We will handle the password injection securely in the next phase
FRIGATE_MQTT_PASSWORD: $FRIGATE_MQTT_PASSWORD
devices:
${concatStringsSep "\n " (map (d: "- ${d}") cfg.devices)}
labels:
- traefik.enable=true
- traefik.docker.network=home-assistant_frontend
- traefik.http.services.frigate.loadbalancer.server.port=8971
- traefik.http.services.frigate.loadbalancer.server.scheme=http
- traefik.http.routers.frigate-https.entrypoints=websecure
- "traefik.http.routers.frigate-https.rule=Host(`frigate.${cfg.domain}`)"
- traefik.http.routers.frigate-https.tls=true
- traefik.http.routers.frigate-https.tls.certresolver=cloudflare
restart: unless-stopped
networks:
home-assistant_backend:
external: true
home-assistant_frontend:
external: true
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "traefik.service" "home-assistant.service" "pi-hole.service" ];
requires = [ "traefik.service" "home-assistant.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.podman-compose pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Type = "exec";
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
};
};
};
}
-7
View File
@@ -1,7 +0,0 @@
{ ... }:
{
imports = [
./services/default.nix
];
}
View File
-16
View File
@@ -1,16 +0,0 @@
{ ... }:
{
imports = [
./adguard.nix
./frigate.nix
./gitea.nix
./home-assistant.nix
./immich.nix
./it-tools.nix
./nextcloud.nix
./passbolt.nix
./pi-hole.nix
./traefik.nix
];
}
-98
View File
@@ -1,98 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "frigate";
compose_file = "podman/frigate/compose.yaml";
config_dir = "/mnt/config/frigate";
data_dir = "/mnt/data/frigate";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
frigate:
image: ghcr.io/blakeblackshear/frigate:stable
container_name: frigate
shm_size: "512MB"
networks:
home-assistant_frontend:
home-assistant_backend:
volumes:
- ${config_dir}:/config
- ${data_dir}/clips:/media/frigate/clips
- ${data_dir}/recordings:/media/frigate/recordings
- ${data_dir}/exports:/media/frigate/exports
- /etc/localtime:/etc/localtime:ro
- type: tmpfs
target: /tmp/cache
tmpfs:
size: 2000000000
environment:
FRIGATE_MQTT_USER: $FRIGATE_MQTT_USER
FRIGATE_MQTT_PASSWORD: $FRIGATE_MQTT_PASSWORD
# --- frigate devices --- #
labels:
- traefik.enable=true
- traefik.docker.network=home-assistant_frontend
- traefik.http.services.frigate.loadbalancer.server.port=8971
- traefik.http.services.frigate.loadbalancer.server.scheme=http
- traefik.http.routers.frigate-https.entrypoints=websecure
- traefik.http.routers.frigate-https.rule=Host(`frigate.$DOMAIN_NAME`)
- traefik.http.routers.frigate-https.tls=true
- traefik.http.routers.frigate-https.tls.certresolver=cloudflare
restart: unless-stopped
networks:
home-assistant_backend:
external: true
home-assistant_frontend:
external: true
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "traefik.service" "home-assistant.service" "pi-hole.service" ];
requires = [ "traefik.service" "home-assistant.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-113
View File
@@ -1,113 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "gitea";
compose_file = "podman/gitea/compose.yaml";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
gitea:
image: docker.io/gitea/gitea:latest
container_name: gitea
networks:
gitea_frontend:
gitea_backend:
volumes:
- gitea_data:/data
- /etc/localtime:/etc/localtime:ro
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=$POSTGRES_HOST:$POSTGRES_PORT
- GITEA__database__NAME=$DB_NAME
- GITEA__database__USER=$DB_USERNAME
- GITEA__database__PASSWD=$DB_PASSWORD
- GITEA__server__SSH_PORT=2424
- GITEA__server__ROOT_URL=gitea.$DOMAIN_NAME
labels:
- traefik.enable=true
- traefik.docker.network=gitea_frontend
- traefik.http.services.gitea.loadbalancer.server.port=3000
- traefik.http.services.gitea.loadbalancer.server.scheme=http
- traefik.http.routers.gitea-https.entrypoints=websecure
- traefik.http.routers.gitea-https.rule=Host(`gitea.$DOMAIN_NAME`)
- traefik.http.routers.gitea-https.tls=true
- traefik.http.routers.gitea-https.tls.certresolver=cloudflare
depends_on:
- gitea-database
restart: unless-stopped
gitea-database:
image: docker.io/library/postgres:17.5
container_name: gitea-database
environment:
- POSTGRES_USER=$DB_USERNAME
- POSTGRES_PASSWORD=$DB_PASSWORD
- POSTGRES_DB=$DB_NAME
networks:
gitea_backend:
volumes:
- gitea_database:/var/lib/postgresql/data
restart: unless-stopped
volumes:
gitea_data:
gitea_database:
networks:
gitea_frontend:
external: true
gitea_backend:
external: true
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "900";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 400))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-83
View File
@@ -1,83 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "home-assistant";
compose_file = "podman/home-assistant/compose.yaml";
config_dir_1 = "/mnt/config/home-assistant";
config_dir_2 = "/mnt/config/mqtt";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
home-assistant:
image: ghcr.io/home-assistant/home-assistant:latest
container_name: home-assistant
networks:
home-assistant_frontend:
home-assistant_backend:
volumes:
- ${config_dir_1}:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
# --- home-assistant devices --- #
labels:
- traefik.enable=true
- traefik.docker.network=home-assistant_frontend
- traefik.http.services.home-assistant.loadbalancer.server.port=8123
- traefik.http.services.home-assistant.loadbalancer.server.scheme=http
- traefik.http.routers.home-assistant-https.entrypoints=websecure
- traefik.http.routers.home-assistant-https.rule=Host(`home-assistant.$DOMAIN_NAME`)
- traefik.http.routers.home-assistant-https.tls=true
- traefik.http.routers.home-assistant-https.tls.certresolver=cloudflare
restart: unless-stopped
frigate-mqtt:
image: eclipse-mosquitto
container_name: mqtt
user: 1000:1000
networks:
home-assistant_backend:
volumes:
- ${config_dir_2}:/mosquitto
restart: unless-stopped
networks:
home-assistant_backend:
external: true
home-assistant_frontend:
external: true
'';
systemd.services.${container_name} = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
# Pull the latest image before running
ExecStartPre = [
"${pkgs.coreutils}/bin/sleep 180"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
-134
View File
@@ -1,134 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "immich";
compose_file = "podman/immich/compose.yaml";
config_dir = "/mnt/config/immich";
data_dir = "/mnt/data/immich";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
immich-server:
image: ghcr.io/immich-app/immich-server:$IMMICH_VERSION
container_name: immich-server
networks:
immich_frontend:
immich_backend:
volumes:
- $UPLOAD_LOCATION:/data
- /etc/localtime:/etc/localtime:ro
# --- immich devices --- #
labels:
- traefik.enable=true
- traefik.docker.network=immich_frontend
- traefik.http.services.immich.loadbalancer.server.port=2283
- traefik.http.services.immich.loadbalancer.server.scheme=http
- traefik.http.routers.immich-https.entrypoints=websecure
- traefik.http.routers.immich-https.rule=Host(`immich.$DOMAIN_NAME`)
- traefik.http.routers.immich-https.tls=true
- traefik.http.routers.immich-https.tls.certresolver=cloudflare
env_file:
- .env
depends_on:
- immich-redis
- immich-database
restart: always
healthcheck:
disable: false
immich-machine-learning:
container_name: immich-machine-learning
image: ghcr.io/immich-app/immich-machine-learning:$IMMICH_VERSION
networks:
immich_backend:
volumes:
- ${config_dir}/models:/cache
env_file:
- .env
restart: always
healthcheck:
disable: false
immich-redis:
container_name: immich-redis
image: docker.io/valkey/valkey:8-bookworm@sha256:a137a2b60aca1a75130022d6bb96af423fefae4eb55faf395732db3544803280
networks:
immich_backend:
healthcheck:
test: redis-cli ping || exit 1
restart: always
immich-database:
container_name: immich-database
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a
networks:
immich_backend:
shm_size: 128mb
volumes:
# Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
- $DB_DATA_LOCATION:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: $DB_PASSWORD
POSTGRES_USER: $DB_USERNAME
POSTGRES_DB: $DB_DATABASE_NAME
POSTGRES_INITDB_ARGS: '--data-checksums'
restart: always
healthcheck:
disable: false
networks:
immich_backend:
external: true
immich_frontend:
external: true
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "900";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 400))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-77
View File
@@ -1,77 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "it-tools";
compose_file = "podman/it-tools/compose.yaml";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
it-tools:
container_name: it-tools
image: corentinth/it-tools
networks:
it-tools_frontend:
labels:
- traefik.enable=true
- traefik.docker.network=it-tools_frontend
- traefik.http.services.it-tools.loadbalancer.server.port=80
- traefik.http.services.it-tools.loadbalancer.server.scheme=http
- traefik.http.routers.it-tools-https.entrypoints=websecure
- traefik.http.routers.it-tools-https.rule=Host(`it-tools.$DOMAIN_NAME`)
- traefik.http.routers.it-tools-https.tls=true
- traefik.http.routers.it-tools-https.tls.certresolver=cloudflare
restart: unless-stopped
networks:
it-tools_frontend:
external: true
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-131
View File
@@ -1,131 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "nextcloud";
compose_file = "podman/nextcloud/compose.yaml";
data_dir = "/mnt/data/nextcloud";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
nextcloud-server:
image: docker.io/library/nextcloud:latest
container_name: nextcloud-server
restart: unless-stopped
networks:
nextcloud_frontend:
nextcloud_backend:
volumes:
- nextcloud_data:/var/www/html
- ${data_dir}:/var/www/html/data
environment:
MYSQL_HOST: nextcloud-database
MYSQL_DATABASE: $MYSQL_DATABASE
MYSQL_USER: $MYSQL_USER
MYSQL_PASSWORD: $MYSQL_PASSWORD
REDIS_HOST: nextcloud-redis
REDIS_HOST_PASSWORD: $REDIS_HOST_PASSWORD
NEXTCLOUD_TRUSTED_DOMAINS: $DOMAIN_NAME
SMTP_HOST: $SMTP_HOST
SMTP_SECURE: tls
SMTP_PORT: $SMTP_PORT
SMTP_NAME: $SMTP_NAME
SMTP_PASSWORD: $SMTP_PASSWORD
MAIL_FROM_ADDRESS: $MAIL_FROM_ADDRESS
MAIL_DOMAIN: $DOMAIN_NAME
APACHE_DISABLE_REWRITE_IP: 1
TRUSTED_PROXIES: traefik
OVERWRITEPROTOCOL: https
labels:
- traefik.enable=true
- traefik.docker.network=nextcloud_frontend
- traefik.http.services.nextcloud.loadbalancer.server.port=80
- traefik.http.services.nextcloud.loadbalancer.server.scheme=http
- traefik.http.routers.nextcloud-https.entrypoints=websecure
- traefik.http.routers.nextcloud-https.rule=Host(`nextcloud.$DOMAIN_NAME`)
- traefik.http.routers.nextcloud-https.tls=true
- traefik.http.routers.nextcloud-https.tls.certresolver=cloudflare
depends_on:
- nextcloud-database
nextcloud-redis:
image: docker.io/library/redis:alpine
name: nextcloud-redis
restart: unless-stopped
networks:
nextcloud_backend:
command: redis-server --requirepass $REDIS_HOST_PASSWORD
nextcloud-database:
image: docker.io/library/mariadb:latest
container_name: nextcloud-database
restart: unless-stopped
networks:
nextcloud_backend:
volumes:
- nextcloud_database:/var/lib/mysql
environment:
MARIADB_DATABASE: $MYSQL_DATABASE
MARIADB_USER: $MYSQL_USER
MARIADB_PASSWORD: $MYSQL_PASSWORD
MARIADB_RANDOM_ROOT_PASSWORD: true
networks:
nextcloud_frontend:
external: true
nextcloud_backend:
external: true
volumes:
nextcloud_data:
nextcloud_database:
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-129
View File
@@ -1,129 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "passbolt";
compose_file = "podman/passbolt/compose.yaml";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
passbolt:
image: passbolt/passbolt:latest-ce-non-root
container_name: passbolt
networks:
passbolt_frontend:
passbolt_backend:
volumes:
- passbolt-gpg:/etc/passbolt/gpg
- passbolt-jwt:/etc/passbolt/jwt
environment:
APP_DEFAULT_TIMEZONE: $TZ
APP_FULL_BASE_URL: https://passbolt.$DOMAIN_NAME
DATASOURCES_DEFAULT_HOST: "passbolt-database"
DATASOURCES_DEFAULT_USERNAME: $PASSBOLT_MYSQL_USER
DATASOURCES_DEFAULT_PASSWORD: $PASSBOLT_MYSQL_PASSWORD
DATASOURCES_DEFAULT_DATABASE: $PASSBOLT_MYSQL_DATABASE
EMAIL_DEFAULT_FROM_NAME: "Passbolt"
EMAIL_TRANSPORT_DEFAULT_HOST: $EMAIL_TRANSPORT_DEFAULT_HOST
EMAIL_TRANSPORT_DEFAULT_PORT: $EMAIL_TRANSPORT_DEFAULT_PORT
EMAIL_TRANSPORT_DEFAULT_USERNAME: $EMAIL_TRANSPORT_DEFAULT_USERNAME
EMAIL_TRANSPORT_DEFAULT_PASSWORD: $EMAIL_TRANSPORT_DEFAULT_PASSWORD
EMAIL_TRANSPORT_DEFAULT_TLS: true
EMAIL_DEFAULT_FROM: $EMAIL_ADDRESS
PASSBOLT_SSL_FORCE: true
labels:
- traefik.enable=true
- traefik.docker.network=passbolt_frontend
- traefik.http.services.passbolt.loadbalancer.server.port=4433
- traefik.http.services.passbolt.loadbalancer.server.scheme=https
- traefik.http.routers.passbolt-https.entrypoints=websecure
- traefik.http.routers.passbolt-https.rule=Host(`passbolt.$DOMAIN_NAME`)
- traefik.http.routers.passbolt-https.tls=true
- traefik.http.routers.passbolt-https.tls.certresolver=cloudflare
command:
[
"/usr/bin/wait-for.sh",
"-t",
"0",
"passbolt-database:3306",
"--",
"/docker-entrypoint.sh",
]
depends_on:
- passbolt-database
restart: unless-stopped
passbolt-database:
image: mariadb:11.3
container_name: passbolt-database
networks:
passbolt_backend:
volumes:
- passbolt-database:/var/lib/mysql
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "true"
MYSQL_DATABASE: $PASSBOLT_MYSQL_DATABASE
MYSQL_USER: $PASSBOLT_MYSQL_USER
MYSQL_PASSWORD: $PASSBOLT_MYSQL_PASSWORD
restart: unless-stopped
networks:
passbolt_backend:
external: true
passbolt_frontend:
external: true
volumes:
passbolt-database:
passbolt-gpg:
passbolt-jwt:
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-106
View File
@@ -1,106 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "pi-hole";
compose_file = "podman/pi-hole/compose.yaml";
config_dir = "/mnt/config/pi-hole";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
pihole:
image: docker.io/pihole/pihole:latest
container_name: pi-hole
networks:
pi-hole_frontend:
ports:
# DNS Ports
- "53:53/tcp"
- "53:53/udp"
environment:
TZ: $TZ
FTLCONF_webserver_api_password: $FTLCONF_webserver_api_password
FTLCONF_dns_hosts: |
$HOME_SERVER_IP frigate.$DOMAIN_NAME
$HOME_SERVER_IP gitea.$DOMAIN_NAME
$HOME_SERVER_IP home-assistant.$DOMAIN_NAME
$HOME_SERVER_IP immich.$DOMAIN_NAME
$HOME_SERVER_IP it-tools.$DOMAIN_NAME
$HOME_SERVER_IP nextcloud.$DOMAIN_NAME
$HOME_SERVER_IP nextcloud-aio.$DOMAIN_NAME
$HOME_SERVER_IP passbolt.$DOMAIN_NAME
$HOME_SERVER_IP pi-hole.$DOMAIN_NAME
$HOME_SERVER_IP traefik.$DOMAIN_NAME
FTLCONF_dhcp_active: "false"
FTLCONF_dns_upstreams: 9.9.9.9;149.112.112.112
FTLCONF_ntp_ipv4_active: "false"
FTLCONF_ntp_ipv6_active: "false"
FTLCONF_ntp_sync_active: "false"
volumes:
- ${config_dir}:/etc/pihole
cap_add:
- SYS_NICE
labels:
- traefik.enable=true
- traefik.docker.network=pi-hole_frontend
- traefik.http.services.pihole.loadbalancer.server.port=80
- traefik.http.services.pihole.loadbalancer.server.scheme=http
- traefik.http.routers.pihole-https.entrypoints=websecure
- traefik.http.routers.pihole-https.rule=Host(`pi-hole.$DOMAIN_NAME`)
- traefik.http.routers.pihole-https.tls=true
- traefik.http.routers.pihole-https.tls.certresolver=cloudflare
restart: unless-stopped
networks:
pi-hole_frontend:
external: true
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep 20'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-64
View File
@@ -1,64 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "traefik";
compose_file = "podman/traefik/compose.yaml";
config_dir = "/mnt/config/traefik";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
traefik:
image: docker.io/library/traefik:latest
container_name: traefik
networks:
TRAEFIK_NETWORKS
ports:
- "80:80"
- "443:443"
volumes:
- /run/user/1000/podman/podman.sock:/run/docker.sock:ro
- ${config_dir}/rules/:/etc/traefik/conf/:ro
- ${config_dir}/traefik.yaml:/etc/traefik/traefik.yaml:ro
- ${config_dir}/certs/:/var/traefik/certs/:rw
environment:
- CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN
labels:
- traefik.enable=true
- traefik.http.services.traefik.loadbalancer.server.port=8080
- traefik.http.services.traefik.loadbalancer.server.scheme=http
- traefik.http.routers.traefik-https.entrypoints=websecure
- traefik.http.routers.traefik-https.rule=Host(`traefik.$DOMAIN_NAME`)
- traefik.http.routers.traefik-https.tls=true
- traefik.http.routers.traefik-https.tls.certresolver=cloudflare
restart: always
networks:
TRAEFIK_REF_NETWORKS
'';
systemd.services.traefik = {
description = "Podman container : ${container_name}";
after = [ "numbus-activation.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
+13 -155
View File
@@ -4,173 +4,31 @@
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
(modulesPath + "/profiles/qemu-guest.nix")
inputs.sops-nix.nixosModules.sops
./disks/disko.nix
./misc/activation.nix
./misc/mail.nix
./misc/networking.nix
./misc/smart.nix
./misc/terminal.nix
# ./disks/pcr-check.nix
# ./disks/snapraid.nix
# ./pcie-coral/coral.nix
];
# Enable email notifications
email.enable = true;
# Hardware settings
hardware.enableRedistributableFirmware = true;
hardware.cpu.intel.updateMicrocode = true;
hardware.cpu.amd.updateMicrocode = true;
# System
system.stateVersion = "25.11";
# Secrets management
sops.defaultSopsFile = ./secrets/secrets.yaml;
sops.age.sshKeyPaths = [ "/home/numbus-admin/.ssh/id_ed25519" ];
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
sops.secrets."ssh_public_keys" = { owner = "numbus-admin"; path = "/etc/ssh/authorized_keys.d/numbus-admin"; };
sops.secrets."sender_email_address_password" = {};
sops.secrets."domain_name" = {};
sops.secrets."podman/frigate" = { owner = "numbus-admin"; path = "/etc/podman/frigate/.env"; };
sops.secrets."podman/gitea" = { owner = "numbus-admin"; path = "/etc/podman/gitea/.env"; };
sops.secrets."podman/home_assistant" = { owner = "numbus-admin"; path = "/etc/podman/home-assistant/.env"; };
sops.secrets."podman/immich" = { owner = "numbus-admin"; path = "/etc/podman/immich/.env"; };
sops.secrets."podman/it_tools" = { owner = "numbus-admin"; path = "/etc/podman/it-tools/.env"; };
sops.secrets."podman/nextcloud" = { owner = "numbus-admin"; path = "/etc/podman/nextcloud/.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
boot.initrd.systemd.enable = true;
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.swraid.mdadmConf = "MAILADDR ${config.email.userAddress},${config.email.adminAddress}";
# boot.initrd.systemd.tpm2.enable = true;
boot.kernel.sysctl = {
"vm.overcommit_memory" = 1;
};
sops.secrets."ssh_public_keys" = { owner = "numbus-admin"; path = "/home/numbus-admin/.ssh/authorized_keys"; mode = "0600"; };
sops.secrets."smtpPassword" = { owner = "numbus-admin"; mode = "0600"; };
sops.secrets."cloudflareDnsApiToken" = { owner = "numbus-admin"; mode = "0600"; };
# # TPM2 PCR check
# systemIdentity.enable = true;
# systemIdentity.pcr15 = "PCR_HASH";
# Timezone
# Server
time.timeZone = "Europe/Paris";
config.numbus.owner = "Raphael";
# Internationalisation properties.
i18n.defaultLocale = "fr_FR.UTF-8";
i18n.extraLocaleSettings = {
LC_ADDRESS = "fr_FR.UTF-8";
LC_IDENTIFICATION = "fr_FR.UTF-8";
LC_MEASUREMENT = "fr_FR.UTF-8";
LC_MONETARY = "fr_FR.UTF-8";
LC_NAME = "fr_FR.UTF-8";
LC_NUMERIC = "fr_FR.UTF-8";
LC_PAPER = "fr_FR.UTF-8";
LC_TELEPHONE = "fr_FR.UTF-8";
LC_TIME = "fr_FR.UTF-8";
};
# Enable email notifications
config.numbus.mail.enable = true;
config.numbus.mail.userAddress = "user@tunea.eu";
config.numbus.mail.adminAddress = "admin@tunea.eu";
config.numbus.mail.smtpUsername = "raphaels.server@gmail.com";
config.numbus.mail.smtpPasswordPath = config.sops.secrets.smtpPassword.path;
# Keyboard mapping
console.keyMap = "fr";
services.xserver.xkb = {
layout = "fr";
variant = "";
};
# Enable SSH
services.openssh.enable = true;
# Allow unfree packages
nixpkgs.config.allowUnfree = true;
# Install packages
environment.systemPackages = with pkgs; [
git
ncdu
fastfetch
tpm2-tss
sops
age
powertop
pciutils
hdparm
hd-idle
hddtemp
smartmontools
cpufrequtils
intel-gpu-tools
podman
podman-compose
podman-tui
slirp4netns
# passt
# netavark
# aardvark-dns
snapraid
mergerfs
mergerfs-tools
];
# Power savings
services.autoaspm.enable = true;
powerManagement.powertop.enable = true;
boot.kernelParams = [
"pcie_aspm=force"
"consoleblank=60"
];
# Enable cron service
services.cron = {
enable = true;
systemCronJobs = [
];
};
# Enable Podman
virtualisation.podman.enable = true;
virtualisation.podman.defaultNetwork.settings.dns_enabled = true;
# Enable libvirt
# virtualisation.libvirtd.enable = true;
# programs.virt-manager.enable = true;
# User account
users.users.numbus-admin = {
shell = pkgs.fish;
isNormalUser = true;
description = "Numbus Admin";
extraGroups = [ "wheel" ];
uid = 1000;
initialPassword = "changeMe!";
# required for auto start before user login
linger = true;
# required for rootless container with multiple users
autoSubUidGidRange = true;
};
# Enable auto updates
system.autoUpgrade = {
enable = true;
allowReboot = true;
flake = inputs.self.outPath;
flags = [ "--print-build-logs" ];
dates = "02:00";
randomizedDelaySec = "45min";
};
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d";
};
# Enable NixOS flakes
nix.settings.experimental-features = [ "nix-command" "flakes" ];
# Enable auto nix-store optimization
nix.settings.auto-optimise-store = true;
system.stateVersion = "25.11";
}
-81
View File
@@ -1,81 +0,0 @@
{
disko.devices = {
# Boot disk LVM configuration
lvm_vg = {
pool = {
type = "lvm_vg";
lvs = {
swap = {
size = "16G";
content = {
type = "swap";
};
};
snapraid = {
size = "1G";
content = {
type = "filesystem";
format = "btrfs";
mountpoint = "/mnt/content-0";
};
};
root = {
size = "100%";
content = {
type = "btrfs";
extraArgs = [ "-f" ];
subvolumes = {
"/rootfs" = {
mountpoint = "/";
mountOptions = [ "compress=zstd" "noatime" ];
};
"/home" = {
mountpoint = "/home";
mountOptions = [ "compress=zstd" ];
};
"/nix" = {
mountpoint = "/nix";
mountOptions = [ "compress=zstd" "noatime" ];
};
};
};
};
};
};
};
disk = {
# Boot disk
"boot-1" = {
type = "disk";
device = "$BOOT_DISK_1_ID";
content = {
type = "gpt";
partitions = {
ESP = {
size = "1G";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted-boot-1";
settings = {
keyFile = "/etc/secrets/disks/boot-1";
allowDiscards = true;
};
content = {
type = "lvm_pv";
vg = "pool";
};
};
};
};
};
};
-125
View File
@@ -1,125 +0,0 @@
{
disko.devices = {
mdadm = {
boot = {
type = "mdadm";
level = 1;
metadata = "1.2";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
};
lvm_vg = {
pool = {
type = "lvm_vg";
lvs = {
swap = {
size = "16G";
lvm_type = "mirror";
content = {
type = "swap";
};
};
snapraid = {
size = "1G";
lvm_type = "mirror";
content = {
type = "filesystem";
format = "btrfs";
mountpoint = "/mnt/content-0";
};
};
root = {
size = "100%";
lvm_type = "mirror";
content = {
type = "btrfs";
extraArgs = [ "-f" ];
subvolumes = {
"/rootfs" = {
mountpoint = "/";
mountOptions = [ "compress=zstd" "noatime" ];
};
"/home" = {
mountpoint = "/home";
mountOptions = [ "compress=zstd" ];
};
"/nix" = {
mountpoint = "/nix";
mountOptions = [ "compress=zstd" "noatime" ];
};
};
};
};
};
};
};
disk = {
boot-1 = {
type = "disk";
device = "$BOOT_DISK_1_ID";
content = {
type = "gpt";
partitions = {
ESP = {
size = "1G";
type = "EF00";
content = {
type = "mdraid";
name = "boot";
};
};
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted-boot-1";
settings = {
allowDiscards = true;
keyFile = "/etc/secrets/disks/boot-1";
};
content = {
type = "lvm_pv";
vg = "pool";
};
};
};
};
};
};
boot-2 = {
type = "disk";
device = "$BOOT_DISK_2_ID";
content = {
type = "gpt";
partitions = {
ESP = {
size = "1G";
type = "EF00";
content = {
type = "mdraid";
name = "boot";
};
};
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted-boot-2";
settings = {
allowDiscards = true;
keyFile = "/etc/secrets/disks/boot-2";
};
content = {
type = "lvm_pv";
vg = "pool";
};
};
};
};
};
};
-28
View File
@@ -1,28 +0,0 @@
"content-${j}" = {
type = "disk";
device = "${CONTENT_DISK_ID}";
content = {
type = "gpt";
partitions = {
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted-content-${j}";
initrdUnlock = false;
settings = {
keyFile = "/etc/secrets/disks/content-${j}";
allowDiscards = ${ALLOW_DISCARDS:-false};
crypttabExtraOpts = [ "nofail" "noauto" ];
};
content = {
type = "filesystem";
format = "xfs";
mountpoint = "/mnt/content-${j}";
mountOptions = [ "nofail" "noauto" ];
};
};
};
};
};
};
-28
View File
@@ -1,28 +0,0 @@
"parity-${j}" = {
type = "disk";
device = "${PARITY_DISK_ID}";
content = {
type = "gpt";
partitions = {
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted-parity-${j}";
initrdUnlock = false;
settings = {
keyFile = "/etc/secrets/disks/parity-${j}";
allowDiscards = ${ALLOW_DISCARDS:-false};
crypttabExtraOpts = [ "nofail" "noauto" ];
};
content = {
type = "filesystem";
format = "xfs";
mountpoint = "/mnt/parity-${j}";
mountOptions = [ "nofail" "noauto" ];
};
};
};
};
};
};
-124
View File
@@ -1,124 +0,0 @@
{ lib, utils, config, ... }:
let
inherit (lib)
head
optional
foldl'
nameValuePair
listToAttrs
optionals
concatStringsSep
sortOn
mkIf
mkEnableOption
mkOption
types
;
in
{
options = {
systemIdentity = {
enable = mkEnableOption "hashing of Luks values into PCR 15 and subsequent checks";
pcr15 = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
The expected value of PCR 15 after all luks partitions have been unlocked
Should be a 64 character hex string as ouput by the sha256 field of
'systemd-analyze pcrs 15 --json=short'
If set to null (the default) it will not check the value.
If the check fails the boot will abort and you will be dropped into an emergency shell, if enabled.
In ermergency shell type:
'systemctl disable check-pcrs'
'systemctl default'
to continue booting
'';
};
};
boot.initrd.luks.devices = lib.mkOption {
type =
with lib.types;
attrsOf (submodule {
config.crypttabExtraOpts = optionals config.systemIdentity.enable [
"tpm2-device=auto"
"tpm2-measure-pcr=yes"
];
});
};
};
config = mkIf config.systemIdentity.enable {
boot.kernelParams = [
"rd.luks=no"
];
boot.initrd.systemd.services =
{
check-pcrs = mkIf (config.systemIdentity.pcr15 != null) {
script = ''
echo "Checking PCR 15 value"
if [[ $(systemd-analyze pcrs 15 --json=short | jq -r ".[0].sha256") != "${config.systemIdentity.pcr15}" ]] ; then
echo "PCR 15 check failed"
exit 1
else
echo "PCR 15 check succeeded"
fi
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
unitConfig.DefaultDependencies = "no";
after = [ "cryptsetup.target" ];
before = [ "sysroot.mount" ];
requiredBy = [ "sysroot.mount" ];
};
}
// (listToAttrs (
foldl' (
acc: attrs:
let
extraOpts = attrs.value.crypttabExtraOpts ++ (optional attrs.value.allowDiscards "discard");
cfg = config.boot.initrd.systemd;
in
[
(nameValuePair "cryptsetup-${attrs.name}" {
unitConfig = {
Description = "Cryptography setup for ${attrs.name}";
DefaultDependencies = "no";
IgnoreOnIsolate = true;
Conflicts = [ "umount.target" ];
BindsTo = "${utils.escapeSystemdPath attrs.value.device}.device";
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
TimeoutSec = "infinity";
KeyringMode = "shared";
OOMScoreAdjust = 500;
ImportCredential = "cryptsetup.*";
ExecStart = ''${cfg.package}/bin/systemd-cryptsetup attach '${attrs.name}' '${attrs.value.device}' '-' '${concatStringsSep "," extraOpts}' '';
ExecStop = ''${cfg.package}/bin/systemd-cryptsetup detach '${attrs.name}' '';
};
after =
[
"cryptsetup-pre.target"
"systemd-udevd-kernel.socket"
"${utils.escapeSystemdPath attrs.value.device}.device"
]
++ (optional cfg.tpm2.enable "systemd-tpm2-setup-early.service")
++ optional (acc != [ ]) "${(head acc).name}.service";
before = [
"blockdev@dev-mapper-${attrs.name}.target"
"cryptsetup.target"
"umount.target"
];
wants = [ "blockdev@dev-mapper-${attrs.name}.target" ];
requiredBy = [ "sysroot.mount" ];
})
]
++ acc
) [ ] (sortOn (x: x.name) (lib.attrsets.attrsToList config.boot.initrd.luks.devices))
));
};
}
-60
View File
@@ -1,60 +0,0 @@
{ config, lib, pkgs, ... }:
{
### --> MergerFS setup
fileSystems."/mnt/data" = {
device = "/mnt/content-*";
fsType = "fuse.mergerfs";
options = [
"category.create=ff"
"cache.files=partial"
"dropcacheonclose=true"
"defaults"
"noauto"
"nofail"
"allow_other"
"moveonenospc=1"
"minfreespace=50G"
"func.getattr=newest"
"fsname=mergerfs_data"
"x-mount.mkdir"
"x-systemd.automount"
"x-systemd.requires=mount-dependencies.service"
];
};
### MergerFS setup <--
systemd.services.mount-dependencies = {
description = "This service will mount the encrypted disks for mergerFS";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
# Bring the service up
ExecStart = pkgs.writeShellScript "mount-disks" ''
$MOUNT_DEPENDENCIES_START
'';
# Take it down gracefully
ExecStop = pkgs.writeShellScript "unmount-disks" ''
$MOUNT_DEPENDENCIES_STOP
'';
Restart = "on-failure";
};
};
### --> SnapRAID setup
services.snapraid = {
enable = true;
contentFiles = [
$SNAPRAID_CONTENT_FILES
];
parityFiles = [
$SNAPRAID_PARITY_FILES
];
dataDisks = {
$SNAPRAID_DATA_DISKS
};
};
### SnapRAID setup <--
}
-25
View File
@@ -1,25 +0,0 @@
{ config, pkgs, lib, ... }:
let
hardDrives = [
DISK_LIST
];
in
{
### --> Disk spindown
systemd.services.hd-idle = {
description = "External HD spin down daemon";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
ExecStart =
let
idleTime = toString 1800;
hardDriveParameter = lib.strings.concatMapStringsSep " " (x: "-a ${x} -i ${idleTime}") hardDrives;
in
"${pkgs.hd-idle}/bin/hd-idle -i 0 ${hardDriveParameter}";
};
};
### Disk spindown <--
}
+7 -12
View File
@@ -2,6 +2,9 @@
inputs = {
# Core Nixpkgs
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
# Numbus server configuration
numbus.url = "git+https://gittea.dev/numbus/numbus-server-module";
numbus.inputs.nixpkgs.follows = "nixpkgs";
# Disk-partitioning helper
disko.url = "github:nix-community/disko";
disko.inputs.nixpkgs.follows = "nixpkgs";
@@ -13,27 +16,20 @@
autoaspm.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, disko, sops-nix, autoaspm, ... }@inputs: let
outputs = { self, nixpkgs, numbus, disko, sops-nix, autoaspm, ... }@inputs: let
# System definition
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
# Helper: collect every *.nix file inside ./podman as a list
podmanModules = let
dir = ./podman;
entries = builtins.readDir dir;
names = builtins.attrNames entries;
nixNames = builtins.filter (n: builtins.match ".*\\.nix" n != null) names;
in map (name: "${dir}/${name}") nixNames;
in {
nixosConfigurations = {
numbus-server = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = { inherit inputs; };
modules = [
# Numbus server configuration
numbus.nixosModules.numbus
# Disk-partitioning helper
disko.nixosModules.disko
# Secrets handling
@@ -43,8 +39,7 @@
# Core host configuration
./configuration.nix
./hardware-configuration.nix
# Podman services - automatically added from ./podman/*.nix
] ++ podmanModules;
]
};
};
};
-129
View File
@@ -1,129 +0,0 @@
{ config, pkgs, ... }:
{
systemd.services.numbus-activation = {
description = "Numbus-Server activation : Correct permissions";
wantedBy = [ "multi-user.target" "traefik.service" ];
after = [ "network.target" "local-fs.target" ];
path = [ pkgs.coreutils pkgs.podman pkgs.sudo ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
#!/usr/bin/env bash
if [[ -e /home/numbus-admin/.numbus-server/activated.true ]]; then
echo "Already activated"
exit 0
fi
echo "Creating directories with correct permissions..."
mkdir -p /mnt/config/ /mnt/data/ /mnt/data/nextcloud/
mkdir -p /home/numbus-admin/.numbus-server/
chown -R numbus-admin:users /mnt/config/
chown -R numbus-admin:users /mnt/data/
chown -R 100032:100032 /mnt/data/nextcloud/
echo "Creating podman networks..."
export PATH=$PATH:/run/wrappers/bin
PODMAN_NETWORKS
mkdir -p /home/numbus-admin/.numbus-server/
touch /home/numbus-admin/.numbus-server/activated.true
chown -R numbus-admin:users /home/numbus-admin/.numbus-server/
echo "Activated successfully !"
'';
};
systemd.services.numbus-quirks = {
description = "Numbus-Server services : Apply quirks";
wantedBy = [ "multi-user.target" ];
after = [
"network.target"
"local-fs.target"
"numbus-activation-chowned.service"
"numbus-activation-networked.service"
"pi-hole.service"
"home-assistant.service"
];
path = [ pkgs.curl pkgs.coreutils pkgs.systemd pkgs.podman pkgs.sudo ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
#!/usr/bin/env bash
set -euo pipefail
if [[ -e /home/numbus-admin/.numbus-server/quirked.true ]]; then
echo "Quirks already applied"
exit 0
fi
DOMAIN_NAME="$(cat /run/secrets/domain_name)"
if [[ -e /etc/nixos/podman/pi-hole.nix ]]; then
echo "Applying Pi-Hole quirks..."
mkdir -p /mnt/config/pi-hole/
chown -R numbus-admin:users /mnt/config/pi-hole/
echo "Waiting for Pi-hole to be ready..."
until [[ -e /mnt/config/pi-hole/pihole-FTL.db ]]; do
sleep 15
done
sleep 60
sudo -u numbus-admin podman exec pi-hole pihole -g
sleep 60
systemctl restart pi-hole.service
echo "Pi-Hole quirk applied and service ready !"
fi
if [[ -e /etc/nixos/podman/home-assistant.nix ]]; then
echo "Applying Home Assistant quirks..."
mkdir -p /mnt/config/home-assistant/
chown -R numbus-admin:users /mnt/config/home-assistant/
echo "Waiting for Home Assistant to be ready..."
until [[ -e /mnt/config/home-assistant/configuration.yaml ]]; do
sleep 15
done
sleep 180
systemctl stop home-assistant.service
cat << 'EOF' >> /mnt/config/home-assistant/configuration.yaml
http:
use_x_forwarded_for: true
trusted_proxies: 10.89.0.0/16
zha:
EOF
systemctl start home-assistant.service
echo "Home Assistant quirk applied and service ready !"
fi
if [[ -e /etc/nixos/podman/frigate.nix ]]; then
echo "Applying Frigate quirks..."
mkdir -p /mnt/config/frigate/
chown -R numbus-admin:users /mnt/config/frigate/
echo "Waiting for Frigate to be ready..."
until [[ -e /mnt/config/frigate/config.yaml ]]; do
sleep 15
done
sleep 180
systemctl stop frigate.service
cat << 'EOF' >> /mnt/config/frigate/config.yaml
tls:
enabled: false
EOF
systemctl start frigate.service
echo "Frigate quirk applied and service ready !"
fi
mkdir -p /home/numbus-admin/.numbus-server/
touch /home/numbus-admin/.numbus-server/quirked.true
chown -R numbus-admin:users /home/numbus-admin/.numbus-server/
echo "Quirks applied successfully !"
'';
};
}
-74
View File
@@ -1,74 +0,0 @@
{ config, pkgs, lib, ... }:
let
cfg = config.email;
in
### --> Mail notifications configuration
{
options.email = {
enable = lib.mkEnableOption "Email sending functionality";
fromAddress = lib.mkOption {
description = "The 'from' address";
type = lib.types.str;
default = "no-reply@DOMAIN_NAME";
};
userAddress = lib.mkOption {
description = "The 'to' address";
type = lib.types.str;
default = "EMAIL_ADDRESS";
};
adminAddress = lib.mkOption {
description = "The admin email address to receive alerts in copy";
type = lib.types.str;
default = "admin@numbus.eu";
};
smtpServer = lib.mkOption {
description = "The SMTP server address";
type = lib.types.str;
default = "SENDER_MAIL_DOMAIN";
};
smtpPort = lib.mkOption {
description = "The SMTP port";
type = lib.types.port;
default = 465;
};
smtpUsername = lib.mkOption {
description = "The SMTP username";
type = lib.types.str;
default = "SENDER_MAIL_ADDRESS";
};
smtpPasswordPath = lib.mkOption {
description = "Path to the secret containing SMTP password";
type = lib.types.path;
default = config.sops.secrets.sender_email_address_password.path;
};
};
config = lib.mkIf cfg.enable {
environment.etc."aliases".text = ''
root: ${config.email.userAddress}, ${config.email.adminAddress}
default: ${config.email.userAddress}, ${config.email.adminAddress}
'';
programs.msmtp = {
enable = true;
defaults = {
aliases = "/etc/aliases";
timeout = 60;
syslog = "on";
};
accounts.default = {
auth = true;
host = config.email.smtpServer;
port = config.email.smtpPort;
from = config.email.fromAddress;
user = config.email.smtpUsername;
tls = true;
tls_starttls = false;
passwordeval = "${pkgs.coreutils}/bin/cat ${config.email.smtpPasswordPath}";
};
};
};
### Mail notifications configuration <--
}
-35
View File
@@ -1,35 +0,0 @@
{ config, pkgs, lib, ... }:
{
# Hostname
networking.hostName = "numbus-server";
networking.networkmanager.enable = false;
# Allow rootless containers to bind to port 53 and up
boot.kernel.sysctl."net.ipv4.ip_unprivileged_port_start" = 53;
# Bridge configuration for VMs
networking.bridges.br0.interfaces = [ "TARGET_INTERFACE" ];
networking.interfaces."TARGET_INTERFACE".useDHCP = false;
networking.interfaces.br0.useDHCP = false;
networking.nameservers = [ "HOME_SERVER_IP" "9.9.9.9" ];
networking.interfaces.br0.ipv4.addresses = [{
address = "HOME_SERVER_IP";
prefixLength = 24;
}];
networking.defaultGateway = {
address = "HOME_ROUTER_IP";
interface = "br0";
};
networking.nftables.enable = true;
# Open ports in the firewall
networking.firewall = {
enable = true;
allowPing = true;
allowedTCPPorts = [ 53 80 443 ];
allowedUDPPorts = [ 53 443 ];
};
}
-62
View File
@@ -1,62 +0,0 @@
{ config, pkgs, ... }:
let
smartd_notifier = pkgs.writeScript "smartd-notify.sh" ''
#!${pkgs.bash}/bin/bash
# 1. Send Technical Email to Admin
ADMIN_EMAIL="${config.email.adminAddress}"
SUBJECT="Numbus Server Alert: $SMARTD_FAILTYPE on $SMARTD_DEVICE"
TECH_BODY="
SMARTD Alert Details:
Server owner: $OWNER_NAME
Device: $SMARTD_DEVICE
Type: $SMARTD_DEVICETYPE
Failure Type: $SMARTD_FAILTYPE
Message: $SMARTD_MESSAGE
Full Message:
$SMARTD_FULLMESSAGE
"
printf "Subject: [ADMIN] $SUBJECT\n\n$TECH_BODY" | /run/wrappers/bin/sendmail -t "$ADMIN_EMAIL"
# 2. Send Friendly Email to Owner
USER_EMAIL="${config.email.userAddress}"
OWNER_NAME=$(cat /etc/numbus-server/owner 2>/dev/null || echo "User")
FRIENDLY_BODY="Cher/Chère $OWNER_NAME,
Votre serveur a automatiquement détecté une panne matérielle de disque dur.
Ce genre de panne est tout à fait normal selon l'âge de votre matériel et n'entraîne
dans la grande majorité des cas aucune perte de données grâce au système de
stockage redondant préventif.
Votre administrateur a été notifié de cette panne. Il vous recontactera dans de très
brefs délais afin de procéder au remplacement, si nécessaire, du disque dur défaillant.
Merci de votre confiance,
L'équipe de support,
Numbus-Server."
printf "Subject: [Alerte] Défaillance matérielle sur votre serveur Numbus\n\n$FRIENDLY_BODY" | /run/wrappers/bin/sendmail -t "$USER_EMAIL"
'';
in
{
### --> SMART disk heath
services.smartd = {
enable = true;
defaults.autodetected = "-a -o on -S on -s (S/../.././00|L/../../6/01) -n standby,q -M exec ${smartd_notifier}";
notifications = {
wall = {
enable = true;
};
mail = {
enable = true;
sender = config.email.fromAddress;
recipient = "${config.email.userAddress},${config.email.adminAddress}";
};
};
};
### SMART disk heath <--
}
-43
View File
@@ -1,43 +0,0 @@
{ config, pkgs, ... }:
{
environment.systemPackages = with pkgs; [
fish
fishPlugins.fzf-fish
fishPlugins.grc
grc
fzf
];
programs.fish = {
enable = true;
interactiveShellInit = ''
set fish_greeting # Disable greeting
fastfetch
echo -e "\n\nWelcome to your Numbus-Server !\n\n- This system is managed by NixOS\n- All changes are futile\n- Please consider buying support if you can't get your server running\n- Have a nice day and enjoy !"
'';
shellAliases = {
nixup = "cd /etc/nixos/ && sudo nix flake update && sudo nixos-rebuild --flake . switch --upgrade && cd -";
nixwitch = "cd /etc/nixos/ && sudo nix flake update && sudo nixos-rebuild --flake . switch && cd -";
systraefik = "sudo systemctl status traefik.service";
syspi-hole = "sudo systemctl status pi-hole.service";
syspassbolt = "sudo systemctl status passbolt.service";
sysnextcloud = "sudo systemctl status nextcloud.service";
sysit-tools = "sudo systemctl status it-tools.service";
sysimmich = "sudo systemctl status immich.service";
syshome-assistant = "sudo systemctl status home-assistant.service";
sysgitea = "sudo systemctl status gitea.service";
sysfrigate = "sudo systemctl status frigate.service";
};
};
# # Login message
# environment.loginShellInit = ''
# if [ "$(id -u)" -eq 1000 ]; then
# if [ -n "$SSH_TTY" ]; then
# fastfetch
# echo -e "\n\nWelcome to your Numbus-Server !\n\n- This system is managed by NixOS\n- All changes are futile\n- Please consider buying support if you can't get your server running\n- Have a nice day and enjoy !"
# fi
# fi
# '';
}
@@ -1,59 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "adguard";
compose_file = "podman/adguard/compose.yaml";
config_dir = "/mnt/config/adguard";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-98
View File
@@ -1,98 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "frigate";
compose_file = "podman/frigate/compose.yaml";
config_dir = "/mnt/config/frigate";
data_dir = "/mnt/data/frigate";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
frigate:
image: ghcr.io/blakeblackshear/frigate:stable
container_name: frigate
shm_size: "512MB"
networks:
home-assistant_frontend:
home-assistant_backend:
volumes:
- ${config_dir}:/config
- ${data_dir}/clips:/media/frigate/clips
- ${data_dir}/recordings:/media/frigate/recordings
- ${data_dir}/exports:/media/frigate/exports
- /etc/localtime:/etc/localtime:ro
- type: tmpfs
target: /tmp/cache
tmpfs:
size: 2000000000
environment:
FRIGATE_MQTT_USER: $FRIGATE_MQTT_USER
FRIGATE_MQTT_PASSWORD: $FRIGATE_MQTT_PASSWORD
# --- frigate devices --- #
labels:
- traefik.enable=true
- traefik.docker.network=home-assistant_frontend
- traefik.http.services.frigate.loadbalancer.server.port=8971
- traefik.http.services.frigate.loadbalancer.server.scheme=http
- traefik.http.routers.frigate-https.entrypoints=websecure
- traefik.http.routers.frigate-https.rule=Host(`frigate.$DOMAIN_NAME`)
- traefik.http.routers.frigate-https.tls=true
- traefik.http.routers.frigate-https.tls.certresolver=cloudflare
restart: unless-stopped
networks:
home-assistant_backend:
external: true
home-assistant_frontend:
external: true
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "traefik.service" "home-assistant.service" "pi-hole.service" ];
requires = [ "traefik.service" "home-assistant.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-113
View File
@@ -1,113 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "gitea";
compose_file = "podman/gitea/compose.yaml";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
gitea:
image: docker.io/gitea/gitea:latest
container_name: gitea
networks:
gitea_frontend:
gitea_backend:
volumes:
- gitea_data:/data
- /etc/localtime:/etc/localtime:ro
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=$POSTGRES_HOST:$POSTGRES_PORT
- GITEA__database__NAME=$DB_NAME
- GITEA__database__USER=$DB_USERNAME
- GITEA__database__PASSWD=$DB_PASSWORD
- GITEA__server__SSH_PORT=2424
- GITEA__server__ROOT_URL=gitea.$DOMAIN_NAME
labels:
- traefik.enable=true
- traefik.docker.network=gitea_frontend
- traefik.http.services.gitea.loadbalancer.server.port=3000
- traefik.http.services.gitea.loadbalancer.server.scheme=http
- traefik.http.routers.gitea-https.entrypoints=websecure
- traefik.http.routers.gitea-https.rule=Host(`gitea.$DOMAIN_NAME`)
- traefik.http.routers.gitea-https.tls=true
- traefik.http.routers.gitea-https.tls.certresolver=cloudflare
depends_on:
- gitea-database
restart: unless-stopped
gitea-database:
image: docker.io/library/postgres:17.5
container_name: gitea-database
environment:
- POSTGRES_USER=$DB_USERNAME
- POSTGRES_PASSWORD=$DB_PASSWORD
- POSTGRES_DB=$DB_NAME
networks:
gitea_backend:
volumes:
- gitea_database:/var/lib/postgresql/data
restart: unless-stopped
volumes:
gitea_data:
gitea_database:
networks:
gitea_frontend:
external: true
gitea_backend:
external: true
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "900";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 400))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
@@ -1,83 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "home-assistant";
compose_file = "podman/home-assistant/compose.yaml";
config_dir_1 = "/mnt/config/home-assistant";
config_dir_2 = "/mnt/config/mqtt";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
home-assistant:
image: ghcr.io/home-assistant/home-assistant:latest
container_name: home-assistant
networks:
home-assistant_frontend:
home-assistant_backend:
volumes:
- ${config_dir_1}:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
# --- home-assistant devices --- #
labels:
- traefik.enable=true
- traefik.docker.network=home-assistant_frontend
- traefik.http.services.home-assistant.loadbalancer.server.port=8123
- traefik.http.services.home-assistant.loadbalancer.server.scheme=http
- traefik.http.routers.home-assistant-https.entrypoints=websecure
- traefik.http.routers.home-assistant-https.rule=Host(`home-assistant.$DOMAIN_NAME`)
- traefik.http.routers.home-assistant-https.tls=true
- traefik.http.routers.home-assistant-https.tls.certresolver=cloudflare
restart: unless-stopped
frigate-mqtt:
image: eclipse-mosquitto
container_name: mqtt
user: 1000:1000
networks:
home-assistant_backend:
volumes:
- ${config_dir_2}:/mosquitto
restart: unless-stopped
networks:
home-assistant_backend:
external: true
home-assistant_frontend:
external: true
'';
systemd.services.${container_name} = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
# Pull the latest image before running
ExecStartPre = [
"${pkgs.coreutils}/bin/sleep 180"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
# Bring the service up
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
# Take it down gracefully
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
-134
View File
@@ -1,134 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "immich";
compose_file = "podman/immich/compose.yaml";
config_dir = "/mnt/config/immich";
data_dir = "/mnt/data/immich";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
immich-server:
image: ghcr.io/immich-app/immich-server:$IMMICH_VERSION
container_name: immich-server
networks:
immich_frontend:
immich_backend:
volumes:
- $UPLOAD_LOCATION:/data
- /etc/localtime:/etc/localtime:ro
# --- immich devices --- #
labels:
- traefik.enable=true
- traefik.docker.network=immich_frontend
- traefik.http.services.immich.loadbalancer.server.port=2283
- traefik.http.services.immich.loadbalancer.server.scheme=http
- traefik.http.routers.immich-https.entrypoints=websecure
- traefik.http.routers.immich-https.rule=Host(`immich.$DOMAIN_NAME`)
- traefik.http.routers.immich-https.tls=true
- traefik.http.routers.immich-https.tls.certresolver=cloudflare
env_file:
- .env
depends_on:
- immich-redis
- immich-database
restart: always
healthcheck:
disable: false
immich-machine-learning:
container_name: immich-machine-learning
image: ghcr.io/immich-app/immich-machine-learning:$IMMICH_VERSION
networks:
immich_backend:
volumes:
- ${config_dir}/models:/cache
env_file:
- .env
restart: always
healthcheck:
disable: false
immich-redis:
container_name: immich-redis
image: docker.io/valkey/valkey:8-bookworm@sha256:a137a2b60aca1a75130022d6bb96af423fefae4eb55faf395732db3544803280
networks:
immich_backend:
healthcheck:
test: redis-cli ping || exit 1
restart: always
immich-database:
container_name: immich-database
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:32324a2f41df5de9efe1af166b7008c3f55646f8d0e00d9550c16c9822366b4a
networks:
immich_backend:
shm_size: 128mb
volumes:
# Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
- $DB_DATA_LOCATION:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: $DB_PASSWORD
POSTGRES_USER: $DB_USERNAME
POSTGRES_DB: $DB_DATABASE_NAME
POSTGRES_INITDB_ARGS: '--data-checksums'
restart: always
healthcheck:
disable: false
networks:
immich_backend:
external: true
immich_frontend:
external: true
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "900";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 400))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-77
View File
@@ -1,77 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "it-tools";
compose_file = "podman/it-tools/compose.yaml";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
it-tools:
container_name: it-tools
image: corentinth/it-tools
networks:
it-tools_frontend:
labels:
- traefik.enable=true
- traefik.docker.network=it-tools_frontend
- traefik.http.services.it-tools.loadbalancer.server.port=80
- traefik.http.services.it-tools.loadbalancer.server.scheme=http
- traefik.http.routers.it-tools-https.entrypoints=websecure
- traefik.http.routers.it-tools-https.rule=Host(`it-tools.$DOMAIN_NAME`)
- traefik.http.routers.it-tools-https.tls=true
- traefik.http.routers.it-tools-https.tls.certresolver=cloudflare
restart: unless-stopped
networks:
it-tools_frontend:
external: true
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
@@ -1,98 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "nextcloud";
compose_file = "podman/nextcloud/compose.yaml";
data_dir = "/mnt/data/nextcloud";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
nextcloud-aio-mastercontainer:
image: ghcr.io/nextcloud-releases/all-in-one:latest
container_name: nextcloud-aio-mastercontainer
networks:
nextcloud-aio:
volumes:
- nextcloud_aio_mastercontainer:/mnt/docker-aio-config
- /run/user/1000/podman/podman.sock:/var/run/docker.sock:ro
environment:
APACHE_PORT: 11000
APACHE_IP_BINDING: 127.0.0.1
NEXTCLOUD_DATADIR: ${data_dir}
NEXTCLOUD_ENABLE_DRI_DEVICE: $NEXTCLOUD_ENABLE_DRI_DEVICE
NEXTCLOUD_UPLOAD_LIMIT: 16G
NEXTCLOUD_MAX_TIME: 3600
NEXTCLOUD_MEMORY_LIMIT: 2048M
NEXTCLOUD_ADDITIONAL_APKS: imagemagick
NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS: imagick
WATCHTOWER_DOCKER_SOCKET_PATH: /run/user/1000/podman/podman.sock
labels:
- traefik.enable=true
- traefik.docker.network=nextcloud-aio
- traefik.http.services.nextcloud-aio.loadbalancer.server.port=8080
- traefik.http.services.nextcloud-aio.loadbalancer.server.scheme=https
- 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.tls=true
- traefik.http.routers.nextcloud-aio-https.tls.certresolver=cloudflare
init: true
restart: always
networks:
nextcloud-aio:
external: true
volumes:
nextcloud_aio_mastercontainer:
name: nextcloud_aio_mastercontainer
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-131
View File
@@ -1,131 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "nextcloud";
compose_file = "podman/nextcloud/compose.yaml";
data_dir = "/mnt/data/nextcloud";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
nextcloud-server:
image: docker.io/library/nextcloud:latest
container_name: nextcloud-server
restart: unless-stopped
networks:
nextcloud_frontend:
nextcloud_backend:
volumes:
- nextcloud_data:/var/www/html
- ${data_dir}:/var/www/html/data
environment:
MYSQL_HOST: nextcloud-database
MYSQL_DATABASE: $MYSQL_DATABASE
MYSQL_USER: $MYSQL_USER
MYSQL_PASSWORD: $MYSQL_PASSWORD
REDIS_HOST: nextcloud-redis
REDIS_HOST_PASSWORD: $REDIS_HOST_PASSWORD
NEXTCLOUD_TRUSTED_DOMAINS: $DOMAIN_NAME
SMTP_HOST: $SMTP_HOST
SMTP_SECURE: tls
SMTP_PORT: $SMTP_PORT
SMTP_NAME: $SMTP_NAME
SMTP_PASSWORD: $SMTP_PASSWORD
MAIL_FROM_ADDRESS: $MAIL_FROM_ADDRESS
MAIL_DOMAIN: $DOMAIN_NAME
APACHE_DISABLE_REWRITE_IP: 1
TRUSTED_PROXIES: traefik
OVERWRITEPROTOCOL: https
labels:
- traefik.enable=true
- traefik.docker.network=nextcloud_frontend
- traefik.http.services.nextcloud.loadbalancer.server.port=80
- traefik.http.services.nextcloud.loadbalancer.server.scheme=http
- traefik.http.routers.nextcloud-https.entrypoints=websecure
- traefik.http.routers.nextcloud-https.rule=Host(`nextcloud.$DOMAIN_NAME`)
- traefik.http.routers.nextcloud-https.tls=true
- traefik.http.routers.nextcloud-https.tls.certresolver=cloudflare
depends_on:
- nextcloud-database
nextcloud-redis:
image: docker.io/library/redis:alpine
name: nextcloud-redis
restart: unless-stopped
networks:
nextcloud_backend:
command: redis-server --requirepass $REDIS_HOST_PASSWORD
nextcloud-database:
image: docker.io/library/mariadb:latest
container_name: nextcloud-database
restart: unless-stopped
networks:
nextcloud_backend:
volumes:
- nextcloud_database:/var/lib/mysql
environment:
MARIADB_DATABASE: $MYSQL_DATABASE
MARIADB_USER: $MYSQL_USER
MARIADB_PASSWORD: $MYSQL_PASSWORD
MARIADB_RANDOM_ROOT_PASSWORD: true
networks:
nextcloud_frontend:
external: true
nextcloud_backend:
external: true
volumes:
nextcloud_data:
nextcloud_database:
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-129
View File
@@ -1,129 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "passbolt";
compose_file = "podman/passbolt/compose.yaml";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
passbolt:
image: passbolt/passbolt:latest-ce-non-root
container_name: passbolt
networks:
passbolt_frontend:
passbolt_backend:
volumes:
- passbolt-gpg:/etc/passbolt/gpg
- passbolt-jwt:/etc/passbolt/jwt
environment:
APP_DEFAULT_TIMEZONE: $TZ
APP_FULL_BASE_URL: https://passbolt.$DOMAIN_NAME
DATASOURCES_DEFAULT_HOST: "passbolt-database"
DATASOURCES_DEFAULT_USERNAME: $PASSBOLT_MYSQL_USER
DATASOURCES_DEFAULT_PASSWORD: $PASSBOLT_MYSQL_PASSWORD
DATASOURCES_DEFAULT_DATABASE: $PASSBOLT_MYSQL_DATABASE
EMAIL_DEFAULT_FROM_NAME: "Passbolt"
EMAIL_TRANSPORT_DEFAULT_HOST: $EMAIL_TRANSPORT_DEFAULT_HOST
EMAIL_TRANSPORT_DEFAULT_PORT: $EMAIL_TRANSPORT_DEFAULT_PORT
EMAIL_TRANSPORT_DEFAULT_USERNAME: $EMAIL_TRANSPORT_DEFAULT_USERNAME
EMAIL_TRANSPORT_DEFAULT_PASSWORD: $EMAIL_TRANSPORT_DEFAULT_PASSWORD
EMAIL_TRANSPORT_DEFAULT_TLS: true
EMAIL_DEFAULT_FROM: $EMAIL_ADDRESS
PASSBOLT_SSL_FORCE: true
labels:
- traefik.enable=true
- traefik.docker.network=passbolt_frontend
- traefik.http.services.passbolt.loadbalancer.server.port=4433
- traefik.http.services.passbolt.loadbalancer.server.scheme=https
- traefik.http.routers.passbolt-https.entrypoints=websecure
- traefik.http.routers.passbolt-https.rule=Host(`passbolt.$DOMAIN_NAME`)
- traefik.http.routers.passbolt-https.tls=true
- traefik.http.routers.passbolt-https.tls.certresolver=cloudflare
command:
[
"/usr/bin/wait-for.sh",
"-t",
"0",
"passbolt-database:3306",
"--",
"/docker-entrypoint.sh",
]
depends_on:
- passbolt-database
restart: unless-stopped
passbolt-database:
image: mariadb:11.3
container_name: passbolt-database
networks:
passbolt_backend:
volumes:
- passbolt-database:/var/lib/mysql
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "true"
MYSQL_DATABASE: $PASSBOLT_MYSQL_DATABASE
MYSQL_USER: $PASSBOLT_MYSQL_USER
MYSQL_PASSWORD: $PASSBOLT_MYSQL_PASSWORD
restart: unless-stopped
networks:
passbolt_backend:
external: true
passbolt_frontend:
external: true
volumes:
passbolt-database:
passbolt-gpg:
passbolt-jwt:
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" "pi-hole.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep $((RANDOM % 180))'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-106
View File
@@ -1,106 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "pi-hole";
compose_file = "podman/pi-hole/compose.yaml";
config_dir = "/mnt/config/pi-hole";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
pihole:
image: docker.io/pihole/pihole:latest
container_name: pi-hole
networks:
pi-hole_frontend:
ports:
# DNS Ports
- "53:53/tcp"
- "53:53/udp"
environment:
TZ: $TZ
FTLCONF_webserver_api_password: $FTLCONF_webserver_api_password
FTLCONF_dns_hosts: |
$HOME_SERVER_IP frigate.$DOMAIN_NAME
$HOME_SERVER_IP gitea.$DOMAIN_NAME
$HOME_SERVER_IP home-assistant.$DOMAIN_NAME
$HOME_SERVER_IP immich.$DOMAIN_NAME
$HOME_SERVER_IP it-tools.$DOMAIN_NAME
$HOME_SERVER_IP nextcloud.$DOMAIN_NAME
$HOME_SERVER_IP nextcloud-aio.$DOMAIN_NAME
$HOME_SERVER_IP passbolt.$DOMAIN_NAME
$HOME_SERVER_IP pi-hole.$DOMAIN_NAME
$HOME_SERVER_IP traefik.$DOMAIN_NAME
FTLCONF_dhcp_active: "false"
FTLCONF_dns_upstreams: 9.9.9.9;149.112.112.112
FTLCONF_ntp_ipv4_active: "false"
FTLCONF_ntp_ipv6_active: "false"
FTLCONF_ntp_sync_active: "false"
volumes:
- ${config_dir}:/etc/pihole
cap_add:
- SYS_NICE
labels:
- traefik.enable=true
- traefik.docker.network=pi-hole_frontend
- traefik.http.services.pihole.loadbalancer.server.port=80
- traefik.http.services.pihole.loadbalancer.server.scheme=http
- traefik.http.routers.pihole-https.entrypoints=websecure
- traefik.http.routers.pihole-https.rule=Host(`pi-hole.$DOMAIN_NAME`)
- traefik.http.routers.pihole-https.tls=true
- traefik.http.routers.pihole-https.tls.certresolver=cloudflare
restart: unless-stopped
networks:
pi-hole_frontend:
external: true
'';
systemd.services."${container_name}" = {
description = "Podman container : ${container_name}";
after = [ "network.target" "traefik.service" ];
requires = [ "traefik.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
TimeoutStartSec = "600";
ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'sleep 20'"
"-${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull"
];
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
systemd.services."update-${container_name}" = {
description = "Update ${container_name} container";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl restart ${container_name}.service";
};
};
systemd.timers."update-${container_name}" = {
timerConfig = {
OnCalendar = "02:00";
RandomizedDelaySec = "60m";
Unit = "update-${container_name}.service";
};
wantedBy = [ "timers.target" ];
};
};
}
-64
View File
@@ -1,64 +0,0 @@
{ config, pkgs, ... }:
let
container_name = "traefik";
compose_file = "podman/traefik/compose.yaml";
config_dir = "/mnt/config/traefik";
in
{
config = {
environment.etc."${compose_file}".text =
/*
yaml
*/
''
services:
traefik:
image: docker.io/library/traefik:latest
container_name: traefik
networks:
TRAEFIK_NETWORKS
ports:
- "80:80"
- "443:443"
volumes:
- /run/user/1000/podman/podman.sock:/run/docker.sock:ro
- ${config_dir}/rules/:/etc/traefik/conf/:ro
- ${config_dir}/traefik.yaml:/etc/traefik/traefik.yaml:ro
- ${config_dir}/certs/:/var/traefik/certs/:rw
environment:
- CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN
labels:
- traefik.enable=true
- traefik.http.services.traefik.loadbalancer.server.port=8080
- traefik.http.services.traefik.loadbalancer.server.scheme=http
- traefik.http.routers.traefik-https.entrypoints=websecure
- traefik.http.routers.traefik-https.rule=Host(`traefik.$DOMAIN_NAME`)
- traefik.http.routers.traefik-https.tls=true
- traefik.http.routers.traefik-https.tls.certresolver=cloudflare
restart: always
networks:
TRAEFIK_REF_NETWORKS
'';
systemd.services.traefik = {
description = "Podman container : ${container_name}";
after = [ "numbus-activation.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.podman pkgs.coreutils ];
serviceConfig = {
User = "numbus-admin";
Environment = [ "XDG_RUNTIME_DIR=/run/user/1000" ];
Type = "exec";
ExecStartPre = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} pull";
ExecStart = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} up --remove-orphans";
ExecStop = "${pkgs.podman-compose}/bin/podman-compose -f /etc/${compose_file} down";
Restart = "on-failure";
RestartSec = "5m";
StartLimitBurst = "3";
};
};
};
}
+4 -77
View File
@@ -1,77 +1,4 @@
ssh_public_keys: "$SSH_PUBLIC_KEY"
sender_email_address_password: "$SENDER_EMAIL_ADDRESS_PASSWORD"
domain_name: "$DOMAIN_NAME"
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"
MYSQL_DATABASE="$NEXTCLOUD_DB_NAME"
MYSQL_USER="$NEXTCLOUD_DB_USERNAME"
MYSQL_PASSWORD="$NEXTCLOUD_DB_PASSWORD"
REDIS_HOST_PASSWORD="$NEXTCLOUD_REDIS_PASSWORD"
SMTP_HOST="$SENDER_EMAIL_DOMAIN"
SMTP_PORT="$SENDER_EMAIL_PORT"
SMTP_NAME="$SENDER_EMAIL_ADDRESS"
SMTP_PASSWORD="$SENDER_EMAIL_ADDRESS_PASSWORD"
MAIL_FROM_ADDRESS="$EMAIL_ADDRESS"
passbolt: |
DOMAIN_NAME="$DOMAIN_NAME"
PASSBOLT_MYSQL_DATABASE="$PASSBOLT_DB_NAME"
PASSBOLT_MYSQL_USER="$PASSBOLT_DB_USERNAME"
PASSBOLT_MYSQL_PASSWORD="$PASSBOLT_DB_PASSWORD"
EMAIL_TRANSPORT_DEFAULT_HOST="$SENDER_EMAIL_DOMAIN"
EMAIL_TRANSPORT_DEFAULT_PORT="$SENDER_EMAIL_PORT"
EMAIL_TRANSPORT_DEFAULT_USERNAME="$SENDER_EMAIL_ADDRESS"
EMAIL_TRANSPORT_DEFAULT_PASSWORD="$SENDER_EMAIL_ADDRESS_PASSWORD"
EMAIL_DEFAULT_FROM="$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"
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"
authorizedSshPublicKeys: |
"$AUTHORIZED_SSH_PUBLIC_KEY"
smtpPassword: "$SMTP_SERVER_PASSWORD"
cloudlfareDnsApiToken: "$CLOUDFLARE_DNS_API_TOKEN"
@@ -1,70 +0,0 @@
# Home-assistant connection settings
mqtt:
host: frigate-mqtt
port: 1883
user: $HOME_ASSISTANT_MQTT_USER
password: $HOME_ASSISTANT_MQTT_PASSWORD
stats_interval: 60
# Ffmpeg configuration
ffmpeg:
hwaccel_args: preset-vaapi
# Snapshots configuration
snapshots:
enabled: true
clean_copy: true
timestamp: true
bounding_box: true
crop: false
retain:
default: 10
objects:
person: 10
# Recordings configuration
record:
enabled: true
retain:
days: 3
mode: motion
alerts:
retain:
days: 30
mode: motion
detections:
retain:
days: 30
mode: motion
# Cameras configuration
cameras:
camera-1:
enabled: true
onvif: # Enable if camera supports it
host: ip_address
port: 2020
user: user
password: password
ffmpeg:
inputs:
- path: rtsp://user:password@ip_address:port/url # Check the camera documentation
roles:
- detect
- record
detect:
enabled: true
width: 1280
height: 720
fps: 5
# Coral TPU configuration
detectors:
coral:
type: edgetpu
device: usb
version: 0.16-0
tls:
enabled: false
@@ -1,8 +0,0 @@
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
listener 1883
## Authentication ##
allow_anonymous false
password_file /mosquitto/config/password.txt
@@ -1,20 +0,0 @@
http:
middlewares:
passbolt:
headers:
FrameDeny: true
AccessControlAllowMethods: 'GET,OPTIONS,PUT'
AccessControlAllowOriginList:
- origin-list-or-null
AccessControlMaxAge: 100
AddVaryHeader: true
BrowserXssFilter: true
ContentTypeNosniff: true
ForceSTSHeader: true
STSIncludeSubdomains: true
STSPreload: true
ContentSecurityPolicy: default-src 'self' 'unsafe-inline'
CustomFrameOptionsValue: SAMEORIGIN
ReferrerPolicy: same-origin
PermissionsPolicy: vibrate 'self'
STSSeconds: 315360000
@@ -1,41 +0,0 @@
http:
routers:
nextcloud:
rule: "Host(`nextcloud.${DOMAIN_NAME}`)"
entrypoints:
- "websecure"
service: nextcloud
middlewares:
- nextcloud-chain
tls:
certresolver: "cloudflare"
services:
nextcloud:
loadBalancer:
servers:
- url: "http://nextcloud-aio-apache:11000"
middlewares:
nextcloud-secure-headers:
headers:
hostsProxyHeaders:
- "X-Forwarded-Host"
referrerPolicy: "same-origin"
BrowserXssFilter: true
ContentTypeNosniff: true
ForceSTSHeader: true
STSIncludeSubdomains: true
STSPreload: true
STSSeconds: 315360000
https-redirect:
redirectscheme:
scheme: https
nextcloud-chain:
chain:
middlewares:
# - ... (e.g. rate limiting middleware)
- https-redirect
- nextcloud-secure-headers
-12
View File
@@ -1,12 +0,0 @@
tls:
options:
default:
minVersion: VersionTLS12
sniStrict: true
curvePreferences:
- CurveP521
- CurveP384
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
@@ -1,53 +0,0 @@
global:
checkNewVersion: false
sendAnonymousUsage: false
# - level: [TRACE, DEBUG, INFO, WARN, ERROR, FATAL]
log:
level: ERROR
accesslog: {}
api:
dashboard: true
insecure: true
entryPoints:
web:
address: :80
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: :443
forwardedHeaders:
trustedIPs:
# Local IPs
- "127.0.0.1/32"
- "10.0.0.0/8"
- "192.168.0.0/16"
- "172.16.0.0/12"
certificatesResolvers:
cloudflare:
acme:
email: ${EMAIL_ADDRESS}
storage: /var/traefik/certs/cloudflare-acme.json
caServer: "https://acme-v02.api.letsencrypt.org/directory"
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "9.9.9.9:53"
serversTransport:
insecureSkipVerify: true
providers:
docker:
exposedByDefault: false
file:
directory: "/etc/traefik/conf/"
watch: true
-25
View File
@@ -1,25 +0,0 @@
#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_DISKS_ID=("/dev/disk/by-id/nvme001-dfzpjvp")
DATA_DISKS_ID=("/dev/disk/by-id/sata-barracuda-veojapoj")
DATA_DISKS_TYPE=("HDD")
SPINDOWN_DISKS_ID=("/dev/disk/by-id/sata-barracuda-veojapoj")
CONTENT_DISK_NUMBER=2
PARITY_DISK_NUMBER=2
-220
View File
@@ -1,220 +0,0 @@
#!/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 <--