mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-08 12:36:44 +03:00
Implement health check API and update health check commands in Docker and Docker Compose
This commit is contained in:
@@ -49,6 +49,6 @@ VOLUME /app/data
|
|||||||
EXPOSE 4321
|
EXPOSE 4321
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||||
CMD wget --no-verbose --tries=1 --spider http://localhost:4321/ || exit 1
|
CMD wget --no-verbose --tries=1 --spider http://localhost:4321/api/health || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ services:
|
|||||||
- DATABASE_URL=sqlite://data/gitea-mirror.db
|
- DATABASE_URL=sqlite://data/gitea-mirror.db
|
||||||
- DELAY=${DELAY:-3600}
|
- DELAY=${DELAY:-3600}
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:4321/health"]
|
test: ["CMD", "curl", "-f", "http://localhost:4321/api/health"]
|
||||||
interval: 1m
|
interval: 1m
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
@@ -88,3 +88,80 @@ export const giteaApi = {
|
|||||||
body: JSON.stringify({ url, token }),
|
body: JSON.stringify({ url, token }),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Health API
|
||||||
|
export interface HealthResponse {
|
||||||
|
status: "ok" | "error";
|
||||||
|
timestamp: string;
|
||||||
|
version: string;
|
||||||
|
database: {
|
||||||
|
connected: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
system: {
|
||||||
|
uptime: {
|
||||||
|
startTime: string;
|
||||||
|
uptimeMs: number;
|
||||||
|
formatted: string;
|
||||||
|
};
|
||||||
|
memory: {
|
||||||
|
rss: string;
|
||||||
|
heapTotal: string;
|
||||||
|
heapUsed: string;
|
||||||
|
external: string;
|
||||||
|
systemTotal: string;
|
||||||
|
systemFree: string;
|
||||||
|
};
|
||||||
|
os: {
|
||||||
|
platform: string;
|
||||||
|
version: string;
|
||||||
|
arch: string;
|
||||||
|
};
|
||||||
|
env: string;
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const healthApi = {
|
||||||
|
check: async (): Promise<HealthResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/health`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({
|
||||||
|
status: "error",
|
||||||
|
error: "Failed to parse error response",
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
...errorData,
|
||||||
|
status: "error",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
} as HealthResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error checking health",
|
||||||
|
version: "unknown",
|
||||||
|
database: { connected: false, message: "Failed to connect to API" },
|
||||||
|
system: {
|
||||||
|
uptime: { startTime: "", uptimeMs: 0, formatted: "N/A" },
|
||||||
|
memory: {
|
||||||
|
rss: "N/A",
|
||||||
|
heapTotal: "N/A",
|
||||||
|
heapUsed: "N/A",
|
||||||
|
external: "N/A",
|
||||||
|
systemTotal: "N/A",
|
||||||
|
systemFree: "N/A",
|
||||||
|
},
|
||||||
|
os: { platform: "", version: "", arch: "" },
|
||||||
|
env: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
126
src/pages/api/health.ts
Normal file
126
src/pages/api/health.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import type { APIRoute } from "astro";
|
||||||
|
import { jsonResponse } from "@/lib/utils";
|
||||||
|
import { db } from "@/lib/db";
|
||||||
|
import { ENV } from "@/lib/config";
|
||||||
|
import os from "os";
|
||||||
|
|
||||||
|
// Track when the server started
|
||||||
|
const serverStartTime = new Date();
|
||||||
|
|
||||||
|
export const GET: APIRoute = async () => {
|
||||||
|
try {
|
||||||
|
// Check database connection by running a simple query
|
||||||
|
const dbStatus = await checkDatabaseConnection();
|
||||||
|
|
||||||
|
// Get system information
|
||||||
|
const systemInfo = {
|
||||||
|
uptime: getUptime(),
|
||||||
|
memory: getMemoryUsage(),
|
||||||
|
os: {
|
||||||
|
platform: os.platform(),
|
||||||
|
version: os.version(),
|
||||||
|
arch: os.arch(),
|
||||||
|
},
|
||||||
|
env: ENV.NODE_ENV,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build response
|
||||||
|
const healthData = {
|
||||||
|
status: "ok",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
version: process.env.npm_package_version || "unknown",
|
||||||
|
database: dbStatus,
|
||||||
|
system: systemInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
return jsonResponse({
|
||||||
|
data: healthData,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Health check failed:", error);
|
||||||
|
|
||||||
|
return jsonResponse({
|
||||||
|
data: {
|
||||||
|
status: "error",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
},
|
||||||
|
status: 503, // Service Unavailable
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check database connection by running a simple query
|
||||||
|
*/
|
||||||
|
async function checkDatabaseConnection() {
|
||||||
|
try {
|
||||||
|
// Run a simple query to check if the database is accessible
|
||||||
|
const result = await db.select({ test: sql`1` }).from(sql`sqlite_master`).limit(1);
|
||||||
|
|
||||||
|
return {
|
||||||
|
connected: true,
|
||||||
|
message: "Database connection successful",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Database connection check failed:", error);
|
||||||
|
|
||||||
|
return {
|
||||||
|
connected: false,
|
||||||
|
message: error instanceof Error ? error.message : "Database connection failed",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get server uptime information
|
||||||
|
*/
|
||||||
|
function getUptime() {
|
||||||
|
const now = new Date();
|
||||||
|
const uptimeMs = now.getTime() - serverStartTime.getTime();
|
||||||
|
|
||||||
|
// Convert to human-readable format
|
||||||
|
const seconds = Math.floor(uptimeMs / 1000);
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
const days = Math.floor(hours / 24);
|
||||||
|
|
||||||
|
return {
|
||||||
|
startTime: serverStartTime.toISOString(),
|
||||||
|
uptimeMs,
|
||||||
|
formatted: `${days}d ${hours % 24}h ${minutes % 60}m ${seconds % 60}s`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get memory usage information
|
||||||
|
*/
|
||||||
|
function getMemoryUsage() {
|
||||||
|
const memoryUsage = process.memoryUsage();
|
||||||
|
|
||||||
|
return {
|
||||||
|
rss: formatBytes(memoryUsage.rss),
|
||||||
|
heapTotal: formatBytes(memoryUsage.heapTotal),
|
||||||
|
heapUsed: formatBytes(memoryUsage.heapUsed),
|
||||||
|
external: formatBytes(memoryUsage.external),
|
||||||
|
systemTotal: formatBytes(os.totalmem()),
|
||||||
|
systemFree: formatBytes(os.freemem()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format bytes to human-readable format
|
||||||
|
*/
|
||||||
|
function formatBytes(bytes: number): string {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import sql tag for raw SQL queries
|
||||||
|
import { sql } from "drizzle-orm";
|
||||||
Reference in New Issue
Block a user