mirror of
https://github.com/fosrl/pangolin.git
synced 2026-04-28 00:36:06 +00:00
Add 1.18 migrations
This commit is contained in:
403
server/setup/scriptsSqlite/1.18.0.ts
Normal file
403
server/setup/scriptsSqlite/1.18.0.ts
Normal file
@@ -0,0 +1,403 @@
|
||||
import { APP_PATH } from "@server/lib/consts";
|
||||
import Database from "better-sqlite3";
|
||||
import path from "path";
|
||||
|
||||
const version = "1.18.0";
|
||||
|
||||
export default async function migration() {
|
||||
console.log(`Running setup script ${version}...`);
|
||||
|
||||
const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||
const db = new Database(location);
|
||||
|
||||
try {
|
||||
db.pragma("foreign_keys = OFF");
|
||||
|
||||
// Query existing targetHealthCheck data with joined siteId and orgId before
|
||||
// the transaction drops and recreates the table
|
||||
const existingHealthChecks = db
|
||||
.prepare(
|
||||
`SELECT
|
||||
thc."targetHealthCheckId",
|
||||
thc."targetId",
|
||||
t."siteId",
|
||||
s."orgId",
|
||||
thc."hcEnabled",
|
||||
thc."hcPath",
|
||||
thc."hcScheme",
|
||||
thc."hcMode",
|
||||
thc."hcHostname",
|
||||
thc."hcPort",
|
||||
thc."hcInterval",
|
||||
thc."hcUnhealthyInterval",
|
||||
thc."hcTimeout",
|
||||
thc."hcHeaders",
|
||||
thc."hcFollowRedirects",
|
||||
thc."hcMethod",
|
||||
thc."hcStatus",
|
||||
thc."hcHealth",
|
||||
thc."hcTlsServerName"
|
||||
FROM 'targetHealthCheck' thc
|
||||
JOIN 'targets' t ON thc."targetId" = t."targetId"
|
||||
JOIN 'sites' s ON t."siteId" = s."siteId"`
|
||||
)
|
||||
.all() as {
|
||||
targetHealthCheckId: number;
|
||||
targetId: number;
|
||||
siteId: number;
|
||||
orgId: string;
|
||||
hcEnabled: number;
|
||||
hcPath: string | null;
|
||||
hcScheme: string | null;
|
||||
hcMode: string | null;
|
||||
hcHostname: string | null;
|
||||
hcPort: number | null;
|
||||
hcInterval: number | null;
|
||||
hcUnhealthyInterval: number | null;
|
||||
hcTimeout: number | null;
|
||||
hcHeaders: string | null;
|
||||
hcFollowRedirects: number | null;
|
||||
hcMethod: string | null;
|
||||
hcStatus: number | null;
|
||||
hcHealth: string | null;
|
||||
hcTlsServerName: string | null;
|
||||
}[];
|
||||
|
||||
console.log(
|
||||
`Found ${existingHealthChecks.length} existing targetHealthCheck row(s) to migrate`
|
||||
);
|
||||
|
||||
db.transaction(() => {
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertEmailActions' (
|
||||
'emailActionId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'alertRuleId' integer NOT NULL,
|
||||
'enabled' integer DEFAULT true NOT NULL,
|
||||
'lastSentAt' integer,
|
||||
FOREIGN KEY ('alertRuleId') REFERENCES 'alertRules'('alertRuleId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertEmailRecipients' (
|
||||
'recipientId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'emailActionId' integer NOT NULL,
|
||||
'userId' text,
|
||||
'roleId' integer,
|
||||
'email' text,
|
||||
FOREIGN KEY ('emailActionId') REFERENCES 'alertEmailActions'('emailActionId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('roleId') REFERENCES 'roles'('roleId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertHealthChecks' (
|
||||
'alertRuleId' integer NOT NULL,
|
||||
'healthCheckId' integer NOT NULL,
|
||||
FOREIGN KEY ('alertRuleId') REFERENCES 'alertRules'('alertRuleId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('healthCheckId') REFERENCES 'targetHealthCheck'('targetHealthCheckId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertResources' (
|
||||
'alertRuleId' integer NOT NULL,
|
||||
'resourceId' integer NOT NULL,
|
||||
FOREIGN KEY ('alertRuleId') REFERENCES 'alertRules'('alertRuleId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertRules' (
|
||||
'alertRuleId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'orgId' text NOT NULL,
|
||||
'name' text NOT NULL,
|
||||
'eventType' text NOT NULL,
|
||||
'enabled' integer DEFAULT true NOT NULL,
|
||||
'cooldownSeconds' integer DEFAULT 300 NOT NULL,
|
||||
'allSites' integer DEFAULT false NOT NULL,
|
||||
'allHealthChecks' integer DEFAULT false NOT NULL,
|
||||
'allResources' integer DEFAULT false NOT NULL,
|
||||
'lastTriggeredAt' integer,
|
||||
'createdAt' integer NOT NULL,
|
||||
'updatedAt' integer NOT NULL,
|
||||
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertSites' (
|
||||
'alertRuleId' integer NOT NULL,
|
||||
'siteId' integer NOT NULL,
|
||||
FOREIGN KEY ('alertRuleId') REFERENCES 'alertRules'('alertRuleId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('siteId') REFERENCES 'sites'('siteId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'alertWebhookActions' (
|
||||
'webhookActionId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'alertRuleId' integer NOT NULL,
|
||||
'webhookUrl' text NOT NULL,
|
||||
'config' text,
|
||||
'enabled' integer DEFAULT true NOT NULL,
|
||||
'lastSentAt' integer,
|
||||
FOREIGN KEY ('alertRuleId') REFERENCES 'alertRules'('alertRuleId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'networks' (
|
||||
'networkId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'niceId' text,
|
||||
'name' text,
|
||||
'scope' text DEFAULT 'global' NOT NULL,
|
||||
'orgId' text NOT NULL,
|
||||
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'siteNetworks' (
|
||||
'siteId' integer NOT NULL,
|
||||
'networkId' integer NOT NULL,
|
||||
FOREIGN KEY ('siteId') REFERENCES 'sites'('siteId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('networkId') REFERENCES 'networks'('networkId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE 'statusHistory' (
|
||||
'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'entityType' text NOT NULL,
|
||||
'entityId' integer NOT NULL,
|
||||
'orgId' text NOT NULL,
|
||||
'status' text NOT NULL,
|
||||
'timestamp' integer NOT NULL,
|
||||
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE INDEX 'idx_statusHistory_entity' ON 'statusHistory' ('entityType','entityId','timestamp');
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE INDEX 'idx_statusHistory_org_timestamp' ON 'statusHistory' ('orgId','timestamp');
|
||||
`
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE '__new_siteResources' (
|
||||
'siteResourceId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'orgId' text NOT NULL,
|
||||
'networkId' integer,
|
||||
'defaultNetworkId' integer,
|
||||
'niceId' text NOT NULL,
|
||||
'name' text NOT NULL,
|
||||
'ssl' integer DEFAULT false NOT NULL,
|
||||
'mode' text NOT NULL,
|
||||
'scheme' text,
|
||||
'proxyPort' integer,
|
||||
'destinationPort' integer,
|
||||
'destination' text NOT NULL,
|
||||
'enabled' integer DEFAULT true NOT NULL,
|
||||
'alias' text,
|
||||
'aliasAddress' text,
|
||||
'tcpPortRangeString' text DEFAULT '*' NOT NULL,
|
||||
'udpPortRangeString' text DEFAULT '*' NOT NULL,
|
||||
'disableIcmp' integer DEFAULT false NOT NULL,
|
||||
'authDaemonPort' integer DEFAULT 22123,
|
||||
'authDaemonMode' text DEFAULT 'site',
|
||||
'domainId' text,
|
||||
'subdomain' text,
|
||||
'fullDomain' text,
|
||||
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('networkId') REFERENCES 'networks'('networkId') ON UPDATE no action ON DELETE set null,
|
||||
FOREIGN KEY ('defaultNetworkId') REFERENCES 'networks'('networkId') ON UPDATE no action ON DELETE restrict,
|
||||
FOREIGN KEY ('domainId') REFERENCES 'domains'('domainId') ON UPDATE no action ON DELETE set null
|
||||
);
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
INSERT INTO '__new_siteResources'("siteResourceId", "orgId", "networkId", "defaultNetworkId", "niceId", "name", "ssl", "mode", "scheme", "proxyPort", "destinationPort", "destination", "enabled", "alias", "aliasAddress", "tcpPortRangeString", "udpPortRangeString", "disableIcmp", "authDaemonPort", "authDaemonMode", "domainId", "subdomain", "fullDomain") SELECT "siteResourceId", "orgId", "networkId", "defaultNetworkId", "niceId", "name", "ssl", "mode", "scheme", "proxyPort", "destinationPort", "destination", "enabled", "alias", "aliasAddress", "tcpPortRangeString", "udpPortRangeString", "disableIcmp", "authDaemonPort", "authDaemonMode", "domainId", "subdomain", "fullDomain" FROM 'siteResources';
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
DROP TABLE 'siteResources';
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE '__new_siteResources' RENAME TO 'siteResources';
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
CREATE TABLE '__new_targetHealthCheck' (
|
||||
'targetHealthCheckId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
'targetId' integer,
|
||||
'orgId' text NOT NULL,
|
||||
'siteId' integer NOT NULL,
|
||||
'name' text,
|
||||
'hcEnabled' integer DEFAULT false NOT NULL,
|
||||
'hcPath' text,
|
||||
'hcScheme' text,
|
||||
'hcMode' text DEFAULT 'http',
|
||||
'hcHostname' text,
|
||||
'hcPort' integer,
|
||||
'hcInterval' integer DEFAULT 30,
|
||||
'hcUnhealthyInterval' integer DEFAULT 30,
|
||||
'hcTimeout' integer DEFAULT 5,
|
||||
'hcHeaders' text,
|
||||
'hcFollowRedirects' integer DEFAULT true,
|
||||
'hcMethod' text DEFAULT 'GET',
|
||||
'hcStatus' integer,
|
||||
'hcHealth' text DEFAULT 'unknown',
|
||||
'hcTlsServerName' text,
|
||||
'hcHealthyThreshold' integer DEFAULT 1,
|
||||
'hcUnhealthyThreshold' integer DEFAULT 1,
|
||||
FOREIGN KEY ('targetId') REFERENCES 'targets'('targetId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY ('siteId') REFERENCES 'sites'('siteId') ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`
|
||||
).run();
|
||||
// INSERT INTO '__new_targetHealthCheck'("targetHealthCheckId", "targetId", "orgId", "siteId", "name", "hcEnabled", "hcPath", "hcScheme", "hcMode", "hcHostname", "hcPort", "hcInterval", "hcUnhealthyInterval", "hcTimeout", "hcHeaders", "hcFollowRedirects", "hcMethod", "hcStatus", "hcHealth", "hcTlsServerName", "hcHealthyThreshold", "hcUnhealthyThreshold") SELECT "targetHealthCheckId", "targetId", "orgId", "siteId", "name", "hcEnabled", "hcPath", "hcScheme", "hcMode", "hcHostname", "hcPort", "hcInterval", "hcUnhealthyInterval", "hcTimeout", "hcHeaders", "hcFollowRedirects", "hcMethod", "hcStatus", "hcHealth", "hcTlsServerName", "hcHealthyThreshold", "hcUnhealthyThreshold" FROM 'targetHealthCheck';
|
||||
db.prepare(
|
||||
`
|
||||
DROP TABLE 'targetHealthCheck';
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE '__new_targetHealthCheck' RENAME TO 'targetHealthCheck';
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE 'subscriptions' ADD 'expiresAt' integer;
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE 'subscriptions' ADD 'trial' integer DEFAULT false;
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE 'requestAuditLog' ADD 'siteResourceId' integer;
|
||||
`
|
||||
).run();
|
||||
db.prepare(
|
||||
`
|
||||
ALTER TABLE 'sites' ADD 'networkId' integer REFERENCES networks(networkId);
|
||||
`
|
||||
).run();
|
||||
})();
|
||||
|
||||
db.pragma("foreign_keys = ON");
|
||||
|
||||
// Re-insert targetHealthCheck rows with corrected IDs:
|
||||
// targetHealthCheckId is set to the same integer as targetId (1:1 mapping),
|
||||
// siteId and orgId are populated from the associated target and site.
|
||||
//
|
||||
// Because targetHealthCheckId is AUTOINCREMENT, inserting explicit values is
|
||||
// allowed, but sqlite_sequence must be updated afterwards so future
|
||||
// auto-increments don't reuse or collide with these IDs.
|
||||
if (existingHealthChecks.length > 0) {
|
||||
const insertHealthCheck = db.prepare(
|
||||
`INSERT INTO 'targetHealthCheck' (
|
||||
"targetHealthCheckId",
|
||||
"targetId",
|
||||
"orgId",
|
||||
"siteId",
|
||||
"hcEnabled",
|
||||
"hcPath",
|
||||
"hcScheme",
|
||||
"hcMode",
|
||||
"hcHostname",
|
||||
"hcPort",
|
||||
"hcInterval",
|
||||
"hcUnhealthyInterval",
|
||||
"hcTimeout",
|
||||
"hcHeaders",
|
||||
"hcFollowRedirects",
|
||||
"hcMethod",
|
||||
"hcStatus",
|
||||
"hcHealth",
|
||||
"hcTlsServerName"
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
);
|
||||
|
||||
const insertAll = db.transaction(() => {
|
||||
for (const hc of existingHealthChecks) {
|
||||
insertHealthCheck.run(
|
||||
hc.targetId, // targetHealthCheckId = targetId (explicit, non-sequential is fine)
|
||||
hc.targetId,
|
||||
hc.orgId,
|
||||
hc.siteId,
|
||||
hc.hcEnabled,
|
||||
hc.hcPath,
|
||||
hc.hcScheme,
|
||||
hc.hcMode,
|
||||
hc.hcHostname,
|
||||
hc.hcPort,
|
||||
hc.hcInterval,
|
||||
hc.hcUnhealthyInterval,
|
||||
hc.hcTimeout,
|
||||
hc.hcHeaders,
|
||||
hc.hcFollowRedirects,
|
||||
hc.hcMethod,
|
||||
hc.hcStatus,
|
||||
hc.hcHealth,
|
||||
hc.hcTlsServerName
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
insertAll();
|
||||
|
||||
// Ensure sqlite_sequence reflects the true max so that future
|
||||
// AUTOINCREMENT inserts never reuse one of the explicitly-set IDs.
|
||||
// INSERT OR IGNORE handles the case where no auto-insert has happened
|
||||
// yet and the row doesn't exist in sqlite_sequence.
|
||||
db.prepare(
|
||||
`INSERT OR IGNORE INTO sqlite_sequence (name, seq) VALUES ('targetHealthCheck', 0)`
|
||||
).run();
|
||||
db.prepare(
|
||||
`UPDATE sqlite_sequence
|
||||
SET seq = MAX(seq, (SELECT COALESCE(MAX("targetHealthCheckId"), 0) FROM 'targetHealthCheck'))
|
||||
WHERE name = 'targetHealthCheck'`
|
||||
).run();
|
||||
|
||||
console.log(
|
||||
`Migrated ${existingHealthChecks.length} targetHealthCheck row(s) with corrected IDs`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`Migrated database`);
|
||||
} catch (e) {
|
||||
console.log("Failed to migrate db:", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
console.log(`${version} migration complete`);
|
||||
}
|
||||
Reference in New Issue
Block a user