Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
data
.DS_Store
.env
peers.json
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ COPY src/ ./src/

ENV PORT=3000
ENV NODE_ENV=production
ENV ENABLE_IPV4_SCAN=false

EXPOSE 3000

Expand Down
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ We utilize the **Hyperswarm** DHT (Distributed Hash Table) to achieve a singular

If you turn your container off, you vanish from the count. If everyone turns it off, the network ceases to exist. If you turn it back on, you are the Creator of the Universe (Population: 1).

### Peer Bootstrap Strategy

When your node starts, it uses a multi-phase bootstrap strategy to find peers. By default, it skips directly to DHT discovery, which is simple and works everywhere. If you want faster initial discovery on your network, you can opt into IPv4 scanning.

**Phase 1: Cached Peers** - If peer caching is enabled, the node will first try to reconnect to peers it has seen before. On startup, if any of these cached peers are still online, connection happens instantly without waiting for discovery. This is only used if you explicitly enable PEER_CACHE_ENABLED. Cached peers older than 24 hours are automatically pruned as stale.

**Phase 2: IPv4 Address Space Scan (Optional)** - If you enable ENABLE_IPV4_SCAN, the node will perform an intelligent scan of the IPv4 address space looking for other Hypermind nodes on your configured port. Instead of scanning sequentially (which would take forever), it uses a Feistel cipher to generate a randomized enumeration of addresses, with each node getting a unique scan order to distribute the search load evenly. This scan times out after a configurable duration before moving on. This feature is disabled by default and must be explicitly enabled if you want it.

**Phase 3: DHT Discovery** - The node joins the Hyperswarm DHT under the topic 'hypermind-lklynet-v1' and waits for peers. This always works eventually and is the default discovery mechanism for all nodes. It may take longer on initial startup without the optional IPv4 scan, but it is completely reliable.


## » Deployment

