@dotrino/content 0.4.3 → 0.5.0
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/README.md +22 -0
- package/bin/cli.js +42 -7
- package/package.json +3 -3
- package/src/selfUpdate.js +81 -0
package/README.md
CHANGED
|
@@ -47,6 +47,28 @@ Requiere **Node ≥ 22.5**. El core usa solo `node:crypto` y `node:sqlite`; la
|
|
|
47
47
|
identidad viene de los pilares del ecosistema (`@dotrino/remote-agent`,
|
|
48
48
|
`@dotrino/identity`).
|
|
49
49
|
|
|
50
|
+
## Actualizarse
|
|
51
|
+
|
|
52
|
+
El node **se actualiza solo** (CONVENCIONES §15): mira una vez al día si hay versión nueva,
|
|
53
|
+
comprueba lo que entrega npm contra el `npm-integrity.json` de la release de este repo y
|
|
54
|
+
lo instala. Lo hace `@dotrino/update/npm`; aquí solo se cablea.
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
dotrino-content update # dice cómo está
|
|
58
|
+
dotrino-content update --approval on # antes de instalar, pide permiso a quien aprueba en tu bóveda
|
|
59
|
+
dotrino-content update --notify off # no avisa de que se actualizó
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- Los dos ajustes son de **esta instancia** (viven junto a su enlace) y no tienen que ver
|
|
63
|
+
con el de la bóveda. Por defecto: se actualiza sin preguntar y avisa.
|
|
64
|
+
- El permiso se pide **una vez por versión**; sin respuesta en un día cuenta como no, y
|
|
65
|
+
`dotrino-content info` lo dice.
|
|
66
|
+
- **Solo se actualiza sola una instalación global de npm en un prefijo del usuario**
|
|
67
|
+
(`npm i -g @dotrino/content` con nvm o un prefijo propio). Si el prefijo es de root, avisa a
|
|
68
|
+
quien aprueba de que hay versión y necesita permisos de administrador. Con `npx`, dentro
|
|
69
|
+
de un proyecto o desde un checkout no se toca nada.
|
|
70
|
+
- Solo se reinicia si hay quien lo levante (systemd, pm2).
|
|
71
|
+
|
|
50
72
|
## Plano de control (Fase 2, por el proxy, cifrado)
|
|
51
73
|
|
|
52
74
|
Con el node enlazado, tus propias apps —cualquier aparato con un certificado de **la
|
package/bin/cli.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* CLI de dotrino-content.
|
|
4
4
|
*
|
|
5
5
|
* dotrino-content enroll <código> enlaza este node a tu vault (una vez)
|
|
6
|
+
* dotrino-content update [--approval on|off] [--notify on|off]
|
|
7
|
+
* cómo se actualiza este node (CONVENCIONES §15)
|
|
6
8
|
* dotrino-content start [--port 3777] [--dir <ruta>] [--max-gb <n>] [--gc-min <min>]
|
|
7
9
|
* [--no-agent] arranca sin el plano de control
|
|
8
10
|
* [--public] abre el puerto de VISTAS PREVIAS (§7.2)
|
|
@@ -34,11 +36,26 @@ const USAGE = `uso:
|
|
|
34
36
|
dotrino-content enroll <código>
|
|
35
37
|
dotrino-content info [--json] qué aparato es este node: su ID (el de «dotrino-vault members»),
|
|
36
38
|
su bóveda y sus permisos. Sin red.
|
|
39
|
+
dotrino-content update [--approval on|off] [--notify on|off]
|
|
40
|
+
este node se actualiza solo. --approval on: antes pide permiso a
|
|
41
|
+
quien aprueba en tu bóveda. --notify off: no avisa de que se
|
|
42
|
+
actualizó. Sin opciones, dice cómo está.
|
|
37
43
|
dotrino-content start [--port 3777] [--dir <ruta>] [--max-gb <n>] [--max-blob-mb <n>] [--gc-min <min>] [--no-agent]
|
|
38
44
|
[--public] [--public-port 3778] [--public-host 0.0.0.0] [--public-max-kb 512]
|
|
39
45
|
[--public-rate 60] [--public-egress-gb <n>] [--public-url https://…] [--app-url https://…]
|
|
40
46
|
[--public-index]`
|
|
41
47
|
|
|
48
|
+
// `update`: los dos ajustes de actualización de ESTA instancia (CONVENCIONES §15). Va antes
|
|
49
|
+
// de `parseArgs` porque `--approval` y `--notify` se pueden dar sin valor (para preguntar
|
|
50
|
+
// cómo están), y `parseArgs` no admite una opción que a veces lleva valor y a veces no.
|
|
51
|
+
if (process.argv[2] === 'update') {
|
|
52
|
+
const { updatePrefsCommand } = await import('@dotrino/update/npm')
|
|
53
|
+
const r = updatePrefsCommand(process.argv.slice(3), { dir: linkDir(), lang: 'es' })
|
|
54
|
+
if (!r.handled) { console.error(`uso: dotrino-content update [--approval on|off] [--notify on|off]`); process.exit(2) }
|
|
55
|
+
;(r.ok ? console.log : console.error)(r.text)
|
|
56
|
+
process.exit(r.ok ? 0 : 2)
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
const { values, positionals } = parseArgs({
|
|
43
60
|
allowPositionals: true,
|
|
44
61
|
options: {
|
|
@@ -84,6 +101,13 @@ if (cmd === 'info') {
|
|
|
84
101
|
if (!link) { console.error(`este node no está enlazado (${dir}): dotrino-content enroll <código>`); process.exit(1) }
|
|
85
102
|
const info = await deviceInfo(link, { kind: 'content', ns: 'content', version: VERSION, dir })
|
|
86
103
|
console.log(values.json ? JSON.stringify(info, null, 2) : formatDeviceInfo(info))
|
|
104
|
+
// Y si hay una actualización que no se instaló (no se aprobó, o necesita root), se dice
|
|
105
|
+
// aquí: es donde se mira qué es este aparato. Fuera del JSON, que lo lee una máquina.
|
|
106
|
+
if (!values.json) {
|
|
107
|
+
const { updateStatusText } = await import('@dotrino/update/npm')
|
|
108
|
+
const estado = updateStatusText({ dir, current: VERSION, lang: 'es' })
|
|
109
|
+
if (estado) console.log('\n' + estado)
|
|
110
|
+
}
|
|
87
111
|
process.exit(0)
|
|
88
112
|
}
|
|
89
113
|
|
|
@@ -172,13 +196,6 @@ server.listen(port, '127.0.0.1', () => {
|
|
|
172
196
|
console.log(`blobs: ${s.blobs} · bytes: ${s.bytes}${maxBytes ? ` / ${maxBytes}` : ''}`)
|
|
173
197
|
})
|
|
174
198
|
|
|
175
|
-
// §15: una vez al día mira si hay versión nueva y lo dice en el log. Solo avisa.
|
|
176
|
-
const { watchForUpdate } = await import('@dotrino/update')
|
|
177
|
-
watchForUpdate({
|
|
178
|
-
current: VERSION, source: 'npm', pkg: '@dotrino/content',
|
|
179
|
-
onNewer: (r) => console.log(`[content] version ${r.version} is available (running ${r.current}): npm i -g @dotrino/content@${r.version}`)
|
|
180
|
-
})
|
|
181
|
-
|
|
182
199
|
// Plano de control: solo si este node ya está enlazado a un vault. Sin enlace sigue
|
|
183
200
|
// siendo lo de antes (un node local), y se dice en voz alta para que nadie crea que
|
|
184
201
|
// tiene administración remota cuando no la tiene.
|
|
@@ -226,11 +243,29 @@ if (values.public) {
|
|
|
226
243
|
})
|
|
227
244
|
}
|
|
228
245
|
|
|
246
|
+
/** @type {null | (() => void)} */
|
|
247
|
+
let stopSelfUpdate = null
|
|
229
248
|
const shutdown = () => {
|
|
230
249
|
clearInterval(gcTimer)
|
|
250
|
+
try { stopSelfUpdate?.() } catch (_) {}
|
|
231
251
|
try { agent?.close() } catch (_) {}
|
|
232
252
|
try { publicServer?.close() } catch (_) {}
|
|
233
253
|
server.close(() => { node.close(); process.exit(0) })
|
|
234
254
|
}
|
|
235
255
|
process.on('SIGINT', shutdown)
|
|
236
256
|
process.on('SIGTERM', shutdown)
|
|
257
|
+
|
|
258
|
+
// §15: SE ACTUALIZA SOLO. Mira al arrancar y una vez al día; lo que baja se comprueba
|
|
259
|
+
// contra la release de GitHub antes de tocar el disco, y solo se reinicia si hay quien lo
|
|
260
|
+
// levante. Los dos ajustes (`dotrino-content update`) son de esta instancia. Va al final
|
|
261
|
+
// porque reiniciar es `shutdown`. Desde un checkout (el node de Dotrino, que se despliega
|
|
262
|
+
// con git) no se toca nada: lo dice una vez y sigue.
|
|
263
|
+
const { startSelfUpdate } = await import('../src/selfUpdate.js')
|
|
264
|
+
const { serviceDir } = await import('../src/vaultEnv.js')
|
|
265
|
+
stopSelfUpdate = startSelfUpdate({
|
|
266
|
+
version: VERSION,
|
|
267
|
+
dir: linkDir(),
|
|
268
|
+
conn: isEnrolled() ? () => ({ dir: serviceDir() }) : null,
|
|
269
|
+
restart: shutdown,
|
|
270
|
+
log
|
|
271
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/content",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Nodo de contenido del ecosistema Dotrino: guarda los bytes del usuario direccionados por su hash (cid), en su propia maquina y, si el dueno quiere, respaldados en su propio bucket (R2, Backblaze, Hetzner, Storj o S3). Lo publico sale por URL directa; lo privado, cifrado y solo por la red Dotrino.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"@dotrino/identity": "0.105.0",
|
|
27
27
|
"@dotrino/proxy-client": "0.25.0",
|
|
28
28
|
"@dotrino/remote-agent": "0.13.0",
|
|
29
|
-
"@dotrino/update": "0.
|
|
30
|
-
"@dotrino/vault": "0.
|
|
29
|
+
"@dotrino/update": "0.5.0",
|
|
30
|
+
"@dotrino/vault": "0.82.0"
|
|
31
31
|
},
|
|
32
32
|
"keywords": [
|
|
33
33
|
"dotrino",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* selfUpdate.js — este node SE ACTUALIZA SOLO (CONVENCIONES §15, dueño 2026-10-08).
|
|
3
|
+
*
|
|
4
|
+
* No implementa nada de eso: mirar, comprobar lo que entrega npm contra la release de
|
|
5
|
+
* GitHub, instalar y recordar lo ya preguntado es de `@dotrino/update/npm`; preguntarle a
|
|
6
|
+
* la bóveda y avisarle es de `@dotrino/vault/service`. Aquí solo está el pegamento, y es
|
|
7
|
+
* lo único que conoce las dos piezas a la vez.
|
|
8
|
+
*
|
|
9
|
+
* Los dos ajustes son de ESTA instancia (`update-prefs.json` en su carpeta) y no tienen
|
|
10
|
+
* que ver con el de la bóveda:
|
|
11
|
+
* · `approval` (apagado): encendido, antes de instalar se le pregunta a la bóveda.
|
|
12
|
+
* · `notify` (encendido): se avisa a quien aprueba de que el node se actualizó, o de
|
|
13
|
+
* que hay versión nueva y esta instalación necesita root.
|
|
14
|
+
*
|
|
15
|
+
* SIN BÓVEDA NO HAY A QUIÉN PREGUNTAR NI A QUIÉN AVISAR, y se dice tal cual: no se pasan
|
|
16
|
+
* los ganchos, así que con `approval` encendida y sin enrolar el resultado es
|
|
17
|
+
* `no-approver` y no se instala. Suponer un sí sería saltarse lo que el dueño pidió.
|
|
18
|
+
*/
|
|
19
|
+
import { watchSelfUpdateNpm } from '@dotrino/update/npm'
|
|
20
|
+
import { askUpdateApproval, reportUpdated, reportUpdateNeedsRoot } from '@dotrino/vault/service'
|
|
21
|
+
|
|
22
|
+
export const PKG = '@dotrino/content'
|
|
23
|
+
export const REPO = 'imdotrino/dotrino-content'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Los tres ganchos que hablan con la bóveda. `conn()` da, EN EL MOMENTO de llamar, con
|
|
27
|
+
* qué identidad se le habla (`{ dir }` o `{ proxyUrl, masterPubkey, device, cert }`): el
|
|
28
|
+
* papel se renueva con el proceso en marcha y uno leído al arrancar ya no valdría.
|
|
29
|
+
*
|
|
30
|
+
* @param {{ product: string, conn: () => object, vault?: any }} o
|
|
31
|
+
*/
|
|
32
|
+
export function vaultUpdateHooks ({ product, conn, vault = { askUpdateApproval, reportUpdated, reportUpdateNeedsRoot } }) {
|
|
33
|
+
return {
|
|
34
|
+
// true = sí · false = no, o pasó el día sin respuesta · lanza = no se pudo preguntar
|
|
35
|
+
// (eso no es una negativa: `@dotrino/update` lo reintenta en la próxima pasada).
|
|
36
|
+
mayUpdate: async ({ version, from }) => {
|
|
37
|
+
try {
|
|
38
|
+
return (await vault.askUpdateApproval({ product, version, from, ...conn() })).ok === true
|
|
39
|
+
} catch (e) {
|
|
40
|
+
// El pedido quedó pendiente y venció sin que nadie contestara: vencido es no.
|
|
41
|
+
if (e?.code === 'unanswered') return false
|
|
42
|
+
throw e
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
onUpdated: ({ version, from }) => vault.reportUpdated({ product, version, from, ...conn() }),
|
|
46
|
+
onNeedsRoot: ({ version, from }) => vault.reportUpdateNeedsRoot({ product, version, from, ...conn() })
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Arranca el vigilante. Devuelve cómo pararlo.
|
|
52
|
+
*
|
|
53
|
+
* @param {{
|
|
54
|
+
* version: string, dir: string, conn?: (() => object) | null,
|
|
55
|
+
* restart: (version: string) => void, log?: (m: string) => void,
|
|
56
|
+
* watch?: typeof watchSelfUpdateNpm, vault?: any
|
|
57
|
+
* }} o
|
|
58
|
+
* dir: la carpeta del enlace de esta instancia (ahí viven sus ajustes de actualización).
|
|
59
|
+
* conn: cómo hablarle a la bóveda, o `null` si este node no está enrolado.
|
|
60
|
+
* restart: salir limpio para que systemd/pm2 levante la versión nueva.
|
|
61
|
+
*/
|
|
62
|
+
export function startSelfUpdate ({ version, dir, conn = null, restart, log = console.log, watch = watchSelfUpdateNpm, vault }) {
|
|
63
|
+
return watch({
|
|
64
|
+
pkg: PKG,
|
|
65
|
+
current: version,
|
|
66
|
+
repo: REPO,
|
|
67
|
+
dir,
|
|
68
|
+
...(conn ? vaultUpdateHooks({ product: PKG, conn, ...(vault ? { vault } : {}) }) : {}),
|
|
69
|
+
onInstalled: ({ version: to, restart: canRestart }) => {
|
|
70
|
+
if (canRestart) {
|
|
71
|
+
log(`[update] ${PKG} ${to} installed · restarting to run it`)
|
|
72
|
+
restart(to)
|
|
73
|
+
} else {
|
|
74
|
+
log(`[update] ${PKG} ${to} installed · nothing restarts this process, so it keeps running ${version} until the next start`)
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
log
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default { PKG, REPO, vaultUpdateHooks, startSelfUpdate }
|