diff --git a/setup/update/update.mjs b/setup/update/update.mjs index b6a68fb..f41f0ab 100644 --- a/setup/update/update.mjs +++ b/setup/update/update.mjs @@ -196,6 +196,69 @@ async function extractZip(zipPath, destDir) { throw new Error('No extraction tool found (unzip, tar, or PowerShell required)') } +// ============================================================================= +// ENVIRONMENT DETECTION +// ============================================================================= + +/** + * Detect if running inside a Docker container + * Checks multiple indicators for accuracy + */ +function isDocker() { + try { + // Method 1: Check for /.dockerenv file (most reliable) + if (existsSync('/.dockerenv')) { + return true + } + + // Method 2: Check /proc/1/cgroup for docker + if (existsSync('/proc/1/cgroup')) { + const cgroupContent = readFileSync('/proc/1/cgroup', 'utf8') + if (cgroupContent.includes('docker') || cgroupContent.includes('/kubepods/')) { + return true + } + } + + // Method 3: Check environment variables + if (process.env.DOCKER === 'true' || + process.env.CONTAINER === 'docker' || + process.env.KUBERNETES_SERVICE_HOST) { + return true + } + + // Method 4: Check /proc/self/mountinfo for overlay filesystem + if (existsSync('/proc/self/mountinfo')) { + const mountinfo = readFileSync('/proc/self/mountinfo', 'utf8') + if (mountinfo.includes('docker') || mountinfo.includes('overlay')) { + return true + } + } + + return false + } catch { + // If any error occurs (e.g., on Windows), assume not Docker + return false + } +} + +/** + * Determine update mode based on config and environment + */ +function getUpdateMode(configData) { + const dockerMode = configData?.update?.dockerMode || 'auto' + + if (dockerMode === 'force-docker') { + return 'docker' + } + + if (dockerMode === 'force-host') { + return 'host' + } + + // Auto-detect + return isDocker() ? 'docker' : 'host' +} + // ============================================================================= // MAIN UPDATE LOGIC // ============================================================================= @@ -281,17 +344,22 @@ async function performUpdate() { return 0 // Exit without creating update marker } - console.log(`\nšŸ“¦ Update available: ${versionCheck.localVersion} → ${versionCheck.remoteVersion}`) - console.log('ā³ Updating... (this may take a moment)\n') - - // Step 1: Read user preferences (silent) + // Step 0.5: Detect environment and determine update mode const configData = readJsonConfig([ 'src/config.jsonc', 'config.jsonc', 'src/config.json', 'config.json' ]) - + + const updateMode = getUpdateMode(configData) + const envIcon = updateMode === 'docker' ? '🐳' : 'šŸ’»' + + console.log(`\nšŸ“¦ Update available: ${versionCheck.localVersion} → ${versionCheck.remoteVersion}`) + console.log(`${envIcon} Environment: ${updateMode === 'docker' ? 'Docker container' : 'Host system'}`) + console.log('ā³ Updating... (this may take a moment)\n') + + // Step 1: Read user preferences (silent) const userConfig = { autoUpdateConfig: configData?.update?.autoUpdateConfig ?? false, autoUpdateAccounts: configData?.update?.autoUpdateAccounts ?? false @@ -570,9 +638,19 @@ async function performUpdate() { rmSync(rollbackDir, { recursive: true, force: true }) console.log(`\nāœ… Updated successfully! (${versionCheck.localVersion} → ${versionCheck.remoteVersion})`) - console.log('šŸ”„ Restarting...\n') - - return 0 + + // Different behavior for Docker vs Host + if (updateMode === 'docker') { + console.log('ļæ½ Docker mode: Update complete') + console.log(' Container will restart automatically if configured\n') + // In Docker, don't restart - let orchestrator handle it + // Just exit cleanly so Docker can restart the container + return 0 + } else { + console.log('ļæ½šŸ”„ Restarting in same process...\n') + // In host mode, signal restart needed + return 0 + } } // ============================================================================= diff --git a/src/config.jsonc b/src/config.jsonc index 99a9ade..29acf3c 100644 --- a/src/config.jsonc +++ b/src/config.jsonc @@ -170,7 +170,7 @@ "update": { "enabled": true, "method": "github-api", - "docker": false, + "dockerMode": "auto", // "auto" = detect automatically, "force-docker" = always use Docker mode, "force-host" = never use Docker mode "scriptPath": "setup/update/update.mjs", "autoUpdateConfig": true, "autoUpdateAccounts": false diff --git a/src/index.ts b/src/index.ts index a3f48bb..1c55e8a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -943,10 +943,38 @@ async function main(): Promise { process.exit(code) } + /** + * Detect if running in Docker container + */ + const isDockerEnvironment = (): boolean => { + try { + // Check /.dockerenv file + if (fs.existsSync('/.dockerenv')) return true + + // Check /proc/1/cgroup + if (fs.existsSync('/proc/1/cgroup')) { + const content = fs.readFileSync('/proc/1/cgroup', 'utf8') + if (content.includes('docker') || content.includes('/kubepods/')) return true + } + + // Check environment variables + if (process.env.DOCKER === 'true' || + process.env.CONTAINER === 'docker' || + process.env.KUBERNETES_SERVICE_HOST) { + return true + } + + return false + } catch { + return false + } + } + const bootstrap = async () => { try { // Check for updates BEFORE initializing and running tasks const updateMarkerPath = path.join(process.cwd(), '.update-happened') + const isDocker = isDockerEnvironment() try { const updateResult = await rewardsBot.runAutoUpdate().catch((e) => { @@ -966,23 +994,30 @@ async function main(): Promise { // Ignore cleanup errors } - // Clear Node's require cache to reload updated modules - Object.keys(require.cache).forEach(key => { - // Only clear cache for project files, not node_modules - if (key.includes('dist') || key.includes('src')) { - delete require.cache[key] - } - }) - - // Recursive restart in same process - log('main', 'UPDATE', 'Reloading with new version...') - setTimeout(() => { - bootstrap().catch(e => { - log('main', 'MAIN-ERROR', 'Fatal after update: ' + (e instanceof Error ? e.message : e), 'error') - process.exit(1) + if (isDocker) { + // Docker mode: exit cleanly to let container restart + log('main', 'UPDATE', 'Update complete - exiting for container restart', 'log', 'green') + process.exit(0) + } else { + // Host mode: reload in same process + // Clear Node's require cache to reload updated modules + Object.keys(require.cache).forEach(key => { + // Only clear cache for project files, not node_modules + if (key.includes('dist') || key.includes('src')) { + delete require.cache[key] + } }) - }, 500) - return + + // Recursive restart in same process + log('main', 'UPDATE', 'Reloading with new version...') + setTimeout(() => { + bootstrap().catch(e => { + log('main', 'MAIN-ERROR', 'Fatal after update: ' + (e instanceof Error ? e.message : e), 'error') + process.exit(1) + }) + }, 500) + return + } } } } catch (updateError) {