### Docker (The Fast Way)
Expand Down Expand Up @@ -126,6 +137,13 @@ See detailed [instructions](https://gethomepage.dev/configs/services/#icons).
| --- | --- | --- |
| `PORT` | `3000` | The port the web dashboard listens on. Since `--network host` is used, this port opens directly on the host. |
| `MAX_PEERS` | `1000000` | Maximum number of peers to track in the swarm. Unless you're expecting the entire internet to join, the default is probably fine. |
| `ENABLE_IPV4_SCAN` | `false` | Enable IPv4 address space scanning for peer discovery. Disabled by default. Set to `true` to scan the entire IPv4 Network for Hypermind nodes. Most users should leave this disabled and rely on DHT discovery. |
| `SCAN_PORT` | `3000` | The port to scan on remote IPv4 addresses when IPv4 scanning is enabled. This should match the port other nodes are listening on. |
| `BOOTSTRAP_TIMEOUT` | `10000` | Time in milliseconds to spend scanning the IPv4 address space before giving up and using DHT discovery. Only used if ENABLE_IPV4_SCAN is true. Set to 0 to skip scanning and go straight to DHT. |
| `PEER_CACHE_ENABLED` | `false` | Enable or disable the peer cache feature. Set to `true` to cache discovered peers for faster reconnection on restart. Cache is disabled by default. |
| `PEER_CACHE_PATH` | `./peers.json` | Path to the JSON file where discovered peers are cached (only used if PEER_CACHE_ENABLED is true). |
| `PEER_CACHE_MAX_AGE` | `86400` | Maximum age in seconds for cached peers before they are considered stale and removed. Default is 24 hours. Only applies when cache is enabled. |
| `BOOTSTRAP_PEER_IP` | (unset) | Debug mode: Set this to an IPv4 address to skip all bootstrap phases and connect directly to that peer. Useful for testing and scenarios where you know peer addresses in advance. |

## » Usage

Expand Down Expand Up @@ -164,7 +182,23 @@ PORT=3001 npm start

```

They should discover each other, and the number will become `2`. Dopamine achieved.
They should discover each other via DHT, and the number will become 2. Dopamine achieved.

### Fast Bootstrap Testing

For testing scenarios where you want to skip the IPv4 scan and immediately use DHT discovery:

```bash
BOOTSTRAP_TIMEOUT=0 npm start
```

Or if you know the exact IP of another node (useful in docker-compose or test environments):

```bash
BOOTSTRAP_PEER_IP=192.168.1.100 npm start
```

This connects directly to that peer, skipping all bootstrap phases. If the connection fails, it falls back to normal bootstrap automatically.

---

Expand All @@ -176,5 +210,8 @@ A: No. We respect your GPU too much.
**Q: Does this store data?**
A: No. It has the short-term working memory of a honeybee (approx. 2.5 seconds). Which is biologically accurate and thematically consistent.

**Q: Should I enable IPv4 scanning?**
A: Probably not. DHT discovery works fine and doesn't require any special configuration. IPv4 scanning is there if you want extremely slow initial peer discovery or if you hate your IP's reputation, but it is not necessary for the network to function. Most deployments should just leave it disabled and let DHT do its thing.
Comment on lines +213 to +214
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LMFAO

Copy link
Author

@alphaO4 alphaO4 Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean it is pure and utterly stupid.
While it can work, with only 200 nodes you might as-well play the lottery.

That being said, I had some successful connections running. (Running the code on 3 containers with 170 Addresses/Sec)


**Q: Why did you make this?**
A: The homelab must grow. ¯\\_(ツ)_/¯
5 changes: 5 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ services:
restart: unless-stopped
environment:
- PORT=3000
# Enable IPv4 scanning (disabled by default, uses DHT only)
# - ENABLE_IPV4_SCAN=true
# Debug: uncomment to connect directly to a peer IP (skip scan/cache)
# This is useful for testing with multiple local instances
# - BOOTSTRAP_PEER_IP=127.0.0.1
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ services:
environment:
- PORT=3000
- MAX_PEERS=10000
# Scan the IPv4 address space for peers (disabled by default)
# Set to 'true' to enable. Without it, only DHT discovery is used.
# - ENABLE_IPV4_SCAN=true
10 changes: 9 additions & 1 deletion server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require('dotenv').config();

const crypto = require('crypto');
const { generateIdentity } = require("./src/core/identity");
const { PeerManager } = require("./src/state/peers");
const { DiagnosticsManager } = require("./src/state/diagnostics");
Expand All @@ -8,6 +9,7 @@ const { relayMessage } = require("./src/p2p/relay");
const { SwarmManager } = require("./src/p2p/swarm");
const { SSEManager } = require("./src/web/sse");
const { createServer, startServer } = require("./src/web/server");
const { bootstrapPeers } = require("./src/discovery/bootstrap");
const { DIAGNOSTICS_INTERVAL } = require("./src/config/constants");

const main = async () => {
Expand Down Expand Up @@ -44,7 +46,13 @@ const main = async () => {
broadcastUpdate
);

await swarmManager.start();
// Bootstrap phase: attempt to find peers via cache or IPv4 scan before joining DHT
// Use entropy from crypto for deterministic per-node Feistel seed
const bootstrapSeed = crypto.randomBytes(8).readBigUInt64BE();
console.log(`[main] Bootstrap seed: ${bootstrapSeed.toString(16)}`);
const bootstrapPeer = await bootstrapPeers(Number(bootstrapSeed), identity);

await swarmManager.start(bootstrapPeer);

diagnostics.startLogging(
() => peerManager.size,
Expand Down
22 changes: 22 additions & 0 deletions src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ const BROADCAST_THROTTLE = 1000;
const DIAGNOSTICS_INTERVAL = 10000;
const PORT = process.env.PORT || 3000;

// Bootstrap configuration
const ENABLE_IPV4_SCAN = process.env.ENABLE_IPV4_SCAN === 'true' || false; // Disabled by default
const SCAN_PORT = parseInt(process.env.SCAN_PORT) || 3000;
const BOOTSTRAP_TIMEOUT = parseInt(process.env.BOOTSTRAP_TIMEOUT) || 10000;
const PEER_CACHE_ENABLED = process.env.PEER_CACHE_ENABLED === 'true' || false; // Disabled by default
const PEER_CACHE_PATH = process.env.PEER_CACHE_PATH || './peers.json';
const PEER_CACHE_MAX_AGE = parseInt(process.env.PEER_CACHE_MAX_AGE) || 86400; // 24 hours in seconds
const BOOTSTRAP_PEER_IP = process.env.BOOTSTRAP_PEER_IP || null; // Debug: direct peer IP (skip scan/cache)
const MAX_SCAN_ATTEMPTS = 500000;
const SCAN_CONCURRENCY = 50;
const SCAN_CONNECTION_TIMEOUT = 300;

module.exports = {
TOPIC_NAME,
TOPIC,
Expand All @@ -39,4 +51,14 @@ module.exports = {
BROADCAST_THROTTLE,
DIAGNOSTICS_INTERVAL,
PORT,
ENABLE_IPV4_SCAN,
SCAN_PORT,
BOOTSTRAP_TIMEOUT,
PEER_CACHE_ENABLED,
PEER_CACHE_PATH,
PEER_CACHE_MAX_AGE,
BOOTSTRAP_PEER_IP,
MAX_SCAN_ATTEMPTS,
SCAN_CONCURRENCY,
SCAN_CONNECTION_TIMEOUT,
};
Loading