Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 110 additions & 16 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ set -euo pipefail
readonly UNBOUND_PORT=5335
readonly NETALERTX_PORT=20211
readonly PYTHON_SUITE_PORT=8090
readonly NETALERTX_IMAGE="jokobsk/netalertx:latest"
readonly SUITE_API_KEY_ENV="${SUITE_API_KEY:-}"
readonly SUITE_LOG_LEVEL_ENV="${SUITE_LOG_LEVEL:-INFO}"
readonly INSTALL_USER=${SUDO_USER:-$(whoami)}
readonly PROJECT_DIR="$(pwd)"
readonly RESOLV_CONF="/etc/resolv.conf"
readonly RESOLV_CONF_BACKUP="/etc/resolv.conf.pi-hole-installer.bak"
readonly -a FALLBACK_RESOLVERS=("1.1.1.1" "9.9.9.9")

# 🎨 Colors
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
Expand All @@ -22,7 +29,24 @@ error() { echo -e "${RED}[✗]${NC} $*"; }
step() { echo -e "\n${YELLOW}[STEP]${NC} $*"; }

# 🛡️ Error handler
trap 'error "Installation failed. See logs above."' ERR
trap 'error "Installation failed. See logs above."; exit 1' ERR

# ---------------------------------------------------------------------------
# 🧪 Helpers
write_resolv_conf() {
local file="$1"; shift
: >"$file"
for resolver in "$@"; do
printf 'nameserver %s\n' "$resolver" >>"$file"
done
}

extract_env_value() {
local key="$1" file="$2"
if [[ -f "$file" ]]; then
grep -E "^${key}=" "$file" | tail -n1 | cut -d= -f2-
fi
}

# ---------------------------------------------------------------------------
# 🔍 System checks
Expand All @@ -34,24 +58,72 @@ check_system() {
success "System checks passed"
}

