@mmmbuto/nexuscrew 0.9.5 → 0.9.6

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.
@@ -732,8 +732,37 @@ function authorizedKeysLine({ remotePort, panelRemotePort, pub } = {}) {
732
732
  // Torna null se la privata non e' leggibile, e' protetta da passphrase (in
733
733
  // batch non si puo' sbloccare) o `ssh-keygen` non c'e': senza poterla derivare
734
734
  // non si compone nessuna riga.
735
- function readPublicKey(identityFile, impl = {}) {
736
- if (!identityFile || typeof identityFile !== 'string') return null;
735
+ // Esiti ENUMERATI della derivazione della pubblica (difetto strutturale
736
+ // registrato dall'auditor: `null | stringa` comprimeva stati DIVERSI, e il
737
+ // livello basso componeva frasi su fatti che non conosce — meta' della 0.9.5
738
+ // e' nata li'). Gli esiti sono DATI, senza testo per l'utente: le frasi si
739
+ // compongono al confine UI/log, mai qui. Si aggancia a P4: una chiave cifrata
740
+ // dava `null`, indistinguibile da «file illeggibile», e il pairing ometteva
741
+ // la riga in silenzio.
742
+ //
743
+ // derived — pubblica derivata; `line` e' la riga
744
+ // no-identity — identityFile dichiarata ma il file NON esiste
745
+ // actual-key-unknown — identityFile non dichiarata: la chiave che ssh
746
+ // usera' DAVVERO (default, agent, config) non e'
747
+ // sapibile da qui; nessun altro canale ripara
748
+ // tool-unavailable — ssh-keygen assente sulla macchina
749
+ // encrypted-or-unreadable — ssh-keygen non riesce sul file (cifrata,
750
+ // permessi, formato) o l'output non e' una riga
751
+ // pubblica valida
752
+ const PUBKEY_DERIVED = 'derived';
753
+ const PUBKEY_NO_IDENTITY = 'no-identity';
754
+ const PUBKEY_ACTUAL_KEY_UNKNOWN = 'actual-key-unknown';
755
+ const PUBKEY_TOOL_UNAVAILABLE = 'tool-unavailable';
756
+ const PUBKEY_ENCRYPTED_OR_UNREADABLE = 'encrypted-or-unreadable';
757
+
758
+ function resolvePublicKey(identityFile, impl = {}) {
759
+ if (!identityFile || typeof identityFile !== 'string' || identityFile.trim() === '') {
760
+ return { outcome: PUBKEY_ACTUAL_KEY_UNKNOWN };
761
+ }
762
+ const existsImpl = impl.existsSync || require('node:fs').existsSync;
763
+ if (!existsImpl(identityFile)) {
764
+ return { outcome: PUBKEY_NO_IDENTITY, path: identityFile };
765
+ }
737
766
  const exec = impl.execImpl || require('node:child_process').execFileSync;
738
767
  // Timeout iniettabile (test): il default 5000 ms e' la RETE, non il
739
768
  // meccanismo — il meccanismo e' l'ambiente controllato qui sotto.
@@ -757,7 +786,7 @@ function readPublicKey(identityFile, impl = {}) {
757
786
  // chiave cifrata (misurato: 0.11 s contro gli 8 s dell'attesa su tty) e
758
787
  // non puo' lasciare helper. Un path inesistente vince su /bin/false: su
759
788
  // Termux i binari stanno sotto $PREFIX, e un path assoluto sbagliato e'
760
- // una scommessa.
789
+ // una scommessa; e non `setsid`, che su macOS non esiste.
761
790
  // LIMITE DA SAPERE: SSH_ASKPASS_REQUIRE esiste da OpenSSH 8.4. Le versioni
762
791
  // piu' vecchie lo ignorano e tornano al tty: li' il timeout qui sopra
763
792
  // resta come rete — il degrado e' «lento», non «appeso».
@@ -770,14 +799,32 @@ function readPublicKey(identityFile, impl = {}) {
770
799
  SSH_ASKPASS: path.join(process.env.HOME || os.homedir(), '.nexuscrew', 'askpass-inesistente'),
771
800
  },
772
801
  });
773
- } catch (_) { return null; } // assente, passphrase, privata illeggibile
802
+ } catch (e) {
803
+ // ENOENT dello SPAWN = il binario ssh-keygen non esiste: un'altra causa
804
+ // rispetto al file che non si deriva, e chi compone il messaggio deve
805
+ // poterle distinguere (prima erano lo stesso null).
806
+ if (e && e.code === 'ENOENT') return { outcome: PUBKEY_TOOL_UNAVAILABLE };
807
+ return { outcome: PUBKEY_ENCRYPTED_OR_UNREADABLE };
808
+ }
774
809
  const righe = String(out || '').split('\n').filter((r) => r.trim() !== '');
