63b9a97cdf
Build and Push Docker Image to Gitea Container Registry / build-and-push (push) Successful in 1m12s
189 lines
7.4 KiB
PL/PgSQL
189 lines
7.4 KiB
PL/PgSQL
-- Initialize PostgreSQL database for FreeRADIUS and RadiusController App
|
|
|
|
-- Standard FreeRADIUS PostgreSQL Schema
|
|
CREATE TABLE IF NOT EXISTS radcheck (
|
|
id SERIAL PRIMARY KEY,
|
|
username VARCHAR(64) NOT NULL DEFAULT '',
|
|
attribute VARCHAR(64) NOT NULL DEFAULT '',
|
|
op VARCHAR(2) NOT NULL DEFAULT ':=',
|
|
value VARCHAR(253) NOT NULL DEFAULT ''
|
|
);
|
|
CREATE INDEX IF NOT EXISTS radcheck_username ON radcheck (username);
|
|
|
|
CREATE TABLE IF NOT EXISTS radreply (
|
|
id SERIAL PRIMARY KEY,
|
|
username VARCHAR(64) NOT NULL DEFAULT '',
|
|
attribute VARCHAR(64) NOT NULL DEFAULT '',
|
|
op VARCHAR(2) NOT NULL DEFAULT '=',
|
|
value VARCHAR(253) NOT NULL DEFAULT ''
|
|
);
|
|
CREATE INDEX IF NOT EXISTS radreply_username ON radreply (username);
|
|
|
|
CREATE TABLE IF NOT EXISTS radgroupcheck (
|
|
id SERIAL PRIMARY KEY,
|
|
groupname VARCHAR(64) NOT NULL DEFAULT '',
|
|
attribute VARCHAR(64) NOT NULL DEFAULT '',
|
|
op VARCHAR(2) NOT NULL DEFAULT ':=',
|
|
value VARCHAR(253) NOT NULL DEFAULT ''
|
|
);
|
|
CREATE INDEX IF NOT EXISTS radgroupcheck_groupname ON radgroupcheck (groupname);
|
|
|
|
CREATE TABLE IF NOT EXISTS radgroupreply (
|
|
id SERIAL PRIMARY KEY,
|
|
groupname VARCHAR(64) NOT NULL DEFAULT '',
|
|
attribute VARCHAR(64) NOT NULL DEFAULT '',
|
|
op VARCHAR(2) NOT NULL DEFAULT '=',
|
|
value VARCHAR(253) NOT NULL DEFAULT ''
|
|
);
|
|
CREATE INDEX IF NOT EXISTS radgroupreply_groupname ON radgroupreply (groupname);
|
|
|
|
CREATE TABLE IF NOT EXISTS radusergroup (
|
|
id SERIAL PRIMARY KEY,
|
|
username VARCHAR(64) NOT NULL DEFAULT '',
|
|
groupname VARCHAR(64) NOT NULL DEFAULT '',
|
|
priority INT NOT NULL DEFAULT 1
|
|
);
|
|
CREATE INDEX IF NOT EXISTS radusergroup_username ON radusergroup (username);
|
|
|
|
CREATE TABLE IF NOT EXISTS radacct (
|
|
radacctid BIGSERIAL PRIMARY KEY,
|
|
acctsessionid VARCHAR(64) NOT NULL DEFAULT '',
|
|
acctuniqueid VARCHAR(32) NOT NULL UNIQUE DEFAULT '',
|
|
username VARCHAR(64) NOT NULL DEFAULT '',
|
|
realm VARCHAR(64) DEFAULT '',
|
|
nasipaddress VARCHAR(46) NOT NULL DEFAULT '',
|
|
nasportid VARCHAR(32) DEFAULT NULL,
|
|
nasporttype VARCHAR(32) DEFAULT NULL,
|
|
acctstarttime TIMESTAMP WITH TIME ZONE DEFAULT NULL,
|
|
acctupdatetime TIMESTAMP WITH TIME ZONE DEFAULT NULL,
|
|
acctstoptime TIMESTAMP WITH TIME ZONE DEFAULT NULL,
|
|
acctinterval INT DEFAULT NULL,
|
|
acctsessiontime BIGINT DEFAULT NULL,
|
|
acctauthentic VARCHAR(32) DEFAULT NULL,
|
|
connectinfo_start VARCHAR(50) DEFAULT NULL,
|
|
connectinfo_stop VARCHAR(50) DEFAULT NULL,
|
|
acctinputoctets BIGINT DEFAULT NULL,
|
|
acctoutputoctets BIGINT DEFAULT NULL,
|
|
calledstationid VARCHAR(50) DEFAULT '',
|
|
callingstationid VARCHAR(50) DEFAULT '',
|
|
acctterminatecause VARCHAR(32) DEFAULT NULL,
|
|
servicetype VARCHAR(32) DEFAULT NULL,
|
|
framedprotocol VARCHAR(32) DEFAULT NULL,
|
|
framedipaddress VARCHAR(46) DEFAULT '',
|
|
framedipv6address VARCHAR(45) DEFAULT '',
|
|
framedipv6prefix VARCHAR(45) DEFAULT '',
|
|
framedinterfaceid VARCHAR(44) DEFAULT '',
|
|
delegatedipv6prefix VARCHAR(45) DEFAULT '',
|
|
class VARCHAR(64) DEFAULT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS radacct_username ON radacct (username);
|
|
CREATE INDEX IF NOT EXISTS radacct_active ON radacct (acctstoptime) WHERE acctstoptime IS NULL;
|
|
CREATE INDEX IF NOT EXISTS radacct_acctuniqueid ON radacct (acctuniqueid);
|
|
|
|
CREATE TABLE IF NOT EXISTS nas (
|
|
id SERIAL PRIMARY KEY,
|
|
nasname VARCHAR(128) NOT NULL,
|
|
shortname VARCHAR(32),
|
|
type VARCHAR(30) DEFAULT 'other',
|
|
ports INT,
|
|
secret VARCHAR(60) NOT NULL,
|
|
server VARCHAR(64),
|
|
community VARCHAR(50),
|
|
description VARCHAR(200) DEFAULT 'RADIUS Client'
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS radpostauth (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
username VARCHAR(64) NOT NULL DEFAULT '',
|
|
pass VARCHAR(64) NOT NULL DEFAULT '',
|
|
reply VARCHAR(32) NOT NULL DEFAULT '',
|
|
authdate TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
CREATE INDEX IF NOT EXISTS radpostauth_username ON radpostauth (username);
|
|
|
|
-- Application Management Tables
|
|
CREATE TABLE IF NOT EXISTS guest_profiles (
|
|
id SERIAL PRIMARY KEY,
|
|
guest_name VARCHAR(255) NOT NULL,
|
|
username VARCHAR(64) NOT NULL,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
session_time_minutes INT NOT NULL DEFAULT 60,
|
|
data_limit_mb BIGINT NOT NULL DEFAULT 500,
|
|
status VARCHAR(32) NOT NULL DEFAULT 'Active'
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS admin_users (
|
|
id SERIAL PRIMARY KEY,
|
|
username VARCHAR(64) UNIQUE NOT NULL,
|
|
password_hash VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS system_settings (
|
|
key VARCHAR(64) PRIMARY KEY,
|
|
value VARCHAR(255) NOT NULL,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Seed Default Settings
|
|
INSERT INTO system_settings (key, value) VALUES ('default_session_time', '60') ON CONFLICT (key) DO NOTHING;
|
|
INSERT INTO system_settings (key, value) VALUES ('default_data_limit', '500') ON CONFLICT (key) DO NOTHING;
|
|
|
|
-- System settings initialized on startup
|
|
-- Admin user is created by administrator during first startup via the web interface
|
|
|
|
-- Dedicated table for robust, per-user data tracking that survives AP counter resets
|
|
CREATE TABLE IF NOT EXISTS guest_data_usage (
|
|
username VARCHAR(64) PRIMARY KEY,
|
|
total_input_octets BIGINT NOT NULL DEFAULT 0,
|
|
total_output_octets BIGINT NOT NULL DEFAULT 0
|
|
);
|
|
|
|
-- Trigger function to safely accumulate data usage from radacct
|
|
CREATE OR REPLACE FUNCTION update_guest_data_usage()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
delta_input BIGINT := 0;
|
|
delta_output BIGINT := 0;
|
|
BEGIN
|
|
-- Handle INSERT (new session)
|
|
IF (TG_OP = 'INSERT') THEN
|
|
delta_input := COALESCE(NEW.acctinputoctets, 0);
|
|
delta_output := COALESCE(NEW.acctoutputoctets, 0);
|
|
-- Handle UPDATE (interim updates)
|
|
ELSIF (TG_OP = 'UPDATE') THEN
|
|
-- Only add if the new value is greater than the old value (protects against counter resets)
|
|
IF (COALESCE(NEW.acctinputoctets, 0) > COALESCE(OLD.acctinputoctets, 0)) THEN
|
|
delta_input := NEW.acctinputoctets - COALESCE(OLD.acctinputoctets, 0);
|
|
ELSIF (COALESCE(NEW.acctinputoctets, 0) < COALESCE(OLD.acctinputoctets, 0)) THEN
|
|
-- Counter reset mid-session! Just add the new value as the delta.
|
|
delta_input := COALESCE(NEW.acctinputoctets, 0);
|
|
END IF;
|
|
|
|
IF (COALESCE(NEW.acctoutputoctets, 0) > COALESCE(OLD.acctoutputoctets, 0)) THEN
|
|
delta_output := NEW.acctoutputoctets - COALESCE(OLD.acctoutputoctets, 0);
|
|
ELSIF (COALESCE(NEW.acctoutputoctets, 0) < COALESCE(OLD.acctoutputoctets, 0)) THEN
|
|
-- Counter reset mid-session!
|
|
delta_output := COALESCE(NEW.acctoutputoctets, 0);
|
|
END IF;
|
|
END IF;
|
|
|
|
-- Upsert the calculated deltas into the usage table per-user
|
|
IF (delta_input > 0 OR delta_output > 0) THEN
|
|
INSERT INTO guest_data_usage (username, total_input_octets, total_output_octets)
|
|
VALUES (NEW.username, delta_input, delta_output)
|
|
ON CONFLICT (username) DO UPDATE
|
|
SET total_input_octets = guest_data_usage.total_input_octets + EXCLUDED.total_input_octets,
|
|
total_output_octets = guest_data_usage.total_output_octets + EXCLUDED.total_output_octets;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Attach trigger to radacct table
|
|
DROP TRIGGER IF EXISTS trg_update_guest_data_usage ON radacct;
|
|
CREATE TRIGGER trg_update_guest_data_usage
|
|
AFTER INSERT OR UPDATE OF acctinputoctets, acctoutputoctets ON radacct
|
|
FOR EACH ROW EXECUTE FUNCTION update_guest_data_usage();
|