High-performance, production-ready LocalSend protocol receiver built in Go. Receive files from any LocalSend-compatible device (iOS, Android, Windows, macOS, Linux) on your NAS or server seamlessly.
- 🔐 Native HTTPS — Auto-generates RSA-2048 self-signed TLS certificates at startup. No external reverse proxy needed.
- 📡 UDP Multicast Discovery — Automatically announces availability to the local network using LocalSend protocol v2.
- 📦 Seamless File Reception — Stream-based file reception with automatic duplicate renaming (
file_timestamp.ext). Files are organized into{sender_fingerprint}/YYYY/MM/subdirectories based on the sender device fingerprint and file's modified time metadata. - 🛡️ Admin Dashboard — Modern responsive web UI at
0.0.0.0:53318for monitoring transfers, managing files, and configuring settings. Mobile-friendly with responsive layout. - ♻️ Resource Efficient — Capped log buffer (1000 entries), ~9MB static binary, ~10MB memory footprint.
- 🐳 Docker Ready — Multi-stage build with health checks, resource limits, and volume persistence.
- 🚀 Zero Dependencies — Single static binary with no runtime dependencies. Deploy anywhere.
# Build from source
go build -o localsend-hub . && go build -o localsend-hub-admin ./cmd/admin
# Run core service (file reception)
./localsend-hub
# Run admin service (management UI) - in another terminal
./localsend-hub-admin# Build and run (starts both services)
./docker.sh
# or: docker compose up -d
# Access admin panel
open http://localhost:53318docker compose up -dThis mounts ./data/received and ./data/config for persistent storage.
| Setting | Value | Description |
|---|---|---|
| Core Port | 53317 (HTTPS) |
LocalSend receiver endpoint |
| Admin Port | 53318 (HTTP) |
Management dashboard (accessible from LAN) |
| Device Alias | LocalSend Hub |
Visible name on local network |
| Device Type | server |
Reported to LocalSend clients |
| Receive Directory | ./received |
Where files are saved |
| File Storage | received/{sender_fingerprint}/YYYY/MM/ |
Organized by sender fingerprint and file modified time |
| Multicast Address | 224.0.0.167:53317 |
LocalSend discovery protocol |
| Max Logs | 1000 |
Ring buffer, oldest dropped |
LocalSend Hub uses a dual-service decoupled architecture for fault isolation:
┌───────────────────────────────────────────┐
│ Container / Host │
│ │
┌────────────┐ │ ┌──────────────┐ ┌──────────────┐ │
│ LocalSend │ │ │ Core Service │ │ Admin Service│ │
│ Clients │◄──┤ │ Port 53317 │ │ Port 53318 │ │
│ (Mobile) │ │ │ (HTTPS) │ │ (HTTP/Local) │ │
└────────────┘ │ │ │ │ │ │
│ │ - TLS Gen │ │ - Web UI │ │
┌──────────┤ │ - File Save │ │ - API │ │
│ │ │ - Multicast │ │ - Config │ │
▼ │ └──────────────┘ └──────────────┘ │
Shared Config File ┌──────────────────┐ │
(localsend_config.json) │ Shared SQLite DB │ │
- Config - Device Identity │ (Transfer Logs) │ │
└──────────────────┘ │
└───────────────────────────────────────────────────────────────┘
Key Benefits:
- Fault Isolation: If Admin Service crashes, Core Service continues receiving files
- Independent Scaling: Each service can be deployed/scaled separately
- Clean Separation: Clear boundaries between file reception and management concerns
- Consistent Logs: Both services share the same SQLite database, no stale data
.
├── main.go # Core service entry point
├── cmd/
│ └── admin/
│ └── main.go # Admin service entry point
├── internal/ # Private implementation (not importable)
│ ├── state/ # 💾 Thread-safe config, sessions
│ │ ├── state.go # Core service state management
│ │ ├── admin_state.go # Admin service state management
│ │ ├── shared.go # Shared types (LogEntry, ConfigData)
│ │ └── admin_provider.go # Interface for cross-process state
│ │ └── persistence.go # JSON config file I/O
│ ├── db/ # 🗄️ SQLite database layer
│ │ └── logdb.go # Transfer logs persistence
│ ├── discovery/ # 📡 UDP multicast announcer
│ │ └── multicast.go # Periodic network discovery
│ ├── core/ # 🌐 HTTPS server + LocalSend handlers
│ │ └── server.go # TLS cert gen, API endpoints
│ └── admin/ # 🛡️ Admin panel + embedded web UI
│ ├── server.go # HTTP server with go:embed
│ └── web/ # Frontend assets
│ ├── index.html # Dashboard HTML
│ ├── style.css # Dark/light theme CSS
│ └── app.js # Vanilla JS frontend
├── Dockerfile # Multi-stage Docker build (both binaries)
├── docker-compose.yml # Docker Compose configuration
├── entrypoint.sh # Docker entrypoint (starts both services)
├── go.mod # Module definition
└── received/ # [Git-ignored] File storage directory
Implements the LocalSend Protocol v2.
| Endpoint | Method | Query Params | Body | Response |
|---|---|---|---|---|
/api/localsend/v2/info |
GET |
— | — | {alias, version, deviceModel, deviceType, fingerprint, download} |
/api/localsend/v2/register |
POST |
— | {alias, version, ...} |
{alias, version, deviceModel, deviceType, fingerprint, download} |
/api/localsend/v2/prepare-upload |
POST |
?pin=xxx (optional) |
{info, files: {id: {id, fileName, size, fileType, sha256, preview, metadata}}} |
{sessionId, files: {fileId: token}} |
/api/localsend/v2/upload |
POST |
sessionId, fileId, token |
Binary (Octet-Stream) | 200 (no body) |
/api/localsend/v2/cancel |
POST |
sessionId |
— | 200 (no body) |
| Endpoint | Method | Description |
|---|---|---|
/ |
GET |
Admin dashboard |
/api/logs |
GET |
Transfer logs (reverse chronological) |
/api/logs |
DELETE |
Clear all logs |
/api/identity |
GET |
Current device identity |
/api/identity |
POST |
Update alias/model/type |
/api/config |
POST |
Update receive directory |
/api/files |
GET |
List received files with metadata |
/files/{filename} |
GET |
Download a received file |
LocalSend Hub uses a simple layered configuration:
- Built-in defaults — loaded first
- Config file — overrides defaults (if file exists)
- Environment variables — overrides everything (if set at startup)
Settings changed via the Admin UI are automatically saved to the config file. Environment variables take effect at container startup and override config file values.
| Variable | Default | Description |
|---|---|---|
LOCALSEND_RECEIVE_DIR |
/app/received |
Directory to save received files |
LOCALSEND_PORT |
53317 |
HTTPS listener port |
LOCALSEND_ADMIN_PORT |
53318 |
Admin panel port |
LOCALSEND_DEVICE_NAME |
LocalSend Hub |
Device name shown to senders |
LOCALSEND_DEVICE_TYPE |
server |
Device type |
LOCALSEND_MAX_LOGS |
1000 |
Max log entries |
LOCALSEND_CONFIG_PATH |
(auto) | Custom config file path (optional) |
LocalSend Hub persists configuration to localsend_config.json. In Docker, the default path is /app/config/localsend_config.json (mounted via volume). The file is auto-created on first run and saved every 15 seconds or on config change.
Transfer logs are stored in a separate SQLite database (localsend_logs.db), ensuring real-time consistency between both services without file polling.
{
"receiveDir": "./received",
"corePort": 53317,
"adminPort": 53318,
"alias": "LocalSend Hub",
"deviceModel": "LocalSend Hub Server",
"deviceType": "server",
"maxLogs": 1000
}| Feature | Implementation |
|---|---|
| Transport Encryption | RSA-2048 self-signed TLS (auto-generated, 10-year validity) |
| Device Fingerprint | SHA-256 hash of TLS certificate DER bytes |
| Path Traversal Prevention | filepath.Base() strips directory components from filenames |
| Admin Panel Isolation | Binds to 0.0.0.0 — accessible from LAN; consider adding auth for remote access |
| Thread Safety | All shared state protected by sync.Mutex |
Pull the official image from GitHub Container Registry:
# Pull latest stable release
docker pull ghcr.io/linychuo/localsend-hub:latest
# Or pull a specific version
docker pull ghcr.io/linychuo/localsend-hub:v1.0.0# Using official image from GHCR
docker run -d \
--name localsend-hub \
--network host \
-v $(pwd)/data/received:/app/received \
-v $(pwd)/data/config:/app/config \
-v $(pwd)/data/sqlite:/app/data \
ghcr.io/linychuo/localsend-hub:latestOr using Docker Compose:
docker compose up -ddocker build -t localsend-hub .
docker run -d \
--name localsend-hub \
-p 53317:53317 \
-p 53318:53318 \
-v $(pwd)/data/received:/app/received \
-v $(pwd)/data/config:/app/config \
-v $(pwd)/data/sqlite:/app/data \
localsend-hubFor multicast to work across your local network, use network_mode: host in Docker Compose:
services:
localsend-hub:
network_mode: host
volumes:
- ./data/received:/app/received
- ./data/config:/app/config
- ./data/sqlite:/app/data
⚠️ Note:network_mode: hostremoves network isolation. Only use on trusted networks.
The container includes an automatic health check that verifies the admin API is responsive every 30 seconds.
- Ensure UDP port
53317is not blocked by firewall rules. - If using Docker, verify multicast routing with
network_mode: host. - Some routers block multicast; check your network configuration.
- The admin panel binds to
0.0.0.0:53318and is accessible from LAN. - For secure remote access, use SSH tunneling:
ssh -L 53318:localhost:53318 user@server - Or add authentication (Basic Auth) before exposing to untrusted networks.
- Verify the
received/directory exists and has write permissions. - Check logs via admin panel:
http://localhost:53318
| Metric | Value |
|---|---|
| Binary Size | ~9 MB |
| Memory (Idle) | ~10 MB |
| CPU (Idle) | < 0.1% |
| Docker Image | ~15 MB (Alpine-based) |
| Startup Time | < 100 ms |
| Feature | Status |
|---|---|
| SHA-256 file integrity verification | 🔲 Planned |
| PIN-based authentication | 🔲 Planned |
| Session TTL expiration | 🔲 Planned |
| Rate limiting | 🔲 Planned |
| Concurrent upload limits | 🔲 Planned |
| Atomic file writes (temp + rename) | 🔲 Planned |
| Cloud storage backend | 🔲 Planned |
| mDNS/Bonjour discovery | 🔲 Planned |
See requirements.md for detailed feature specifications.
MIT License. See LICENSE for details.
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Commit your changes (
git commit -m 'Add my feature') - Push to the branch (
git push origin feature/my-feature) - Open a Pull Request
- LocalSend — For the open protocol specification
- Go standard library — For the robust
net/http,crypto/tls, andnetpackages