feat(deploy): prisma migration history, /version, galactus standalone deploy
Closes the gap between "what tag did I deploy" and "what is actually running", and gives the schema a history that can be reasoned about across releases. Migrations - Baseline the existing schema as 0000_init (migrate diff --from-empty). The schema had only ever been applied with `prisma db push`, so no history existed and schema state was disconnected from app version. Existing databases must be baselined once with `migrate resolve --applied 0000_init`; the workflows print this remedy on P3005. - Run `prisma migrate deploy` as a deploy STEP, not the container CMD — as a CMD, N replicas would race each other applying the same migration. Version reporting - GET /version on the API reports the APP_VERSION / GIT_SHA / BUILD_DATE that build.yml already baked into both images but nothing ever read. - The web footer shows the web build and flags an api/web mismatch. The two cannot drift at build time (one matrix run) but can at deploy time. - Both deploy workflows now fail if the running API does not report the tag that was dispatched — a stack naming a tag is not proof of what is running. - scripts/set-version.mjs stamps every package.json, which had all sat at 0.1.0 while real releases shipped as v1.x. Pre-migrate backup - deploy/scripts/pre-migrate-backup.mjs dumps the database from INSIDE the still-running old API container over Portainer's Docker API, so the file lands in the volume the Operaciones restore screen reads. A dump taken on the CI runner would be unreachable by the only restore path we have. Verifies the artefact with `gzip -t` before letting the migration proceed. galactus - deploy/galactus/*.compose.yml: standalone-Docker ports of the Swarm stacks. Plain compose silently ignores `deploy:`, so restart_policy becomes `restart: unless-stopped` — without it nothing returns after a host reboot. - .gitea/workflows/deploy-galactus.yml drives endpoint 3 with its own secrets. Fixes - deploy.yml passed `endpoint_id` and `pull_image` to cssnr/portainer-stack-deploy-action, which has no such inputs (they are `endpoint` and `pull`). The endpoint was silently never set. docs/DEPLOY_AND_MIGRATIONS.md documents expand/contract as the rule for schema changes: Prisma has no down-migrations, so a code rollback never rolls the schema back, and restoring the replication master from a dump diverges every replica. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { logout, me, updateUiScale } from "@/lib/api";
|
||||
import { getApiVersion, logout, me, updateUiScale } from "@/lib/api";
|
||||
import { shortSha, webBuildInfo } from "@/lib/build-info";
|
||||
import { AuthContext, can } from "@/lib/abilities";
|
||||
import { ROLE_LABEL } from "@/lib/labels";
|
||||
import {
|
||||
@@ -181,6 +182,52 @@ function NavMenu({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* What is deployed, from both halves. build.yml builds api + web in one matrix
|
||||
* run, so their versions cannot drift at build time — but they can at DEPLOY
|
||||
* time, if a stack is applied with only one image's tag moved. Showing both and
|
||||
* flagging a mismatch is the cheap check that catches a half-applied release.
|
||||
*/
|
||||
function BuildFooter() {
|
||||
const web = webBuildInfo();
|
||||
const [api, setApi] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
getApiVersion()
|
||||
.then((v) => {
|
||||
if (alive) setApi(v.version);
|
||||
})
|
||||
.catch(() => {
|
||||
// The shell already redirects to /login when the API is unreachable;
|
||||
// a missing version line is not worth a second error surface.
|
||||
});
|
||||
return () => {
|
||||
alive = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const mismatch = api !== null && api !== web.version;
|
||||
|
||||
return (
|
||||
<footer className="shell-footer">
|
||||
<span>Jorge Cuadros & Asociados</span>
|
||||
<span
|
||||
className="shell-footer-build"
|
||||
title={`web ${web.version} (${shortSha(web.gitSha)}) — ${web.buildDate}`}
|
||||
>
|
||||
v{web.version}
|
||||
{api !== null && (mismatch ? ` · API v${api}` : "")}
|
||||
</span>
|
||||
{mismatch && (
|
||||
<span className="shell-footer-warn" role="status">
|
||||
versiones desincronizadas
|
||||
</span>
|
||||
)}
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppShell({ children }: { children: ReactNode }) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
@@ -381,6 +428,7 @@ export function AppShell({ children }: { children: ReactNode }) {
|
||||
)}
|
||||
</header>
|
||||
<main className="shell-main">{children}</main>
|
||||
<BuildFooter />
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user