285 lines
12 KiB
JavaScript
285 lines
12 KiB
JavaScript
"use strict";
|
|
|
|
function registerDeploymentIpc({
|
|
register, store, resolveRepository, unraid, deployments, evaluateDeploymentPolicy,
|
|
audit, deployKeys, repositories, inventoryReviews, diagnostics, git, gitea, ssh,
|
|
preflight,
|
|
}) {
|
|
register("deployment:save-profile", async ({ fullName, profile }) => {
|
|
const saved = await store.saveDeploymentProfile(fullName, profile);
|
|
await diagnostics.info("deployment.profile.saved", {
|
|
repository: fullName,
|
|
profile: saved,
|
|
});
|
|
return { profile: saved, state: store.getPublicState() };
|
|
});
|
|
register("deployment:delete-profile", async ({ fullName, profileId }) => {
|
|
const profiles = await store.deleteDeploymentProfile(fullName, profileId);
|
|
await diagnostics.info("deployment.profile.deleted", {
|
|
repository: fullName,
|
|
profileId,
|
|
});
|
|
return { profiles, state: store.getPublicState() };
|
|
});
|
|
register("deployment:preflight", async ({ repository, profileId }) => {
|
|
const current = await resolveRepository(repository);
|
|
const profile = store.getDeploymentProfile(current.fullName, profileId);
|
|
if (profile?.provider === "ssh-unraid")
|
|
return unraid.preflight({ repository: current, profileId });
|
|
return preflight.runDeployment({ repository: current, profileId });
|
|
});
|
|
register("deployment:repair-write-access", async ({ repository, profileId }) => {
|
|
const current = await resolveRepository(repository);
|
|
const profile = store.getDeploymentProfile(current.fullName, profileId);
|
|
if (profile?.provider !== "ssh-unraid")
|
|
throw new Error("Write-access repair is available only for SSH / Unraid deployment profiles.");
|
|
const result = await unraid.repairWriteAccess({ repository: current, profileId });
|
|
await audit.append("deployment.write-access.repaired", {
|
|
repository: current.fullName,
|
|
profileId,
|
|
changed: result.changed,
|
|
remotePath: result.after?.remotePath || result.before?.remotePath || null,
|
|
});
|
|
return result;
|
|
});
|
|
register(
|
|
"deployment:dispatch",
|
|
async ({
|
|
repository,
|
|
profileId,
|
|
sha,
|
|
note = "",
|
|
override = false,
|
|
overrideReason = "",
|
|
}) => {
|
|
const current = await resolveRepository(repository);
|
|
const profile = store.getDeploymentProfile(current.fullName, profileId);
|
|
const policy = evaluateDeploymentPolicy(profile, {
|
|
note,
|
|
override,
|
|
reason: overrideReason,
|
|
});
|
|
await audit.append("deployment.requested", {
|
|
repository: current.fullName,
|
|
profileId,
|
|
sha,
|
|
note: policy.note,
|
|
overridden: policy.overridden,
|
|
overrideReason: policy.reason,
|
|
});
|
|
const operation =
|
|
profile?.provider === "ssh-unraid"
|
|
? await unraid.deploy({ repository: current, profileId, sha })
|
|
: await deployments.deploy({ repository: current, profileId, sha });
|
|
if (operation?.id)
|
|
await store.addOperation({
|
|
...operation,
|
|
releaseNote: policy.note,
|
|
policyOverride: policy.overridden
|
|
? { reason: policy.reason, violations: policy.violations }
|
|
: null,
|
|
});
|
|
return operation;
|
|
},
|
|
);
|
|
register(
|
|
"deployment:rollback",
|
|
async ({ repository, profileId, targetSha }) => {
|
|
const current = await resolveRepository(repository);
|
|
const profile = store.getDeploymentProfile(current.fullName, profileId);
|
|
if (profile?.provider === "ssh-unraid")
|
|
return unraid.rollback({ repository: current, profileId, targetSha });
|
|
return deployments.rollback({
|
|
repository: current,
|
|
profileId,
|
|
targetSha,
|
|
});
|
|
},
|
|
);
|
|
register("deployment:link-server-workload", async ({ repository, serverId, workloadId, deploymentMode = "server-git", remoteFolder = "" }) => {
|
|
const current = await resolveRepository(repository);
|
|
const result = await unraid.linkServerWorkload({
|
|
repository: current,
|
|
serverId,
|
|
workloadId,
|
|
deploymentMode,
|
|
remoteFolder,
|
|
});
|
|
return { ...result, state: store.getPublicState() };
|
|
});
|
|
register("deployment:configure-server-git-access", async ({ repository, profileId }) => {
|
|
const current = await resolveRepository(repository);
|
|
const result = await unraid.configureServerGitAccess({ repository: current, profileId });
|
|
await audit.append("deployment.server-git-access-configured", {
|
|
repository: current.fullName,
|
|
profileId,
|
|
keyFingerprint: result.keyFingerprint,
|
|
hostFingerprint: result.hostFingerprint,
|
|
});
|
|
return { ...result, state: store.getPublicState() };
|
|
});
|
|
register("deployment:verify-server-git-profile", async ({ repository, profileId }) => {
|
|
const current = await resolveRepository(repository);
|
|
const result = await unraid.verifyServerGitProfile({ repository: current, profileId });
|
|
await audit.append("deployment.server-git-access-verified", {
|
|
repository: current.fullName,
|
|
profileId,
|
|
readiness: result.readiness,
|
|
ready: result.ready,
|
|
checkedAt: result.checkedAt,
|
|
});
|
|
return result;
|
|
});
|
|
register("deployment:deploy-key-inventory", async ({ repository, profileId }) => {
|
|
const current = await resolveRepository(repository);
|
|
return deployKeys.inventory({ repository: current, profileId });
|
|
});
|
|
register("deployment:plan-deploy-key-rotation", async ({ repository, profileId }) => {
|
|
const current = await resolveRepository(repository);
|
|
return deployKeys.planRotation({ repository: current, profileId });
|
|
});
|
|
register("deployment:apply-deploy-key-rotation", async ({ repository, profileId, planId }) => {
|
|
const current = await resolveRepository(repository);
|
|
const result = await deployKeys.rotate({ repository: current, profileId, expectedPlanId: planId });
|
|
return { ...result, state: store.getPublicState() };
|
|
});
|
|
register("deployment:plan-deploy-key-revocation", async ({ repository, profileId }) => {
|
|
const current = await resolveRepository(repository);
|
|
return deployKeys.planRevocation({ repository: current, profileId });
|
|
});
|
|
register("deployment:apply-deploy-key-revocation", async ({ repository, profileId, planId }) => {
|
|
const current = await resolveRepository(repository);
|
|
const result = await deployKeys.revoke({ repository: current, profileId, expectedPlanId: planId });
|
|
return { ...result, state: store.getPublicState() };
|
|
});
|
|
register("deployment:restore-deploy-key", async ({ repository, profileId }) => {
|
|
const current = await resolveRepository(repository);
|
|
const result = await deployKeys.restore({ repository: current, profileId });
|
|
return { ...result, state: store.getPublicState() };
|
|
});
|
|
register("deployment:discover-server-workloads", async () => {
|
|
const repositoryList = await repositories.refresh();
|
|
const remoteRepositories = repositoryList.filter(
|
|
(repository) => repository.owner?.login !== "local",
|
|
);
|
|
const results = [];
|
|
for (const server of store.data.servers || []) {
|
|
try {
|
|
results.push(
|
|
await unraid.discoverServerWorkloads(server.id, remoteRepositories),
|
|
);
|
|
} catch (error) {
|
|
results.push({
|
|
serverId: server.id,
|
|
serverName: server.name,
|
|
detected: 0,
|
|
adopted: 0,
|
|
verified: 0,
|
|
linked: 0,
|
|
unmatched: 0,
|
|
needsReview: 0,
|
|
capabilities: {},
|
|
warnings: [],
|
|
workloads: [],
|
|
error: error.message,
|
|
});
|
|
}
|
|
}
|
|
return results;
|
|
});
|
|
register("deployment:plan-server-reconciliation", async ({ serverId }) => {
|
|
const repositoryList = await repositories.refresh();
|
|
const remoteRepositories = repositoryList.filter((repository) => repository.owner?.login !== "local");
|
|
const result = await unraid.planServerInventoryReconciliation(serverId, remoteRepositories, { autoLink: true });
|
|
await audit.append("deployment.server-reconciliation-planned", {
|
|
serverId,
|
|
planId: result.plan.id,
|
|
summary: result.plan.summary,
|
|
});
|
|
return result;
|
|
});
|
|
register("deployment:apply-server-reconciliation", async ({ serverId, planId }) => {
|
|
const repositoryList = await repositories.refresh();
|
|
const remoteRepositories = repositoryList.filter((repository) => repository.owner?.login !== "local");
|
|
const result = await unraid.reconcileServerInventory(serverId, remoteRepositories, { autoLink: true, expectedPlanId: planId });
|
|
await audit.append("deployment.server-reconciliation-applied", {
|
|
serverId,
|
|
planId,
|
|
adopted: result.adopted,
|
|
refreshed: result.refreshed,
|
|
retired: result.retired,
|
|
recoverySnapshot: result.recoverySnapshot?.filePath || null,
|
|
});
|
|
return { ...result, state: store.getPublicState() };
|
|
});
|
|
register("deployment:plan-inventory-review", async ({ serverId, workloadId, action, reason = "", repositoryFullName = null }) => {
|
|
const repositoryList = await repositories.refresh();
|
|
const inventory = await unraid.scanServerInventory(serverId, repositoryList.filter((item) => item.owner?.login !== "local"));
|
|
const workload = inventory.workloads.find((item) => item.workloadId === workloadId);
|
|
if (!workload) throw Object.assign(new Error("The workload changed or disappeared. Rescan before reviewing it."), { code: "INVENTORY_REVIEW_WORKLOAD_STALE" });
|
|
return inventoryReviews.preview({ serverId, workload, action, reason, repositoryFullName });
|
|
});
|
|
register("deployment:apply-inventory-review", async ({ serverId, workloadId, action, reason = "", repositoryFullName = null, planId }) => {
|
|
const repositoryList = await repositories.refresh();
|
|
const inventory = await unraid.scanServerInventory(serverId, repositoryList.filter((item) => item.owner?.login !== "local"));
|
|
const workload = inventory.workloads.find((item) => item.workloadId === workloadId);
|
|
if (!workload) throw Object.assign(new Error("The workload changed or disappeared. Rescan before applying the review."), { code: "INVENTORY_REVIEW_WORKLOAD_STALE" });
|
|
const plan = inventoryReviews.preview({ serverId, workload, action, reason, repositoryFullName });
|
|
const result = await inventoryReviews.apply({ plan, expectedPlanId: planId });
|
|
return { ...result, inventory: await unraid.scanServerInventory(serverId, repositoryList.filter((item) => item.owner?.login !== "local")), state: store.getPublicState() };
|
|
});
|
|
register("deployment:profile-state", async ({ fullName, profileId }) => {
|
|
const profile = store.getDeploymentProfile(fullName, profileId);
|
|
if (profile?.provider === "ssh-unraid") {
|
|
let giteaSha = null;
|
|
try {
|
|
const [owner, repo] = String(fullName || "").split("/");
|
|
const branch = await gitea.getBranch(owner, repo, profile.branch);
|
|
giteaSha = branch?.commit?.id || branch?.commit?.sha || null;
|
|
} catch {}
|
|
return unraid.refreshProfileState(fullName, profileId, giteaSha);
|
|
}
|
|
return deployments.refreshProfileState(fullName, profileId);
|
|
});
|
|
register(
|
|
"deployment:apply-dockerman-metadata",
|
|
async ({ repository, profileId }) => {
|
|
const current = await resolveRepository(repository);
|
|
return unraid.applyDockerManMetadata({ repository: current, profileId });
|
|
},
|
|
);
|
|
register("deployment:reconcile", async ({ fullName, profileId }) => {
|
|
const profile = store.getDeploymentProfile(fullName, profileId);
|
|
if (profile?.provider !== "ssh-unraid")
|
|
return deployments.refreshProfileState(fullName, profileId);
|
|
const [owner, repo] = String(fullName || "").split("/");
|
|
const branch = await gitea.getBranch(owner, repo, profile.branch);
|
|
const giteaSha =
|
|
branch?.commit?.id ||
|
|
branch?.commit?.sha ||
|
|
branch?.commit?.commit?.id ||
|
|
null;
|
|
const state = await unraid.refreshProfileState(
|
|
fullName,
|
|
profileId,
|
|
giteaSha,
|
|
);
|
|
const operations = store.data.operations.filter(
|
|
(item) =>
|
|
item.profileId === profileId &&
|
|
item.provider === "ssh-unraid" &&
|
|
!["success", "failed", "cancelled", "rolled-back"].includes(
|
|
item.status,
|
|
),
|
|
);
|
|
for (const operation of operations)
|
|
await unraid.refreshOperation(operation.id);
|
|
return {
|
|
state,
|
|
operations: await unraid.reconcileRecordedOperations(profileId, state),
|
|
};
|
|
});
|
|
}
|
|
|
|
module.exports = { registerDeploymentIpc };
|