mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 14:13:18 +00:00
Compare commits
1 commit
fc5c2c0791
...
ce3e704ab7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce3e704ab7 |
5 changed files with 16 additions and 54 deletions
|
|
@ -78,10 +78,10 @@ export const config = {
|
|||
redisUrlCore: isDev ? (Deno.env.get("REDIS_URL_CORE") ?? "") : required("REDIS_URL_CORE"),
|
||||
|
||||
// --- Contabo Object Storage (S3-compatible) -- expedientes/PDFs/logos ---
|
||||
s3Endpoint: (Deno.env.get("S3_ENDPOINT") ?? "").trim().replace(/\/$/, ""),
|
||||
s3Bucket: (Deno.env.get("S3_BUCKET") ?? "").trim(),
|
||||
s3Region: (Deno.env.get("S3_REGION") ?? "auto").trim() || "auto",
|
||||
s3AccessKeyId: (Deno.env.get("S3_ACCESS_KEY_ID") ?? "").trim(),
|
||||
s3Endpoint: Deno.env.get("S3_ENDPOINT") ?? "",
|
||||
s3Bucket: Deno.env.get("S3_BUCKET") ?? "",
|
||||
s3Region: Deno.env.get("S3_REGION") ?? "us-east-1",
|
||||
s3AccessKeyId: Deno.env.get("S3_ACCESS_KEY_ID") ?? "",
|
||||
s3SecretAccessKey: Deno.env.get("S3_SECRET_ACCESS_KEY") ?? "",
|
||||
|
||||
isDev,
|
||||
|
|
|
|||
|
|
@ -1277,9 +1277,7 @@ await Promise.all([
|
|||
}
|
||||
})),
|
||||
startup("storage S3/R2", pingStorage().then((s) => {
|
||||
if (!s.ok) {
|
||||
throw new Error(`Storage no responde: ${s.error ?? "sin detalle"}`);
|
||||
}
|
||||
if (!s.ok) throw new Error("Storage no responde (HeadBucket R2 o disco local)");
|
||||
if (!config.isDev && !s.configured) {
|
||||
throw new Error("S3_ENDPOINT/S3_BUCKET/S3_* son obligatorios fuera de desarrollo");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
/**
|
||||
* Sonda S3/R2: ListObjects (o put/get/delete). HeadBucket no se usa:
|
||||
* R2 suele devolver 403 con tokens acotados al bucket.
|
||||
* Sonda de Contabo (S3): HeadBucket + put/get/delete.
|
||||
*
|
||||
* Desde api/:
|
||||
* deno run --allow-net --allow-env --allow-read --allow-write scripts/verify-storage.ts
|
||||
|
|
@ -28,9 +27,9 @@ if (!s3Configured()) {
|
|||
console.log(`S3 endpoint=${config.s3Endpoint} bucket=${config.s3Bucket} region=${config.s3Region}`);
|
||||
const ping = await pingStorage();
|
||||
if (!ping.ok) {
|
||||
console.error(`FAIL - ping bucket: ${ping.error ?? "sin detalle"}`);
|
||||
console.error("FAIL - HeadBucket: no se pudo alcanzar el bucket (credenciales, red o nombre).");
|
||||
Deno.exit(1);
|
||||
}
|
||||
console.log("OK - ping bucket (ListObjects o sonda write)");
|
||||
console.log("OK - HeadBucket");
|
||||
await probeStorageReadWrite();
|
||||
console.log("OK - put/get/delete de objeto sonda");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
S3Client,
|
||||
PutObjectCommand,
|
||||
GetObjectCommand,
|
||||
ListObjectsV2Command,
|
||||
HeadBucketCommand,
|
||||
DeleteObjectCommand,
|
||||
} from "npm:@aws-sdk/client-s3@3";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
|
|
@ -32,58 +32,23 @@ export type StoragePing = {
|
|||
configured: boolean;
|
||||
ok: boolean;
|
||||
backend: "s3" | "local";
|
||||
error?: string;
|
||||
};
|
||||
|
||||
function s3Err(e: unknown): string {
|
||||
if (e && typeof e === "object") {
|
||||
const o = e as {
|
||||
name?: string;
|
||||
message?: string;
|
||||
Code?: string;
|
||||
$metadata?: { httpStatusCode?: number };
|
||||
};
|
||||
return [o.name, o.Code, o.$metadata?.httpStatusCode, o.message].filter(Boolean).join(": ");
|
||||
}
|
||||
return String(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* R2: HeadBucket suele dar 403 con token de Account API acotado al bucket.
|
||||
* ListObjectsV2 (o un put/get sonda) es lo que realmente usamos en runtime.
|
||||
*/
|
||||
/** HeadBucket (S3) o escritura de prueba en disco local. */
|
||||
export async function pingStorage(): Promise<StoragePing> {
|
||||
if (!s3Configured()) {
|
||||
try {
|
||||
await mkdir(join(DATA_DIR, "local-objects"), { recursive: true });
|
||||
return { configured: false, ok: true, backend: "local" };
|
||||
} catch (e) {
|
||||
return { configured: false, ok: false, backend: "local", error: s3Err(e) };
|
||||
} catch {
|
||||
return { configured: false, ok: false, backend: "local" };
|
||||
}
|
||||
}
|
||||
try {
|
||||
await getClient().send(
|
||||
new ListObjectsV2Command({ Bucket: config.s3Bucket, MaxKeys: 1 }),
|
||||
);
|
||||
await getClient().send(new HeadBucketCommand({ Bucket: config.s3Bucket }));
|
||||
return { configured: true, ok: true, backend: "s3" };
|
||||
} catch (listErr) {
|
||||
try {
|
||||
await probeStorageReadWrite();
|
||||
return { configured: true, ok: true, backend: "s3" };
|
||||
} catch (writeErr) {
|
||||
const error = `list=${s3Err(listErr)}; write=${s3Err(writeErr)}`;
|
||||
console.error(
|
||||
"[storage] ping failed",
|
||||
error,
|
||||
"endpoint=",
|
||||
config.s3Endpoint,
|
||||
"bucket=",
|
||||
config.s3Bucket,
|
||||
"region=",
|
||||
config.s3Region,
|
||||
);
|
||||
return { configured: true, ok: false, backend: "s3", error };
|
||||
}
|
||||
} catch {
|
||||
return { configured: true, ok: false, backend: "s3" };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ sudo docker logs api-<uuid>-<timestamp>
|
|||
Busca `[startup] FAIL …`. Causas típicas:
|
||||
|
||||
- **Redis:** `REDIS_URL_IAM` / `REDIS_URL_CORE` con el UUID de **Postgres**. Redis tiene el suyo (Redis URL internal).
|
||||
- **S3/R2:** endpoint `https://<ACCOUNT_ID>.r2.cloudflarestorage.com` (sin barra final, **sin** el nombre del bucket), `S3_REGION=auto`, token **Account API** con Object Read & Write al bucket. R2 a menudo responde 403 a HeadBucket; el ping de la API usa ListObjects.
|
||||
- **S3/R2:** faltan `S3_*` o HeadBucket falla (token Account API, Object Read & Write, bucket correcto, `S3_REGION=auto`).
|
||||
- **Postgres `_app`:** Liquibase usa `*_OWNER`; el runtime usa `DATABASE_URL_PLATFORM`, `DATABASE_URL_IAM`, `DATABASE_URL_CORE` (passwords distintos).
|
||||
|
||||
### Coolify pide `PLATFORM_OWNER_PASSWORD`, `IAM_APP_PASSWORD`, `*_REDIS_PASSWORD`…
|
||||
|
|
|
|||
Loading…
Reference in a new issue