# 🧰 Ubuntu resolver handling (Port 53)
handle_systemd_resolved() {
step "Checking systemd-resolved on Ubuntu (port 53)"
if [[ -r /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
if [[ "${ID:-}" == ubuntu* || "${ID_LIKE:-}" == *ubuntu* ]]; then
if systemctl list-unit-files | grep -q '^systemd-resolved\.service'; then
if systemctl is-active --quiet systemd-resolved; then
warn "systemd-resolved is active; stopping to free port 53"
systemctl stop systemd-resolved || true
fi
systemctl disable systemd-resolved || true
if [[ -L $RESOLV_CONF ]]; then
warn "$RESOLV_CONF is a symlink; replacing with static resolver"
if [[ ! -e $RESOLV_CONF_BACKUP ]]; then
mv -f "$RESOLV_CONF" "$RESOLV_CONF_BACKUP"
else
rm -f "$RESOLV_CONF"
fi
fi
write_resolv_conf "$RESOLV_CONF" "${FALLBACK_RESOLVERS[@]}"
fi
fi
fi
success "Resolver prepared with external fallbacks"
}

finalize_resolver_configuration() {
log "Pointing system resolver to Pi-hole"
local resolvers=("127.0.0.1" "${FALLBACK_RESOLVERS[@]}")
write_resolv_conf "$RESOLV_CONF" "${resolvers[@]}"
success "System resolver now prefers Pi-hole on 127.0.0.1"
}

# 🔌 Port conflicts
check_ports() {
step "Checking ports"
local ports=($UNBOUND_PORT $NETALERTX_PORT $PYTHON_SUITE_PORT 53)
for port in "${ports[@]}"; do
if ss -tuln | grep -q ":$port "; then
warn "Port $port already in use"
fi
done
if command -v ss >/dev/null; then
for port in "${ports[@]}"; do
if ss -tuln | grep -q ":$port "; then
warn "Port $port already in use"
fi
done
elif command -v netstat >/dev/null; then
for port in "${ports[@]}"; do
if netstat -tuln | grep -q ":$port "; then
warn "Port $port already in use"
fi
done
else
warn "Neither ss nor netstat available; skipping port checks"
fi
}

# 📦 Packages
install_packages() {
step "Installing system packages"
apt-get update -qq
python3 python3-venv python3-pip git docker.io openssl systemd sqlite3
apt-get install -y unbound ca-certificates curl dnsutils \
python3 python3-venv python3-pip git docker.io openssl systemd sqlite3 iproute2
success "System packages installed"
}

# 🔐 Unbound config
configure_unbound() {
step "Configuring Unbound"
install -d -m 0755 /var/lib/unbound
Expand All @@ -74,6 +146,7 @@ server:
cache-max-ttl: 86400
trust-anchor-file: /var/lib/unbound/root.key
root-hints: /var/lib/unbound/root.hints
tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt

forward-zone:
name: "."
Expand All @@ -100,6 +173,7 @@ install_pihole() {
sed -i "s/^PIHOLE_DNS_1=.*/PIHOLE_DNS_1=127.0.0.1#$UNBOUND_PORT/" /etc/pihole/setupVars.conf
sed -i "s/^PIHOLE_DNS_2=.*/PIHOLE_DNS_2=/" /etc/pihole/setupVars.conf
pihole restartdns
finalize_resolver_configuration
success "Pi-hole configured with Unbound"
}

Expand All @@ -122,12 +196,33 @@ install_netalertx() {
setup_python_suite() {
step "Setting up Python suite"
cd "$PROJECT_DIR"
# Ensure data directory exists and is writable by service user
install -d -m 0755 "$PROJECT_DIR/data"
chown -R "$INSTALL_USER:$INSTALL_USER" "$PROJECT_DIR/data"

[[ -d .venv ]] || sudo -u "$INSTALL_USER" python3 -m venv .venv
sudo -u "$INSTALL_USER" .venv/bin/pip install -U pip
sudo -u "$INSTALL_USER" .venv/bin/pip install -r requirements.txt
sudo -u "$INSTALL_USER" .venv/bin/python scripts/bootstrap.py || true

cat > /etc/systemd/system/pihole-suite.service <<EOF
local env_file="$PROJECT_DIR/.env"
local suite_api_key="$SUITE_API_KEY_ENV"
if [[ -z "$suite_api_key" ]]; then
suite_api_key="$(extract_env_value "SUITE_API_KEY" "$env_file")"
fi
if [[ -z "$suite_api_key" ]]; then
suite_api_key="$(openssl rand -hex 16)"
fi
Comment on lines +208 to +215
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Mehrere Quellen für SUITE_API_KEY können zu Verwirrung führen.

Bitte klären oder dokumentieren Sie die Rangfolge der SUITE_API_KEY-Zuweisung, um Verwirrung zu vermeiden, wenn sich Umgebungs- und .env-Werte unterscheiden.

Suggested change
local env_file="$PROJECT_DIR/.env"
local suite_api_key="$SUITE_API_KEY_ENV"
if [[ -z "$suite_api_key" ]]; then
suite_api_key="$(extract_env_value "SUITE_API_KEY" "$env_file")"
fi
if [[ -z "$suite_api_key" ]]; then
suite_api_key="$(openssl rand -hex 16)"
fi
# SUITE_API_KEY assignment order of precedence:
# 1. If SUITE_API_KEY_ENV is set (from environment), use it.
# 2. Else, if SUITE_API_KEY is set in .env file, use that.
# 3. Else, generate a random value.
local env_file="$PROJECT_DIR/.env"
local suite_api_key="$SUITE_API_KEY_ENV"
if [[ -z "$suite_api_key" ]]; then
suite_api_key="$(extract_env_value "SUITE_API_KEY" "$env_file")"
fi
if [[ -z "$suite_api_key" ]]; then
suite_api_key="$(openssl rand -hex 16)"
fi
Original comment in English

suggestion: Multiple sources for SUITE_API_KEY may cause confusion.

Please clarify or document the order of precedence for SUITE_API_KEY assignment to prevent confusion if environment and .env values differ.

Suggested change
local env_file="$PROJECT_DIR/.env"
local suite_api_key="$SUITE_API_KEY_ENV"
if [[ -z "$suite_api_key" ]]; then
suite_api_key="$(extract_env_value "SUITE_API_KEY" "$env_file")"
fi
if [[ -z "$suite_api_key" ]]; then
suite_api_key="$(openssl rand -hex 16)"
fi
# SUITE_API_KEY assignment order of precedence:
# 1. If SUITE_API_KEY_ENV is set (from environment), use it.
# 2. Else, if SUITE_API_KEY is set in .env file, use that.
# 3. Else, generate a random value.
local env_file="$PROJECT_DIR/.env"
local suite_api_key="$SUITE_API_KEY_ENV"
if [[ -z "$suite_api_key" ]]; then
suite_api_key="$(extract_env_value "SUITE_API_KEY" "$env_file")"
fi
if [[ -z "$suite_api_key" ]]; then
suite_api_key="$(openssl rand -hex 16)"
fi


cat > "$env_file" <<ENV
SUITE_API_KEY=$suite_api_key
SUITE_PORT=$PYTHON_SUITE_PORT
SUITE_DATA_DIR=$PROJECT_DIR/data
SUITE_LOG_LEVEL=$SUITE_LOG_LEVEL_ENV
ENV
chown "$INSTALL_USER:$INSTALL_USER" "$env_file"

cat > /etc/systemd/system/pihole-suite.service <<SERVICE_EOF
[Unit]
Description=Pi-hole Suite (API + monitoring)
After=network.target pihole-FTL.service
Expand All @@ -147,18 +242,16 @@ RestartSec=5
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
PrivateTmp=yes
ReadWritePaths=$PROJECT_DIR $PROJECT_DIR/data

[Install]
WantedBy=multi-user.target
EOF
SERVICE_EOF

cat > .env <<ENV
SUITE_API_KEY=$SUITE_API_KEY
SUITE_PORT=$PYTHON_SUITE_PORT
SUITE_DATA_DIR=$PROJECT_DIR/data
SUITE_LOG_LEVEL=${SUITE_LOG_LEVEL:-INFO}
ENV
systemctl daemon-reload
systemctl enable --now pihole-suite.service
success "Python suite running on :$PYTHON_SUITE_PORT"
Expand All @@ -168,6 +261,7 @@ ENV
run_health_checks() {
step "Running health checks"
dig +short @127.0.0.1 -p $UNBOUND_PORT example.com | grep -q "." && success "Unbound OK" || error "Unbound FAIL"
pihole status | grep -iEq "blocking.+enabled|enabled" && success "Pi-hole OK" || warn "Pi-hole status unclear"
docker ps | grep -q netalertx && success "NetAlertX OK" || warn "NetAlertX missing"
systemctl is-active --quiet pihole-suite && success "Python suite OK" || warn "Python suite not active"
}
Expand All @@ -179,7 +273,7 @@ show_summary() {
echo "Pi-hole admin: http://$(hostname -I | awk '{print $1}')/admin"
echo "NetAlertX: http://$(hostname -I | awk '{print $1}'):$NETALERTX_PORT"
echo "Python Suite: http://127.0.0.1:$PYTHON_SUITE_PORT"
echo "API Key: $SUITE_API_KEY"
echo "API Key: $(extract_env_value "SUITE_API_KEY" "$PROJECT_DIR/.env")"
}

# 🚀 Main
Expand Down
1 change: 0 additions & 1 deletion pyalloc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import logging
import sqlite3
import threading
import time
from typing import Optional

from .allocator import IPPool
Expand Down