()
| 199 | } |
| 200 | |
| 201 | ensureConfigExists() { |
| 202 | // If config already exists, nothing to do |
| 203 | if (fs.existsSync(this.configPath)) { |
| 204 | return; |
| 205 | } |
| 206 | |
| 207 | logger.info('Auto-creating config.json from config.example.json template'); |
| 208 | |
| 209 | // Ensure config directory exists |
| 210 | const configDir = path.dirname(this.configPath); |
| 211 | if (!fs.existsSync(configDir)) { |
| 212 | fs.mkdirSync(configDir, { recursive: true }); |
| 213 | logger.info({ path: configDir }, 'Created config directory'); |
| 214 | } |
| 215 | |
| 216 | // Load config.example.json (will throw if not found - which is required) |
| 217 | const examplePath = this.getConfigExamplePath(); |
| 218 | const exampleContent = fs.readFileSync(examplePath, 'utf8'); |
| 219 | const defaultConfig = JSON.parse(exampleContent); |
| 220 | |
| 221 | // Remove comment-only keys |
| 222 | delete defaultConfig['//comment']; |
| 223 | |
| 224 | // Generate UUID for this instance |
| 225 | defaultConfig.uuid = uuidv4(); |
| 226 | |
| 227 | // Apply platform-specific environment variable overrides |
| 228 | if (process.env.PLEX_URL) { |
| 229 | defaultConfig.plexUrl = process.env.PLEX_URL; |
| 230 | logger.info({ plexUrl: process.env.PLEX_URL }, 'Applied PLEX_URL from environment'); |
| 231 | } |
| 232 | |
| 233 | // Write the config file and provide actionable guidance if permissions fail |
| 234 | try { |
| 235 | // mode 0o640 keeps plexApiKey and other secrets out of world-readable (umask is 0o002 → 0o664 default) |
| 236 | fs.writeFileSync(this.configPath, JSON.stringify(defaultConfig, null, 2), { mode: 0o640 }); |
| 237 | this.tightenConfigPermissions(); |
| 238 | logger.info({ configPath: this.configPath }, 'Created config.json from template'); |
| 239 | } catch (error) { |
| 240 | if (error.code === 'EACCES') { |
| 241 | const uid = typeof process.getuid === 'function' ? process.getuid() : null; |
| 242 | const gid = typeof process.getgid === 'function' ? process.getgid() : null; |
| 243 | logger.error( |
| 244 | { |
| 245 | configPath: this.configPath, |
| 246 | uid, |
| 247 | gid, |
| 248 | youtarrUid: process.env.YOUTARR_UID, |
| 249 | youtarrGid: process.env.YOUTARR_GID, |
| 250 | }, |
| 251 | 'Unable to write config.json because bind-mounted config directory is not writable. Ensure host ownership matches YOUTARR_UID/YOUTARR_GID (see README manual compose instructions).' |
| 252 | ); |
| 253 | } |
| 254 | throw error; |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | getConfig() { |
no test coverage detected