peterretief/peertopeer

0

stars

7

commits

Go

primary language

Sep 12, 2026

updated

README

Distributed Store

Peer-to-peer file storage for an existing Headscale/Tailscale mesh.

The design is documented in AGENT.md. The deployed desktop watcher encrypts files, splits each chunk into 5 data shards plus 2 parity shards, distributes those shards across online peers, and writes a portable .dstore manifest that can restore the original file. New files are processed in bounded 16 MiB chunks so memory use does not grow with the file size; standalone commands retain the legacy 2+1 defaults unless their layout flags are set.

Important: the .dstore manifest contains the AES key. Anyone who receives the manifest and can reach at least 5 shard URLs for every version-2 chunk can recreate the original file.

Prerequisites

  • Go 1.24+
  • Tailscale or Headscale running on each peer
  • Each peer must have a unique hostname configured in Tailscale

Quick Start

Build the binary:

go build -o bin/dstore ./cmd/dstore

Run the agent (serves shards + watches for new files):

bin/dstore agent -origin outfiles -shards .dstore-shards -addr :8080

To use specific shard nodes instead of random online Tailscale peers, pass -peers:

bin/dstore agent -origin outfiles -shards .dstore-shards -addr :8080 \
  -peers node-a=100.64.0.10:8080,node-b=100.64.0.11:8080,node-c=100.64.0.12:8080

The peer format is hostname=host[:port]. If fewer peers are listed than shard count, shards are assigned round-robin across those peers.

Drop a file or directory into outfiles/. The agent will process regular files recursively and leave .dstore manifests beside each original path:

  1. Encrypt it with AES-256-GCM
  2. Split each encrypted chunk into 7 Reed-Solomon shards (5 data + 2 parity)
  3. Randomly select online peers from your Tailscale mesh, or use the explicit -peers list
  4. Push each shard to a peer via HTTP PUT
  5. Store a local copy as well
  6. Create filename.dstore in outfiles/
  7. Remove the original file

Peer-to-Peer Workflow

On each mesh peer

Install and start the agent:

./scripts/install-user-service.sh
systemctl --user start peertopeer-dstore
systemctl --user enable peertopeer-dstore

Or run directly:

bin/dstore agent -origin outfiles -shards .dstore-shards -addr :8080

Storing a file

Drop any file into the outfiles/ directory on any peer. The agent automatically:

  • Picks random online peers from your Tailscale mesh, or uses the configured -peers shard nodes
  • Pushes shards to each peer's shard server
  • Creates a .dstore manifest in outfiles/

Restoring a file

Double-click (after desktop integration):

./scripts/install-desktop-integration.sh
xdg-open outfiles/example.txt.dstore

CLI:

bin/dstore restore -stub outfiles/example.txt.dstore

Restore fetches shards from the URLs in the manifest. Only 5 of 7 shards per chunk need to be reachable. If a peer is offline, the remaining shards are sufficient.

Sharing a file

Email the .dstore file to another mesh member. They can restore it on any peer that can reach at least 5 of the 7 shard URLs for every chunk.

Commands

CommandDescription
peersList Tailscale peers visible to this node
agentCombined shard server + file watcher (recommended)
watchWatch a directory for new files to shard
processOne-shot: process all current files in a directory
restoreRestore a file from a .dstore manifest
serve-shardsRun only the shard HTTP server

peers

bin/dstore peers
bin/dstore peers -online-only

agent

bin/dstore agent \
  -origin outfiles \
  -shards .dstore-shards \
  -addr :8080 \
  -base-url "" \
  -interval 2s \
  -peers node-a=100.64.0.10:8080,node-b=100.64.0.11:8080
FlagDefaultDescription
-originoutfilesDirectory to watch for files to shard
-shards.dstore-shardsDirectory for local shard storage
-addr:8080HTTP listen address
-base-urlautoOverride URL in manifests (default: Tailscale IPv4)
-interval2sWatch poll interval
-peersautoComma-separated explicit shard nodes as hostname=host[:port]; overrides random peer selection
-peer-portsnoneComma-separated hostname:port overrides for auto-selected peers
-data-shards2Number of data shards (the active service passes 5)
-parity-shards1Number of parity shards (the active service passes 2)
-chunk-size16 MiBPlaintext bytes processed at a time
-max-file-bytes64 MiBMaximum input file size for this command

