vibora 7.5.2 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/vibora.js +1 -1
- package/dist/assets/{index-oEWLASTY.js → index-mGhyfHBb.js} +93 -93
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/server/index.js +159 -24
package/dist/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/png" href="/logo.png" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Vibora - Harness Attention. Ship.</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-mGhyfHBb.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-_oJ5ZjNK.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -178317,7 +178317,11 @@ async function checkConfigDirWritable(configDir) {
|
|
|
178317
178317
|
return false;
|
|
178318
178318
|
}
|
|
178319
178319
|
}
|
|
178320
|
-
function getConfigFilename(appId) {
|
|
178320
|
+
function getConfigFilename(appId, appName) {
|
|
178321
|
+
if (appName) {
|
|
178322
|
+
const sanitized = appName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
178323
|
+
return `vibora-${sanitized}-${appId}.yml`;
|
|
178324
|
+
}
|
|
178321
178325
|
return `vibora-${appId}.yml`;
|
|
178322
178326
|
}
|
|
178323
178327
|
async function checkRouteConflict(configDir, domain, currentAppId) {
|
|
@@ -178356,7 +178360,7 @@ async function addRoute(config, appId, domain, upstreamUrl, options) {
|
|
|
178356
178360
|
return { success: false, error };
|
|
178357
178361
|
}
|
|
178358
178362
|
const routerId = `vibora-${appId}`;
|
|
178359
|
-
const filename = getConfigFilename(appId);
|
|
178363
|
+
const filename = getConfigFilename(appId, options?.appName);
|
|
178360
178364
|
const filepath = join19(config.configDir, filename);
|
|
178361
178365
|
let tlsConfig;
|
|
178362
178366
|
let tlsStores;
|
|
@@ -178426,22 +178430,33 @@ async function addRoute(config, appId, domain, upstreamUrl, options) {
|
|
|
178426
178430
|
return { success: false, error };
|
|
178427
178431
|
}
|
|
178428
178432
|
}
|
|
178429
|
-
async function removeRoute(config, appId) {
|
|
178430
|
-
const
|
|
178431
|
-
|
|
178432
|
-
|
|
178433
|
-
|
|
178434
|
-
|
|
178435
|
-
|
|
178436
|
-
|
|
178437
|
-
|
|
178433
|
+
async function removeRoute(config, appId, appName) {
|
|
178434
|
+
const filesToTry = [
|
|
178435
|
+
appName ? getConfigFilename(appId, appName) : null,
|
|
178436
|
+
getConfigFilename(appId)
|
|
178437
|
+
].filter(Boolean);
|
|
178438
|
+
let deleted = false;
|
|
178439
|
+
let lastError;
|
|
178440
|
+
for (const filename of filesToTry) {
|
|
178441
|
+
const filepath = join19(config.configDir, filename);
|
|
178442
|
+
try {
|
|
178443
|
+
await unlink2(filepath);
|
|
178444
|
+
log2.deploy.info("Removed Traefik route", { appId, filepath });
|
|
178445
|
+
deleted = true;
|
|
178446
|
+
} catch (err) {
|
|
178447
|
+
if (err.code !== "ENOENT") {
|
|
178448
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
178449
|
+
}
|
|
178450
|
+
}
|
|
178451
|
+
}
|
|
178452
|
+
if (deleted || !lastError) {
|
|
178453
|
+
if (!deleted) {
|
|
178438
178454
|
log2.deploy.debug("Traefik route file not found, nothing to remove", { appId });
|
|
178439
|
-
return { success: true };
|
|
178440
178455
|
}
|
|
178441
|
-
|
|
178442
|
-
log2.deploy.error("Failed to remove Traefik route", { appId, error });
|
|
178443
|
-
return { success: false, error };
|
|
178456
|
+
return { success: true };
|
|
178444
178457
|
}
|
|
178458
|
+
log2.deploy.error("Failed to remove Traefik route", { appId, error: lastError });
|
|
178459
|
+
return { success: false, error: lastError };
|
|
178445
178460
|
}
|
|
178446
178461
|
|
|
178447
178462
|
// server/services/traefik-docker.ts
|
|
@@ -178785,6 +178800,55 @@ function cleanupDeployment(deploymentId) {
|
|
|
178785
178800
|
// server/services/deployment.ts
|
|
178786
178801
|
var cachedTraefikConfig = null;
|
|
178787
178802
|
var cachedPublicIp = null;
|
|
178803
|
+
var deploymentLogStates = new Map;
|
|
178804
|
+
function getOrCreateLogState(appId) {
|
|
178805
|
+
let state = deploymentLogStates.get(appId);
|
|
178806
|
+
if (!state) {
|
|
178807
|
+
state = {
|
|
178808
|
+
logs: [],
|
|
178809
|
+
subscribers: new Set,
|
|
178810
|
+
isComplete: false
|
|
178811
|
+
};
|
|
178812
|
+
deploymentLogStates.set(appId, state);
|
|
178813
|
+
}
|
|
178814
|
+
return state;
|
|
178815
|
+
}
|
|
178816
|
+
function broadcastProgress(appId, progress) {
|
|
178817
|
+
const state = getOrCreateLogState(appId);
|
|
178818
|
+
state.logs.push(progress);
|
|
178819
|
+
if (progress.stage === "done" || progress.stage === "failed" || progress.stage === "cancelled") {
|
|
178820
|
+
state.isComplete = true;
|
|
178821
|
+
state.finalEvent = progress;
|
|
178822
|
+
}
|
|
178823
|
+
for (const subscriber of state.subscribers) {
|
|
178824
|
+
try {
|
|
178825
|
+
subscriber(progress);
|
|
178826
|
+
} catch {}
|
|
178827
|
+
}
|
|
178828
|
+
}
|
|
178829
|
+
function subscribeToDeploymentLogs(appId, onProgress) {
|
|
178830
|
+
const state = getOrCreateLogState(appId);
|
|
178831
|
+
for (const progress of state.logs) {
|
|
178832
|
+
try {
|
|
178833
|
+
onProgress(progress);
|
|
178834
|
+
} catch {}
|
|
178835
|
+
}
|
|
178836
|
+
state.subscribers.add(onProgress);
|
|
178837
|
+
return {
|
|
178838
|
+
unsubscribe: () => {
|
|
178839
|
+
state.subscribers.delete(onProgress);
|
|
178840
|
+
},
|
|
178841
|
+
isComplete: state.isComplete,
|
|
178842
|
+
finalEvent: state.finalEvent
|
|
178843
|
+
};
|
|
178844
|
+
}
|
|
178845
|
+
function hasActiveDeploymentLogs(appId) {
|
|
178846
|
+
const state = deploymentLogStates.get(appId);
|
|
178847
|
+
return !!state && !state.isComplete;
|
|
178848
|
+
}
|
|
178849
|
+
function clearDeploymentLogs(appId) {
|
|
178850
|
+
deploymentLogStates.delete(appId);
|
|
178851
|
+
}
|
|
178788
178852
|
async function detectPublicIp() {
|
|
178789
178853
|
if (cachedPublicIp)
|
|
178790
178854
|
return cachedPublicIp;
|
|
@@ -179000,16 +179064,14 @@ async function deployApp(appId, options = {}, onProgress) {
|
|
|
179000
179064
|
const [subdomain, ...domainParts] = service.domain.split(".");
|
|
179001
179065
|
const rootDomain = domainParts.join(".");
|
|
179002
179066
|
const upstreamUrl = `http://${projectName}_${service.serviceName}:${service.containerPort}`;
|
|
179003
|
-
|
|
179067
|
+
const routeOptions = { appName: app15.name };
|
|
179004
179068
|
if (settings.integrations.cloudflareApiToken && rootDomain) {
|
|
179005
179069
|
onProgress?.({ stage: "configuring", message: `Generating SSL certificate for ${rootDomain}...` });
|
|
179006
179070
|
const certResult = await createOriginCACertificate(rootDomain);
|
|
179007
179071
|
if (certResult.success && certResult.certPath && certResult.keyPath) {
|
|
179008
|
-
routeOptions = {
|
|
179009
|
-
|
|
179010
|
-
|
|
179011
|
-
keyFile: `${TRAEFIK_CERTS_MOUNT}/${rootDomain}/key.pem`
|
|
179012
|
-
}
|
|
179072
|
+
routeOptions.tlsCert = {
|
|
179073
|
+
certFile: `${TRAEFIK_CERTS_MOUNT}/${rootDomain}/cert.pem`,
|
|
179074
|
+
keyFile: `${TRAEFIK_CERTS_MOUNT}/${rootDomain}/key.pem`
|
|
179013
179075
|
};
|
|
179014
179076
|
log2.deploy.info("Using Origin CA certificate for TLS", {
|
|
179015
179077
|
domain: service.domain,
|
|
@@ -179246,7 +179308,7 @@ async function stopApp(appId) {
|
|
|
179246
179308
|
for (const service of services) {
|
|
179247
179309
|
if (service.exposed && service.domain) {
|
|
179248
179310
|
if (traefikConfig) {
|
|
179249
|
-
await removeRoute(traefikConfig, appId);
|
|
179311
|
+
await removeRoute(traefikConfig, appId, app15.name);
|
|
179250
179312
|
}
|
|
179251
179313
|
const [subdomain, ...domainParts] = service.domain.split(".");
|
|
179252
179314
|
const rootDomain = domainParts.join(".");
|
|
@@ -179261,6 +179323,22 @@ async function stopApp(appId) {
|
|
|
179261
179323
|
await db.update(appServices).set({ status: "stopped", containerId: null, updatedAt: new Date().toISOString() }).where(eq(appServices.id, service.id));
|
|
179262
179324
|
}
|
|
179263
179325
|
}
|
|
179326
|
+
const tunnel = await db.query.tunnels.findFirst({
|
|
179327
|
+
where: eq(tunnels.appId, appId)
|
|
179328
|
+
});
|
|
179329
|
+
if (tunnel) {
|
|
179330
|
+
try {
|
|
179331
|
+
await deleteTunnel(tunnel.tunnelId);
|
|
179332
|
+
log2.deploy.info("Deleted Cloudflare tunnel", { tunnelId: tunnel.tunnelId, appId });
|
|
179333
|
+
} catch (err) {
|
|
179334
|
+
log2.deploy.warn("Failed to delete Cloudflare tunnel", {
|
|
179335
|
+
tunnelId: tunnel.tunnelId,
|
|
179336
|
+
appId,
|
|
179337
|
+
error: String(err)
|
|
179338
|
+
});
|
|
179339
|
+
}
|
|
179340
|
+
await db.delete(tunnels).where(eq(tunnels.appId, appId));
|
|
179341
|
+
}
|
|
179264
179342
|
await db.update(apps).set({ status: "stopped", updatedAt: new Date().toISOString() }).where(eq(apps.id, appId));
|
|
179265
179343
|
log2.deploy.info("App stopped", { appId });
|
|
179266
179344
|
return { success: true };
|
|
@@ -179718,7 +179796,7 @@ app15.delete("/:id", async (c) => {
|
|
|
179718
179796
|
}
|
|
179719
179797
|
if (stopContainers) {
|
|
179720
179798
|
const projectName = getProjectName(id, repo?.displayName);
|
|
179721
|
-
if (existing.status === "running") {
|
|
179799
|
+
if (existing.status === "running" || existing.status === "building") {
|
|
179722
179800
|
const stopResult = await stopApp(id);
|
|
179723
179801
|
if (!stopResult.success) {
|
|
179724
179802
|
log2.deploy.warn("stopApp failed during app deletion, attempting direct stack removal", {
|
|
@@ -179745,7 +179823,7 @@ app15.delete("/:id", async (c) => {
|
|
|
179745
179823
|
}
|
|
179746
179824
|
const traefikConfig = await detectTraefik();
|
|
179747
179825
|
if (traefikConfig) {
|
|
179748
|
-
await removeRoute(traefikConfig, id).catch((err) => {
|
|
179826
|
+
await removeRoute(traefikConfig, id, existing.name).catch((err) => {
|
|
179749
179827
|
log2.deploy.warn("Failed to remove Traefik route during app deletion", {
|
|
179750
179828
|
appId: id,
|
|
179751
179829
|
error: String(err)
|
|
@@ -179780,6 +179858,7 @@ app15.delete("/:id", async (c) => {
|
|
|
179780
179858
|
});
|
|
179781
179859
|
await db.delete(appServices).where(eq(appServices.appId, id));
|
|
179782
179860
|
await db.delete(deployments).where(eq(deployments.appId, id));
|
|
179861
|
+
await db.delete(tunnels).where(eq(tunnels.appId, id));
|
|
179783
179862
|
await db.delete(apps).where(eq(apps.id, id));
|
|
179784
179863
|
refreshGitWatchers().catch(() => {});
|
|
179785
179864
|
return c.json({ success: true });
|
|
@@ -179873,12 +179952,14 @@ app15.get("/:id/deploy/stream", async (c) => {
|
|
|
179873
179952
|
if (!existing) {
|
|
179874
179953
|
return c.json({ error: "App not found" }, 404);
|
|
179875
179954
|
}
|
|
179955
|
+
clearDeploymentLogs(id);
|
|
179876
179956
|
c.header("X-Accel-Buffering", "no");
|
|
179877
179957
|
return streamSSE(c, async (stream3) => {
|
|
179878
179958
|
await stream3.write(`: ping
|
|
179879
179959
|
|
|
179880
179960
|
`);
|
|
179881
179961
|
const result = await deployApp(id, { deployedBy: "manual" }, async (progress) => {
|
|
179962
|
+
broadcastProgress(id, progress);
|
|
179882
179963
|
try {
|
|
179883
179964
|
await stream3.writeSSE({
|
|
179884
179965
|
event: "progress",
|
|
@@ -179887,11 +179968,15 @@ app15.get("/:id/deploy/stream", async (c) => {
|
|
|
179887
179968
|
} catch {}
|
|
179888
179969
|
});
|
|
179889
179970
|
if (result.success) {
|
|
179971
|
+
const finalProgress = { stage: "done", message: "Deployment complete" };
|
|
179972
|
+
broadcastProgress(id, finalProgress);
|
|
179890
179973
|
await stream3.writeSSE({
|
|
179891
179974
|
event: "complete",
|
|
179892
179975
|
data: JSON.stringify({ success: true, deployment: result.deployment })
|
|
179893
179976
|
});
|
|
179894
179977
|
} else {
|
|
179978
|
+
const finalProgress = { stage: "failed", message: result.error || "Deployment failed" };
|
|
179979
|
+
broadcastProgress(id, finalProgress);
|
|
179895
179980
|
await stream3.writeSSE({
|
|
179896
179981
|
event: "error",
|
|
179897
179982
|
data: JSON.stringify({ success: false, error: result.error })
|
|
@@ -179899,6 +179984,51 @@ app15.get("/:id/deploy/stream", async (c) => {
|
|
|
179899
179984
|
}
|
|
179900
179985
|
});
|
|
179901
179986
|
});
|
|
179987
|
+
app15.get("/:id/deploy/watch", async (c) => {
|
|
179988
|
+
const id = c.req.param("id");
|
|
179989
|
+
const existing = await db.query.apps.findFirst({
|
|
179990
|
+
where: eq(apps.id, id)
|
|
179991
|
+
});
|
|
179992
|
+
if (!existing) {
|
|
179993
|
+
return c.json({ error: "App not found" }, 404);
|
|
179994
|
+
}
|
|
179995
|
+
if (!hasActiveDeploymentLogs(id) && existing.status !== "building" && existing.status !== "pending") {
|
|
179996
|
+
return c.json({ error: "No deployment in progress" }, 404);
|
|
179997
|
+
}
|
|
179998
|
+
c.header("X-Accel-Buffering", "no");
|
|
179999
|
+
return streamSSE(c, async (stream3) => {
|
|
180000
|
+
await stream3.write(`: ping
|
|
180001
|
+
|
|
180002
|
+
`);
|
|
180003
|
+
const { unsubscribe, isComplete, finalEvent } = subscribeToDeploymentLogs(id, (progress) => {
|
|
180004
|
+
try {
|
|
180005
|
+
stream3.writeSSE({
|
|
180006
|
+
event: "progress",
|
|
180007
|
+
data: JSON.stringify(progress)
|
|
180008
|
+
});
|
|
180009
|
+
} catch {}
|
|
180010
|
+
});
|
|
180011
|
+
if (isComplete && finalEvent) {
|
|
180012
|
+
const event = finalEvent.stage === "done" ? "complete" : "error";
|
|
180013
|
+
const data = finalEvent.stage === "done" ? { success: true } : { success: false, error: finalEvent.message };
|
|
180014
|
+
await stream3.writeSSE({
|
|
180015
|
+
event,
|
|
180016
|
+
data: JSON.stringify(data)
|
|
180017
|
+
});
|
|
180018
|
+
unsubscribe();
|
|
180019
|
+
return;
|
|
180020
|
+
}
|
|
180021
|
+
await new Promise((resolve6) => {
|
|
180022
|
+
const checkComplete = setInterval(() => {
|
|
180023
|
+
if (!hasActiveDeploymentLogs(id)) {
|
|
180024
|
+
clearInterval(checkComplete);
|
|
180025
|
+
unsubscribe();
|
|
180026
|
+
resolve6();
|
|
180027
|
+
}
|
|
180028
|
+
}, 1000);
|
|
180029
|
+
});
|
|
180030
|
+
});
|
|
180031
|
+
});
|
|
179902
180032
|
app15.post("/:id/cancel-deploy", async (c) => {
|
|
179903
180033
|
const id = c.req.param("id");
|
|
179904
180034
|
const existing = await db.query.apps.findFirst({
|
|
@@ -179921,6 +180051,11 @@ app15.post("/:id/stop", async (c) => {
|
|
|
179921
180051
|
if (!existing) {
|
|
179922
180052
|
return c.json({ error: "App not found" }, 404);
|
|
179923
180053
|
}
|
|
180054
|
+
if (existing.status === "building") {
|
|
180055
|
+
log2.deploy.info("Cancelling active deployment before stop", { appId: id });
|
|
180056
|
+
await cancelDeploymentByAppId(id);
|
|
180057
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1000));
|
|
180058
|
+
}
|
|
179924
180059
|
const result = await stopApp(id);
|
|
179925
180060
|
if (!result.success) {
|
|
179926
180061
|
return c.json({ error: result.error }, 500);
|