| 1 | import fs from 'node:fs'; |
| 2 | import path from 'node:path'; |
| 3 | import yaml from 'yaml'; |
| 4 | import color from 'chalk'; |
| 5 | import _ from 'lodash'; |
| 6 | import { serverDirectory } from './server-directory.js'; |
| 7 | import { keyToEnv, setConfigFilePath } from './util.js'; |
| 8 | |
| 9 | const keyMigrationMap = [ |
| 10 | { |
| 11 | oldKey: 'disableThumbnails', |
| 12 | newKey: 'thumbnails.enabled', |
| 13 | migrate: (value) => !value, |
| 14 | }, |
| 15 | { |
| 16 | oldKey: 'thumbnailsQuality', |
| 17 | newKey: 'thumbnails.quality', |
| 18 | migrate: (value) => value, |
| 19 | }, |
| 20 | { |
| 21 | oldKey: 'avatarThumbnailsPng', |
| 22 | newKey: 'thumbnails.format', |
| 23 | migrate: (value) => (value ? 'png' : 'jpg'), |
| 24 | }, |
| 25 | { |
| 26 | oldKey: 'disableChatBackup', |
| 27 | newKey: 'backups.chat.enabled', |
| 28 | migrate: (value) => !value, |
| 29 | }, |
| 30 | { |
| 31 | oldKey: 'numberOfBackups', |
| 32 | newKey: 'backups.common.numberOfBackups', |
| 33 | migrate: (value) => value, |
| 34 | }, |
| 35 | { |
| 36 | oldKey: 'maxTotalChatBackups', |
| 37 | newKey: 'backups.chat.maxTotalBackups', |
| 38 | migrate: (value) => value, |
| 39 | }, |
| 40 | { |
| 41 | oldKey: 'chatBackupThrottleInterval', |
| 42 | newKey: 'backups.chat.throttleInterval', |
| 43 | migrate: (value) => value, |
| 44 | }, |
| 45 | { |
| 46 | oldKey: 'enableExtensions', |
| 47 | newKey: 'extensions.enabled', |
| 48 | migrate: (value) => value, |
| 49 | }, |
| 50 | { |
| 51 | oldKey: 'enableExtensionsAutoUpdate', |
| 52 | newKey: 'extensions.autoUpdate', |
| 53 | migrate: (value) => value, |
| 54 | }, |
| 55 | { |
| 56 | oldKey: 'extras.disableAutoDownload', |
| 57 | newKey: 'extensions.models.autoDownload', |
| 58 | migrate: (value) => !value, |
| 59 | }, |
| 60 | { |
| 61 | oldKey: 'extras.classificationModel', |
| 62 | newKey: 'extensions.models.classification', |
| 63 | migrate: (value) => value, |
| 64 | }, |
| 65 | { |
| 66 | oldKey: 'extras.captioningModel', |
| 67 | newKey: 'extensions.models.captioning', |
| 68 | migrate: (value) => value, |
| 69 | }, |
| 70 | { |
| 71 | oldKey: 'extras.embeddingModel', |
| 72 | newKey: 'extensions.models.embedding', |
| 73 | migrate: (value) => value, |
| 74 | }, |
| 75 | { |
| 76 | oldKey: 'extras.speechToTextModel', |
| 77 | newKey: 'extensions.models.speechToText', |
| 78 | migrate: (value) => value, |
| 79 | }, |
| 80 | { |
| 81 | oldKey: 'extras.textToSpeechModel', |
| 82 | newKey: 'extensions.models.textToSpeech', |
| 83 | migrate: (value) => value, |
| 84 | }, |
| 85 | { |
| 86 | oldKey: 'minLogLevel', |
| 87 | newKey: 'logging.minLogLevel', |
| 88 | migrate: (value) => value, |
| 89 | }, |
| 90 | { |
| 91 | oldKey: 'cardsCacheCapacity', |
| 92 | newKey: 'performance.memoryCacheCapacity', |
| 93 | migrate: (value) => `${value}mb`, |
| 94 | }, |
| 95 | { |
| 96 | oldKey: 'cookieSecret', |
| 97 | newKey: 'cookieSecret', |
| 98 | migrate: () => void 0, |
| 99 | remove: true, |
| 100 | }, |
| 101 | { |
| 102 | oldKey: 'autorun', |
| 103 | newKey: 'browserLaunch.enabled', |
| 104 | migrate: (value) => value, |
| 105 | }, |
| 106 | { |
| 107 | oldKey: 'autorunHostname', |
| 108 | newKey: 'browserLaunch.hostname', |
| 109 | migrate: (value) => value, |
| 110 | }, |
| 111 | { |
| 112 | oldKey: 'autorunPortOverride', |
| 113 | newKey: 'browserLaunch.port', |
| 114 | migrate: (value) => value, |
| 115 | }, |
| 116 | { |
| 117 | oldKey: 'avoidLocalhost', |
| 118 | newKey: 'browserLaunch.avoidLocalhost', |
| 119 | migrate: (value) => value, |
| 120 | }, |
| 121 | { |
| 122 | oldKey: 'extras.promptExpansionModel', |
| 123 | newKey: 'extras.promptExpansionModel', |
| 124 | migrate: () => void 0, |
| 125 | remove: true, |
| 126 | }, |
| 127 | { |
| 128 | oldKey: 'autheliaAuth', |
| 129 | newKey: 'sso.autheliaAuth', |
| 130 | migrate: (value) => value, |
| 131 | }, |
| 132 | { |
| 133 | oldKey: 'authentikAuth', |
| 134 | newKey: 'sso.authentikAuth', |
| 135 | migrate: (value) => value, |
| 136 | }, |
| 137 | ]; |
| 138 | |
| 139 | /** |
| 140 | * Gets all keys from an object recursively. |
| 141 | * @param {object} obj Object to get all keys from |
| 142 | * @param {string} prefix Prefix to prepend to all keys |
| 143 | * @returns {string[]} Array of all keys in the object |
| 144 | */ |
| 145 | function getAllKeys(obj, prefix = '') { |
| 146 | if (typeof obj !== 'object' || Array.isArray(obj) || obj === null) { |
| 147 | return []; |
| 148 | } |
| 149 | |
| 150 | return _.flatMap(Object.keys(obj), key => { |
| 151 | const newPrefix = prefix ? `${prefix}.${key}` : key; |
| 152 | if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { |
| 153 | return getAllKeys(obj[key], newPrefix); |
| 154 | } else { |
| 155 | return [newPrefix]; |
| 156 | } |
| 157 | }); |
| 158 | } |
| 159 | |
| 160 | /** |
| 161 | * Compares the current config.yaml with the default config.yaml and adds any missing values. |
| 162 | * @param {string} configPath Path to config.yaml |
| 163 | */ |
| 164 | export function addMissingConfigValues(configPath) { |
| 165 | try { |
| 166 | const defaultConfig = yaml.parse(fs.readFileSync(path.join(serverDirectory, './default/config.yaml'), 'utf8')); |
| 167 | |
| 168 | if (!fs.existsSync(configPath)) { |
| 169 | console.warn(color.yellow(`Warning: config.yaml not found at ${configPath}. Creating a new one with default values.`)); |
| 170 | fs.writeFileSync(configPath, yaml.stringify(defaultConfig)); |
| 171 | return; |
| 172 | } |
| 173 | |
| 174 | let config = yaml.parse(fs.readFileSync(configPath, 'utf8')); |
| 175 | |
| 176 | // Migrate old keys to new keys |
| 177 | const migratedKeys = []; |
| 178 | for (const { oldKey, newKey, migrate, remove } of keyMigrationMap) { |
| 179 | // Migrate environment variables |
| 180 | const oldEnvKey = keyToEnv(oldKey); |
| 181 | const newEnvKey = keyToEnv(newKey); |
| 182 | if (process.env[oldEnvKey] && !process.env[newEnvKey]) { |
| 183 | const oldValue = process.env[oldEnvKey]; |
| 184 | const newValue = migrate(oldValue); |
| 185 | process.env[newEnvKey] = newValue; |
| 186 | delete process.env[oldEnvKey]; |
| 187 | console.warn(color.yellow(`Warning: Using a deprecated environment variable: ${oldEnvKey}. Please use ${newEnvKey} instead.`)); |
| 188 | console.log(`Redirecting ${color.blue(oldEnvKey)}=${oldValue} -> ${color.blue(newEnvKey)}=${newValue}`); |
| 189 | } |
| 190 | |
| 191 | if (_.has(config, oldKey)) { |
| 192 | if (remove) { |
| 193 | _.unset(config, oldKey); |
| 194 | migratedKeys.push({ |
| 195 | oldKey, |
| 196 | newValue: void 0, |
| 197 | }); |
| 198 | continue; |
| 199 | } |
| 200 | |
| 201 | const oldValue = _.get(config, oldKey); |
| 202 | const newValue = migrate(oldValue); |
| 203 | _.set(config, newKey, newValue); |
| 204 | _.unset(config, oldKey); |
| 205 | |
| 206 | migratedKeys.push({ |
| 207 | oldKey, |
| 208 | newKey, |
| 209 | oldValue, |
| 210 | newValue, |
| 211 | }); |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | // Get all keys from the original config |
| 216 | const originalKeys = getAllKeys(config); |
| 217 | |
| 218 | // Use lodash's defaultsDeep function to recursively apply default properties |
| 219 | config = _.defaultsDeep(config, defaultConfig); |
| 220 | |
| 221 | // Get all keys from the updated config |
| 222 | const updatedKeys = getAllKeys(config); |
| 223 | |
| 224 | // Find the keys that were added |
| 225 | const addedKeys = _.difference(updatedKeys, originalKeys); |
| 226 | |
| 227 | if (addedKeys.length === 0 && migratedKeys.length === 0) { |
| 228 | return; |
| 229 | } |
| 230 | |
| 231 | if (addedKeys.length > 0) { |
| 232 | console.log('Adding missing config values to config.yaml:', addedKeys); |
| 233 | } |
| 234 | |
| 235 | if (migratedKeys.length > 0) { |
| 236 | console.log('Migrating config values in config.yaml:', migratedKeys); |
| 237 | } |
| 238 | |
| 239 | fs.writeFileSync(configPath, yaml.stringify(config)); |
| 240 | } catch (error) { |
| 241 | console.warn(color.yellow('Could not add missing config values to config.yaml'), error); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | /** |
| 246 | * Performs early initialization tasks before the server starts. |
| 247 | * @param {string} configPath Path to config.yaml |
| 248 | */ |
| 249 | export function initConfig(configPath) { |
| 250 | console.log('Using config path:', color.green(configPath)); |
| 251 | setConfigFilePath(configPath); |
| 252 | addMissingConfigValues(configPath); |
| 253 | } |