Updated mail alerts. Added automatic DNS records creation with cloudflare

This commit is contained in:
Raphaël Numbus
2026-01-17 14:51:41 +01:00
parent 6d1d3be3b3
commit 98a144d408
4 changed files with 127 additions and 85 deletions
+80 -45
View File
@@ -141,7 +141,7 @@ hardware_detection() {
ssh_to_host 'bash -s' << SSHEND ssh_to_host 'bash -s' << SSHEND
for brand in Intel AMD NVIDIA; do for brand in Intel AMD NVIDIA; do
if lspci -nn 2>/dev/null | grep -i "vga" | grep -iq "\${brand}"; then if lspci -nn > /dev/null 2>&1 | grep -i "vga" | grep -iq "\${brand}"; then
TARGET_GRAPHICS="true" TARGET_GRAPHICS="true"
TARGET_GRAPHICS_BRAND+=("\${brand}") TARGET_GRAPHICS_BRAND+=("\${brand}")
else else
@@ -149,10 +149,10 @@ for brand in Intel AMD NVIDIA; do
fi fi
done done
ls /dev/dri/ 2>/dev/null | grep -iq "renderD128" && TARGET_GRAPHICS_RENDERER="true" || TARGET_GRAPHICS_RENDERER="false" ls /dev/dri/ > /dev/null 2>&1 | grep -iq "renderD128" && TARGET_GRAPHICS_RENDERER="true" || TARGET_GRAPHICS_RENDERER="false"
lsusb 2>/dev/null | grep -iq "google" && TARGET_USB_CORAL="true" || TARGET_USB_CORAL="false" lsusb > /dev/null 2>&1 | grep -iq "google" && TARGET_USB_CORAL="true" || TARGET_USB_CORAL="false"
lspci -nn 2>/dev/null | grep -iq "089a" && TARGET_PCIE_CORAL="true" || TARGET_PCIE_CORAL="false" lspci -nn > /dev/null 2>&1 | grep -iq "089a" && TARGET_PCIE_CORAL="true" || TARGET_PCIE_CORAL="false"
ls /dev/serial/by-id/ 2>/dev/null | grep -i "zigbee" && TARGET_ZIGBEE_DEVICE=\$(ls /dev/serial/by-id/ 2>/dev/null | grep -i "zigbee" | head -n 1) || TARGET_ZIGBEE_DEVICE="" ls /dev/serial/by-id/ > /dev/null 2>&1 | grep -i "zigbee" && TARGET_ZIGBEE_DEVICE=\$(ls /dev/serial/by-id/ > /dev/null 2>&1 | grep -i "zigbee" | head -n 1) || TARGET_ZIGBEE_DEVICE=""
TARGET_INTERFACE=\$(ip -4 route show default | awk '{print \$5}' | head -n1) TARGET_INTERFACE=\$(ip -4 route show default | awk '{print \$5}' | head -n1)
@@ -186,7 +186,7 @@ for DISK in \$(lsblk -x SIZE -d -n -e 7,11 -o NAME); do
else DISK_TYPE+=("Other") else DISK_TYPE+=("Other")
fi fi
# Disk health # Disk health
if [[ \$(echo "$REMOTE_PASS" | sudo -S smartctl -H /dev/\$DISK 2>/dev/null | grep 'self-assessment' | awk '{print \$6}') == "PASSED" ]]; then if [[ \$(echo "$REMOTE_PASS" | sudo -S smartctl -H /dev/\$DISK > /dev/null 2>&1 | grep 'self-assessment' | awk '{print \$6}') == "PASSED" ]]; then
DISK_HEALTH+=("PASSED") DISK_HEALTH+=("PASSED")
else else
DISK_HEALTH+=("N/A") DISK_HEALTH+=("N/A")
@@ -258,12 +258,20 @@ services_selection() {
local SELECTED_SERVICES_DESCRIPTION=$(gum choose --no-limit --header "Homelab services:" "${SERVICES_DESCRIPTION[@]}") local SELECTED_SERVICES_DESCRIPTION=$(gum choose --no-limit --header "Homelab services:" "${SERVICES_DESCRIPTION[@]}")
for i in ${!AVAILABLE_SERVICES[@]}; do for i in ${!AVAILABLE_SERVICES[@]}; do
if printf '%s' "$SELECTED_SERVICES_DESCRIPTION" | grep -iq "${AVAILABLE_SERVICES[$i]}"; then if printf '%s' "${SELECTED_SERVICES_DESCRIPTION}" | grep -iq "${AVAILABLE_SERVICES[${i}]}"; then
SELECTED_SERVICES+=(${AVAILABLE_SERVICES[$i]}) SELECTED_SERVICES+=("${AVAILABLE_SERVICES[${i}]}")
if [[ "${AVAILABLE_SERVICES[${i}]}" == "nextcloud" ]]; then
SELECTED_SERVICES_DNS+=("nextcloud.${DOMAIN_NAME}" "nextcloud-aio.${DOMAIN_NAME}")
elif [[ "${AVAILABLE_SERVICES[${i}]}" == "virtualization" ]]; then
:
else
SELECTED_SERVICES_DNS+=("${AVAILABLE_SERVICES[${i}]}.${DOMAIN_NAME}")
fi
fi fi
done done
export SELECTED_SERVICES export SELECTED_SERVICES
export SELECTED_SERVICES_DNS
} }
disks_selection() { disks_selection() {
@@ -291,13 +299,13 @@ disks_selection() {
local GUM_PRINTED_ELEMENT=$(printf "%-12s %-12s %-12s %-12s %s" \ local GUM_PRINTED_ELEMENT=$(printf "%-12s %-12s %-12s %-12s %s" \
"${DISK_NAME[${i}]}" "${DISK_TYPE[${i}]}" "${DISK_SIZE[${i}]}" \ "${DISK_NAME[${i}]}" "${DISK_TYPE[${i}]}" "${DISK_SIZE[${i}]}" \
"${DISK_HEALTH[${i}]}" "${DISK_DEVPATH[${i}]}") "${DISK_HEALTH[${i}]}" "${DISK_DEVPATH[${i}]}")
local GUM_PRINTED_ELEMENTS+=("$GUM_PRINTED_ELEMENT") local GUM_PRINTED_ELEMENTS+=("${GUM_PRINTED_ELEMENT}")
done done
echo "" echo ""
gum style --foreground 212 "➡️ Please choose one (stripe) or two (mirror) disks for your NixOS boot installation :" gum style --foreground 212 "➡️ Please choose one (stripe) or two (mirror) disks for your NixOS boot installation :"
local SELECTED_BOOT_DISK=$(gum choose --limit 2 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}") local SELECTED_BOOT_DISK=$(gum choose --limit 2 --header "${HEADER}" "${GUM_PRINTED_ELEMENTS[@]}")
for i in ${!DISK_NAME[@]}; do for i in ${!DISK_NAME[@]}; do
if printf '%s' "$SELECTED_BOOT_DISK" | grep -iqw "${DISK_NAME[${i}]}"; then if printf '%s' "$SELECTED_BOOT_DISK" | grep -iqw "${DISK_NAME[${i}]}"; then
@@ -733,55 +741,82 @@ export_configuration() {
} }
cloudflare_dns_setup() { cloudflare_dns_setup() {
local ZONE_ID && local RECORD_COUNT && local IS_MATCHING
local DNS_RECORDS && local CREATION_STATUS
create_records() {
local SUBDOMAIN="${1}"
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 "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"${SUBDOMAIN}\",\"content\":\"${HOME_SERVER_IP}\",\"ttl\":1,\"proxied\":false}" | jq -r '.success')
if [[ "${CREATION_STATUS}" == "true" ]]; then
echo " ✅ Successfully create a DNS record for ${SUBDOMAIN}"
else
echo -e " ❌ Failed to create a DNS record for ${SUBDOMAIN}. Check documentation to \n
learn how you can create them manually."
fi
}
erase_records() {
local SUBDOMAIN="${1}"
local DELETION_STATUS
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "
⚠️ $(gum style --foreground 212 'WARNING:') One or more existing type A DNS records found for \`${SUBDOMAIN}\`.
This script can clear those DNS records for you and create the correct ones needed for the server.
If you are unsure that these records are actually in use, please select \"no\"."
gum confirm "Select \"yes\" to clear ALL EXISTING type A DNS records for this subdomain and automatically create the correct ones." \
|| { 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 "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 "Content-Type: application/json" > /dev/null 2>&1
done
create_records "${SUBDOMAIN}"
}
echo -e "\n\n ☁️ Configuring Cloudflare DNS records..." echo -e "\n\n ☁️ Configuring Cloudflare DNS records..."
# 1. Get Zone ID # Get Zone ID
local ZONE_ID
ZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${DOMAIN_NAME}" \ 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 ${CF_DNS_API_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.result[0].id') -H "Content-Type: application/json" | jq -r '.result[0].id')
if [[ "${ZONE_ID}" == "null" || -z "${ZONE_ID}" ]]; then if [[ "${ZONE_ID}" == "null" || -z "${ZONE_ID}" ]]; then
echo -e "\n\n ⚠️ Could not fetch Zone ID for ${DOMAIN_NAME}. Please check your Cloudflare \"DNS ZONE\" API token" echo -e "\n\n ⚠️ Could not fetch Zone ID for ${DOMAIN_NAME}. Please check your Cloudflare \"DNS ZONE\" API token"
echo "Check out the Numbus-Server documentation to see out to get one." echo "Check the Numbus-Server documentation to learn how to get one."
fi fi
# 2. Iterate services # Check for existing records and create them if non-existent
for service in "${SELECTED_SERVICES[@]}"; do for service_domain in "${SELECTED_SERVICES_DNS[@]}"; do
if [[ "${service}" == "virtualization" ]]; then continue; fi DNS_RECORDS=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?name=${service_domain}&type=A" \
local SUBDOMAIN="${service}.${DOMAIN_NAME}"
echo -n " - Checking for existing record : ${SUBDOMAIN}..."
# Check existence
local RECORD_ID
RECORD_ID=$(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 ${CF_DNS_API_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.result[0].id') -H "Content-Type: application/json")
if [[ "${RECORD_ID}" != "null" && -n "${RECORD_ID}" ]]; then RECORD_COUNT=$(echo "${DNS_RECORDS}" | jq '.result | length')
RECORD_ID_CONTENT=$(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}" \ if [[ "${RECORD_COUNT}" -eq 0 ]]; then
-H "Content-Type: application/json" | jq -r '.result[0].content') echo -e "\n ⚠️ No DNS record found for ${service_domain}"
if [[ "${RECORD_ID_CONTENT}" == "${HOME_SERVER_IP}" ]]; then create_records "${service_domain}"
echo " ✅ Already configured" elif [[ "${RECORD_COUNT}" -eq 1 ]]; then
if [[ $(echo "${DNS_RECORDS}" | jq ".result[0].content == \"${HOME_SERVER_IP}\"") == "true" ]]; then
echo -e "\n ✅ DNS record already configured for ${service_domain}"
else else
echo " ⚠️ A DNS record is configured but does not point to the correct IP" echo -e "\n ⚠️ No DNS record found for ${service_domain}"
echo "Do you want to update it? It could break past DNS record you defined" erase_records "${service_domain}"
fi
else
echo -n " ⏳ Creating..."
local CREATE_RES
CREATE_RES=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-H "Authorization: Bearer ${CF_DNS_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"${SUBDOMAIN}\",\"content\":\"${HOME_SERVER_IP}\",\"ttl\":1,\"proxied\":false}" | jq -r '.success')
if [[ "${CREATE_RES}" == "true" ]]; then
echo " ✅ Created."
else
echo " ❌ Failed."
fi fi
elif [[ "${RECORD_COUNT}" -gt 1 ]]; then
erase_records "${service_domain}"
fi fi
done done
} }
+1 -1
View File
@@ -44,7 +44,7 @@
boot.initrd.systemd.enable = true; boot.initrd.systemd.enable = true;
boot.loader.systemd-boot.enable = true; boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true; boot.loader.efi.canTouchEfiVariables = true;
boot.swraid.mdadmConf = "MAILADDR ${config.email.toAddress}"; boot.swraid.mdadmConf = "MAILADDR ${config.email.userAddress},${config.email.adminAddress}";
# boot.initrd.systemd.tpm2.enable = true; # boot.initrd.systemd.tpm2.enable = true;
# TPM2 PCR check # TPM2 PCR check
+3 -3
View File
@@ -13,7 +13,7 @@ in
type = lib.types.str; type = lib.types.str;
default = "no-reply@DOMAIN_NAME"; default = "no-reply@DOMAIN_NAME";
}; };
toAddress = lib.mkOption { userAddress = lib.mkOption {
description = "The 'to' address"; description = "The 'to' address";
type = lib.types.str; type = lib.types.str;
default = "EMAIL_ADDRESS"; default = "EMAIL_ADDRESS";
@@ -47,8 +47,8 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
environment.etc."aliases".text = '' environment.etc."aliases".text = ''
root: ${config.email.toAddress}, ${config.email.adminAddress} root: ${config.email.userAddress}, ${config.email.adminAddress}
default: ${config.email.toAddress}, ${config.email.adminAddress} default: ${config.email.userAddress}, ${config.email.adminAddress}
''; '';
programs.msmtp = { programs.msmtp = {
+15 -8
View File
@@ -10,6 +10,7 @@ let
TECH_BODY=" TECH_BODY="
SMARTD Alert Details: SMARTD Alert Details:
Server owner: $OWNER_NAME
Device: $SMARTD_DEVICE Device: $SMARTD_DEVICE
Type: $SMARTD_DEVICETYPE Type: $SMARTD_DEVICETYPE
Failure Type: $SMARTD_FAILTYPE Failure Type: $SMARTD_FAILTYPE
@@ -22,17 +23,23 @@ let
# 2. Send Friendly Email to Owner # 2. Send Friendly Email to Owner
OWNER_NAME=$(cat /etc/numbus-server/owner 2>/dev/null || echo "User") OWNER_NAME=$(cat /etc/numbus-server/owner 2>/dev/null || echo "User")
USER_EMAIL="${config.email.toAddress}" USER_EMAIL="${config.email.userAddress}"
FRIENDLY_BODY="Hello $OWNER_NAME, FRIENDLY_BODY="Cher/Chère $OWNER_NAME,
We detected a potential hardware issue on your server ($SMARTD_DEVICE). Votre serveur a automatiquement détecté une panne matérielle de disque dur.
Don't panic! The administrator has been notified and received the technical details. Ce genre de panne est tout à fait normal selon l'âge de votre matériel et n'entraîne
They will contact you if any action is required on your part. dans la grande majorité des cas aucune perte de données grâce au système de
stockage redondant préventif.
Your Numbus Server" 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.
printf "Subject: [Alert] Hardware check on your server\n\n$FRIENDLY_BODY" | /run/wrappers/bin/sendmail -t "$USER_EMAIL" 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 in
{ {
@@ -47,7 +54,7 @@ in
mail = { mail = {
enable = true; enable = true;
sender = config.email.fromAddress; sender = config.email.fromAddress;
recipient = "${config.email.toAddress},${config.email.adminAddress}"; recipient = "${config.email.userAddress},${config.email.adminAddress}";
}; };
}; };
}; };