Docker Compose projeleri için tam kapsamlı yönetim paneli ve otomatik deployment altyapısı eklendi. Sistem özellikleri: - Belirtilen root dizin altındaki docker-compose dosyası içeren projeleri tarama - Git repo bağlantısı ile branch yönetimi ve klonlama/pull işlemleri - Docker compose up/down komutları ile otomatik deploy - Gitea webhook entegrasyonu ile commit bazlı tetikleme - Deploy geçmişi, log kayıtları ve durum takibi (running/success/failed) - Deploy metrikleri ve dashboard görselleştirmesi - Webhook token ve secret yönetimi ile güvenlik - Proje favicon servisi Teknik değişiklikler: - Backend: deploymentProject, deploymentRun ve settings modelleri eklendi - Backend: deploymentService ile git ve docker işlemleri otomatize edildi - Backend: webhook doğrulaması için signature kontrolü eklendi - Docker: docker-cli ve docker-compose bağımlılıkları eklendi - Frontend: deployments ve settings sayfaları eklendi - Frontend: dashboard'a deploy metrikleri ve aktivite akışı eklendi - API: /api/deployments ve /api/settings yolları eklendi
67 lines
2.5 KiB
TypeScript
67 lines
2.5 KiB
TypeScript
import { Router, Request } from "express";
|
|
import crypto from "crypto";
|
|
import { deploymentService } from "../services/deploymentService.js";
|
|
|
|
const router = Router();
|
|
|
|
type RawBodyRequest = Request & { rawBody?: Buffer };
|
|
|
|
function getHeaderValue(value: string | string[] | undefined) {
|
|
if (!value) return "";
|
|
return Array.isArray(value) ? value[0] : value;
|
|
}
|
|
|
|
function verifySignature(rawBody: Buffer, secret: string, signature: string) {
|
|
const cleaned = signature.startsWith("sha256=") ? signature.slice(7) : signature;
|
|
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
|
|
if (cleaned.length !== expected.length) return false;
|
|
return crypto.timingSafeEqual(Buffer.from(cleaned), Buffer.from(expected));
|
|
}
|
|
|
|
router.post("/api/deployments/webhook/:token", async (req, res) => {
|
|
const { token } = req.params;
|
|
const settings = await deploymentService.ensureSettings();
|
|
|
|
const authHeader = getHeaderValue(req.headers.authorization);
|
|
if (!authHeader) {
|
|
return res.status(401).json({ message: "Yetkisiz" });
|
|
}
|
|
const providedToken = authHeader.startsWith("Bearer ")
|
|
? authHeader.slice("Bearer ".length)
|
|
: authHeader;
|
|
if (providedToken !== settings.webhookToken) {
|
|
return res.status(401).json({ message: "Yetkisiz" });
|
|
}
|
|
|
|
const signatureHeader =
|
|
getHeaderValue(req.headers["x-gitea-signature"]) ||
|
|
getHeaderValue(req.headers["x-gitea-signature-256"]);
|
|
const rawBody = (req as RawBodyRequest).rawBody;
|
|
if (!rawBody || !signatureHeader) {
|
|
return res.status(401).json({ message: "Imza eksik" });
|
|
}
|
|
if (!verifySignature(rawBody, settings.webhookSecret, signatureHeader)) {
|
|
return res.status(401).json({ message: "Imza dogrulanamadi" });
|
|
}
|
|
|
|
const payload = req.body as { ref?: string; head_commit?: { message?: string }; commits?: Array<{ message?: string }> };
|
|
const ref = payload?.ref || "";
|
|
const branch = ref.startsWith("refs/heads/") ? ref.replace("refs/heads/", "") : ref;
|
|
const commitMessage =
|
|
payload?.head_commit?.message || payload?.commits?.[payload.commits.length - 1]?.message;
|
|
|
|
const project = await deploymentService.findByWebhookToken(token);
|
|
if (!project) return res.status(404).json({ message: "Deployment bulunamadi" });
|
|
|
|
if (branch && branch !== project.branch) {
|
|
return res.json({ ignored: true });
|
|
}
|
|
|
|
deploymentService
|
|
.runDeployment(project._id.toString(), commitMessage ? { message: commitMessage } : undefined)
|
|
.catch(() => undefined);
|
|
return res.json({ queued: true });
|
|
});
|
|
|
|
export default router;
|