Add healthcheck for client
This commit is contained in:
parent
418e09f08a
commit
c92aa63d71
5 changed files with 106 additions and 5 deletions
|
|
@ -16,10 +16,14 @@ LABEL org.opencontainers.image.licenses="MIT"
|
|||
LABEL org.opencontainers.image.authors="andras@schmelczer.dev"
|
||||
|
||||
COPY --from=builder /build/local-client-cli/dist/cli.js /app/cli.js
|
||||
COPY --from=builder /build/local-client-cli/dist/healthcheck.js /app/healthcheck.js
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=1 \
|
||||
CMD node /app/healthcheck.js /tmp/vaultlink-health.json
|
||||
|
||||
WORKDIR /vault
|
||||
|
||||
VOLUME ["/vault"]
|
||||
|
||||
ENTRYPOINT ["node", "/app/cli.js"]
|
||||
ENTRYPOINT ["node", "/app/cli.js", "--health", "/tmp/vaultlink-health.json"]
|
||||
CMD ["--help"]
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export interface CliArgs {
|
|||
ignorePatterns?: string[];
|
||||
webSocketRetryIntervalMs?: number;
|
||||
logLevel: LogLevel;
|
||||
health?: string;
|
||||
}
|
||||
|
||||
export function parseArgs(argv: string[]): CliArgs {
|
||||
|
|
@ -51,6 +52,10 @@ export function parseArgs(argv: string[]): CliArgs {
|
|||
"[OPTIONAL] Log level (DEBUG, INFO, WARNING, ERROR)",
|
||||
"INFO"
|
||||
)
|
||||
.option(
|
||||
"--health <path>",
|
||||
"[OPTIONAL] Path to health status file for Docker healthcheck"
|
||||
)
|
||||
.addHelpText(
|
||||
"after",
|
||||
`
|
||||
|
|
@ -78,6 +83,7 @@ Examples:
|
|||
| number
|
||||
| undefined;
|
||||
const logLevelStr = (opts.logLevel as string | undefined) ?? "INFO";
|
||||
const health = opts.health as string | undefined;
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-type-assertion */
|
||||
|
||||
if (localPath === undefined) {
|
||||
|
|
@ -117,6 +123,7 @@ Examples:
|
|||
maxFileSizeMB: maxFileSizeMb,
|
||||
ignorePatterns: ignorePattern,
|
||||
webSocketRetryIntervalMs: websocketRetryIntervalMs,
|
||||
logLevel
|
||||
logLevel,
|
||||
health
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import * as path from "path";
|
||||
import * as fs from "fs/promises";
|
||||
import * as fsSync from "fs";
|
||||
import type { NetworkConnectionStatus } from "sync-client";
|
||||
import {
|
||||
SyncClient,
|
||||
DEFAULT_SETTINGS,
|
||||
|
|
@ -13,6 +15,19 @@ import { FileWatcher } from "./file-watcher";
|
|||
import { formatLogLine, colorize, styleText } from "./logger-formatter";
|
||||
import packageJson from "../package.json";
|
||||
|
||||
function writeHealthStatus(
|
||||
filePath: string,
|
||||
connectionStatus: NetworkConnectionStatus
|
||||
): void {
|
||||
try {
|
||||
fsSync.writeFileSync(filePath, JSON.stringify(connectionStatus));
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to write health status to ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const LOG_LEVEL_ORDER = {
|
||||
[LogLevel.DEBUG]: 0,
|
||||
[LogLevel.INFO]: 1,
|
||||
|
|
@ -78,6 +93,7 @@ async function main(): Promise<void> {
|
|||
syncConcurrency:
|
||||
args.syncConcurrency ?? DEFAULT_SETTINGS.syncConcurrency,
|
||||
maxFileSizeMB: args.maxFileSizeMB ?? DEFAULT_SETTINGS.maxFileSizeMB,
|
||||
diffCacheSizeMB: DEFAULT_SETTINGS.diffCacheSizeMB,
|
||||
ignorePatterns,
|
||||
webSocketRetryIntervalMs:
|
||||
args.webSocketRetryIntervalMs ??
|
||||
|
|
@ -119,6 +135,15 @@ async function main(): Promise<void> {
|
|||
nativeLineEndings: process.platform === "win32" ? "\r\n" : "\n"
|
||||
});
|
||||
|
||||
if (args.health !== undefined) {
|
||||
const healthFile = args.health;
|
||||
setInterval(() => {
|
||||
void client.checkConnection().then((status) => {
|
||||
writeHealthStatus(healthFile, status);
|
||||
});
|
||||
}, 30 * 1000); // every 30 seconds
|
||||
}
|
||||
|
||||
// Add colored log formatter with level filtering
|
||||
client.logger.addOnMessageListener((logLine) => {
|
||||
// Only show messages at or above the configured log level
|
||||
|
|
@ -132,7 +157,10 @@ async function main(): Promise<void> {
|
|||
const fileWatcher = new FileWatcher(absolutePath, client);
|
||||
|
||||
client.addWebSocketStatusChangeListener(() => {
|
||||
client.logger.info("WebSocket status changed");
|
||||
const isConnected = client.isWebSocketConnected;
|
||||
client.logger.info(
|
||||
`WebSocket status changed: ${isConnected ? "connected" : "disconnected"}`
|
||||
);
|
||||
});
|
||||
|
||||
client.addRemainingSyncOperationsListener((remaining) => {
|
||||
|
|
|
|||
59
frontend/local-client-cli/src/healthcheck.ts
Normal file
59
frontend/local-client-cli/src/healthcheck.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Healthcheck script for Docker container
|
||||
* Checks if the sync client is connected to the server
|
||||
*/
|
||||
|
||||
import * as fs from "fs";
|
||||
import type { NetworkConnectionStatus } from "sync-client";
|
||||
|
||||
function isHealthStatus(value: unknown): value is NetworkConnectionStatus {
|
||||
if (typeof value !== "object" || value === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
if (process.argv.length < 3) {
|
||||
console.error("Usage: healthcheck <path-to-health-file>");
|
||||
process.exit(1);
|
||||
}
|
||||
const [, , healthFile] = process.argv;
|
||||
|
||||
try {
|
||||
// Check if health file exists
|
||||
if (!fs.existsSync(healthFile)) {
|
||||
console.error(`Health file does not exist: ${healthFile}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Read and parse health status
|
||||
const content = fs.readFileSync(healthFile, "utf-8");
|
||||
const parsed: unknown = JSON.parse(content);
|
||||
|
||||
// Validate the parsed object using type guard
|
||||
if (!isHealthStatus(parsed)) {
|
||||
throw new Error("Invalid health status format");
|
||||
}
|
||||
|
||||
const status = parsed;
|
||||
|
||||
if (!status.isSuccessful || !status.isWebSocketConnected) {
|
||||
console.error("Not connected to server: " + status.serverMessage);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Healthy: Connected to server");
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Health check failed: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -2,7 +2,10 @@ const path = require("path");
|
|||
const webpack = require("webpack");
|
||||
|
||||
module.exports = {
|
||||
entry: "./src/cli.ts",
|
||||
entry: {
|
||||
cli: "./src/cli.ts",
|
||||
healthcheck: "./src/healthcheck.ts"
|
||||
},
|
||||
target: "node",
|
||||
mode: "production",
|
||||
optimization: {
|
||||
|
|
@ -21,7 +24,7 @@ module.exports = {
|
|||
},
|
||||
output: {
|
||||
globalObject: "this",
|
||||
filename: "cli.js",
|
||||
filename: "[name].js",
|
||||
path: path.resolve(__dirname, "dist")
|
||||
},
|
||||
plugins: [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue