/** * Configuration management for Coolify MCP server. * * Simplified config that reads from ~/.config/coolify-mks-cli-mcp/config.json * or falls back to environment variables. * * @module */ import { readFile, writeFile, mkdir } from "node:fs/promises"; import { existsSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; import { ok, err, isOk, type Result } from "@mks2508/no-throw"; const CONFIG_DIR = join(homedir(), ".config", "coolify-mks-cli-mcp"); const CONFIG_FILE = join(CONFIG_DIR, "config.json"); /** * Coolify configuration structure. */ export interface ICoolifyConfig { /** Coolify instance URL */ url?: string; /** Coolify API token */ token?: string; } /** * Loads Coolify configuration from config file or environment variables. * * Priority: Config file > Environment variables * * @returns Result with configuration or error * * @example * ```typescript * const result = await loadConfig() * if (isOk(result)) { * console.log('Coolify URL:', result.value.url) * } * ``` */ export async function loadConfig(): Promise> { try { if (existsSync(CONFIG_FILE)) { const content = await readFile(CONFIG_FILE, "utf-8"); const parsed = JSON.parse(content) as ICoolifyConfig; return ok(parsed); } // Fallback to environment variables return ok({ url: process.env.COOLIFY_URL, token: process.env.COOLIFY_TOKEN, }); } catch (error) { return err(error instanceof Error ? error : new Error(String(error))); } } /** * Saves Coolify configuration to file. * * Creates the config directory if it doesn't exist. * * @param config - Configuration to save * @returns Result indicating success or error * * @example * ```typescript * const result = await saveConfig({ url: 'https://coolify.example.com', token: 'xxx' }) * ``` */ export async function saveConfig( config: ICoolifyConfig, ): Promise> { try { if (!existsSync(CONFIG_DIR)) { await mkdir(CONFIG_DIR, { recursive: true }); } await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2)); return ok(undefined); } catch (error) { return err(error instanceof Error ? error : new Error(String(error))); } } /** * Gets the Coolify URL from config or environment. * * @returns Coolify URL or undefined */ export async function getCoolifyUrl(): Promise { const result = await loadConfig(); if (isOk(result)) { return result.value.url || process.env.COOLIFY_URL; } return process.env.COOLIFY_URL; } /** * Gets the Coolify token from config or environment. * * @returns Coolify token or undefined */ export async function getCoolifyToken(): Promise { const result = await loadConfig(); if (isOk(result)) { return result.value.token || process.env.COOLIFY_TOKEN; } return process.env.COOLIFY_TOKEN; } // ─── App Settings Cache ────────────────────────────────────────────────────── // Coolify API GET does not return application_settings (is_auto_deploy_enabled, // is_force_https_enabled, etc). We cache these locally when the user sets them // via our CLI so we can display them later in `show`. const SETTINGS_CACHE_FILE = join(CONFIG_DIR, "app-settings-cache.json"); /** * Cached application settings that the Coolify API doesn't expose in GET responses. */ export interface ICachedAppSettings { /** Auto-deploy on git push */ isAutoDeployEnabled?: boolean; /** Watch paths for selective deploy */ watchPaths?: string | null; /** When this cache entry was last updated */ cachedAt?: string; } /** * Reads the full settings cache from disk. * * @returns Map of appUuid → cached settings */ async function readSettingsCache(): Promise> { try { if (existsSync(SETTINGS_CACHE_FILE)) { const content = await readFile(SETTINGS_CACHE_FILE, "utf-8"); return JSON.parse(content); } } catch { // Corrupted file — start fresh } return {}; } /** * Caches application settings locally after a successful PATCH. * * @param appUuid - Application UUID * @param settings - Settings to cache (merged with existing) */ export async function cacheAppSettings( appUuid: string, settings: Partial, ): Promise { try { const cache = await readSettingsCache(); cache[appUuid] = { ...cache[appUuid], ...settings, cachedAt: new Date().toISOString(), }; if (!existsSync(CONFIG_DIR)) { await mkdir(CONFIG_DIR, { recursive: true }); } await writeFile(SETTINGS_CACHE_FILE, JSON.stringify(cache, null, 2)); } catch { // Non-critical — silently ignore cache write failures } } /** * Reads cached settings for an application. * * @param appUuid - Application UUID * @returns Cached settings or null if not cached */ export async function getCachedAppSettings( appUuid: string, ): Promise { const cache = await readSettingsCache(); return cache[appUuid] || null; } export { CONFIG_DIR, CONFIG_FILE };