V002:Change the folder name

This commit is contained in:
Yatmesh
2026-07-30 16:22:39 +05:30
parent ca6b2316f3
commit 2113ef90fa
@@ -0,0 +1,343 @@
#!/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 <<SQL
CREATE DATABASE IF NOT EXISTS \`$DB_NAME\`
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$db_password_sql';
ALTER USER '$DB_USER'@'localhost' IDENTIFIED BY '$db_password_sql';
GRANT SELECT, INSERT, UPDATE, DELETE ON \`$DB_NAME\`.* TO '$DB_USER'@'localhost';
FLUSH PRIVILEGES;
SQL
log "Verifying the FreeRADIUS MariaDB service account"
if ! MYSQL_PWD="$DB_PASSWORD" mariadb --protocol=socket \
--user="$DB_USER" --database="$DB_NAME" --batch --skip-column-names \
--execute="SELECT 1" | grep -qx 1; then
die "MariaDB login verification failed for '$DB_USER'@'localhost'."
fi
FR_DIR="/etc/freeradius/3.0"
SQL_SCHEMA="$FR_DIR/mods-config/sql/main/mariadb/schema.sql"
[[ -f "$SQL_SCHEMA" ]] || SQL_SCHEMA="/etc/freeradius/3.0/mods-config/sql/main/mysql/schema.sql"
[[ -f "$SQL_SCHEMA" ]] || die "FreeRADIUS MariaDB/MySQL schema was not installed."
if ! mariadb "$DB_NAME" -Nse "SHOW TABLES LIKE 'radcheck'" | grep -qx radcheck; then
log "Importing the official FreeRADIUS SQL schema"
mariadb "$DB_NAME" < "$SQL_SCHEMA"
else
log "FreeRADIUS SQL tables already exist; leaving them intact"
fi
sql_password_ini="$(ini_escape "$DB_PASSWORD")"
SQL_CONFIG="$FR_DIR/mods-available/sql"
[[ -e "$SQL_CONFIG" ]] || die "FreeRADIUS SQL module configuration was not found."
if [[ ! -e "${SQL_CONFIG}.before-aaa-installer" ]]; then
cp -a "$SQL_CONFIG" "${SQL_CONFIG}.before-aaa-installer"
fi
log "Configuring and enabling the FreeRADIUS SQL module"
sed -Ei \
-e 's|^[[:space:]]*dialect[[:space:]]*=.*|dialect = "mysql"|' \
-e 's|^[[:space:]]*driver[[:space:]]*=.*|driver = "rlm_sql_${dialect}"|' \
-e 's|^[[:space:]]*server[[:space:]]*=.*|server = "localhost"|' \
-e 's|^[[:space:]]*port[[:space:]]*=.*|port = 3306|' \
-e "s|^[[:space:]]*login[[:space:]]*=.*|login = \"$DB_USER\"|" \
-e "s|^[[:space:]]*password[[:space:]]*=.*|password = \"$sql_password_ini\"|" \
-e "s|^[[:space:]]*radius_db[[:space:]]*=.*|radius_db = \"$DB_NAME\"|" \
-e 's|^[[:space:]]*#[[:space:]]*read_clients[[:space:]]*=.*|read_clients = yes|' \
-e 's|^[[:space:]]*read_clients[[:space:]]*=.*|read_clients = yes|' \
"$SQL_CONFIG"
# MariaDB is localhost-only, so disable the sample SQL TLS block. This does not
# change RADIUS EAP/TLS. Use verified TLS instead if MariaDB is moved off-host.
sed -Ei '/^[[:space:]]*tls[[:space:]]*\{[[:space:]]*$/,/^[[:space:]]*\}[[:space:]]*$/ {
/^[[:space:]]*#/! s/^/#/
}' "$SQL_CONFIG"
ln -sfn ../mods-available/sql "$FR_DIR/mods-enabled/sql"
if ! grep -Fqx "login = \"$DB_USER\"" "$SQL_CONFIG" ||
! grep -Fqx "radius_db = \"$DB_NAME\"" "$SQL_CONFIG"; then
die "FreeRADIUS SQL login/database settings were not written correctly."
fi
enabled_sql_target="$(readlink -f "$FR_DIR/mods-enabled/sql")"
[[ "$enabled_sql_target" == "$(readlink -f "$SQL_CONFIG")" ]] ||
die "mods-enabled/sql does not point to the configured SQL module."
for site in "$FR_DIR/sites-enabled/default" "$FR_DIR/sites-enabled/inner-tunnel"; do
[[ -f "$site" ]] || continue
[[ -e "${site}.before-aaa-installer" ]] || cp -a "$site" "${site}.before-aaa-installer"
# Enable existing SQL calls in authorize/accounting/session/post-auth sections.
sed -Ei 's/^([[:space:]]*)-sql([[:space:]]*(#.*)?)$/\1sql\2/' "$site"
done
# Bind the public/default virtual server to every IPv4 interface. Do not expose
# inner-tunnel, which is intentionally a localhost-only internal listener.
sed -Ei 's/^([[:space:]]*)ipaddr[[:space:]]*=.*/\1ipaddr = */' \
"$FR_DIR/sites-enabled/default"
install -d -m 0750 "$FR_DIR/clients.d"
cat > "$FR_DIR/clients.d/aaa-installer.conf" <<EOF
# Managed by install_freeradius_mariadb.sh
# NAS clients are loaded from the MariaDB nas table by the SQL module.
EOF
chmod 0640 "$FR_DIR/clients.d/aaa-installer.conf"
if ! grep -Eq '^[[:space:]]*\$INCLUDE[[:space:]]+clients\.d/' "$FR_DIR/clients.conf"; then
printf '\n$INCLUDE clients.d/\n' >> "$FR_DIR/clients.conf"
fi
if [[ "$CREATE_TEST_USER" == "yes" ]]; then
log "Creating or updating the SQL-backed test user"
mariadb "$DB_NAME" <<SQL
DELETE FROM radcheck
WHERE username = '$test_username_sql' AND attribute = 'Cleartext-Password';
INSERT INTO radcheck (username, attribute, op, value)
VALUES ('$test_username_sql', 'Cleartext-Password', ':=', '$test_password_sql');
SQL
fi
log "Creating or updating SQL-backed NAS clients"
radius_client_network_sql="$(sql_escape "$RADIUS_CLIENT_NETWORK")"
radius_client_secret_sql="$(sql_escape "$RADIUS_CLIENT_SECRET")"
mariadb "$DB_NAME" <<SQL
DELETE FROM nas WHERE shortname IN ('aaa_nas_network', 'aaa_local_database_test');
INSERT INTO nas (nasname, shortname, type, ports, secret, server, community, description)
VALUES ('$radius_client_network_sql', 'aaa_nas_network', 'other', NULL,
'$radius_client_secret_sql', NULL, NULL, 'NAS network managed by installer');
SQL
if [[ "$RADIUS_CLIENT_NETWORK" != "127.0.0.2/32" ]]; then
mariadb "$DB_NAME" <<SQL
INSERT INTO nas (nasname, shortname, type, ports, secret, server, community, description)
VALUES ('127.0.0.2', 'aaa_local_database_test', 'other', NULL,
'$radius_client_secret_sql', NULL, NULL, 'Local SQL client loading test');
SQL
fi
log "Validating configuration and starting FreeRADIUS"
freeradius -XC
systemctl enable freeradius
systemctl restart freeradius
systemctl --no-pager --full is-active freeradius
ss -lunp | grep -E ':(1812|1813)[[:space:]]' ||
die "FreeRADIUS is running but UDP 1812/1813 listeners were not detected."
if [[ "$CREATE_TEST_USER" == "yes" ]]; then
log "Testing database-loaded NAS and SQL user authentication with radclient"
RADTEST_OUTPUT="$(
printf 'User-Name = "%s"\nUser-Password = "%s"\n' "$TEST_USERNAME" "$TEST_PASSWORD" |
radclient -x -i 127.0.0.2 127.0.0.1 auth "$RADIUS_CLIENT_SECRET" 2>&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" <<EOF
server {
listen 80;
listen [::]:80;
server_name $API_DOMAIN;
location /.well-known/acme-challenge/ { root $LE_WEBROOT; }
location / {
proxy_pass http://127.0.0.1:$API_PORT;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
EOF
ln -sfn "$NGINX_SITE" /etc/nginx/sites-enabled/hotspot-otp-api
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl enable --now nginx
systemctl reload nginx
if command -v ufw >/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" <<EOF
limit_req_zone \$binary_remote_addr zone=hotspot_api_ip:10m rate=10r/m;
upstream hotspot_otp_api {
server 127.0.0.1:$API_PORT;
keepalive 16;
}
server {
listen 80;
listen [::]:80;
server_name $API_DOMAIN;
location /.well-known/acme-challenge/ { root $LE_WEBROOT; }
location / { return 301 https://\$host\$request_uri; }
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name $API_DOMAIN;
ssl_certificate /etc/letsencrypt/live/$API_DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$API_DOMAIN/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
client_max_body_size 16k;
location / {
limit_req zone=hotspot_api_ip burst=10 nodelay;
proxy_pass http://hotspot_otp_api;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header X-Correlation-ID \$request_id;
proxy_connect_timeout 5s;
proxy_read_timeout 15s;
}
}
EOF
nginx -t
systemctl reload nginx
systemctl enable --now certbot.timer
cat > "$CREDENTIAL_FILE" <<EOF
Created: $(date --iso-8601=seconds)
MariaDB database: $DB_NAME
MariaDB user: $DB_USER
MariaDB password: $DB_PASSWORD
MariaDB host: localhost
Allowed RADIUS client network: $RADIUS_CLIENT_NETWORK
RADIUS client shared secret: $RADIUS_CLIENT_SECRET
Test user created: $CREATE_TEST_USER
Test username: $([[ "$CREATE_TEST_USER" == "yes" ]] && printf '%s' "$TEST_USERNAME" || printf 'N/A')
Test password: $([[ "$CREATE_TEST_USER" == "yes" ]] && printf '%s' "$TEST_PASSWORD" || printf 'N/A')
API URL: https://$API_DOMAIN
API local port: $API_PORT
EOF
chmod 0600 "$CREDENTIAL_FILE"
log "Installation completed"
printf 'Credentials were saved root-only at: %s\n' "$CREDENTIAL_FILE"
if [[ "$CREATE_TEST_USER" == "yes" ]]; then
printf 'Repeat the test using the radclient command documented in README.md.\n'
fi
printf 'Logs: journalctl -u freeradius -f\n'
printf 'API URL: https://%s\n' "$API_DOMAIN"