restore

bin/dstore restore \
  -stub outfiles/photo.dstore \
  -shards .dstore-shards \
  -output photo.jpg
FlagDefaultDescription
-stub(required)Path to the .dstore manifest
-shards(none)Local shard directory fallback
-output(stub path minus .dstore)Output path for restored file

Service Management

systemctl --user start peertopeer-dstore    # start
systemctl --user stop peertopeer-dstore     # stop
systemctl --user status peertopeer-dstore   # status
systemctl --user enable peertopeer-dstore   # auto-start on login
journalctl --user -u peertopeer-dstore -f   # follow logs

Docker peers on the Headscale server

To run multiple storage peers on the same Linux machine as an existing Headscale server, use the included Compose stack. It creates two separate Tailscale nodes (peer-a and peer-b) by default with persistent identities and storage; --profile extra adds peer-c and peer-d. No public dstore ports are exposed.

The current erasure layout needs three ready storage peers for automatic file placement, so enable the extra profile or add a remote dstore node before using a peer as a file origin.

Create a reusable Headscale pre-authentication key, copy the environment template, and start the stack:

headscale users create dstore
headscale preauthkeys create --user dstore --reusable --expiration 24h
cp .env.peers.example .env.peers
# Set HEADSCALE_URL and HEADSCALE_AUTHKEY in .env.peers.
docker compose --env-file .env.peers -f docker-compose.peers.yml up -d --build

See docs/docker-peers.md for verification, storage paths, and the required /dev/net/tun Docker capability.

Desktop Integration

Install .dstore file association:

./scripts/install-desktop-integration.sh

After installation, double-clicking a .dstore file restores the original file.

Security

ConcernMechanism
Transport encryptionWireGuard (Tailscale/Headscale)
Peer authenticationTailscale identity (tailscale whois)
Shard confidentialityAES-256-GCM before sharding
Key distributionLives only in the .dstore manifest
Tamper detectionContent-addressed shard IDs (hash = filename)
Delete abuse preventionOnly the original writer can delete a shard

Testing

go test ./...

Contributors

peterretief

7 commits

peterretief/peertopeer

0

stars

7

commits

Go

primary language

Sep 12, 2026

updated

README

Distributed Store

Peer-to-peer file storage for an existing Headscale/Tailscale mesh.

The design is documented in AGENT.md. The deployed desktop watcher encrypts files, splits each chunk into 5 data shards plus 2 parity shards, distributes those shards across online peers, and writes a portable .dstore manifest that can restore the original file. New files are processed in bounded 16 MiB chunks so memory use does not grow with the file size; standalone commands retain the legacy 2+1 defaults unless their layout flags are set.

Important: the .dstore manifest contains the AES key. Anyone who receives the manifest and can reach at least 5 shard URLs for every version-2 chunk can recreate the original file.

Prerequisites

  • Go 1.24+
  • Tailscale or Headscale running on each peer
  • Each peer must have a unique hostname configured in Tailscale

Quick Start

Build the binary:

go build -o bin/dstore ./cmd/dstore

Run the agent (serves shards + watches for new files):

bin/dstore agent -origin outfiles -shards .dstore-shards -addr :8080

To use specific shard nodes instead of random online Tailscale peers, pass -peers:

bin/dstore agent -origin outfiles -shards .dstore-shards -addr :8080 \
  -peers node-a=100.64.0.10:8080,node-b=100.64.0.11:8080,node-c=100.64.0.12:8080

The peer format is hostname=host[:port]. If fewer peers are listed than shard count, shards are assigned round-robin across those peers.

Drop a file or directory into outfiles/. The agent will process regular files recursively and leave .dstore manifests beside each original path:

  1. Encrypt it with AES-256-GCM
  2. Split each encrypted chunk into 7 Reed-Solomon shards (5 data + 2 parity)
  3. Randomly select online peers from your Tailscale mesh, or use the explicit -peers list
  4. Push each shard to a peer via HTTP PUT
  5. Store a local copy as well
  6. Create filename.dstore in outfiles/
  7. Remove the original file

Peer-to-Peer Workflow

On each mesh peer

Install and start the agent:

./scripts/install-user-service.sh
systemctl --user start peertopeer-dstore
systemctl --user enable peertopeer-dstore

