Add sen:// subscription with master keys

A master key is a sen:// link with a device limit. Each device
generates its own WireGuard keypair and registers only the public key,
then polls a signed /sub/v1/config, so params and generation changes
reach users without re-importing a key.

- awg-ctrl: peers table and /api/peers*, /api/profile; nextIp and
  rebuildConf take peers into account (output unchanged without them)
- awg-ui: link codec and signatures (sen.ts), registry, panel routes
  and the public listener on SUB_PORT (sub.ts)
- responses are always Ed25519-signed; TLS with a pinned self-signed
  certificate is optional
- devices report a client version, shown as platform-version
- "Мастер-ключи" tab: a table with the devices nested under each key,
  a device-limit meter, per-key settings and a placeholder for the
  servers a key serves; cards on mobile
- install.sh/CLI: subscription signing key, optional certificate, port;
  kept across reinstalls with KEEP_DATA
- add the missing mock-server.ts used by npm run dev:mock
This commit is contained in:
2026-09-25 17:24:52 +03:00
parent fd87884bde
commit 6e3959d16c
12 changed files with 1643 additions and 7 deletions
+9 -1
View File
@@ -7,6 +7,7 @@ import fs from "fs";
import crypto from "crypto";
import axios from "axios";
import Database from "better-sqlite3";
import { createSub } from "./sub";
const app = express();
const PORT = Number(process.env.PORT) || 8080;
@@ -279,6 +280,12 @@ ext.delete("/users/:name", wrap(async (req, res) => {
// его и потребует JWT вместо ключа.
app.use("/api/v1", ext);
// sen://-подписка: мастер-ключи и устройства (роуты панели, за JWT) и публичный
// листенер /sub/v1 на своём порту SUB_PORT. Публичная часть сюда не монтируется.
const sub = createSub({ uidb, ctrl, baseDir: __dirname });
app.use("/ui/masterkeys", requireAuth, sub.masterKeys);
app.use("/ui/devices", requireAuth, sub.devices);
app.use(["/api", "/health", "/awg"], requireAuth, proxy);
app.use(express.static(path.join(__dirname, "dist")));
@@ -287,4 +294,5 @@ app.get("*", (_req: Request, res: Response) => {
res.sendFile(path.join(__dirname, "dist", "index.html"));
});
app.listen(PORT, () => { console.log(`ui: listening on :${PORT}`); });
app.listen(PORT, () => { console.log(`ui: listening on :${PORT}`); });
sub.start();