feat: Add Docker environment detection and update mode configuration; enhance update process for Docker and host systems

This commit is contained in:
2025-11-09 23:10:13 +01:00
parent 8b1d95a7c7
commit 26d9cab668
3 changed files with 138 additions and 25 deletions

View File

@@ -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('<27> 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('<27>🔄 Restarting in same process...\n')
// In host mode, signal restart needed
return 0
}
}
// =============================================================================

View File

@@ -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

View File

@@ -943,10 +943,38 @@ async function main(): Promise<void> {
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<void> {
// 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) {