archy/neode-ui/scripts/download-app-icons.js

174 lines
4.6 KiB
JavaScript
Raw Normal View History

chore: baseline codex hardening before lifecycle refactor Snapshots the in-flight hardening work so subsequent reconcile/Quadlet phases land on a clean before/after diff. Changes: - core/container/src/podman_client.rs: image_uses_insecure_registry() whitelist for the OVH (146.59.87.168:3000) and legacy Hetzner (23.182.128.160:3000) HTTP mirrors; podman_network_settings() lifts custom networks into the Networks map so containers can join them. - core/archipelago/src/container/prod_orchestrator.rs: ensure_container_network() creates per-manifest networks on demand; apply_data_uid() now goes through host_sudo for mkdir -p + chown so bind-mount roots get created and chowned without password prompts. - core/archipelago/src/api/rpc/package/{install,update,stacks}.rs: podman pull adds --tls-verify=false only for whitelisted registries. - core/archipelago/src/bootstrap.rs: removes stale dev-mode systemd override on startup (live nodes carried it from old installers). - core/archipelago/src/config.rs: ignore ARCHIPELAGO_DEV_MODE in prod binaries — it had been silently rerouting volumes to /tmp. - apps/bitcoin-{core,knots}/manifest.yml: locate bitcoind at runtime so image-layout differences don't break entrypoint. - scripts/app-catalog-image-smoke-test.py: production catalog/image smoke test that probes a target node before users click Install. - .gitignore: cover .codex, .pnpm-store, __pycache__, *.bak. Removes filebrowser.rs.bak and two stale catalog.json.bak files (verified identical to live counterparts). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 08:52:29 -04:00
#!/usr/bin/env node
/**
* Script to download app icons from GitHub repositories
* Downloads icons for all dummy apps from Start9Labs/{app-id}-startos repos
*/
import fs from 'fs'
import path from 'path'
import https from 'https'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const appIds = [
'bitcoin',
'btcpay-server',
'homeassistant',
'grafana',
'endurain',
'fedimint',
'morphos-server',
'lightning-stack',
'mempool',
'ollama',
'searxng',
'onlyoffice',
'penpot'
]
// Map app IDs to their Start9 repo names (some might differ)
const repoMap = {
'bitcoin': 'bitcoind-startos',
'btcpay-server': 'btcpayserver-startos',
'homeassistant': 'home-assistant-startos',
'grafana': 'grafana-startos',
'lightning-stack': 'lnd-startos',
'mempool': 'mempool-startos',
'searxng': 'searxng-startos',
'onlyoffice': 'onlyoffice-startos',
'penpot': 'penpot-startos',
}
// Custom icon URLs for apps without Start9 repos
const customIconUrls = {
'fedimint': [
'https://raw.githubusercontent.com/fedibtc/fedimint-ui/master/apps/router/public/favicon.svg',
],
}
const iconDir = path.join(__dirname, '../public/assets/img/app-icons')
// Ensure directory exists
if (!fs.existsSync(iconDir)) {
fs.mkdirSync(iconDir, { recursive: true })
}
function downloadFile(url, filepath) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(filepath)
https.get(url, (response) => {
if (response.statusCode === 200) {
response.pipe(file)
file.on('finish', () => {
file.close()
console.log(`✅ Downloaded: ${path.basename(filepath)}`)
resolve()
})
} else if (response.statusCode === 404) {
file.close()
fs.unlinkSync(filepath) // Delete empty file
console.log(`⚠️ Not found: ${url}`)
reject(new Error(`404: ${url}`))
} else {
file.close()
fs.unlinkSync(filepath)
reject(new Error(`HTTP ${response.statusCode}: ${url}`))
}
}).on('error', (err) => {
file.close()
if (fs.existsSync(filepath)) {
fs.unlinkSync(filepath)
}
reject(err)
})
})
}
async function downloadIcon(appId) {
const targetExt = 'webp' // Prefer webp for consistency with mempool, etc.
const fallbackExts = ['webp', 'png', 'svg']
const filepath = path.join(iconDir, `${appId}.webp`)
// Skip if file already exists
if (appId === 'fedimint' && fs.existsSync(path.join(iconDir, 'fedimint.png'))) {
console.log(`⏭️ Skipping ${appId} (fedimint.png exists)`)
return true
}
for (const ext of fallbackExts) {
const fp = path.join(iconDir, `${appId}.${ext}`)
if (fs.existsSync(fp)) {
console.log(`⏭️ Skipping ${appId} (already exists)`)
return true
}
}
// Try custom URLs first (e.g. fedimint from fedimint-ui)
if (customIconUrls[appId]) {
for (const url of customIconUrls[appId]) {
try {
const ext = url.endsWith('.svg') ? 'svg' : (url.endsWith('.png') ? 'png' : 'webp')
const fp = path.join(iconDir, `${appId}.${ext}`)
await downloadFile(url, fp)
return true
} catch (err) {
continue
}
}
}
const repoName = repoMap[appId] || `${appId}-startos`
const iconPaths = ['icon.png', 'icon.svg', 'assets/icon.png', 'assets/icon.svg']
for (const iconPath of iconPaths) {
const url = `https://raw.githubusercontent.com/Start9Labs/${repoName}/main/${iconPath}`
const extension = iconPath.endsWith('.svg') ? 'svg' : 'png'
const fp = path.join(iconDir, `${appId}.${extension}`)
try {
await downloadFile(url, fp)
return true
} catch (err) {
continue
}
}
console.log(`❌ Failed to download icon for ${appId}`)
return false
}
async function main() {
console.log('Downloading app icons from GitHub...\n')
const results = {
success: [],
failed: []
}
for (const appId of appIds) {
try {
const success = await downloadIcon(appId)
if (success) {
results.success.push(appId)
} else {
results.failed.push(appId)
}
// Small delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 500))
} catch (err) {
console.error(`Error downloading ${appId}:`, err.message)
results.failed.push(appId)
}
}
console.log(`\n✅ Successfully downloaded ${results.success.length} icons`)
if (results.failed.length > 0) {
console.log(`❌ Failed to download ${results.failed.length} icons:`, results.failed.join(', '))
}
}
main().catch(console.error)