Some Alternative ideas

Using the built in Linux services and slapping some CNet like UI on them.

SQLite DB setup to play with:

PRAGMA foreign_keys = ON;

-- =========================
-- ACCESS GROUPS
-- =========================
CREATE TABLE access_groups (
    id INTEGER PRIMARY KEY CHECK(id BETWEEN 0 AND 31),
    name TEXT NOT NULL,
    permissions_mask INTEGER NOT NULL DEFAULT 0,
    daily_time_limit INTEGER DEFAULT 60,        -- minutes
    download_limit INTEGER DEFAULT 100,         -- files per day
    upload_credit_ratio REAL DEFAULT 1.0,
    is_sysop INTEGER DEFAULT 0
);

-- =========================
-- USERS
-- =========================
CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    handle TEXT UNIQUE NOT NULL,
    real_name TEXT,
    password_hash TEXT NOT NULL,
    access_group_id INTEGER NOT NULL DEFAULT 0,
    override_grant_mask INTEGER DEFAULT 0,
    override_deny_mask INTEGER DEFAULT 0,
    time_limit_override INTEGER,
    download_limit_override INTEGER,
    ratio_override REAL,
    credit_balance INTEGER DEFAULT 0,
    total_calls INTEGER DEFAULT 0,
    total_uploads INTEGER DEFAULT 0,
    total_downloads INTEGER DEFAULT 0,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    last_login DATETIME,
    FOREIGN KEY(access_group_id) REFERENCES access_groups(id)
);

-- =========================
-- MESSAGE AREAS
-- =========================
CREATE TABLE message_areas (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    description TEXT,
    read_perm_mask INTEGER DEFAULT 0,
    post_perm_mask INTEGER DEFAULT 0,
    is_ftn INTEGER DEFAULT 0,
    ftn_area_tag TEXT
);

-- =========================
-- MESSAGES
-- =========================
CREATE TABLE messages (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    area_id INTEGER NOT NULL,
    from_user_id INTEGER,
    to_user TEXT,
    subject TEXT,
    body TEXT NOT NULL,
    posted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    ftn_msgid TEXT,
    ftn_reply TEXT,
    FOREIGN KEY(area_id) REFERENCES message_areas(id),
    FOREIGN KEY(from_user_id) REFERENCES users(id)
);

-- =========================
-- FILE AREAS
-- =========================
CREATE TABLE file_areas (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    description TEXT,
    path TEXT NOT NULL,
    download_perm_mask INTEGER DEFAULT 0,
    upload_perm_mask INTEGER DEFAULT 0
);

-- =========================
-- FILE ENTRIES
-- =========================
CREATE TABLE files (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    area_id INTEGER NOT NULL,
    filename TEXT NOT NULL,
    description TEXT,
    uploader_id INTEGER,
    size INTEGER,
    upload_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    download_count INTEGER DEFAULT 0,
    FOREIGN KEY(area_id) REFERENCES file_areas(id),
    FOREIGN KEY(uploader_id) REFERENCES users(id)
);

-- =========================
-- DOORS
-- =========================
CREATE TABLE doors (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    command TEXT NOT NULL,
    min_access_group INTEGER DEFAULT 0
);

-- =========================
-- NODES (Multi-node tracking)
-- =========================
CREATE TABLE nodes (
    id INTEGER PRIMARY KEY,
    user_id INTEGER,
    login_time DATETIME,
    status TEXT,
    FOREIGN KEY(user_id) REFERENCES users(id)
);

-- =========================
-- SYSTEM CONFIG
-- =========================
CREATE TABLE system_config (
    key TEXT PRIMARY KEY,
    value TEXT
);

Not sure?

INSERT INTO access_groups (id, name, permissions_mask, is_sysop)
VALUES
(0, 'New User', 0, 0),
(1, 'Regular User', 15, 0),
(5, 'Power User', 255, 0),
(31, 'SysOp', 4294967295, 1);

VDE (Python)

Field(
    label="Handle",
    db_column="handle",
    type="text",
    max_length=30
)

Field(
    label="Access Group",
    db_column="access_group_id",
    type="int",
    min=0,
    max=31
)

Field(
    label="Post Messages",
    type="tristate_permission",
    perm_bit=1
)