775
- if (righe.length !== 1) return null;
810
+ if (righe.length !== 1) return { outcome: PUBKEY_ENCRYPTED_OR_UNREADABLE };
776
811
  const riga = righe[0].trim();
777
- if (/[\u0000-\u0008\u000b-\u001f\u007f]/.test(riga)) return null;
812
+ if (riga.length === 0 || /[\u0000-\u0008\u000b-\u001f\u007f]/.test(riga)) {
813
+ return { outcome: PUBKEY_ENCRYPTED_OR_UNREADABLE };
814
+ }
778
815
  const campi = riga.split(/[ \t]+/);
779
- if (campi.length < 2 || !/^[A-Za-z0-9+/]+={0,2}$/.test(campi[1])) return null;
780
- return riga;
816
+ if (campi.length < 2 || !/^[A-Za-z0-9+/]+={0,2}$/.test(campi[1])) {
817
+ return { outcome: PUBKEY_ENCRYPTED_OR_UNREADABLE };
818
+ }
819
+ return { outcome: PUBKEY_DERIVED, line: riga };
820
+ }
821
+
822
+ // Contratto storico (supervisor e chiamanti esistenti): null | stringa.
823
+ // Sopra resolvePublicKey per gli esiti enumerati; questo resta per chi
824
+ // compone ancora sul contratto vecchio — il confine UI/log migrera'.
825
+ function readPublicKey(identityFile, impl = {}) {
826
+ const r = resolvePublicKey(identityFile, impl);
827
+ return r.outcome === PUBKEY_DERIVED ? r.line : null;
781
828
  }
782
829
 
783
830
  // La riga per UN nodo, dal nodo. Sta qui e non nella route perche' la stessa