Or run directly:

bin/dstore agent -origin outfiles -shards .dstore-shards -addr :8080

Storing a file

Drop any file into the outfiles/ directory on any peer. The agent automatically:

  • Picks random online peers from your Tailscale mesh, or uses the configured -peers shard nodes
  • Pushes shards to each peer's shard server
  • Creates a .dstore manifest in outfiles/

Restoring a file

Double-click (after desktop integration):

./scripts/install-desktop-integration.sh
xdg-open outfiles/example.txt.dstore

CLI:

bin/dstore restore -stub outfiles/example.txt.dstore

Restore fetches shards from the URLs in the manifest. Only 5 of 7 shards per chunk need to be reachable. If a peer is offline, the remaining shards are sufficient.

Sharing a file

Email the .dstore file to another mesh member. They can restore it on any peer that can reach at least 5 of the 7 shard URLs for every chunk.

Commands

CommandDescription
peersList Tailscale peers visible to this node
agentCombined shard server + file watcher (recommended)
watchWatch a directory for new files to shard
processOne-shot: process all current files in a directory
restoreRestore a file from a .dstore manifest
serve-shardsRun only the shard HTTP server

peers

bin/dstore peers
bin/dstore peers -online-only

agent

bin/dstore agent \
  -origin outfiles \
  -shards .dstore-shards \
  -addr :8080 \
  -base-url "" \
  -interval 2s \
  -peers node-a=100.64.0.10:8080,node-b=100.64.0.11:8080
FlagDefaultDescription
-originoutfilesDirectory to watch for files to shard
-shards.dstore-shardsDirectory for local shard storage
-addr:8080HTTP listen address
-base-urlautoOverride URL in manifests (default: Tailscale IPv4)
-interval2sWatch poll interval
-peersautoComma-separated explicit shard nodes as hostname=host[:port]; overrides random peer selection
-peer-portsnoneComma-separated hostname:port overrides for auto-selected peers
-data-shards2Number of data shards (the active service passes 5)
-parity-shards1Number of parity shards (the active service passes 2)
-chunk-size16 MiBPlaintext bytes processed at a time
-max-file-bytes64 MiBMaximum input file size for this command

restore

bin/dstore restore \
  -stub outfiles/photo.dstore \
  -shards .dstore-shards \
  -output photo.jpg
FlagDefaultDescription
-stub(required)Path to the .dstore manifest
-shards(none)Local shard directory fallback
-output(stub path minus .dstore)Output path for restored file

Service Management

systemctl --user start peertopeer-dstore    # start
systemctl --user stop peertopeer-dstore     # stop
systemctl --user status peertopeer-dstore   # status
systemctl --user enable peertopeer-dstore   # auto-start on login
journalctl --user -u peertopeer-dstore -f   # follow logs

Docker peers on the Headscale server

To run multiple storage peers on the same Linux machine as an existing Headscale server, use the included Compose stack. It creates two separate Tailscale nodes (peer-a and peer-b) by default with persistent identities and storage; --profile extra adds peer-c and peer-d. No public dstore ports are exposed.

The current erasure layout needs three ready storage peers for automatic file placement, so enable the extra profile or add a remote dstore node before using a peer as a file origin.

Create a reusable Headscale pre-authentication key, copy the environment template, and start the stack:

headscale users create dstore
headscale preauthkeys create --user dstore --reusable --expiration 24h
cp .env.peers.example .env.peers
# Set HEADSCALE_URL and HEADSCALE_AUTHKEY in .env.peers.
docker compose --env-file .env.peers -f docker-compose.peers.yml up -d --build

See docs/docker-peers.md for verification, storage paths, and the required /dev/net/tun Docker capability.

Desktop Integration

Install .dstore file association:

./scripts/install-desktop-integration.sh

After installation, double-clicking a .dstore file restores the original file.

Security

ConcernMechanism
Transport encryptionWireGuard (Tailscale/Headscale)
Peer authenticationTailscale identity (tailscale whois)
Shard confidentialityAES-256-GCM before sharding
Key distributionLives only in the .dstore manifest
Tamper detectionContent-addressed shard IDs (hash = filename)
Delete abuse preventionOnly the original writer can delete a shard

Testing

go test ./...

Contributors

peterretief

7 commits

Languages

Go

92.4%

Shell

7.1%