- Interface model with per-interface subnet/port/keys; import/adopt existing wg-quick configs (key-less imported peers, optional key rotation), cascade delete - Split wireguard.py into a package (keys via cryptography X25519, status, addressing, conf parse/render, sync, host tuning) - ECharts horizontal topology view (interface -> peers -> site subnets) - Advanced options: MTU, MSS clamping, FwMark/Table, custom PostUp/PostDown, per-peer keepalive override - Runtime settings (sample interval/retention, online threshold, UI refresh) with traffic sample pruning; host tuning (UDP buffers, backlog, GRO forwarding) - Precompiled Tailwind CSS replacing Play CDN runtime (fixes FOUC); stable table layout and diffed polling renders - Host network mode in compose; NAT/isolation iptables moved into app sync Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
134 lines
5.5 KiB
Python
134 lines
5.5 KiB
Python
from sqlalchemy import create_engine, text
|
|
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
|
|
|
from .config import settings
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
pass
|
|
|
|
|
|
engine = create_engine(
|
|
f"sqlite:///{settings.data_dir / 'wireguard.db'}",
|
|
connect_args={"check_same_thread": False},
|
|
)
|
|
SessionLocal = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False)
|
|
|
|
|
|
def get_db():
|
|
db: Session = SessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
PEER_MIGRATIONS = {
|
|
"note": "ALTER TABLE peers ADD COLUMN note VARCHAR(256) NOT NULL DEFAULT ''",
|
|
"dns": "ALTER TABLE peers ADD COLUMN dns VARCHAR(128) NOT NULL DEFAULT ''",
|
|
"extra_allowed_ips": "ALTER TABLE peers ADD COLUMN extra_allowed_ips VARCHAR(512) NOT NULL DEFAULT ''",
|
|
"client_allowed_ips": "ALTER TABLE peers ADD COLUMN client_allowed_ips VARCHAR(512) NOT NULL DEFAULT ''",
|
|
"quota_bytes": "ALTER TABLE peers ADD COLUMN quota_bytes INTEGER NOT NULL DEFAULT 0",
|
|
"cum_rx": "ALTER TABLE peers ADD COLUMN cum_rx INTEGER NOT NULL DEFAULT 0",
|
|
"cum_tx": "ALTER TABLE peers ADD COLUMN cum_tx INTEGER NOT NULL DEFAULT 0",
|
|
"last_rx": "ALTER TABLE peers ADD COLUMN last_rx INTEGER NOT NULL DEFAULT 0",
|
|
"last_tx": "ALTER TABLE peers ADD COLUMN last_tx INTEGER NOT NULL DEFAULT 0",
|
|
}
|
|
|
|
PEER_COLUMNS = (
|
|
"id, interface_id, name, public_key, private_key_enc, preshared_key_enc, "
|
|
"address, enabled, expires_at, created_at, note, dns, extra_allowed_ips, "
|
|
"client_allowed_ips, quota_bytes, cum_rx, cum_tx, last_rx, last_tx"
|
|
)
|
|
|
|
|
|
def _columns(conn, table: str) -> set[str]:
|
|
return {row[1] for row in conn.execute(text(f"PRAGMA table_info({table})"))}
|
|
|
|
|
|
def _rebuild_peers_with_interface(conn) -> None:
|
|
# SQLite cannot alter unique constraints in place; rebuild the table so
|
|
# name/address become unique per interface instead of globally.
|
|
conn.execute(text("ALTER TABLE peers RENAME TO peers_old"))
|
|
conn.execute(
|
|
text(
|
|
"""
|
|
CREATE TABLE peers (
|
|
id INTEGER NOT NULL PRIMARY KEY,
|
|
interface_id INTEGER NOT NULL DEFAULT 1 REFERENCES interfaces (id) ON DELETE CASCADE,
|
|
name VARCHAR(64) NOT NULL,
|
|
public_key VARCHAR(64) NOT NULL UNIQUE,
|
|
private_key_enc VARCHAR(256) NOT NULL DEFAULT '',
|
|
preshared_key_enc VARCHAR(256) NOT NULL DEFAULT '',
|
|
address VARCHAR(64) NOT NULL,
|
|
enabled BOOLEAN NOT NULL DEFAULT 1,
|
|
expires_at DATETIME,
|
|
created_at DATETIME NOT NULL,
|
|
note VARCHAR(256) NOT NULL DEFAULT '',
|
|
dns VARCHAR(128) NOT NULL DEFAULT '',
|
|
extra_allowed_ips VARCHAR(512) NOT NULL DEFAULT '',
|
|
client_allowed_ips VARCHAR(512) NOT NULL DEFAULT '',
|
|
quota_bytes INTEGER NOT NULL DEFAULT 0,
|
|
cum_rx INTEGER NOT NULL DEFAULT 0,
|
|
cum_tx INTEGER NOT NULL DEFAULT 0,
|
|
last_rx INTEGER NOT NULL DEFAULT 0,
|
|
last_tx INTEGER NOT NULL DEFAULT 0,
|
|
CONSTRAINT uq_peer_iface_name UNIQUE (interface_id, name),
|
|
CONSTRAINT uq_peer_iface_address UNIQUE (interface_id, address)
|
|
)
|
|
"""
|
|
)
|
|
)
|
|
old_columns = _columns(conn, "peers_old")
|
|
select_columns = ", ".join(
|
|
column.strip() if column.strip() in old_columns else f"1 AS {column.strip()}"
|
|
for column in PEER_COLUMNS.split(",")
|
|
)
|
|
conn.execute(
|
|
text(
|
|
f"INSERT INTO peers ({PEER_COLUMNS}) SELECT {select_columns} FROM peers_old"
|
|
)
|
|
)
|
|
conn.execute(text("DROP TABLE peers_old"))
|
|
conn.execute(
|
|
text("CREATE INDEX ix_peers_interface_id ON peers (interface_id)")
|
|
)
|
|
|
|
|
|
INTERFACE_MIGRATIONS = {
|
|
"mtu": "ALTER TABLE interfaces ADD COLUMN mtu INTEGER NOT NULL DEFAULT 0",
|
|
"mss_clamp": "ALTER TABLE interfaces ADD COLUMN mss_clamp BOOLEAN NOT NULL DEFAULT 0",
|
|
"fwmark": "ALTER TABLE interfaces ADD COLUMN fwmark VARCHAR(32) NOT NULL DEFAULT ''",
|
|
"route_table": "ALTER TABLE interfaces ADD COLUMN route_table VARCHAR(32) NOT NULL DEFAULT ''",
|
|
"post_up": "ALTER TABLE interfaces ADD COLUMN post_up VARCHAR(2048) NOT NULL DEFAULT ''",
|
|
"post_down": "ALTER TABLE interfaces ADD COLUMN post_down VARCHAR(2048) NOT NULL DEFAULT ''",
|
|
}
|
|
|
|
|
|
def run_migrations() -> None:
|
|
with engine.connect() as conn:
|
|
peer_columns = _columns(conn, "peers")
|
|
for column, ddl in PEER_MIGRATIONS.items():
|
|
if peer_columns and column not in peer_columns:
|
|
conn.execute(text(ddl))
|
|
if peer_columns and "interface_id" not in peer_columns:
|
|
_rebuild_peers_with_interface(conn)
|
|
peer_columns = _columns(conn, "peers")
|
|
if peer_columns and "persistent_keepalive" not in peer_columns:
|
|
conn.execute(
|
|
text("ALTER TABLE peers ADD COLUMN persistent_keepalive INTEGER")
|
|
)
|
|
iface_columns = _columns(conn, "interfaces")
|
|
for column, ddl in INTERFACE_MIGRATIONS.items():
|
|
if iface_columns and column not in iface_columns:
|
|
conn.execute(text(ddl))
|
|
sample_columns = _columns(conn, "traffic_samples")
|
|
if sample_columns and "interface_name" not in sample_columns:
|
|
conn.execute(
|
|
text(
|
|
"ALTER TABLE traffic_samples ADD COLUMN "
|
|
"interface_name VARCHAR(15) NOT NULL DEFAULT ''"
|
|
)
|
|
)
|
|
conn.commit()
|