Changeset 7a235bc in Klonkt
- Timestamp:
- 06/25/2026 05:56:08 PM (3 months ago)
- Branches:
- main
- Children:
- f0c3f83
- Parents:
- 6a51968
- Location:
- src
- Files:
-
- 3 edited
-
routes/admin-updates.js (modified) (5 diffs)
-
services/i18n.js (modified) (3 diffs)
-
views/pages/admin-updates.ejs (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/admin-updates.js
r6a51968 r7a235bc 1 1 /** 2 2 * Admin: Updates (god-only). 3 * Git-based v1 for instances running from a bare repo.4 3 * GET /admin/updates -> current vs. latest version + status 5 * POST /admin/updates/run -> fetch latest main + restart (detached script)4 * POST /admin/updates/run -> fetch latest + restart (fleet only; see below) 6 5 * 7 * The instance knows its "current" commit from .klonkt-version (written by the 8 * script) and the "latest" from the bare repo (KLONKT_GIT_DIR). For external 9 * self-hosters a SIGNED release feed will follow later (see monetization plan); 10 * this v1 is intentionally simple and only for Robin's own VPS instances. 6 * Two topologies are supported, detected automatically: 7 * - CHECKOUT (external self-hoster): the app dir is itself a git clone with 8 * origin = GitHub. "Latest" = origin/<branch> (fetched on view). Updating is 9 * done out-of-band by `klonkt-update` (needs root for systemd), so the page 10 * shows that command instead of an in-app button. 11 * - BARE (Robin's own VPS fleet): a bare repo at KLONKT_GIT_DIR; the app dir is 12 * a `checkout -f` work-tree (no .git). "Latest" = <branch>. The detached 13 * self-update script runs in-app (no root needed) → the button works. 14 * git stderr is ignored so a foreign/missing repo never spams "fatal: ...". 11 15 */ 12 16 … … 21 25 22 26 const HOME = process.env.HOME || ''; 27 const APP_DIR = process.cwd(); 28 const BRANCH = process.env.KLONKT_BRANCH || 'main'; 23 29 const GIT_DIR = process.env.KLONKT_GIT_DIR || path.join(HOME, 'git-repos/prutfolio.git'); 24 30 const UPDATE_SCRIPT = process.env.KLONKT_UPDATE_SCRIPT || path.join(HOME, 'bin/klonkt-self-update.sh'); 25 31 26 function versionFile() { return path.join(process.cwd(), '.klonkt-version'); } 32 // The app dir is a git CHECKOUT (GitHub install) when it has a .git; otherwise we 33 // fall back to the BARE repo (fleet). This split keeps the version check pointed at 34 // a repo that actually exists, so it never logs "fatal: not a git repository". 35 const IS_CHECKOUT = (() => { try { return fs.existsSync(path.join(APP_DIR, '.git')); } catch { return false; } })(); 36 const REMOTE_REF = IS_CHECKOUT ? `origin/${BRANCH}` : BRANCH; // what "latest" resolves to 37 27 38 function appVersion() { 28 try { return JSON.parse(fs.readFileSync(path.join( process.cwd(), 'package.json'), 'utf8')).version || null; }39 try { return JSON.parse(fs.readFileSync(path.join(APP_DIR, 'package.json'), 'utf8')).version || null; } 29 40 catch { return null; } 30 41 } 42 // stderr is ignored on purpose → a missing/foreign repo fails silently (returns null). 31 43 function git(args) { 32 try { return execFileSync('git', ['--git-dir', GIT_DIR, ...args], { encoding: 'utf8', timeout: 5000 }).trim(); } 33 catch { return null; } 44 try { 45 const base = IS_CHECKOUT ? ['-C', APP_DIR] : ['--git-dir', GIT_DIR]; 46 return execFileSync('git', [...base, ...args], { encoding: 'utf8', timeout: 8000, stdio: ['ignore', 'pipe', 'ignore'] }).trim(); 47 } catch { return null; } 34 48 } 35 49 function currentSha() { 36 try { return fs.readFileSync(versionFile(), 'utf8').trim() || null; } catch { return null; } 50 if (IS_CHECKOUT) return git(['rev-parse', 'HEAD']); 51 try { return fs.readFileSync(path.join(APP_DIR, '.klonkt-version'), 'utf8').trim() || null; } catch { return null; } 37 52 } 38 53 39 // Last 5 commits on main= the "recent changes" you'll get when updating.54 // Last 5 commits = the "recent changes" you'll get when updating. 40 55 function recentChanges() { 41 const out = git(['log', '-5', '--format=%s%x1f%cd', '--date=short', 'main']);56 const out = git(['log', '-5', '--format=%s%x1f%cd', '--date=short', REMOTE_REF]); 42 57 if (!out) return []; 43 58 return out.split('\n').map((l) => { … … 48 63 49 64 router.get('/', requireGod, (req, res) => { 65 // For a GitHub checkout, refresh the remote ref so "latest" is current. Quiet + 66 // shallow; offline just leaves the last-known ref. stderr ignored (no log noise). 67 if (IS_CHECKOUT) { 68 try { execFileSync('git', ['-C', APP_DIR, 'fetch', '--quiet', '--depth', '1', 'origin', BRANCH], { timeout: 20000, stdio: 'ignore' }); } catch { /* offline / no remote */ } 69 } 50 70 const cur = currentSha(); 51 const latest = git(['rev-parse', 'main']); 71 const latest = git(['rev-parse', REMOTE_REF]); 72 // The in-app "Update now" button only works with the detached self-update script 73 // (the fleet). A systemd install updates via `klonkt-update` (root) → show that. 74 const canSelfUpdate = (() => { try { return fs.existsSync(UPDATE_SCRIPT); } catch { return false; } })(); 52 75 renderPage(req, res, 'pages/admin-updates', { 53 76 pageTitle: 'Updates', … … 57 80 currentDesc: cur ? git(['log', '-1', '--format=%s · %cd', '--date=short', cur]) : null, 58 81 latestSha: latest ? latest.slice(0, 8) : null, 59 latestDesc: latest ? git(['log', '-1', '--format=%s · %cd', '--date=short', 'main']) : null,82 latestDesc: latest ? git(['log', '-1', '--format=%s · %cd', '--date=short', REMOTE_REF]) : null, 60 83 upToDate: !!(cur && latest && cur === latest), 61 84 canCheck: !!latest, 62 behind: (cur && latest && cur !== latest) ? git(['rev-list', '--count', cur + '..main']) : null, 85 canSelfUpdate, 86 manualCommand: (!canSelfUpdate && IS_CHECKOUT) ? 'sudo klonkt-update' : null, 87 behind: (cur && latest && cur !== latest) ? git(['rev-list', '--count', cur + '..' + REMOTE_REF]) : null, 63 88 changes: recentChanges(), 64 89 success: req.query.success || null, … … 69 94 router.post('/run', requireGod, (req, res) => { 70 95 if (!fs.existsSync(UPDATE_SCRIPT)) { 71 return res.redirect('/admin/updates?error=' + encodeURIComponent(' Update-script ontbreektop de server.'));96 return res.redirect('/admin/updates?error=' + encodeURIComponent('In-app updaten is hier niet beschikbaar — werk bij met `klonkt-update` op de server.')); 72 97 } 73 98 try { 74 // Detached + unlinked: survives the pm2-reload that restarts this app.75 const child = spawn('bash', [UPDATE_SCRIPT, process.cwd()], { detached: true, stdio: 'ignore' });99 // Detached + unlinked: survives the reload that restarts this app. 100 const child = spawn('bash', [UPDATE_SCRIPT, APP_DIR], { detached: true, stdio: 'ignore' }); 76 101 child.unref(); 77 102 } catch (e) { -
src/services/i18n.js
r6a51968 r7a235bc 628 628 'aupd.update_now': 'Nu bijwerken', 629 629 'aupd.help': 'Bijwerken haalt de nieuwste code op en herstart deze site kort (~10s). Doe dit rustig — er gaat niets verloren (je posts, instellingen en cirkel blijven staan).', 630 'aupd.manual_hint': 'Werk bij vanaf GitHub door dit op je server uit te voeren:', 630 631 'aepk.title': 'Perskit bewerken', 631 632 'aepk.back_admin': 'Beheer', … … 1625 1626 'aupd.update_now': 'Update now', 1626 1627 'aupd.help': 'Updating fetches the latest code and restarts this site briefly (~10s). Take your time — nothing is lost (your posts, settings and circle stay intact).', 1628 'aupd.manual_hint': 'Update from GitHub by running this on your server:', 1627 1629 'aepk.title': 'Edit press kit', 1628 1630 'aepk.back_admin': 'Admin', … … 2621 2623 'aupd.update_now': 'Jetzt aktualisieren', 2622 2624 'aupd.help': 'Das Aktualisieren holt den neuesten Code und startet diese Seite kurz neu (~10s). Lass dir Zeit — es geht nichts verloren (deine Beiträge, Einstellungen und dein Zirkel bleiben erhalten).', 2625 'aupd.manual_hint': 'Aktualisiere von GitHub, indem du dies auf deinem Server ausführst:', 2623 2626 'aepk.title': 'Pressekit bearbeiten', 2624 2627 'aepk.back_admin': 'Verwaltung', -
src/views/pages/admin-updates.ejs
r6a51968 r7a235bc 38 38 </div> 39 39 40 <% if (canCheck ) { %>40 <% if (canCheck && canSelfUpdate) { %> 41 41 <form method="post" action="/admin/updates/run" class="set-form" 42 42 onsubmit="return confirm('<%= t('aupd.run_confirm') %>');"> … … 45 45 </button> 46 46 </form> 47 <% } else if (typeof manualCommand !== 'undefined' && manualCommand && !upToDate) { %> 48 <p class="set-help" style="margin:.2rem 0 .45rem"><%= t('aupd.manual_hint') %></p> 49 <pre class="upd-cmd"><code><%= manualCommand %></code></pre> 47 50 <% } %> 48 51 … … 76 79 .upd-desc { color: var(--ink-muted); } 77 80 .upd-status { margin: 1.1rem 0; } 81 .upd-cmd { background: var(--paper); border: 1px solid var(--rule); border-radius: 6px; padding: .6rem .8rem; margin: 0; overflow-x: auto; } 82 .upd-cmd code { font-size: .9em; } 78 83 .upd-badge { font-size: .8rem; font-weight: 700; padding: .25rem .7rem; border-radius: 20px; } 79 84 .upd-badge.ok { background: #d1fae5; color: #065f46; }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)