pinokiod 3.270.0 → 3.271.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/kernel/index.js CHANGED
@@ -803,6 +803,7 @@ class Kernel {
803
803
  async init(options) {
804
804
 
805
805
  let home = this.store.get("home") || process.env.PINOKIO_HOME
806
+ this.homedir = home
806
807
 
807
808
  // reset shells if they exist
808
809
  if (this.shell) {
@@ -820,10 +821,9 @@ class Kernel {
820
821
  this.kv = new KV(this)
821
822
  this.cloudflare = new Cloudflare()
822
823
  this.peer = new Peer(this)
824
+ await this.peer.initialize(this)
823
825
  this.git = new Git(this)
824
826
 
825
- this.homedir = home
826
-
827
827
  // if (home) {
828
828
  // this.homedir = home
829
829
  // } else {
package/kernel/peer.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const dgram = require('dgram');
2
2
  const axios = require('axios');
3
3
  const os = require('os')
4
+ const systeminformation = require('systeminformation')
4
5
  const Environment = require("./environment")
5
6
  class PeerDiscovery {
6
7
  constructor(kernel, port = 41234, message = 'ping', interval = 1000) {
@@ -12,9 +13,8 @@ class PeerDiscovery {
12
13
  this.peers = new Set();
13
14
  this.interface_addresses = []
14
15
  this.host_candidates = []
15
- this.host = this._getLocalIPAddress()
16
+ this.host = null
16
17
  this.default_port = 42000
17
- this.peers.add(this.host)
18
18
  this.router_info_cache = {}
19
19
  // this.start();
20
20
  }
@@ -85,6 +85,13 @@ class PeerDiscovery {
85
85
  this.active = false
86
86
  }
87
87
  }
88
+ // Prepare host/peer state before the rest of the kernel bootstraps
89
+ async initialize(kernel) {
90
+ await this.refreshLocalAddress()
91
+ if (kernel) {
92
+ await this.check(kernel)
93
+ }
94
+ }
88
95
  async start(kernel) {
89
96
  let env = await Environment.get(kernel.homedir, kernel)
90
97
 
@@ -576,23 +583,70 @@ class PeerDiscovery {
576
583
  _isLocalLAN(ip) {
577
584
  return this.isRFC1918(ip)
578
585
  }
579
- _getLocalIPAddress() {
580
- this.interface_addresses = this._collectInterfaceAddresses()
581
- const shareable = this.interface_addresses.filter((entry) => entry.shareable)
582
- this.host_candidates = shareable
583
- const lanCandidate = shareable.find((entry) => entry.scope === 'lan')
584
- if (lanCandidate) {
585
- return lanCandidate.address
586
+ // Refresh LAN/IP selection; keeps peers set in sync with the active address
587
+ async refreshLocalAddress() {
588
+ try {
589
+ const { host, host_candidates, interface_addresses } = await this._getLocalIPAddress()
590
+ this.interface_addresses = interface_addresses
591
+ this.host_candidates = host_candidates
592
+ if (host && this.host !== host) {
593
+ if (this.host) {
594
+ this.peers.delete(this.host)
595
+ }
596
+ this.host = host
597
+ } else if (!this.host) {
598
+ this.host = host
599
+ }
600
+ if (this.host) {
601
+ this.peers.add(this.host)
602
+ }
603
+ return this.host
604
+ } catch (err) {
605
+ console.error('peer refreshLocalAddress error', err)
606
+ if (!this.host) {
607
+ this.host = null
608
+ }
609
+ return this.host
610
+ }
611
+ }
612
+ async _getLocalIPAddress() {
613
+ const interface_addresses = await this._collectInterfaceAddresses()
614
+ const shareable = interface_addresses.filter((entry) => entry.shareable)
615
+ const host_candidates = shareable.map((entry) => ({
616
+ address: entry.address,
617
+ netmask: entry.netmask,
618
+ interface: entry.interface,
619
+ scope: entry.scope,
620
+ shareable: entry.shareable,
621
+ type: entry.type || null,
622
+ operstate: entry.operstate || null,
623
+ virtual: entry.virtual || false,
624
+ default: entry.default || false,
625
+ prefixLength: entry.prefixLength,
626
+ mac: entry.mac || null,
627
+ score: this._scoreCandidate(entry)
628
+ }))
629
+ let selectedHost = null
630
+ let bestScore = -Infinity
631
+ host_candidates.forEach((candidate, index) => {
632
+ const score = typeof candidate.score === 'number' ? candidate.score : -Infinity
633
+ if (score > bestScore) {
634
+ bestScore = score
635
+ selectedHost = candidate.address
636
+ } else if (score === bestScore && selectedHost === null) {
637
+ selectedHost = candidate.address
638
+ }
639
+ })
640
+ if (!selectedHost && shareable.length > 0) {
641
+ selectedHost = shareable[0].address
586
642
  }
587
- const cgnatCandidate = shareable.find((entry) => entry.scope === 'cgnat')
588
- if (cgnatCandidate) {
589
- return cgnatCandidate.address
643
+ if (!selectedHost && interface_addresses.length > 0) {
644
+ selectedHost = interface_addresses[0].address
590
645
  }
591
- const publicCandidate = shareable.find((entry) => entry.scope === 'public')
592
- if (publicCandidate) {
593
- return publicCandidate.address
646
+ if (!selectedHost) {
647
+ selectedHost = '127.0.0.1'
594
648
  }
595
- return shareable.length > 0 ? shareable[0].address : null
649
+ return { host: selectedHost, host_candidates, interface_addresses }
596
650
  }
597
651
  isPrivateOrCGNAT(ip) {
598
652
  return this.isRFC1918(ip) || this.isCGNAT(ip)
@@ -626,7 +680,7 @@ class PeerDiscovery {
626
680
  }
627
681
  return secondOctet >= 16 && secondOctet <= 31
628
682
  }
629
- _collectInterfaceAddresses() {
683
+ _collectInterfaceAddressesSync() {
630
684
  const interfaces = os.networkInterfaces()
631
685
  const results = []
632
686
  const seen = new Set()
@@ -653,12 +707,51 @@ class PeerDiscovery {
653
707
  interface: ifaceName,
654
708
  internal: Boolean(iface.internal),
655
709
  scope: classification.scope,
656
- shareable: classification.shareable
710
+ shareable: classification.shareable,
711
+ mac: typeof iface.mac === 'string' ? iface.mac : null
657
712
  })
658
713
  }
659
714
  }
660
715
  return results
661
716
  }
717
+ async _collectInterfaceAddresses() {
718
+ const baseEntries = this._collectInterfaceAddressesSync()
719
+ let metadata = []
720
+ try {
721
+ metadata = await systeminformation.networkInterfaces()
722
+ } catch (err) {
723
+ metadata = []
724
+ }
725
+ const metadataMap = new Map()
726
+ if (Array.isArray(metadata)) {
727
+ metadata.forEach((entry) => {
728
+ if (entry && typeof entry.iface === 'string') {
729
+ metadataMap.set(this._normalizeInterfaceName(entry.iface), entry)
730
+ }
731
+ })
732
+ }
733
+ return baseEntries.map((entry) => {
734
+ const key = this._normalizeInterfaceName(entry.interface)
735
+ const meta = key ? metadataMap.get(key) : null
736
+ const prefixLength = this._prefixLengthFromNetmask(entry.netmask)
737
+ return {
738
+ ...entry,
739
+ prefixLength,
740
+ type: meta && meta.type ? meta.type : null,
741
+ operstate: meta && meta.operstate ? meta.operstate : null,
742
+ speed: typeof meta?.speed === 'number' ? meta.speed : null,
743
+ virtual: Boolean(meta && meta.virtual),
744
+ default: Boolean(meta && meta.default),
745
+ mac: entry.mac || (meta && meta.mac) || null
746
+ }
747
+ })
748
+ }
749
+ _normalizeInterfaceName(name) {
750
+ if (!name || typeof name !== 'string') {
751
+ return ''
752
+ }
753
+ return name.trim().toLowerCase()
754
+ }
662
755
  classifyAddress(address, isInternal = false) {
663
756
  if (!address || typeof address !== 'string') {
664
757
  return { scope: 'unknown', shareable: false }
@@ -684,6 +777,80 @@ class PeerDiscovery {
684
777
  }
685
778
  return { scope: 'public', shareable: true }
686
779
  }
780
+ _prefixLengthFromNetmask(netmask) {
781
+ if (!netmask || typeof netmask !== 'string') {
782
+ return null
783
+ }
784
+ const octets = this._parseIPv4(netmask)
785
+ if (!octets) {
786
+ return null
787
+ }
788
+ let bits = 0
789
+ for (const octet of octets) {
790
+ bits += this._countBits(octet)
791
+ }
792
+ return bits
793
+ }
794
+ _countBits(value) {
795
+ let count = 0
796
+ let v = value & 255
797
+ while (v) {
798
+ count += v & 1
799
+ v >>= 1
800
+ }
801
+ return count
802
+ }
803
+ // Heuristically rank interface candidates so physical LAN adapters win over VPN/tunnels
804
+ _scoreCandidate(entry) {
805
+ if (!entry || !entry.shareable) {
806
+ return -Infinity
807
+ }
808
+ let score = 0
809
+ switch (entry.scope) {
810
+ case 'lan':
811
+ score += 100
812
+ break
813
+ case 'cgnat':
814
+ score += 60
815
+ break
816
+ case 'public':
817
+ score += 40
818
+ break
819
+ default:
820
+ score -= 50
821
+ break
822
+ }
823
+ if (entry.default) {
824
+ score += 20
825
+ }
826
+ const type = entry.type ? entry.type.toLowerCase() : ''
827
+ if (type === 'wired') {
828
+ score += 25
829
+ } else if (type === 'wireless') {
830
+ score += 18
831
+ } else if (type === 'vpn') {
832
+ score -= 40
833
+ } else if (type === 'cellular') {
834
+ score += 5
835
+ }
836
+ if (entry.virtual) {
837
+ score -= 25
838
+ }
839
+ if (entry.operstate && entry.operstate.toLowerCase() === 'up') {
840
+ score += 5
841
+ } else if (entry.operstate) {
842
+ score -= 10
843
+ }
844
+ if (typeof entry.prefixLength === 'number') {
845
+ if (entry.prefixLength <= 24) {
846
+ score += 5
847
+ }
848
+ if (entry.prefixLength >= 30) {
849
+ score -= 20
850
+ }
851
+ }
852
+ return score
853
+ }
687
854
  _buildExternalHostEntries(externalPort) {
688
855
  if (!externalPort && externalPort !== 0) {
689
856
  return []
@@ -741,7 +908,7 @@ class PeerDiscovery {
741
908
  return entries
742
909
  }
743
910
  _broadcastTargets() {
744
- const addresses = this._collectInterfaceAddresses()
911
+ const addresses = this._collectInterfaceAddressesSync()
745
912
  this.interface_addresses = addresses
746
913
  const targets = new Set()
747
914
  for (const entry of addresses) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "3.270.0",
3
+ "version": "3.271.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {