@dotrino/vaultd 0.152.0 → 0.153.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/ctl.js +20 -5
- package/src/tui/app.js +77 -3
- package/src/tui/i18n.js +18 -0
- package/src/update.js +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vaultd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.153.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Certificador personal de Dotrino: daemon headless que custodia la clave maestra y delega capacidades a tus dispositivos por el proxy. Tu CA propia.",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@dotrino/identity": "^0.111.0",
|
|
26
26
|
"@dotrino/opaque": "0.1.0",
|
|
27
27
|
"@dotrino/passmanager": "0.17.0",
|
|
28
|
-
"@dotrino/proxy-client": "0.
|
|
28
|
+
"@dotrino/proxy-client": "^0.31.1",
|
|
29
29
|
"@dotrino/roadmap": "0.3.19",
|
|
30
30
|
"@dotrino/store": "0.11.0",
|
|
31
31
|
"@dotrino/update": "0.5.1",
|
package/src/ctl.js
CHANGED
|
@@ -1832,8 +1832,8 @@ async function waitForService (version, { timeoutMs = 4 * 60_000 } = {}) {
|
|
|
1832
1832
|
* marcador que deja el servicio cuando se actualiza solo. Si no se puede escribir se dice:
|
|
1833
1833
|
* la actualización está hecha, lo que se pierde es el aviso.
|
|
1834
1834
|
*/
|
|
1835
|
-
function noteUpdated (to) {
|
|
1836
|
-
try {
|
|
1835
|
+
async function noteUpdated (to) {
|
|
1836
|
+
try { (await import('./update.js')).noteUpdated(dir, { from: readState().version || VERSION, to }) } catch (e) {
|
|
1837
1837
|
console.error('No se pudo dejar el aviso de actualización para tus aprobadores: %s', e.message)
|
|
1838
1838
|
}
|
|
1839
1839
|
}
|
|
@@ -1883,8 +1883,23 @@ async function cmdUpdate (args = []) {
|
|
|
1883
1883
|
}
|
|
1884
1884
|
console.log('firmada por el release de %s ✓', 'imdotrino/dotrino-vault')
|
|
1885
1885
|
console.log('Instalada la %s.', res.version)
|
|
1886
|
-
noteUpdated(res.version)
|
|
1887
|
-
if (
|
|
1886
|
+
await noteUpdated(res.version)
|
|
1887
|
+
if (args.includes('--no-wait')) return
|
|
1888
|
+
// AL TERMINAR SE OFRECE REINICIAR (dueño, 2026-10-13). El servicio se reinicia solo al
|
|
1889
|
+
// ver el binario nuevo, pero espera a verlo quieto dos minutos: quien está delante puede
|
|
1890
|
+
// no querer esperar. Solo se pregunta con alguien delante y con systemd; si no, se espera.
|
|
1891
|
+
const s = ipcRead(stateFile, null)
|
|
1892
|
+
if (s && alive(s.pid) && s.version !== res.version && process.stdin.isTTY && process.platform === 'linux' && !IN_DOCKER) {
|
|
1893
|
+
const resp = (await askText('¿Reiniciar la bóveda ahora para usarla? Cierra las bóvedas abiertas. [s/N] ')).trim().toLowerCase()
|
|
1894
|
+
if (resp === 's' || resp === 'y') {
|
|
1895
|
+
try { execFileSync('systemctl', ['--user', 'restart', 'dotrino-vault'], { stdio: 'inherit' }) } catch (e) {
|
|
1896
|
+
console.error('No se pudo reiniciar: %s', e.message)
|
|
1897
|
+
console.error('Hazlo a mano: %s', RESTART_HINT)
|
|
1898
|
+
process.exit(1)
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
await waitForService(res.version)
|
|
1888
1903
|
return
|
|
1889
1904
|
}
|
|
1890
1905
|
|
|
@@ -1924,7 +1939,7 @@ async function cmdUpdate (args = []) {
|
|
|
1924
1939
|
B, file, r.version, Z)
|
|
1925
1940
|
return
|
|
1926
1941
|
}
|
|
1927
|
-
noteUpdated(r.version)
|
|
1942
|
+
await noteUpdated(r.version)
|
|
1928
1943
|
console.log('Listo. El servicio se reinicia solo; compruébalo con: dotrino-vault status')
|
|
1929
1944
|
}
|
|
1930
1945
|
|
package/src/tui/app.js
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* que entrar antes), el candado es `k` (la `l` es el idioma) y la contraseña `c`.
|
|
31
31
|
*/
|
|
32
32
|
import fs from 'node:fs'
|
|
33
|
-
import { execFile } from 'node:child_process'
|
|
33
|
+
import { execFile, spawnSync } from 'node:child_process'
|
|
34
34
|
import { parseEnvInput } from '../../lib/src/envtext.js'
|
|
35
35
|
import { createTerm, widthOf } from './term.js'
|
|
36
36
|
import { qrToString } from '../qr.js'
|
|
@@ -39,6 +39,7 @@ import * as vc from '../vaultControl.js'
|
|
|
39
39
|
import { readSettings, writeSettings } from '../settings.js'
|
|
40
40
|
import * as hw from '../hwkeys.js'
|
|
41
41
|
import { VERSION } from '../version.js'
|
|
42
|
+
import * as up from '../update.js'
|
|
42
43
|
import { DEVICE_CAPS } from '@dotrino/identity/acta'
|
|
43
44
|
|
|
44
45
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
@@ -184,6 +185,65 @@ function lockGlyph (p) {
|
|
|
184
185
|
return p.locked ? '🔒' : '🔓'
|
|
185
186
|
}
|
|
186
187
|
|
|
188
|
+
function restartDaemonService () {
|
|
189
|
+
return new Promise((res, rej) => {
|
|
190
|
+
execFile('systemctl', ['--user', 'restart', 'dotrino-vault'], { timeout: 20000 }, (err, so, se) => {
|
|
191
|
+
if (err) rej(new Error(String(se || '').trim() || err.message)); else res()
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* AL ENTRAR SE DICE SI HAY VERSIÓN NUEVA, Y SE OFRECE INSTALARLA (dueño, 2026-10-13: como
|
|
198
|
+
* hace la app de Terminal). El servicio ya se actualiza solo; esto es para quien está
|
|
199
|
+
* mirando: enterarse ahí mismo, instalarla sin salir y, al terminar, reiniciar.
|
|
200
|
+
*
|
|
201
|
+
* Se mira EN SEGUNDO PLANO: la pantalla sale al momento y el aviso llega cuando el registro
|
|
202
|
+
* contesta. «No se pudo mirar» no es «estás al día»: no se dice nada y queda en `st.update`.
|
|
203
|
+
*/
|
|
204
|
+
function checkForUpdate (st, { latest = up.latestRelease } = {}) {
|
|
205
|
+
if (VERSION === 'dev') return
|
|
206
|
+
st.update = { checking: true }
|
|
207
|
+
Promise.resolve().then(() => latest()).then((r) => {
|
|
208
|
+
if (!r?.ok) { st.update = { failed: r?.reason || 'unknown' }; return }
|
|
209
|
+
st.update = up.isNewer(r.version, VERSION) ? { release: r, offer: true } : { current: true }
|
|
210
|
+
}).catch((e) => { st.update = { failed: e?.message || String(e) } })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Pregunta UNA vez por sesión. Decir que no deja el aviso arriba, sin volver a preguntar. */
|
|
214
|
+
function offerUpdate (term, st) {
|
|
215
|
+
const i = L(st)
|
|
216
|
+
const r = st.update.release
|
|
217
|
+
st.update = { release: r }
|
|
218
|
+
setConfirm(st, { text: i.updateOffer(r.version, VERSION), onYes: () => installUpdate(term, st, r) })
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function installUpdate (term, st, release, { install = up.installUserRelease, userInstall = up.isUserInstall } = {}) {
|
|
222
|
+
const i = L(st)
|
|
223
|
+
// Solo la instalación de usuario se puede cambiar desde aquí. Un paquete del sistema es
|
|
224
|
+
// de root: no se pide la contraseña por dentro, se dice el comando.
|
|
225
|
+
if (!userInstall()) { flash(st, i.updateNeedsCommand, 'warn'); return }
|
|
226
|
+
const r = await guard(term, st, i.updateInstalling(release.version), () => install(release))
|
|
227
|
+
if (!r.ok) return
|
|
228
|
+
if (!r.v.ok) { flash(st, i.updateFailed(r.v.reason), 'danger'); return }
|
|
229
|
+
try { up.noteUpdated(vc.vaultDir(), { from: st.state?.version || VERSION, to: r.v.version }) } catch (e) {
|
|
230
|
+
flash(st, i.updateNoteFailed(e.message), 'warn')
|
|
231
|
+
}
|
|
232
|
+
st.update = { installed: r.v.version }
|
|
233
|
+
// Reiniciar se PREGUNTA: cierra las bóvedas abiertas y corta lo que estén haciendo los
|
|
234
|
+
// aparatos. Si dices que no, el servicio se reinicia solo en un par de minutos.
|
|
235
|
+
setConfirm(st, {
|
|
236
|
+
text: i.updateRestartOffer(r.v.version),
|
|
237
|
+
onYes: async () => {
|
|
238
|
+
const rr = await guard(term, st, i.updateRestarting, () => restartDaemonService())
|
|
239
|
+
if (!rr.ok) return
|
|
240
|
+
st.relaunch = true
|
|
241
|
+
st.quit = true
|
|
242
|
+
},
|
|
243
|
+
onNo: () => { flash(st, i.updateRestartLater(r.v.version)) }
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
187
247
|
function startDaemonService () {
|
|
188
248
|
return new Promise((res) => {
|
|
189
249
|
execFile('systemctl', ['--user', 'start', 'dotrino-vault'], { timeout: 8000 }, (err, so, se) => {
|
|
@@ -2502,6 +2562,8 @@ function render (term, st) {
|
|
|
2502
2562
|
? t.muted(i.updateWaiting(u.version, dia(u.askedAt + 24 * 60 * 60 * 1000)))
|
|
2503
2563
|
: t.warn(i.updateNotApproved(u.version, dia(u.askedAt))))
|
|
2504
2564
|
}
|
|
2565
|
+
// Hay versión nueva y se dijo que no (o todavía no se preguntó): se queda dicho aquí.
|
|
2566
|
+
if (!lines[2] && st.update?.release) lines[2] = ' ' + t.warn(i.updateAvailable(st.update.release.version))
|
|
2505
2567
|
// Dispositivos/Scopes son pestañas de la bóveda activa (se entra desde Bóvedas);
|
|
2506
2568
|
// el resto muestra su título simple.
|
|
2507
2569
|
lines[3] = INNER_TABS.includes(st.screen) ? ' ' + renderTabs(st, t) : ' ' + t.title('» ' + title(i, st.screen))
|
|
@@ -2642,11 +2704,14 @@ export async function runTui () {
|
|
|
2642
2704
|
st.state = vc.readState()
|
|
2643
2705
|
st.daemonUp = true
|
|
2644
2706
|
await refreshAll(term, st)
|
|
2707
|
+
checkForUpdate(st)
|
|
2645
2708
|
|
|
2646
2709
|
let running = true
|
|
2647
2710
|
while (running) {
|
|
2648
2711
|
st.state = vc.readState()
|
|
2649
2712
|
st.daemonUp = vc.daemonAlive()
|
|
2713
|
+
// El aviso de versión nueva sale cuando no hay otra cosa preguntando.
|
|
2714
|
+
if (st.update?.offer && !st.input && !st.confirm) offerUpdate(term, st)
|
|
2650
2715
|
// ¿Se cerró sola mientras nadie miraba? Olvida su contraseña y sal de su contenido.
|
|
2651
2716
|
await forgetAutoLocked(term, st)
|
|
2652
2717
|
// caducar el flash a los ~4 s
|
|
@@ -2656,7 +2721,10 @@ export async function runTui () {
|
|
|
2656
2721
|
const base = (st.screen === 'pairing' || st.screen === 'join' || (st.screen === 'devices' && !st.input && !st.confirm)) ? 800 : 0
|
|
2657
2722
|
// Despertar a tiempo del bloqueo automático, además del sondeo de la pantalla.
|
|
2658
2723
|
const wake = autoLockWakeIn(st)
|
|
2659
|
-
|
|
2724
|
+
let tick = base && wake ? Math.min(base, wake) : (base || wake)
|
|
2725
|
+
// Mientras se mira si hay versión nueva hay que despertar sin tecla, o el aviso no
|
|
2726
|
+
// saldría hasta que tocaras algo.
|
|
2727
|
+
if (st.update?.checking) tick = tick ? Math.min(tick, 500) : 500
|
|
2660
2728
|
const key = await term.readKey(tick)
|
|
2661
2729
|
|
|
2662
2730
|
if (key.name === 'resize') continue
|
|
@@ -2728,7 +2796,13 @@ export async function runTui () {
|
|
|
2728
2796
|
for (const id of st.unlockedHere) { try { await vc.lockProfile(id) } catch (_) {} }
|
|
2729
2797
|
term.close()
|
|
2730
2798
|
}
|
|
2799
|
+
// TRAS ACTUALIZAR Y REINICIAR, esta pantalla también se vuelve a abrir: es el binario
|
|
2800
|
+
// anterior el que sigue en memoria. Se lanza el nuevo en esta misma terminal.
|
|
2801
|
+
if (st.relaunch) {
|
|
2802
|
+
const r = spawnSync(process.execPath, process.argv.slice(1), { stdio: 'inherit' })
|
|
2803
|
+
process.exit(r.status ?? 0)
|
|
2804
|
+
}
|
|
2731
2805
|
}
|
|
2732
2806
|
|
|
2733
2807
|
// Solo para pruebas headless (render sin terminal real). No usar en runtime.
|
|
2734
|
-
export const __test = { render, settingsRows, onKeySettings, doorRows, onKeyDoors, pairCapsRows, onKeyPairCaps, onKeyDevices, draftChanges, saveCapsDrafts, onKeySecrets, loginRows, onKeyLogins, refreshLogins, activeLocked, autoLockedIds, autoLockWakeIn, forgetAutoLocked, autoLockMin, refreshAll, ensureUnlocked, profileRows, deviceRows, secretRows, devVarRows, meRows, capsRows, onKeyCaps, pairModeRows, pairingBody, scrollBody, fitHelp, wrapHelp, wrapWords, joinBody, onKeyJoin, promptJoin, onKeyProfiles, onInputKey, toggleLang, mergeMembersAndCerts, seguirAqui, resetToque: () => { ultimoToque = 0 } }
|
|
2808
|
+
export const __test = { checkForUpdate, offerUpdate, installUpdate, render, settingsRows, onKeySettings, doorRows, onKeyDoors, pairCapsRows, onKeyPairCaps, onKeyDevices, draftChanges, saveCapsDrafts, onKeySecrets, loginRows, onKeyLogins, refreshLogins, activeLocked, autoLockedIds, autoLockWakeIn, forgetAutoLocked, autoLockMin, refreshAll, ensureUnlocked, profileRows, deviceRows, secretRows, devVarRows, meRows, capsRows, onKeyCaps, pairModeRows, pairingBody, scrollBody, fitHelp, wrapHelp, wrapWords, joinBody, onKeyJoin, promptJoin, onKeyProfiles, onInputKey, toggleLang, mergeMembersAndCerts, seguirAqui, resetToque: () => { ultimoToque = 0 } }
|
package/src/tui/i18n.js
CHANGED
|
@@ -28,6 +28,15 @@ const es = {
|
|
|
28
28
|
daemonStopped: 'DETENIDO',
|
|
29
29
|
updateWaiting: (version, until) => `⬆ la ${version} espera tu aprobación hasta ${until} UTC: mira Pedidos en tu teléfono`,
|
|
30
30
|
updateNotApproved: (version, when) => `⬆ se pidió permiso para la ${version} el ${when} UTC y no se aprobó: no se vuelve a pedir · dotrino-vault update`,
|
|
31
|
+
updateAvailable: (version) => `⬆ hay la versión ${version}: instálala con dotrino-vault update`,
|
|
32
|
+
updateOffer: (version, current) => `Hay la versión ${version} y esta es la ${current}. ¿Instalarla ahora?`,
|
|
33
|
+
updateInstalling: (version) => `Bajando la ${version} y comprobando la firma…`,
|
|
34
|
+
updateFailed: (reason) => `No se instaló nada: ${reason}`,
|
|
35
|
+
updateNeedsCommand: 'Esta instalación no se puede cambiar desde aquí. Sal y corre: dotrino-vault update',
|
|
36
|
+
updateNoteFailed: (reason) => `Instalada, pero no se pudo dejar el aviso para tus aprobadores: ${reason}`,
|
|
37
|
+
updateRestartOffer: (version) => `Instalada la ${version}. ¿Reiniciar ahora la bóveda y esta pantalla? Cierra las bóvedas abiertas`,
|
|
38
|
+
updateRestarting: 'Reiniciando la bóveda…',
|
|
39
|
+
updateRestartLater: (version) => `La ${version} queda instalada: la bóveda se reinicia sola en un par de minutos`,
|
|
31
40
|
daemonStale: (running, installed) => `⚠ el servicio corre ${running} y el binario instalado es ${installed}: reinícialo (systemctl --user restart dotrino-vault)`,
|
|
32
41
|
activeVault: 'Bóveda activa: ',
|
|
33
42
|
noName: '(sin nombre)',
|
|
@@ -516,6 +525,15 @@ const en = {
|
|
|
516
525
|
daemonStopped: 'STOPPED',
|
|
517
526
|
updateWaiting: (version, until) => `⬆ ${version} is waiting for your approval until ${until} UTC: check Requests on your phone`,
|
|
518
527
|
updateNotApproved: (version, when) => `⬆ approval for ${version} was asked on ${when} UTC and not given: it will not ask again · dotrino-vault update`,
|
|
528
|
+
updateAvailable: (version) => `⬆ version ${version} is out: install it with dotrino-vault update`,
|
|
529
|
+
updateOffer: (version, current) => `Version ${version} is out and this is ${current}. Install it now?`,
|
|
530
|
+
updateInstalling: (version) => `Downloading ${version} and checking its signature…`,
|
|
531
|
+
updateFailed: (reason) => `Nothing was installed: ${reason}`,
|
|
532
|
+
updateNeedsCommand: 'This install cannot be changed from here. Quit and run: dotrino-vault update',
|
|
533
|
+
updateNoteFailed: (reason) => `Installed, but the note for your approvers could not be written: ${reason}`,
|
|
534
|
+
updateRestartOffer: (version) => `${version} is installed. Restart the vault and this screen now? It locks the open vaults`,
|
|
535
|
+
updateRestarting: 'Restarting the vault…',
|
|
536
|
+
updateRestartLater: (version) => `${version} is installed: the vault restarts by itself in a couple of minutes`,
|
|
519
537
|
daemonStale: (running, installed) => `⚠ the service runs ${running} but the installed binary is ${installed}: restart it (systemctl --user restart dotrino-vault)`,
|
|
520
538
|
activeVault: 'Active vault: ',
|
|
521
539
|
tooSmall: 'Terminal too small',
|
package/src/update.js
CHANGED
|
@@ -16,11 +16,22 @@ import { execFileSync } from 'node:child_process'
|
|
|
16
16
|
import { latestVersion, isNewer, CHECK_EVERY_MS } from '@dotrino/update'
|
|
17
17
|
import { pickAsset, download, verifyArtifact as verify, fetchVerified } from '@dotrino/update/fetch'
|
|
18
18
|
import { VERSION } from './version.js'
|
|
19
|
+
import { ipcWrite } from './ipc.js'
|
|
19
20
|
|
|
20
21
|
export const REPO = 'imdotrino/dotrino-vault'
|
|
21
22
|
export { isNewer, CHECK_EVERY_MS, download }
|
|
22
23
|
|
|
23
24
|
/** La última release del vault. `{ ok:false, reason }` si no se pudo mirar, que NO es «al día». */
|
|
25
|
+
/**
|
|
26
|
+
* DEJA DICHO QUE SE INSTALÓ una versión nueva, para que el servicio se lo cuente a quien
|
|
27
|
+
* aprueba cuando arranque con ella (`anunciarActualizacion` en daemon.js). Es el mismo
|
|
28
|
+
* marcador que deja el servicio cuando se actualiza solo, y lo dejan igual el comando
|
|
29
|
+
* `update` y la TUI.
|
|
30
|
+
*/
|
|
31
|
+
export function noteUpdated (dir, { from, to }) {
|
|
32
|
+
ipcWrite(path.join(dir, 'updated.json'), { v: 1, from, to, at: Date.now() })
|
|
33
|
+
}
|
|
34
|
+
|
|
24
35
|
export const latestRelease = (opts = {}) =>
|
|
25
36
|
latestVersion({ source: 'github', repo: REPO, product: 'dotrino-vault', version: VERSION, ...opts })
|
|
26
37
|
|