This repository has been archived on 2026-09-30. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
ForgeFlow/src/main/ipc/deployment-handlers.cjs
T
NuklearRabbit 8cca1bfc01
Managed validation / full (pull_request) Successful in 44s
ChatGPT validation / quality (push) Failing after 2m28s
Prepare ForgeFlow for public release
2026-08-31 20:10:07 +02:00

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 };