mirror of
https://github.com/pockethost/pockethost.git
synced 2025-11-24 06:25:48 +00:00
feat(mothership): add cloudflare custom domain updater and remove HandleMigrateCnamesToDomains
This commit is contained in:
parent
9c81a2923e
commit
13aa539e8a
@ -91,7 +91,7 @@ const HandleInstanceDelete = (c) => {
|
||||
const HandleInstanceResolve = (c) => {
|
||||
const dao = $app.dao();
|
||||
const log = mkLog(`GET:instance/resolve`);
|
||||
log(`***TOP OF GET`);
|
||||
log(`TOP OF GET`);
|
||||
const host = c.queryParam("host");
|
||||
if (!host) throw new BadRequestError(`Host is required when resolving an instance.`);
|
||||
const instance = (() => {
|
||||
@ -203,6 +203,43 @@ const removeEmptyKeys = (obj) => {
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/handlers/instance/api/HandleInstanceUpdate.ts
|
||||
const callCloudflareAPI = (endpoint, method, body, log) => {
|
||||
const apiToken = $os.getenv("MOTHERSHIP_CLOUDFLARE_API_TOKEN");
|
||||
const zoneId = $os.getenv("MOTHERSHIP_CLOUDFLARE_ZONE_ID");
|
||||
if (!apiToken || !zoneId) {
|
||||
if (log) log("Cloudflare API credentials not configured - skipping Cloudflare operations");
|
||||
return null;
|
||||
}
|
||||
const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/${endpoint}`;
|
||||
try {
|
||||
const config = {
|
||||
url,
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
timeout: 30
|
||||
};
|
||||
if (body) config.body = JSON.stringify(body);
|
||||
if (log) log(`Making Cloudflare API call: ${method} ${url}`, config);
|
||||
const response = $http.send(config);
|
||||
if (log) log(`Cloudflare API response:`, response);
|
||||
return response;
|
||||
} catch (error$1) {
|
||||
if (log) log(`Cloudflare API error:`, error$1);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const createCloudflareCustomHostname = (hostname, log) => {
|
||||
return callCloudflareAPI("custom_hostnames", "POST", {
|
||||
hostname,
|
||||
ssl: {
|
||||
method: "http",
|
||||
type: "dv"
|
||||
}
|
||||
}, log);
|
||||
};
|
||||
const HandleInstanceUpdate = (c) => {
|
||||
const dao = $app.dao();
|
||||
const log = mkLog(`PUT:instance`);
|
||||
@ -239,6 +276,14 @@ const HandleInstanceUpdate = (c) => {
|
||||
log(`authRecord`, JSON.stringify(authRecord));
|
||||
if (!authRecord) throw new Error(`Expected authRecord here`);
|
||||
if (record.get("uid") !== authRecord.id) throw new BadRequestError(`Not authorized`);
|
||||
const oldCname = record.getString("cname").trim();
|
||||
const newCname = cname ? cname.trim() : "";
|
||||
const cnameChanged = oldCname !== newCname;
|
||||
if (cnameChanged && newCname) {
|
||||
log(`CNAME changed from "${oldCname}" to "${newCname}" - adding to Cloudflare`);
|
||||
const createResponse = createCloudflareCustomHostname(newCname, log);
|
||||
if (createResponse) log(`Cloudflare API call completed for "${newCname}" - frontend will poll for health`);
|
||||
}
|
||||
const sanitized = removeEmptyKeys({
|
||||
subdomain,
|
||||
version,
|
||||
@ -385,45 +430,6 @@ const HandleMigrateRegions = (e) => {
|
||||
log(`Migrated regions`);
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/handlers/instance/model/HandleInstanceBeforeUpdate.ts
|
||||
const HandleInstanceBeforeUpdate = (e) => {
|
||||
const dao = e.dao || $app.dao();
|
||||
const log = mkLog(`instances-validate-before-update`);
|
||||
const id = e.model.getId();
|
||||
const version = e.model.get("version");
|
||||
if (!versions.includes(version)) {
|
||||
const msg = `[ERROR] Invalid version '${version}' for [${id}]. Version must be one of: ${versions.join(", ")}`;
|
||||
log(`${msg}`);
|
||||
throw new BadRequestError(msg);
|
||||
}
|
||||
const cname = e.model.get("cname");
|
||||
if (cname.length > 0) {
|
||||
const result = new DynamicModel({ id: "" });
|
||||
const inUse = (() => {
|
||||
try {
|
||||
dao.db().newQuery(`select id from instances where cname='${cname}' and id <> '${id}'`).one(result);
|
||||
} catch (e$1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
if (inUse) {
|
||||
const msg = `[ERROR] [${id}] Custom domain ${cname} already in use.`;
|
||||
log(`${msg}`);
|
||||
throw new BadRequestError(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/handlers/instance/model/HandleInstanceVersionValidation.ts
|
||||
const HandleInstanceVersionValidation = (e) => {
|
||||
const dao = e.dao || $app.dao();
|
||||
const version = e.model.get("version");
|
||||
if (!versions.includes(version)) throw new BadRequestError(`Invalid version ${version}. Version must be one of: ${versions.join(", ")}`);
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/util/mkAudit.ts
|
||||
const mkAudit = (log, dao) => {
|
||||
@ -439,8 +445,8 @@ const mkAudit = (log, dao) => {
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/handlers/instance/model/HandleNotifyDiscordAfterCreate.ts
|
||||
const HandleNotifyDiscordAfterCreate = (e) => {
|
||||
//#region src/lib/handlers/instance/model/AfterCreate_notify_discord.ts
|
||||
const AfterCreate_notify_discord = (e) => {
|
||||
const dao = e.dao || $app.dao();
|
||||
const log = mkLog(`instances:create:discord:notify`);
|
||||
const audit = mkAudit(log, dao);
|
||||
@ -460,6 +466,46 @@ const HandleNotifyDiscordAfterCreate = (e) => {
|
||||
}
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/handlers/instance/model/BeforeUpdate_cname.ts
|
||||
const BeforeUpdate_cname = (e) => {
|
||||
const dao = e.dao || $app.dao();
|
||||
const log = mkLog(`BeforeUpdate_cname`);
|
||||
const id = e.model.getId();
|
||||
const newCname = e.model.get("cname").trim();
|
||||
if (newCname.length > 0) {
|
||||
const result = new DynamicModel({ id: "" });
|
||||
const inUse = (() => {
|
||||
try {
|
||||
dao.db().newQuery(`select id from instances where cname='${newCname}' and id <> '${id}'`).one(result);
|
||||
} catch (e$1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
if (inUse) {
|
||||
const msg = `[ERROR] [${id}] Custom domain ${newCname} already in use.`;
|
||||
log(`${msg}`);
|
||||
throw new BadRequestError(msg);
|
||||
}
|
||||
}
|
||||
log(`CNAME validation passed for: "${newCname}"`);
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/handlers/instance/model/BeforeUpdate_version.ts
|
||||
const BeforeUpdate_version = (e) => {
|
||||
const dao = e.dao || $app.dao();
|
||||
const log = mkLog(`BeforeUpdate_version`);
|
||||
const version = e.model.get("version");
|
||||
log(`Validating version ${version}`);
|
||||
if (!versions.includes(version)) {
|
||||
const msg = `Invalid version ${version}. Version must be one of: ${versions.join(", ")}`;
|
||||
log(`[ERROR] ${msg}`);
|
||||
throw new BadRequestError(msg);
|
||||
}
|
||||
};
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/util/mkNotifier.ts
|
||||
const mkNotifier = (log, dao) => (channel, template, user_id, context = {}) => {
|
||||
@ -3088,12 +3134,13 @@ const HandleVersionsRequest = (c) => {
|
||||
};
|
||||
|
||||
//#endregion
|
||||
exports.HandleInstanceBeforeUpdate = HandleInstanceBeforeUpdate;
|
||||
exports.AfterCreate_notify_discord = AfterCreate_notify_discord;
|
||||
exports.BeforeUpdate_cname = BeforeUpdate_cname;
|
||||
exports.BeforeUpdate_version = BeforeUpdate_version;
|
||||
exports.HandleInstanceCreate = HandleInstanceCreate;
|
||||
exports.HandleInstanceDelete = HandleInstanceDelete;
|
||||
exports.HandleInstanceResolve = HandleInstanceResolve;
|
||||
exports.HandleInstanceUpdate = HandleInstanceUpdate;
|
||||
exports.HandleInstanceVersionValidation = HandleInstanceVersionValidation;
|
||||
exports.HandleInstancesResetIdle = HandleInstancesResetIdle;
|
||||
exports.HandleLemonSqueezySale = HandleLemonSqueezySale;
|
||||
exports.HandleMailSend = HandleMailSend;
|
||||
@ -3102,7 +3149,6 @@ exports.HandleMigrateCnamesToDomains = HandleMigrateCnamesToDomains;
|
||||
exports.HandleMigrateInstanceVersions = HandleMigrateInstanceVersions;
|
||||
exports.HandleMigrateRegions = HandleMigrateRegions;
|
||||
exports.HandleMirrorData = HandleMirrorData;
|
||||
exports.HandleNotifyDiscordAfterCreate = HandleNotifyDiscordAfterCreate;
|
||||
exports.HandleProcessNotification = HandleProcessNotification;
|
||||
exports.HandleProcessSingleNotification = HandleProcessSingleNotification;
|
||||
exports.HandleSesError = HandleSesError;
|
||||
|
||||
@ -13,24 +13,20 @@ routerAdd("GET", "/api/instance/resolve", (c) => {
|
||||
return require(`${__hooks}/mothership`).HandleInstanceResolve(c);
|
||||
}, $apis.requireAdminAuth());
|
||||
/** Validate instance version */
|
||||
onModelBeforeCreate((e) => {
|
||||
return require(`${__hooks}/mothership`).HandleInstanceVersionValidation(e);
|
||||
onModelBeforeUpdate((e) => {
|
||||
return require(`${__hooks}/mothership`).BeforeUpdate_version(e);
|
||||
}, "instances");
|
||||
onModelAfterCreate((e) => {}, "instances");
|
||||
/** Validate cname */
|
||||
onModelBeforeUpdate((e) => {
|
||||
return require(`${__hooks}/mothership`).BeforeUpdate_cname(e);
|
||||
}, "instances");
|
||||
/** Notify discord on instance create */
|
||||
onAfterBootstrap((e) => {});
|
||||
onAfterBootstrap((e) => {});
|
||||
/** Reset instance status to idle on start */
|
||||
onAfterBootstrap((e) => {
|
||||
return require(`${__hooks}/mothership`).HandleInstancesResetIdle(e);
|
||||
});
|
||||
/** Migrate existing cnames to domains table */
|
||||
onAfterBootstrap((e) => {
|
||||
return require(`${__hooks}/mothership`).HandleMigrateCnamesToDomains(e);
|
||||
});
|
||||
/** Validate instance version */
|
||||
onModelBeforeUpdate((e) => {
|
||||
return require(`${__hooks}/mothership`).HandleInstanceBeforeUpdate(e);
|
||||
}, "instances");
|
||||
|
||||
//#endregion
|
||||
//#region src/lib/handlers/lemon/hooks.ts
|
||||
|
||||
@ -1,6 +1,62 @@
|
||||
import { mkLog, StringKvLookup } from '$util/Logger'
|
||||
import { removeEmptyKeys } from '$util/removeEmptyKeys'
|
||||
|
||||
// Helper function to make Cloudflare API calls
|
||||
const callCloudflareAPI = (endpoint: string, method: string, body?: any, log?: any) => {
|
||||
const apiToken = $os.getenv('MOTHERSHIP_CLOUDFLARE_API_TOKEN')
|
||||
const zoneId = $os.getenv('MOTHERSHIP_CLOUDFLARE_ZONE_ID')
|
||||
|
||||
if (!apiToken || !zoneId) {
|
||||
if (log) log('Cloudflare API credentials not configured - skipping Cloudflare operations')
|
||||
return null
|
||||
}
|
||||
|
||||
const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/${endpoint}`
|
||||
|
||||
try {
|
||||
const config: any = {
|
||||
url: url,
|
||||
method: method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout: 30,
|
||||
}
|
||||
|
||||
if (body) {
|
||||
config.body = JSON.stringify(body)
|
||||
}
|
||||
|
||||
if (log) log(`Making Cloudflare API call: ${method} ${url}`, config)
|
||||
|
||||
const response = $http.send(config)
|
||||
|
||||
if (log) log(`Cloudflare API response:`, response)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
if (log) log(`Cloudflare API error:`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create custom hostname in Cloudflare
|
||||
const createCloudflareCustomHostname = (hostname: string, log: any) => {
|
||||
return callCloudflareAPI(
|
||||
'custom_hostnames',
|
||||
'POST',
|
||||
{
|
||||
hostname: hostname,
|
||||
ssl: {
|
||||
method: 'http',
|
||||
type: 'dv',
|
||||
},
|
||||
},
|
||||
log
|
||||
)
|
||||
}
|
||||
|
||||
export const HandleInstanceUpdate = (c: echo.Context) => {
|
||||
const dao = $app.dao()
|
||||
const log = mkLog(`PUT:instance`)
|
||||
@ -67,6 +123,21 @@ export const HandleInstanceUpdate = (c: echo.Context) => {
|
||||
throw new BadRequestError(`Not authorized`)
|
||||
}
|
||||
|
||||
// Check if CNAME changed and handle Cloudflare
|
||||
const oldCname = record.getString('cname').trim()
|
||||
const newCname = cname ? cname.trim() : ''
|
||||
const cnameChanged = oldCname !== newCname
|
||||
|
||||
if (cnameChanged && newCname) {
|
||||
log(`CNAME changed from "${oldCname}" to "${newCname}" - adding to Cloudflare`)
|
||||
|
||||
// Blindly add to Cloudflare
|
||||
const createResponse = createCloudflareCustomHostname(newCname, log)
|
||||
if (createResponse) {
|
||||
log(`Cloudflare API call completed for "${newCname}" - frontend will poll for health`)
|
||||
}
|
||||
}
|
||||
|
||||
const sanitized = removeEmptyKeys({
|
||||
subdomain,
|
||||
version,
|
||||
|
||||
@ -31,14 +31,20 @@ routerAdd(
|
||||
$apis.requireAdminAuth()
|
||||
)
|
||||
/** Validate instance version */
|
||||
onModelBeforeCreate((e) => {
|
||||
return require(`${__hooks}/mothership`).HandleInstanceVersionValidation(e)
|
||||
onModelBeforeUpdate((e) => {
|
||||
return require(`${__hooks}/mothership`).BeforeUpdate_version(e)
|
||||
}, 'instances')
|
||||
|
||||
onModelAfterCreate((e) => {
|
||||
// return require(`${__hooks}/mothership`).HandleNotifyDiscordAfterCreate(e)
|
||||
/** Validate cname */
|
||||
onModelBeforeUpdate((e) => {
|
||||
return require(`${__hooks}/mothership`).BeforeUpdate_cname(e)
|
||||
}, 'instances')
|
||||
|
||||
/** Notify discord on instance create */
|
||||
// onModelAfterCreate((e) => {
|
||||
// return require(`${__hooks}/mothership`).AfterCreate_notify_discord(e)
|
||||
// }, 'instances')
|
||||
|
||||
onAfterBootstrap((e) => {
|
||||
// return require(`${__hooks}/mothership`).HandleMigrateInstanceVersions(e)
|
||||
})
|
||||
@ -51,13 +57,3 @@ onAfterBootstrap((e) => {
|
||||
onAfterBootstrap((e) => {
|
||||
return require(`${__hooks}/mothership`).HandleInstancesResetIdle(e)
|
||||
})
|
||||
|
||||
/** Migrate existing cnames to domains table */
|
||||
onAfterBootstrap((e) => {
|
||||
return require(`${__hooks}/mothership`).HandleMigrateCnamesToDomains(e)
|
||||
})
|
||||
|
||||
/** Validate instance version */
|
||||
onModelBeforeUpdate((e) => {
|
||||
return require(`${__hooks}/mothership`).HandleInstanceBeforeUpdate(e)
|
||||
}, 'instances')
|
||||
|
||||
@ -6,6 +6,6 @@ export * from './bootstrap/HandleInstancesResetIdle'
|
||||
export * from './bootstrap/HandleMigrateCnamesToDomains'
|
||||
export * from './bootstrap/HandleMigrateInstanceVersions'
|
||||
export * from './bootstrap/HandleMigrateRegions'
|
||||
export * from './model/HandleInstanceBeforeUpdate'
|
||||
export * from './model/HandleInstanceVersionValidation'
|
||||
export * from './model/HandleNotifyDiscordAfterCreate'
|
||||
export * from './model/AfterCreate_notify_discord'
|
||||
export * from './model/BeforeUpdate_cname'
|
||||
export * from './model/BeforeUpdate_version'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { mkLog } from '$util/Logger'
|
||||
import { mkAudit } from '$util/mkAudit'
|
||||
|
||||
export const HandleNotifyDiscordAfterCreate = (e: core.ModelEvent) => {
|
||||
export const AfterCreate_notify_discord = (e: core.ModelEvent) => {
|
||||
const dao = e.dao || $app.dao()
|
||||
|
||||
const log = mkLog(`instances:create:discord:notify`)
|
||||
@ -0,0 +1,32 @@
|
||||
import { mkLog } from '$util/Logger'
|
||||
|
||||
export const BeforeUpdate_cname = (e: core.ModelEvent) => {
|
||||
const dao = e.dao || $app.dao()
|
||||
const log = mkLog(`BeforeUpdate_cname`)
|
||||
const id = e.model.getId()
|
||||
const newCname = e.model.get('cname').trim()
|
||||
|
||||
// Only check if cname is already in use locally
|
||||
if (newCname.length > 0) {
|
||||
const result = new DynamicModel({
|
||||
id: '',
|
||||
})
|
||||
|
||||
const inUse = (() => {
|
||||
try {
|
||||
dao.db().newQuery(`select id from instances where cname='${newCname}' and id <> '${id}'`).one(result)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})()
|
||||
|
||||
if (inUse) {
|
||||
const msg = `[ERROR] [${id}] Custom domain ${newCname} already in use.`
|
||||
log(`${msg}`)
|
||||
throw new BadRequestError(msg)
|
||||
}
|
||||
}
|
||||
|
||||
log(`CNAME validation passed for: "${newCname}"`)
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { mkLog } from '$util/Logger'
|
||||
import { versions } from '$util/versions'
|
||||
|
||||
export const BeforeUpdate_version = (e: core.ModelEvent) => {
|
||||
const dao = e.dao || $app.dao()
|
||||
|
||||
const log = mkLog(`BeforeUpdate_version`)
|
||||
|
||||
const version = e.model.get('version')
|
||||
log(`Validating version ${version}`)
|
||||
if (!versions.includes(version)) {
|
||||
const msg = `Invalid version ${version}. Version must be one of: ${versions.join(', ')}`
|
||||
log(`[ERROR] ${msg}`)
|
||||
throw new BadRequestError(msg)
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
import { mkLog } from '$util/Logger'
|
||||
import { versions } from '$util/versions'
|
||||
|
||||
export const HandleInstanceBeforeUpdate = (e: core.ModelEvent) => {
|
||||
const dao = e.dao || $app.dao()
|
||||
|
||||
const log = mkLog(`instances-validate-before-update`)
|
||||
|
||||
const id = e.model.getId()
|
||||
|
||||
const version = e.model.get('version')
|
||||
if (!versions.includes(version)) {
|
||||
const msg = `[ERROR] Invalid version '${version}' for [${id}]. Version must be one of: ${versions.join(', ')}`
|
||||
log(`${msg}`)
|
||||
throw new BadRequestError(msg)
|
||||
}
|
||||
|
||||
const cname = e.model.get('cname')
|
||||
if (cname.length > 0) {
|
||||
const result = new DynamicModel({
|
||||
id: '',
|
||||
})
|
||||
|
||||
const inUse = (() => {
|
||||
try {
|
||||
dao.db().newQuery(`select id from instances where cname='${cname}' and id <> '${id}'`).one(result)
|
||||
} catch (e) {
|
||||
// log(` cname OK ${cname}`)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})()
|
||||
|
||||
if (inUse) {
|
||||
const msg = `[ERROR] [${id}] Custom domain ${cname} already in use.`
|
||||
log(`${msg}`)
|
||||
throw new BadRequestError(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import { versions } from '$util/versions'
|
||||
|
||||
export const HandleInstanceVersionValidation = (e: core.ModelEvent) => {
|
||||
const dao = e.dao || $app.dao()
|
||||
|
||||
const version = e.model.get('version')
|
||||
if (!versions.includes(version)) {
|
||||
throw new BadRequestError(`Invalid version ${version}. Version must be one of: ${versions.join(', ')}`)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user