Add Coolify deployment workflow
Deploy / build-and-deploy (push) Failing after 11m57s
Deploy / build-and-deploy (push) Failing after 11m57s
This commit is contained in:
1 parent
49bb59a4ab
commit
a9dbc23d7e
5 files changed
+230
-11
No files matched your search
@@ -19,32 +19,51 @@ const net = require("net");
|
||||
|
||||
const ROOT = __dirname;
|
||||
const PUBLIC_DIR = path.join(ROOT, "public");
|
||||
const LEADS_DIR = path.join(ROOT, "leads");
|
||||
const DATA_DIR = process.env.DATA_DIR
|
||||
? path.resolve(process.env.DATA_DIR)
|
||||
: ROOT;
|
||||
const LEADS_DIR = path.join(DATA_DIR, "leads");
|
||||
const LEADS_JSON = path.join(LEADS_DIR, "leads.ndjson");
|
||||
const LEADS_CSV = path.join(LEADS_DIR, "leads.csv");
|
||||
|
||||
/* GameJam-Speicher */
|
||||
const GAMEJAM_DIR = path.join(ROOT, "gamejam");
|
||||
const GAMEJAM_DIR = path.join(DATA_DIR, "gamejam");
|
||||
const SUBMISSIONS_JSON = path.join(GAMEJAM_DIR, "submissions.ndjson");
|
||||
const GAMES_DIR = path.join(PUBLIC_DIR, "gamejam-games");
|
||||
const GAMES_DIR = process.env.DATA_DIR
|
||||
? path.join(DATA_DIR, "gamejam-games")
|
||||
: path.join(PUBLIC_DIR, "gamejam-games");
|
||||
const MAX_UPLOAD = 5 * 1024 * 1024; /* 5 MB pro eingereichtem Spiel */
|
||||
const APP_REVISION = process.env.APP_REVISION || "local";
|
||||
|
||||
function envFlag(name, defaultValue) {
|
||||
const raw = process.env[name];
|
||||
if (raw == null || raw === "") return defaultValue;
|
||||
return !/^(0|false|no|off)$/i.test(raw);
|
||||
}
|
||||
|
||||
function envInt(name, defaultValue) {
|
||||
const raw = Number.parseInt(process.env[name] || "", 10);
|
||||
return Number.isFinite(raw) ? raw : defaultValue;
|
||||
}
|
||||
|
||||
/* Virenscan über ClamAV. Der Server spricht das clamd-Socket-Protokoll
|
||||
direkt — keine zusätzliche Abhängigkeit. Voraussetzung: ClamAV ist
|
||||
installiert und der Daemon "clamd" läuft (Einrichtung siehe README).
|
||||
enabled:false schaltet den Scan ab (z. B. lokal ohne ClamAV). */
|
||||
const CLAMAV = {
|
||||
enabled: true,
|
||||
host: "127.0.0.1",
|
||||
port: 3310, /* clamd TCPSocket-Standardport */
|
||||
timeoutMs: 15000
|
||||
enabled: envFlag("CLAMAV_ENABLED", true),
|
||||
host: process.env.CLAMAV_HOST || "127.0.0.1",
|
||||
port: envInt("CLAMAV_PORT", 3310), /* clamd TCPSocket-Standardport */
|
||||
timeoutMs: envInt("CLAMAV_TIMEOUT_MS", 15000)
|
||||
};
|
||||
|
||||
/* Bevorzugte Ports — der erste freie wird genutzt. */
|
||||
const PORT_CANDIDATES = [4317, 4318, 4417, 5317, 8317, 0];
|
||||
const PORT_CANDIDATES = process.env.PORT
|
||||
? [envInt("PORT", 3000)]
|
||||
: [4317, 4318, 4417, 5317, 8317, 0];
|
||||
/* 0.0.0.0 = auf allen Schnittstellen erreichbar, auch für andere
|
||||
Geräte im selben WLAN (z. B. Handys, die den QR-Code scannen). */
|
||||
const HOST = "0.0.0.0";
|
||||
const HOST = process.env.HOST || process.env.HOSTNAME || "0.0.0.0";
|
||||
|
||||
/* Erste IPv4-LAN-Adresse dieses Rechners ermitteln. */
|
||||
function getLanIP() {
|
||||
@@ -88,6 +107,18 @@ function ensureGamejamStore() {
|
||||
if (!fs.existsSync(GAMES_DIR)) fs.mkdirSync(GAMES_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
function assertInside(baseDir, candidate) {
|
||||
const relative = path.relative(baseDir, candidate);
|
||||
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
||||
}
|
||||
|
||||
function writeProbe(dir) {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
const file = path.join(dir, ".write-probe-" + process.pid + "-" + Date.now());
|
||||
fs.writeFileSync(file, "ok", "utf8");
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
|
||||
function csvCell(value) {
|
||||
const s = String(value == null ? "" : value);
|
||||
return '"' + s.replace(/"/g, '""').replace(/\r?\n/g, " ") + '"';
|
||||
@@ -116,8 +147,13 @@ function serveStatic(req, res) {
|
||||
if (urlPath === "/") urlPath = "/index.html";
|
||||
|
||||
/* Path-Traversal verhindern */
|
||||
const filePath = path.normalize(path.join(PUBLIC_DIR, urlPath));
|
||||
if (!filePath.startsWith(PUBLIC_DIR)) {
|
||||
let baseDir = PUBLIC_DIR;
|
||||
let filePath = path.normalize(path.join(PUBLIC_DIR, urlPath));
|
||||
if (urlPath.startsWith("/gamejam-games/")) {
|
||||
baseDir = GAMES_DIR;
|
||||
filePath = path.normalize(path.join(GAMES_DIR, urlPath.replace(/^\/gamejam-games\//, "")));
|
||||
}
|
||||
if (!assertInside(baseDir, filePath)) {
|
||||
res.writeHead(403).end("Forbidden");
|
||||
return;
|
||||
}
|
||||
@@ -439,10 +475,57 @@ function sendJSON(res, code, obj) {
|
||||
res.end(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
function handleHealthz(req, res) {
|
||||
if (req.method !== "GET" && req.method !== "HEAD") {
|
||||
return sendJSON(res, 405, { error: "Methode nicht erlaubt." });
|
||||
}
|
||||
sendJSON(res, 200, {
|
||||
ok: true,
|
||||
service: "toolctrl-website",
|
||||
revision: APP_REVISION
|
||||
});
|
||||
}
|
||||
|
||||
async function handleReadyz(req, res) {
|
||||
if (req.method !== "GET" && req.method !== "HEAD") {
|
||||
return sendJSON(res, 405, { error: "Methode nicht erlaubt." });
|
||||
}
|
||||
|
||||
const checks = {
|
||||
leadsWritable: false,
|
||||
gamejamWritable: false,
|
||||
gamesWritable: false,
|
||||
clamav: !CLAMAV.enabled
|
||||
};
|
||||
|
||||
try {
|
||||
writeProbe(LEADS_DIR);
|
||||
checks.leadsWritable = true;
|
||||
writeProbe(GAMEJAM_DIR);
|
||||
checks.gamejamWritable = true;
|
||||
writeProbe(GAMES_DIR);
|
||||
checks.gamesWritable = true;
|
||||
if (CLAMAV.enabled) checks.clamav = await clamavPing();
|
||||
} catch (e) {
|
||||
console.error("[readyz] Prüfung fehlgeschlagen:", e.message);
|
||||
}
|
||||
|
||||
const ok = Object.keys(checks).every((key) => checks[key] === true);
|
||||
sendJSON(res, ok ? 200 : 503, {
|
||||
ok: ok,
|
||||
service: "toolctrl-website",
|
||||
revision: APP_REVISION,
|
||||
checks: checks
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- Router ---------- */
|
||||
const server = http.createServer((req, res) => {
|
||||
const route = req.url.split("?")[0];
|
||||
|
||||
if (route === "/healthz") return handleHealthz(req, res);
|
||||
if (route === "/readyz") return handleReadyz(req, res);
|
||||
|
||||
if (route === "/api/lead") {
|
||||
if (req.method !== "POST") {
|
||||
return sendJSON(res, 405, { error: "Methode nicht erlaubt." });
|
||||
@@ -502,6 +585,8 @@ function listen(candidates) {
|
||||
}
|
||||
console.log(" Leads: " + LEADS_CSV);
|
||||
console.log(" GameJam-Einreichungen: " + SUBMISSIONS_JSON);
|
||||
console.log(" Daten: " + DATA_DIR);
|
||||
console.log(" Revision: " + APP_REVISION);
|
||||
if (CLAMAV.enabled) {
|
||||
clamavPing().then((ok) => {
|
||||
console.log(" Virenscan: " + (ok
|
||||
|
||||
Reference in new issue
Block a user