/** * Service set-credentials command. * * Credential values travel only by flag or environment variable (D7). Fields * omitted from the invocation retain their existing encrypted values, which * lets an operator move a provider endpoint without rotating its API token. */ import { type DigitalOceanCredentials, type ProxmoxCredentials, getContainerServiceByServiceId, getServiceCredentials, updateServiceCredentials, } from '../../services/container-service'; import type { CommandResult } from '../types'; type Flags = Record; function credentialUpdateValue(flags: Flags, flag: string, envVar: string): string | undefined { const flagValue = flags[flag]; if (flagValue === true) { throw new Error(`--${flag} requires a value`); } if (typeof flagValue === 'string') { const value = flagValue.trim(); if (!value) throw new Error(`--${flag} requires a non-empty value`); return value; } const envValue = process.env[envVar]?.trim(); return envValue || undefined; } export async function handleServiceSetCredentials( args: string[], flags: Flags = {}, ): Promise { const serviceId = args[0]; if (!serviceId) { return { success: false, error: 'Service ID is required\n\nUsage: celilo service set-credentials [options]', }; } try { const service = await getContainerServiceByServiceId(serviceId); if (!service) { return { success: false, error: `Service not found: ${serviceId}\n\nRun 'celilo service list' to see available services.`, }; } const current = await getServiceCredentials(service.id); let changed = false; if (service.providerName === 'proxmox') { const existing = current as ProxmoxCredentials; const apiUrl = credentialUpdateValue(flags, 'api-url', 'PROXMOX_API_URL'); const apiTokenId = credentialUpdateValue(flags, 'api-token-id', 'PROXMOX_API_TOKEN_ID'); const apiTokenSecret = credentialUpdateValue( flags, 'api-token-secret', 'PROXMOX_API_TOKEN_SECRET', ); changed = Boolean(apiUrl || apiTokenId || apiTokenSecret); if (changed) { await updateServiceCredentials(service.id, { api_url: apiUrl ?? existing.api_url, api_token_id: apiTokenId ?? existing.api_token_id, api_token_secret: apiTokenSecret ?? existing.api_token_secret, }); } } else if (service.providerName === 'digitalocean') { const existing = current as DigitalOceanCredentials; const apiToken = credentialUpdateValue(flags, 'api-token', 'DIGITALOCEAN_API_TOKEN'); changed = Boolean(apiToken); if (changed) { await updateServiceCredentials(service.id, { api_token: apiToken ?? existing.api_token, }); } } else { return { success: false, error: `Credential updates are not supported for provider: ${service.providerName}`, }; } if (!changed) { return { success: false, error: service.providerName === 'proxmox' ? 'No credential changes supplied. Pass --api-url, --api-token-id, or --api-token-secret (or set the corresponding PROXMOX_API_* environment variable).' : 'No credential changes supplied. Pass --api-token or set $DIGITALOCEAN_API_TOKEN.', }; } return { success: true, message: `Updated credentials for service '${serviceId}'. Verification status cleared; run: celilo service verify ${serviceId}`, }; } catch (error) { return { success: false, error: `Failed to update service credentials: ${error instanceof Error ? error.message : String(error)}`, }; } }