@@ -796,14 +843,21 @@ function readPublicKey(identityFile, impl = {}) {
796
843
  // provisionare un'identita' per peer — lavoro suo, non un ramo di questa
797
844
  // funzione.
798
845
  function authorizedKeysForNode(nodo, panelRemotePort) {
799
- const pub = readPublicKey(nodo && nodo.identityFile);
800
- if (!pub) return null;
801
- return authorizedKeysLine({ remotePort: nodo.remotePort, panelRemotePort, pub });
846
+ // Esito enumerato, non piu' null|stringa: chi risponde (pairing) porta il
847
+ // DATO fino al confine, e li' — non qui — si compone cio' che si dice.
848
+ const r = resolvePublicKey(nodo && nodo.identityFile);
849
+ if (r.outcome !== PUBKEY_DERIVED) return { outcome: r.outcome, line: null };
850
+ return {
851
+ outcome: r.outcome,
852
+ line: authorizedKeysLine({ remotePort: nodo.remotePort, panelRemotePort, pub: r.line }),
853
+ };
802
854
  }
803
855
 
804
856
  module.exports = {
805
857
  SSH_BASE_OPTS,
806
- authorizedKeysLine, readPublicKey, authorizedKeysForNode,
858
+ authorizedKeysLine, readPublicKey, resolvePublicKey, authorizedKeysForNode,
859
+ PUBKEY_DERIVED, PUBKEY_NO_IDENTITY, PUBKEY_ACTUAL_KEY_UNKNOWN,
860
+ PUBKEY_TOOL_UNAVAILABLE, PUBKEY_ENCRYPTED_OR_UNREADABLE,
807
861
  buildForwardArgs, buildReverseArgs, backoffDelay,
808
862
  tunnelDir, tunnelPidPath, tunnelLogPath, tunnelStatePath, readTunnelState,
809
863
  prepareTunnelDir, openTunnelLog,
@@ -423,11 +423,17 @@ function createPairHandler(deps) {
423
423
  }
424
424
  send(res, 200, {
425
425
  paired: true, name: b.name, instanceId: joined.instanceId, transport: 'auto', health: { status: health.status },
426
- ...(authorizedKeysPayload ? {
427
- authorizedKeys: authorizedKeysPayload,
428
- authorizedKeysNote: 'il peer ha un pannello sulla propria porta ' + peerPanelPort
429
- + ': SOSTITUISCI la riga già installata in ~/.ssh/authorized_keys del NODO con questa (due destinazioni), altrimenti il canale del pannello sarà rifiutato. È la riga della chiave DICHIARATA per questo nodo (-i), non un\'eventuale chiave "jump" dello stesso nodo. Se ssh sceglie un\'altra identità (agent o config), la riga giusta è quella della chiave che usa davvero.',
426
+ ...(authorizedKeysPayload && authorizedKeysPayload.outcome === 'derived' ? {
427
+ authorizedKeys: authorizedKeysPayload.line,
428
+ // La nota si compone al confine UI/log (pubkey-format), non qui.
429
+ authorizedKeysNote: require('../nodes/pubkey-format.js')
430
+ .pubkeyPairingNote(authorizedKeysPayload, { panelPort: peerPanelPort }),
430
431
  } : {}),
432
+ // Esito enumerato come DATO (resolver a cinque esiti): prima una
433
+ // chiave cifrata era indistinguibile da «file illeggibile» e la riga
434
+ // spariva in silenzio. Il confine UI/log compone le frasi su questo;
435
+ // qui si porta il fatto, non il testo.
436
+ ...(authorizedKeysPayload ? { authorizedKeysOutcome: authorizedKeysPayload.outcome } : {}),
431
437
  });
432
438
  } catch (e) {
433
439
  await rollback();
@@ -57,6 +57,7 @@ const {
57
57
  } = require('../proxy/federation.js');
58
58
  const { rotateToken } = require('../auth/token.js');
59
59
  const { generateService, installService, installPath: svcInstallPath } = require('../cli/service.js');
60
+ const { resolveBootPaths } = require('../cli/stable-alias.js');
60
61
  const { detectPlatform, nodeBin, repoRoot, uid } = require('../cli/platform.js');
61
62
  const { isServiceRunning, readRoles, bootState } = require('../cli/commands.js');
62
63
  const { configJsonPath } = require('../config.js');
@@ -993,6 +994,15 @@ function settingsRoutes(deps = {}) {
993
994
  uid: seams.uid || uid(),
994
995
  installPath: seams.serviceInstallPath,
995
996
  };
997
+ // 1-bis (R23): come in init — il path del boot si risolve alla
998
+ // scrittura e cio' che non ha alias stabile torna DICHIARATO. Le
999
+ // warnings sono DATI nella risposta, non testo composto qui.
1000
+ const svcBoot = resolveBootPaths({
1001
+ nodeBin: ctx.nodeBin,
1002
+ entryPath: path.join(ctx.repoRoot, 'bin', 'nexuscrew.js'),
1003
+ });
1004
+ ctx.nodeBin = svcBoot.nodeBin;
1005
+ ctx.entryPath = svcBoot.entryPath;
996
1006
  const content = generateService(platform, ctx);
997
1007
  const out = installService(platform, content, ctx, { activate: false });
998
1008
  send(res, 200, {
@@ -1000,6 +1010,7 @@ function settingsRoutes(deps = {}) {
1000
1010
  target: out.target,
1001
1011
  note: 'unit rigenerata; nessun restart automatico — riavvia il service per applicarla',
1002
1012
  skippedActivation: out.skippedActivation,
1013
+ warnings: svcBoot.warnings,
1003
1014
  });
1004
1015
  } catch (e) { send(res, 500, { error: String(e.message || e) }); }
1005
1016
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/nexuscrew",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "description": "Faithful browser tmux client — attach to live sessions over a real PTY, localhost-only, mobile-easy",
5
5
  "main": "lib/server.js",
6
6
  "bin": {