Improve settings #168
5 changed files with 106 additions and 5 deletions
Add healthcheck for client
commit
c92aa63d71
|
|
@ -16,10 +16,14 @@ LABEL org.opencontainers.image.licenses="MIT"
|
||||||
LABEL org.opencontainers.image.authors="andras@schmelczer.dev"
|
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/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
|
WORKDIR /vault
|
||||||
|
|
||||||
VOLUME ["/vault"]
|
VOLUME ["/vault"]
|
||||||
|
|
||||||
ENTRYPOINT ["node", "/app/cli.js"]
|
ENTRYPOINT ["node", "/app/cli.js", "--health", "/tmp/vaultlink-health.json"]
|
||||||
CMD ["--help"]
|
CMD ["--help"]
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export interface CliArgs {
|
||||||
ignorePatterns?: string[];
|
ignorePatterns?: string[];
|
||||||
webSocketRetryIntervalMs?: number;
|
webSocketRetryIntervalMs?: number;
|
||||||
logLevel: LogLevel;
|
logLevel: LogLevel;
|
||||||
|
health?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseArgs(argv: string[]): CliArgs {
|
export function parseArgs(argv: string[]): CliArgs {
|
||||||
|
|
@ -51,6 +52,10 @@ export function parseArgs(argv: string[]): CliArgs {
|
||||||
"[OPTIONAL] Log level (DEBUG, INFO, WARNING, ERROR)",
|
"[OPTIONAL] Log level (DEBUG, INFO, WARNING, ERROR)",
|
||||||
"INFO"
|
"INFO"
|
||||||
)
|
)
|
||||||
|
.option(
|
||||||
|
"--health <path>",
|
||||||
|
"[OPTIONAL] Path to health status file for Docker healthcheck"
|
||||||
|
)
|
||||||
.addHelpText(
|
.addHelpText(
|
||||||
"after",
|
"after",
|
||||||
`
|
`
|
||||||
|
|
@ -78,6 +83,7 @@ Examples:
|
||||||
| number
|
| number
|
||||||
| undefined;
|
| undefined;
|
||||||
const logLevelStr = (opts.logLevel as string | undefined) ?? "INFO";
|
const logLevelStr = (opts.logLevel as string | undefined) ?? "INFO";
|
||||||
|
const health = opts.health as string | undefined;
|
||||||
/* eslint-enable @typescript-eslint/no-unsafe-type-assertion */
|
/* eslint-enable @typescript-eslint/no-unsafe-type-assertion */
|
||||||
|
|
||||||
if (localPath === undefined) {
|
if (localPath === undefined) {
|
||||||
|
|
@ -117,6 +123,7 @@ Examples:
|
||||||
maxFileSizeMB: maxFileSizeMb,
|
maxFileSizeMB: maxFileSizeMb,
|
||||||
ignorePatterns: ignorePattern,
|
ignorePatterns: ignorePattern,
|
||||||
webSocketRetryIntervalMs: websocketRetryIntervalMs,
|
webSocketRetryIntervalMs: websocketRetryIntervalMs,
|
||||||
logLevel
|
logLevel,
|
||||||
|
health
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as fs from "fs/promises";
|
import * as fs from "fs/promises";
|
||||||
|
import * as fsSync from "fs";
|
||||||
|
import type { NetworkConnectionStatus } from "sync-client";
|
||||||
import {
|
import {
|
||||||
SyncClient,
|
SyncClient,
|
||||||
DEFAULT_SETTINGS,
|
DEFAULT_SETTINGS,
|
||||||
|
|
@ -13,6 +15,19 @@ import { FileWatcher } from "./file-watcher";
|
||||||
import { formatLogLine, colorize, styleText } from "./logger-formatter";
|
import { formatLogLine, colorize, styleText } from "./logger-formatter";
|
||||||
import packageJson from "../package.json";
|
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 = {
|
const LOG_LEVEL_ORDER = {
|
||||||
[LogLevel.DEBUG]: 0,
|
[LogLevel.DEBUG]: 0,
|
||||||
[LogLevel.INFO]: 1,
|
[LogLevel.INFO]: 1,
|
||||||
|
|
@ -78,6 +93,7 @@ async function main(): Promise<void> {
|
||||||
syncConcurrency:
|
syncConcurrency:
|
||||||
args.syncConcurrency ?? DEFAULT_SETTINGS.syncConcurrency,
|
args.syncConcurrency ?? DEFAULT_SETTINGS.syncConcurrency,
|
||||||
maxFileSizeMB: args.maxFileSizeMB ?? DEFAULT_SETTINGS.maxFileSizeMB,
|
maxFileSizeMB: args.maxFileSizeMB ?? DEFAULT_SETTINGS.maxFileSizeMB,
|
||||||
|
diffCacheSizeMB: DEFAULT_SETTINGS.diffCacheSizeMB,
|
||||||
ignorePatterns,
|
ignorePatterns,
|
||||||
webSocketRetryIntervalMs:
|
webSocketRetryIntervalMs:
|
||||||
args.webSocketRetryIntervalMs ??
|
args.webSocketRetryIntervalMs ??
|
||||||
|
|
@ -119,6 +135,15 @@ async function main(): Promise<void> {
|
||||||
nativeLineEndings: process.platform === "win32" ? "\r\n" : "\n"
|
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
|
// Add colored log formatter with level filtering
|
||||||
client.logger.addOnMessageListener((logLine) => {
|
client.logger.addOnMessageListener((logLine) => {
|
||||||
// Only show messages at or above the configured log level
|
// 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);
|
const fileWatcher = new FileWatcher(absolutePath, client);
|
||||||
|
|
||||||
client.addWebSocketStatusChangeListener(() => {
|
client.addWebSocketStatusChangeListener(() => {
|
||||||
client.logger.info("WebSocket status changed");
|
const isConnected = client.isWebSocketConnected;
|
||||||
|
client.logger.info(
|
||||||
|
`WebSocket status changed: ${isConnected ? "connected" : "disconnected"}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.addRemainingSyncOperationsListener((remaining) => {
|
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
|
||||||
|
The type guard always returns true for any non-null object, failing to validate the actual structure of NetworkConnectionStatus. This will pass invalid objects that lack required properties like isSuccessful, isWebSocketConnected, or serverMessage, leading to runtime errors when those properties are accessed on line 44.
The type guard always returns true for any non-null object, failing to validate the actual structure of NetworkConnectionStatus. This will pass invalid objects that lack required properties like isSuccessful, isWebSocketConnected, or serverMessage, leading to runtime errors when those properties are accessed on line 44.
```suggestion
const obj = value as Record<string, unknown>;
return (
typeof obj.isSuccessful === "boolean" &&
typeof obj.isWebSocketConnected === "boolean" &&
typeof obj.serverMessage === "string"
);
```
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* 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");
|
const webpack = require("webpack");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: "./src/cli.ts",
|
entry: {
|
||||||
|
cli: "./src/cli.ts",
|
||||||
|
healthcheck: "./src/healthcheck.ts"
|
||||||
|
},
|
||||||
target: "node",
|
target: "node",
|
||||||
mode: "production",
|
mode: "production",
|
||||||
optimization: {
|
optimization: {
|
||||||
|
|
@ -21,7 +24,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
globalObject: "this",
|
globalObject: "this",
|
||||||
filename: "cli.js",
|
filename: "[name].js",
|
||||||
path: path.resolve(__dirname, "dist")
|
path: path.resolve(__dirname, "dist")
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue
The setInterval creates an untracked timer that will continue running indefinitely. Consider storing the timer reference and clearing it during cleanup or process termination to prevent potential resource leaks.