add migration

This commit is contained in:
miloschwartz 2025-04-28 23:07:11 -04:00
parent 81adcd9234
commit 3ebc01df8c
No known key found for this signature in database
5 changed files with 4128 additions and 1461 deletions

5357
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -104,7 +104,7 @@ export const exitNodes = sqliteTable("exitNodes", {
name: text("name").notNull(), name: text("name").notNull(),
address: text("address").notNull(), // this is the address of the wireguard interface in gerbil address: text("address").notNull(), // this is the address of the wireguard interface in gerbil
endpoint: text("endpoint").notNull(), // this is how to reach gerbil externally - gets put into the wireguard config endpoint: text("endpoint").notNull(), // this is how to reach gerbil externally - gets put into the wireguard config
publicKey: text("pubicKey").notNull(), publicKey: text("publicKey").notNull(),
listenPort: integer("listenPort").notNull(), listenPort: integer("listenPort").notNull(),
reachableAt: text("reachableAt") // this is the internal address of the gerbil http server for command control reachableAt: text("reachableAt") // this is the internal address of the gerbil http server for command control
}); });

View file

@ -1,29 +1,205 @@
import db from "@server/db"; import Database from "better-sqlite3";
import { sql } from "drizzle-orm"; import path from "path";
import fs from "fs";
import yaml from "js-yaml";
import { encodeBase32LowerCaseNoPadding } from "@oslojs/encoding";
import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts";
const version = "1.3.0"; const version = "1.3.0";
const location = path.join(APP_PATH, "db", "db.sqlite");
await migration();
export default async function migration() { export default async function migration() {
console.log(`Running setup script ${version}...`); console.log(`Running setup script ${version}...`);
try { const db = new Database(location);
db.transaction((trx) => {
trx.run(
sql`ALTER TABLE resources ADD stickySession integer DEFAULT false NOT NULL;`
);
trx.run(
sql`ALTER TABLE 'resources' ADD 'tlsServerName' text;`
);
trx.run(
sql`ALTER TABLE 'resources' ADD 'setHostHeader' text;`
);
});
try {
db.pragma("foreign_keys = OFF");
db.transaction(() => {
db.exec(`
CREATE TABLE 'apiKeyActions' (
'apiKeyId' text NOT NULL,
'actionId' text NOT NULL,
FOREIGN KEY ('apiKeyId') REFERENCES 'apiKeys'('apiKeyId') ON UPDATE no action ON DELETE cascade,
FOREIGN KEY ('actionId') REFERENCES 'actions'('actionId') ON UPDATE no action ON DELETE cascade
);
CREATE TABLE 'apiKeyOrg' (
'apiKeyId' text NOT NULL,
'orgId' text NOT NULL,
FOREIGN KEY ('apiKeyId') REFERENCES 'apiKeys'('apiKeyId') ON UPDATE no action ON DELETE cascade,
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade
);
CREATE TABLE 'apiKeys' (
'apiKeyId' text PRIMARY KEY NOT NULL,
'name' text NOT NULL,
'apiKeyHash' text NOT NULL,
'lastChars' text NOT NULL,
'dateCreated' text NOT NULL,
'isRoot' integer DEFAULT false NOT NULL
);
CREATE TABLE 'hostMeta' (
'hostMetaId' text PRIMARY KEY NOT NULL,
'createdAt' integer NOT NULL
);
CREATE TABLE 'idp' (
'idpId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
'name' text NOT NULL,
'type' text NOT NULL,
'defaultRoleMapping' text,
'defaultOrgMapping' text,
'autoProvision' integer DEFAULT false NOT NULL
);
CREATE TABLE 'idpOidcConfig' (
'idpOauthConfigId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
'idpId' integer NOT NULL,
'clientId' text NOT NULL,
'clientSecret' text NOT NULL,
'authUrl' text NOT NULL,
'tokenUrl' text NOT NULL,
'identifierPath' text NOT NULL,
'emailPath' text,
'namePath' text,
'scopes' text NOT NULL,
FOREIGN KEY ('idpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE cascade
);
CREATE TABLE 'idpOrg' (
'idpId' integer NOT NULL,
'orgId' text NOT NULL,
'roleMapping' text,
'orgMapping' text,
FOREIGN KEY ('idpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE cascade,
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade
);
CREATE TABLE 'licenseKey' (
'licenseKeyId' text PRIMARY KEY NOT NULL,
'instanceId' text NOT NULL,
'token' text NOT NULL
);
CREATE TABLE '__new_user' (
'id' text PRIMARY KEY NOT NULL,
'email' text,
'username' text NOT NULL,
'name' text,
'type' text NOT NULL,
'idpId' integer,
'passwordHash' text,
'twoFactorEnabled' integer DEFAULT false NOT NULL,
'twoFactorSecret' text,
'emailVerified' integer DEFAULT false NOT NULL,
'dateCreated' text NOT NULL,
'serverAdmin' integer DEFAULT false NOT NULL,
FOREIGN KEY ('idpId') REFERENCES 'idp'('idpId') ON UPDATE no action ON DELETE cascade
);
INSERT INTO '__new_user'(
"id", "email", "username", "name", "type", "idpId", "passwordHash",
"twoFactorEnabled", "twoFactorSecret", "emailVerified", "dateCreated", "serverAdmin"
)
SELECT
"id",
"email",
COALESCE("email", 'unknown'),
NULL,
'internal',
NULL,
"passwordHash",
"twoFactorEnabled",
"twoFactorSecret",
"emailVerified",
"dateCreated",
"serverAdmin"
FROM 'user';
DROP TABLE 'user';
ALTER TABLE '__new_user' RENAME TO 'user';
ALTER TABLE 'resources' ADD 'stickySession' integer DEFAULT false NOT NULL;
ALTER TABLE 'resources' ADD 'tlsServerName' text;
ALTER TABLE 'resources' ADD 'setHostHeader' text;
CREATE TABLE 'exitNodes_new' (
'exitNodeId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
'name' text NOT NULL,
'address' text NOT NULL,
'endpoint' text NOT NULL,
'publicKey' text NOT NULL,
'listenPort' integer NOT NULL,
'reachableAt' text
);
INSERT INTO 'exitNodes_new' (
'exitNodeId', 'name', 'address', 'endpoint', 'publicKey', 'listenPort', 'reachableAt'
)
SELECT
exitNodeId,
name,
address,
endpoint,
pubicKey,
listenPort,
reachableAt
FROM exitNodes;
DROP TABLE 'exitNodes';
ALTER TABLE 'exitNodes_new' RENAME TO 'exitNodes';
`);
})(); // <-- executes the transaction immediately
db.pragma("foreign_keys = ON");
console.log(`Migrated database schema`); console.log(`Migrated database schema`);
} catch (e) { } catch (e) {
console.log("Unable to migrate database schema"); console.log("Unable to migrate database schema");
throw e; throw e;
} }
// Update config file
try {
const filePaths = [configFilePath1, configFilePath2];
let filePath = "";
for (const path of filePaths) {
if (fs.existsSync(path)) {
filePath = path;
break;
}
}
if (!filePath) {
throw new Error(
`No config file found (expected config.yml or config.yaml).`
);
}
const fileContents = fs.readFileSync(filePath, "utf8");
let rawConfig: any = yaml.load(fileContents);
if (!rawConfig.server.secret) {
rawConfig.server.secret = generateIdFromEntropySize(32);
}
const updatedYaml = yaml.dump(rawConfig);
fs.writeFileSync(filePath, updatedYaml, "utf8");
console.log(`Added new config option: server.secret`);
} catch (e) {
console.log(
`Unable to add new config option: server.secret. Please add it manually.`
);
console.error(e);
}
console.log(`${version} migration complete`); console.log(`${version} migration complete`);
} }
function generateIdFromEntropySize(size: number): string {
const buffer = crypto.getRandomValues(new Uint8Array(size));
return encodeBase32LowerCaseNoPadding(buffer);
}

View file

@ -28,7 +28,7 @@ export function SitePriceCalculator({
onOpenChange, onOpenChange,
mode mode
}: SitePriceCalculatorProps) { }: SitePriceCalculatorProps) {
const [siteCount, setSiteCount] = useState(1); const [siteCount, setSiteCount] = useState(3);
const pricePerSite = 5; const pricePerSite = 5;
const licenseFlatRate = 125; const licenseFlatRate = 125;

View file

@ -7,6 +7,10 @@ import { Metadata } from "next";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { cache } from "react"; import { cache } from "react";
import { rootNavItems } from "../navigation"; import { rootNavItems } from "../navigation";
import { ListUserOrgsResponse } from "@server/routers/org";
import { internal } from "@app/lib/api";
import { AxiosResponse } from "axios";
import { authCookieHeader } from "@app/lib/api/cookies";
export const metadata: Metadata = { export const metadata: Metadata = {
title: `Setup - Pangolin`, title: `Setup - Pangolin`,
@ -33,10 +37,28 @@ export default async function SetupLayout({
redirect("/"); redirect("/");
} }
let orgs: ListUserOrgsResponse["orgs"] = [];
try {
const getOrgs = cache(async () =>
internal.get<AxiosResponse<ListUserOrgsResponse>>(
`/user/${user.userId}/orgs`,
await authCookieHeader()
)
);
const res = await getOrgs();
if (res && res.data.data.orgs) {
orgs = res.data.data.orgs;
}
} catch (e) {}
return ( return (
<> <>
<UserProvider user={user}> <UserProvider user={user}>
<Layout navItems={rootNavItems} showBreadcrumbs={false}> <Layout
navItems={rootNavItems}
showBreadcrumbs={false}
orgs={orgs}
>
<div className="w-full max-w-2xl mx-auto md:mt-32 mt-4"> <div className="w-full max-w-2xl mx-auto md:mt-32 mt-4">
{children} {children}
</div> </div>