#!/usr/bin/env bash set -Eeuo pipefail umask 077 # FreeRADIUS 3 + MariaDB installer for Ubuntu 22.04/24.04. # Run as root. Values can be overridden as environment variables; see --help. DB_NAME="${DB_NAME:-radius}" # Use a service-specific variable. Generic DB_USER is commonly exported by # applications and previously caused FreeRADIUS to be configured as "hotspot". RADIUS_DB_USER="${RADIUS_DB_USER:-radius}" DB_USER="$RADIUS_DB_USER" DB_PASSWORD="${DB_PASSWORD:-}" RADIUS_CLIENT_NETWORK="${RADIUS_CLIENT_NETWORK:-127.0.0.2/32}" RADIUS_CLIENT_SECRET="${RADIUS_CLIENT_SECRET:-}" TEST_USERNAME="${TEST_USERNAME:-shree}" TEST_PASSWORD="${TEST_PASSWORD:-test}" CREATE_TEST_USER="${CREATE_TEST_USER:-yes}" CREDENTIAL_FILE="${CREDENTIAL_FILE:-/root/freeradius-db-credentials}" API_DOMAIN="${API_DOMAIN:-}" API_PORT="${API_PORT:-}" LE_EMAIL="${LE_EMAIL:-}" usage() { cat <<'EOF' Usage: sudo [VAR=value ...] ./install_freeradius_mariadb.sh Optional environment variables: DB_NAME MariaDB database name (default: radius) RADIUS_DB_USER MariaDB user, restricted to localhost (default: radius) DB_PASSWORD Database password (random when omitted) RADIUS_CLIENT_NETWORK Allowed NAS IP/CIDR (default: 127.0.0.2/32) RADIUS_CLIENT_SECRET NAS shared secret (random when omitted) CREATE_TEST_USER Create a test RADIUS user: yes/no (default: yes) TEST_USERNAME SQL-backed test username (default: shree) TEST_PASSWORD SQL-backed test password (default: test) CREDENTIAL_FILE Root-only result file API_DOMAIN Public API domain (prompted when omitted) API_PORT Local API port (prompted; default: 8000) LE_EMAIL Let's Encrypt email (prompted when omitted) Example: sudo RADIUS_CLIENT_NETWORK=10.10.0.0/24 \ RADIUS_CLIENT_SECRET='replace-with-a-long-secret' \ ./install_freeradius_mariadb.sh EOF } log() { printf '\n\033[1;34m==>\033[0m %s\n' "$*"; } die() { printf '\nERROR: %s\n' "$*" >&2; exit 1; } sql_escape() { local escaped="${1//\\/\\\\}" printf '%s' "${escaped//\'/\'\'}" } ini_escape() { # Escape characters that are special in a double-quoted FreeRADIUS value or # in the pipe-delimited sed replacement used below. printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/&/\\\&/g; s/|/\\|/g' } generate_secret() { openssl rand -base64 36 | tr -d '\n'; } [[ "${1:-}" != "--help" && "${1:-}" != "-h" ]] || { usage; exit 0; } [[ $EUID -eq 0 ]] || die "Run this script as root (sudo)." [[ -r /etc/os-release ]] || die "Cannot identify the operating system." # shellcheck disable=SC1091 . /etc/os-release [[ "${ID:-}" == "ubuntu" ]] || die "This installer supports Ubuntu only." [[ "$DB_NAME" =~ ^[A-Za-z0-9_]+$ ]] || die "DB_NAME may contain only letters, numbers, and underscores." [[ "$DB_USER" =~ ^[A-Za-z0-9_]+$ ]] || die "RADIUS_DB_USER may contain only letters, numbers, and underscores." [[ "$CREATE_TEST_USER" == "yes" || "$CREATE_TEST_USER" == "no" ]] || die "CREATE_TEST_USER must be yes or no." [[ "$DB_PASSWORD$RADIUS_CLIENT_SECRET$TEST_USERNAME$TEST_PASSWORD" != *$'\n'* ]] || die "Credentials and usernames must not contain newline characters." [[ -n "$API_DOMAIN" ]] || read -r -p "API domain name (example: api.example.com): " API_DOMAIN [[ -n "$API_PORT" ]] || read -r -p "Local API port [8000]: " API_PORT API_PORT="${API_PORT:-8000}" [[ -n "$LE_EMAIL" ]] || read -r -p "Email for Let's Encrypt notices: " LE_EMAIL API_DOMAIN="${API_DOMAIN,,}" [[ "$API_DOMAIN" =~ ^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$ ]] || die "Enter a valid public domain name." [[ "$API_PORT" =~ ^[0-9]+$ ]] && (( API_PORT >= 1 && API_PORT <= 65535 )) || die "API port must be between 1 and 65535." [[ "$LE_EMAIL" =~ ^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$ ]] || die "Enter a valid email address." export DEBIAN_FRONTEND=noninteractive log "Installing FreeRADIUS, MariaDB, Nginx, Certbot, and supporting packages" apt-get update apt-get install -y freeradius freeradius-mysql freeradius-utils mariadb-server \ openssl ca-certificates nginx certbot python3-certbot-nginx systemctl enable --now mariadb DB_PASSWORD="${DB_PASSWORD:-$(generate_secret)}" RADIUS_CLIENT_SECRET="${RADIUS_CLIENT_SECRET:-$(generate_secret)}" if [[ "$CREATE_TEST_USER" == "yes" ]]; then TEST_PASSWORD="${TEST_PASSWORD:-test}" fi db_password_sql="$(sql_escape "$DB_PASSWORD")" test_username_sql="$(sql_escape "$TEST_USERNAME")" test_password_sql="$(sql_escape "$TEST_PASSWORD")" log "Creating the MariaDB database and localhost-only service account" mariadb < "$FR_DIR/clients.d/aaa-installer.conf" <> "$FR_DIR/clients.conf" fi if [[ "$CREATE_TEST_USER" == "yes" ]]; then log "Creating or updating the SQL-backed test user" mariadb "$DB_NAME" <&1 )" || { printf '%s\n' "$RADTEST_OUTPUT" >&2 die "radclient failed. Inspect logs with: journalctl -u freeradius -n 100" } printf '%s\n' "$RADTEST_OUTPUT" grep -q "Access-Accept" <<< "$RADTEST_OUTPUT" || die "SQL authentication test did not return Access-Accept." printf 'SQL authentication test: PASS (Access-Accept for %s)\n' "$TEST_USERNAME" fi if command -v ufw >/dev/null 2>&1 && ufw status | grep -q '^Status: active'; then log "Opening RADIUS authentication/accounting ports in active UFW" ufw allow from "$RADIUS_CLIENT_NETWORK" to any port 1812 proto udp ufw allow from "$RADIUS_CLIENT_NETWORK" to any port 1813 proto udp fi log "Configuring Nginx and requesting a Let's Encrypt certificate" LE_WEBROOT="/var/www/letsencrypt" NGINX_SITE="/etc/nginx/sites-available/hotspot-otp-api" install -d -m 0755 "$LE_WEBROOT" cat > "$NGINX_SITE" </dev/null 2>&1 && ufw status | grep -q '^Status: active'; then ufw allow 'Nginx Full' fi certbot certonly --webroot -w "$LE_WEBROOT" -d "$API_DOMAIN" \ --email "$LE_EMAIL" --agree-tos --no-eff-email --non-interactive cat > "$NGINX_SITE" < "$CREDENTIAL_FILE" <