Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea7bc75fac | ||
|
|
2e9cde01f8 |
@@ -149,11 +149,12 @@ type C struct {
|
|||||||
BunkerEnabled bool `env:"ORLY_BUNKER_ENABLED" default:"false" usage:"enable NIP-46 bunker signing service (requires WireGuard)"`
|
BunkerEnabled bool `env:"ORLY_BUNKER_ENABLED" default:"false" usage:"enable NIP-46 bunker signing service (requires WireGuard)"`
|
||||||
BunkerPort int `env:"ORLY_BUNKER_PORT" default:"3335" usage:"internal port for bunker WebSocket (only accessible via WireGuard)"`
|
BunkerPort int `env:"ORLY_BUNKER_PORT" default:"3335" usage:"internal port for bunker WebSocket (only accessible via WireGuard)"`
|
||||||
|
|
||||||
// Tor hidden service configuration
|
// Tor hidden service configuration (subprocess mode - runs tor binary automatically)
|
||||||
TorEnabled bool `env:"ORLY_TOR_ENABLED" default:"false" usage:"enable Tor hidden service integration (requires external Tor daemon)"`
|
TorEnabled bool `env:"ORLY_TOR_ENABLED" default:"true" usage:"enable Tor hidden service (spawns tor subprocess; disable with false if tor not installed)"`
|
||||||
TorPort int `env:"ORLY_TOR_PORT" default:"3336" usage:"internal port that Tor forwards .onion traffic to"`
|
TorPort int `env:"ORLY_TOR_PORT" default:"3336" usage:"internal port for Tor hidden service traffic"`
|
||||||
TorHSDir string `env:"ORLY_TOR_HS_DIR" usage:"Tor HiddenServiceDir path to read .onion hostname (e.g., /var/lib/tor/orly-relay)"`
|
TorDataDir string `env:"ORLY_TOR_DATA_DIR" usage:"Tor data directory (default: $ORLY_DATA_DIR/tor)"`
|
||||||
TorOnionAddress string `env:"ORLY_TOR_ONION_ADDRESS" usage:"manual .onion address override (optional, auto-detected from TorHSDir if empty)"`
|
TorBinary string `env:"ORLY_TOR_BINARY" default:"tor" usage:"path to tor binary (default: search in PATH)"`
|
||||||
|
TorSOCKS int `env:"ORLY_TOR_SOCKS" default:"0" usage:"SOCKS port for outbound Tor connections (0=disabled)"`
|
||||||
|
|
||||||
// Cashu access token configuration (NIP-XX)
|
// Cashu access token configuration (NIP-XX)
|
||||||
CashuEnabled bool `env:"ORLY_CASHU_ENABLED" default:"false" usage:"enable Cashu blind signature tokens for access control"`
|
CashuEnabled bool `env:"ORLY_CASHU_ENABLED" default:"false" usage:"enable Cashu blind signature tokens for access control"`
|
||||||
@@ -645,11 +646,17 @@ func (cfg *C) GetStorageConfigValues() (
|
|||||||
func (cfg *C) GetTorConfigValues() (
|
func (cfg *C) GetTorConfigValues() (
|
||||||
enabled bool,
|
enabled bool,
|
||||||
port int,
|
port int,
|
||||||
hsDir string,
|
dataDir string,
|
||||||
onionAddress string,
|
binary string,
|
||||||
|
socksPort int,
|
||||||
) {
|
) {
|
||||||
|
dataDir = cfg.TorDataDir
|
||||||
|
if dataDir == "" {
|
||||||
|
dataDir = filepath.Join(cfg.DataDir, "tor")
|
||||||
|
}
|
||||||
return cfg.TorEnabled,
|
return cfg.TorEnabled,
|
||||||
cfg.TorPort,
|
cfg.TorPort,
|
||||||
cfg.TorHSDir,
|
dataDir,
|
||||||
cfg.TorOnionAddress
|
cfg.TorBinary,
|
||||||
|
cfg.TorSOCKS
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ type ExtendedRelayInfo struct {
|
|||||||
// Informer interface implementation or predefined server configuration. It
|
// Informer interface implementation or predefined server configuration. It
|
||||||
// returns this document as a JSON response to the client.
|
// returns this document as a JSON response to the client.
|
||||||
func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
r.Header.Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Header().Set("Vary", "Accept")
|
||||||
log.D.Ln("handling relay information document")
|
log.D.Ln("handling relay information document")
|
||||||
var info *relayinfo.T
|
var info *relayinfo.T
|
||||||
nips := []relayinfo.NIP{
|
nips := []relayinfo.NIP{
|
||||||
|
|||||||
16
app/main.go
16
app/main.go
@@ -549,22 +549,24 @@ func Run(
|
|||||||
log.I.F("archive relay manager initialized with %d relays", len(archiveRelays))
|
log.I.F("archive relay manager initialized with %d relays", len(archiveRelays))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Tor hidden service if enabled
|
// Initialize Tor hidden service if enabled (spawns tor subprocess)
|
||||||
torEnabled, torPort, torHSDir, torOnionAddr := cfg.GetTorConfigValues()
|
torEnabled, torPort, torDataDir, torBinary, torSOCKSPort := cfg.GetTorConfigValues()
|
||||||
if torEnabled {
|
if torEnabled {
|
||||||
torCfg := &tor.Config{
|
torCfg := &tor.Config{
|
||||||
Port: torPort,
|
Port: torPort,
|
||||||
HSDir: torHSDir,
|
DataDir: torDataDir,
|
||||||
OnionAddress: torOnionAddr,
|
Binary: torBinary,
|
||||||
Handler: l,
|
SOCKSPort: torSOCKSPort,
|
||||||
|
Handler: l,
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
l.torService, err = tor.New(torCfg)
|
l.torService, err = tor.New(torCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.W.F("failed to create Tor service: %v", err)
|
log.W.F("Tor disabled: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if err = l.torService.Start(); err != nil {
|
if err = l.torService.Start(); err != nil {
|
||||||
log.W.F("failed to start Tor service: %v", err)
|
log.W.F("failed to start Tor service: %v", err)
|
||||||
|
l.torService = nil
|
||||||
} else {
|
} else {
|
||||||
if addr := l.torService.OnionWSAddress(); addr != "" {
|
if addr := l.torService.OnionWSAddress(); addr != "" {
|
||||||
log.I.F("Tor hidden service listening on port %d, address: %s", torPort, addr)
|
log.I.F("Tor hidden service listening on port %d, address: %s", torPort, addr)
|
||||||
|
|||||||
@@ -738,6 +738,12 @@ func (s *Server) handleExport(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set(
|
w.Header().Set(
|
||||||
"Content-Disposition", "attachment; filename=\""+filename+"\"",
|
"Content-Disposition", "attachment; filename=\""+filename+"\"",
|
||||||
)
|
)
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
|
||||||
|
// Flush headers to start streaming immediately
|
||||||
|
if flusher, ok := w.(http.Flusher); ok {
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// Stream export
|
// Stream export
|
||||||
s.DB.Export(s.Ctx, w, pks...)
|
s.DB.Export(s.Ctx, w, pks...)
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
# ORLY Relay with Tor Hidden Service - Systemd Unit
|
|
||||||
#
|
|
||||||
# This is an example systemd unit for running ORLY with Tor support.
|
|
||||||
# Copy and customize for your deployment.
|
|
||||||
#
|
|
||||||
# Installation:
|
|
||||||
# 1. Copy to /etc/systemd/system/orly-tor.service
|
|
||||||
# 2. Edit paths and environment variables as needed
|
|
||||||
# 3. sudo systemctl daemon-reload
|
|
||||||
# 4. sudo systemctl enable orly-tor
|
|
||||||
# 5. sudo systemctl start orly-tor
|
|
||||||
#
|
|
||||||
# Prerequisites:
|
|
||||||
# - Tor daemon running (systemctl enable tor && systemctl start tor)
|
|
||||||
# - Hidden service configured (run scripts/tor-setup.sh)
|
|
||||||
|
|
||||||
[Unit]
|
|
||||||
Description=ORLY Nostr Relay with Tor Hidden Service
|
|
||||||
Documentation=https://git.mleku.dev/mleku/orly
|
|
||||||
After=network.target tor.service
|
|
||||||
Requires=tor.service
|
|
||||||
Wants=tor.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=orly
|
|
||||||
Group=orly
|
|
||||||
|
|
||||||
# Working directory
|
|
||||||
WorkingDirectory=/opt/orly
|
|
||||||
|
|
||||||
# Main relay binary
|
|
||||||
ExecStart=/opt/orly/orly
|
|
||||||
|
|
||||||
# Environment configuration
|
|
||||||
# Core settings
|
|
||||||
Environment=ORLY_PORT=3334
|
|
||||||
Environment=ORLY_DATA_DIR=/var/lib/orly
|
|
||||||
Environment=ORLY_LOG_LEVEL=info
|
|
||||||
|
|
||||||
# Tor hidden service settings
|
|
||||||
Environment=ORLY_TOR_ENABLED=true
|
|
||||||
Environment=ORLY_TOR_PORT=3336
|
|
||||||
Environment=ORLY_TOR_HS_DIR=/var/lib/tor/orly-relay
|
|
||||||
|
|
||||||
# ACL mode (choose one: none, follows, managed)
|
|
||||||
Environment=ORLY_ACL_MODE=none
|
|
||||||
|
|
||||||
# TLS (optional - uncomment and configure for production)
|
|
||||||
# Environment=ORLY_TLS_DOMAINS=relay.example.com
|
|
||||||
|
|
||||||
# Resource limits
|
|
||||||
LimitNOFILE=65535
|
|
||||||
LimitNPROC=4096
|
|
||||||
|
|
||||||
# Restart policy
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
# Security hardening
|
|
||||||
NoNewPrivileges=yes
|
|
||||||
ProtectSystem=strict
|
|
||||||
ProtectHome=yes
|
|
||||||
ReadWritePaths=/var/lib/orly
|
|
||||||
PrivateTmp=yes
|
|
||||||
|
|
||||||
# Allow reading Tor hidden service directory
|
|
||||||
# Note: The Tor user must grant read access to the orly user
|
|
||||||
# Option 1: Add orly user to debian-tor group
|
|
||||||
# Option 2: Use ACLs: setfacl -R -m u:orly:rx /var/lib/tor/orly-relay
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@@ -1,294 +0,0 @@
|
|||||||
# Tor Hidden Service Setup for ORLY Relay
|
|
||||||
|
|
||||||
This guide explains how to configure ORLY to automatically mirror your relay as a Tor hidden service, making it accessible via a `.onion` address.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
When Tor support is enabled:
|
|
||||||
1. ORLY listens on a dedicated internal port for Tor traffic
|
|
||||||
2. The Tor daemon forwards `.onion` traffic to this port
|
|
||||||
3. ORLY automatically detects the `.onion` address
|
|
||||||
4. The `.onion` address is included in NIP-11 relay information
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Development (Local Testing)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# One-time setup (requires Tor installed)
|
|
||||||
./scripts/tor-dev-setup.sh
|
|
||||||
|
|
||||||
# Start relay with Tor
|
|
||||||
ORLY_TOR_ENABLED=true ORLY_TOR_HS_DIR=~/.tor/orly-dev/hidden_service ./orly
|
|
||||||
```
|
|
||||||
|
|
||||||
### Production
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# One-time setup (requires root)
|
|
||||||
sudo ./scripts/tor-setup.sh
|
|
||||||
|
|
||||||
# Start relay with Tor
|
|
||||||
ORLY_TOR_ENABLED=true ORLY_TOR_HS_DIR=/var/lib/tor/orly-relay ./orly
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
| Variable | Default | Description |
|
|
||||||
|----------|---------|-------------|
|
|
||||||
| `ORLY_TOR_ENABLED` | `false` | Enable Tor hidden service integration |
|
|
||||||
| `ORLY_TOR_PORT` | `3336` | Internal port Tor forwards traffic to |
|
|
||||||
| `ORLY_TOR_HS_DIR` | - | Path to Tor's HiddenServiceDir |
|
|
||||||
| `ORLY_TOR_ONION_ADDRESS` | - | Manual `.onion` override (optional) |
|
|
||||||
|
|
||||||
### Example Configurations
|
|
||||||
|
|
||||||
**Basic Tor setup:**
|
|
||||||
```bash
|
|
||||||
export ORLY_TOR_ENABLED=true
|
|
||||||
export ORLY_TOR_HS_DIR=/var/lib/tor/orly-relay
|
|
||||||
./orly
|
|
||||||
```
|
|
||||||
|
|
||||||
**Custom port:**
|
|
||||||
```bash
|
|
||||||
export ORLY_TOR_ENABLED=true
|
|
||||||
export ORLY_TOR_PORT=3337
|
|
||||||
export ORLY_TOR_HS_DIR=/var/lib/tor/orly-relay
|
|
||||||
./orly
|
|
||||||
```
|
|
||||||
|
|
||||||
**Manual address (if auto-detection doesn't work):**
|
|
||||||
```bash
|
|
||||||
export ORLY_TOR_ENABLED=true
|
|
||||||
export ORLY_TOR_ONION_ADDRESS=abc123xyz.onion
|
|
||||||
./orly
|
|
||||||
```
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
Internet Users Tor Users
|
|
||||||
│ │
|
|
||||||
▼ ▼
|
|
||||||
┌──────────┐ ┌──────────────┐
|
|
||||||
│ Regular │ │ Tor │
|
|
||||||
│ Traffic │ │ Network │
|
|
||||||
│ (HTTPS) │ │ │
|
|
||||||
└────┬─────┘ └──────┬───────┘
|
|
||||||
│ │
|
|
||||||
│ Port 443 │ .onion:80
|
|
||||||
▼ ▼
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ORLY Relay │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────┐ ┌───────────────┐ │
|
|
||||||
│ │ Main Server │ │ Tor Service │ │
|
|
||||||
│ │ Port 3334 │ │ Port 3336 │ │
|
|
||||||
│ └──────┬──────┘ └───────┬───────┘ │
|
|
||||||
│ │ │ │
|
|
||||||
│ └────────┬────────┘ │
|
|
||||||
│ ▼ │
|
|
||||||
│ ┌────────────┐ │
|
|
||||||
│ │ Database │ │
|
|
||||||
│ └────────────┘ │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Address Detection
|
|
||||||
|
|
||||||
1. The Tor daemon creates a hidden service directory containing:
|
|
||||||
- `hostname` - The `.onion` address
|
|
||||||
- `hs_ed25519_secret_key` - Private key (persistent)
|
|
||||||
- `hs_ed25519_public_key` - Public key
|
|
||||||
|
|
||||||
2. ORLY watches the `hostname` file and automatically detects the address
|
|
||||||
|
|
||||||
3. The address is included in NIP-11 relay information under the `addresses` field
|
|
||||||
|
|
||||||
### NIP-11 Integration
|
|
||||||
|
|
||||||
When Tor is enabled and the `.onion` address is detected, the NIP-11 relay info includes:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "ORLY",
|
|
||||||
"description": "...",
|
|
||||||
"pubkey": "...",
|
|
||||||
"addresses": [
|
|
||||||
"wss://relay.example.com",
|
|
||||||
"ws://abc123xyz.onion/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Manual Tor Configuration
|
|
||||||
|
|
||||||
If you prefer to configure Tor manually instead of using the setup scripts:
|
|
||||||
|
|
||||||
### 1. Install Tor
|
|
||||||
|
|
||||||
**Debian/Ubuntu:**
|
|
||||||
```bash
|
|
||||||
sudo apt update && sudo apt install tor
|
|
||||||
```
|
|
||||||
|
|
||||||
**Arch Linux:**
|
|
||||||
```bash
|
|
||||||
sudo pacman -S tor
|
|
||||||
```
|
|
||||||
|
|
||||||
**macOS:**
|
|
||||||
```bash
|
|
||||||
brew install tor
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Configure Hidden Service
|
|
||||||
|
|
||||||
Add to `/etc/tor/torrc`:
|
|
||||||
|
|
||||||
```
|
|
||||||
HiddenServiceDir /var/lib/tor/orly-relay/
|
|
||||||
HiddenServicePort 80 127.0.0.1:3336
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Set Permissions
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create directory
|
|
||||||
sudo mkdir -p /var/lib/tor/orly-relay
|
|
||||||
|
|
||||||
# Set ownership (Debian/Ubuntu)
|
|
||||||
sudo chown debian-tor:debian-tor /var/lib/tor/orly-relay
|
|
||||||
sudo chmod 700 /var/lib/tor/orly-relay
|
|
||||||
|
|
||||||
# Or on other systems
|
|
||||||
sudo chown tor:tor /var/lib/tor/orly-relay
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Restart Tor
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl restart tor
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Verify
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check the .onion address
|
|
||||||
sudo cat /var/lib/tor/orly-relay/hostname
|
|
||||||
```
|
|
||||||
|
|
||||||
## Systemd Service
|
|
||||||
|
|
||||||
For production deployments, use the provided systemd unit:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Copy unit file
|
|
||||||
sudo cp deploy/orly-tor.service /etc/systemd/system/
|
|
||||||
|
|
||||||
# Edit configuration
|
|
||||||
sudo nano /etc/systemd/system/orly-tor.service
|
|
||||||
|
|
||||||
# Enable and start
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl enable orly-tor
|
|
||||||
sudo systemctl start orly-tor
|
|
||||||
```
|
|
||||||
|
|
||||||
### Permissions for Hidden Service Directory
|
|
||||||
|
|
||||||
The ORLY process needs read access to the Tor hidden service directory:
|
|
||||||
|
|
||||||
**Option 1: Add user to Tor group**
|
|
||||||
```bash
|
|
||||||
sudo usermod -aG debian-tor orly
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option 2: Use ACLs**
|
|
||||||
```bash
|
|
||||||
sudo setfacl -R -m u:orly:rx /var/lib/tor/orly-relay
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### .onion address not appearing in NIP-11
|
|
||||||
|
|
||||||
1. Check if Tor is running:
|
|
||||||
```bash
|
|
||||||
systemctl status tor
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Check if hostname file exists:
|
|
||||||
```bash
|
|
||||||
cat /var/lib/tor/orly-relay/hostname
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Check ORLY logs for Tor-related messages
|
|
||||||
|
|
||||||
4. Verify environment variables are set:
|
|
||||||
```bash
|
|
||||||
echo $ORLY_TOR_ENABLED
|
|
||||||
echo $ORLY_TOR_HS_DIR
|
|
||||||
```
|
|
||||||
|
|
||||||
### Permission denied errors
|
|
||||||
|
|
||||||
Ensure ORLY can read the hidden service directory:
|
|
||||||
```bash
|
|
||||||
# Check permissions
|
|
||||||
ls -la /var/lib/tor/orly-relay/
|
|
||||||
|
|
||||||
# Fix with ACL
|
|
||||||
sudo setfacl -m u:$(whoami):rx /var/lib/tor/orly-relay
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tor connection timeouts
|
|
||||||
|
|
||||||
1. Check Tor logs:
|
|
||||||
```bash
|
|
||||||
journalctl -u tor -f
|
|
||||||
```
|
|
||||||
|
|
||||||
2. For development, check:
|
|
||||||
```bash
|
|
||||||
tail -f ~/.tor/orly-dev/tor.log
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Ensure Tor can reach the network (check firewall rules)
|
|
||||||
|
|
||||||
### Different .onion address after restart
|
|
||||||
|
|
||||||
This means the hidden service key was lost. The key is stored in:
|
|
||||||
- Production: `/var/lib/tor/orly-relay/hs_ed25519_secret_key`
|
|
||||||
- Development: `~/.tor/orly-dev/hidden_service/hs_ed25519_secret_key`
|
|
||||||
|
|
||||||
To preserve your `.onion` address, back up the entire hidden service directory.
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
1. **Keep the hidden service key safe** - The `hs_ed25519_secret_key` file is your identity. If compromised, attackers can impersonate your relay.
|
|
||||||
|
|
||||||
2. **Restrict file permissions** - Hidden service directories should be `chmod 700`.
|
|
||||||
|
|
||||||
3. **Separate Tor traffic** - The dedicated Tor port (3336) keeps Tor traffic isolated from regular traffic.
|
|
||||||
|
|
||||||
4. **Regular updates** - Keep Tor updated for security patches.
|
|
||||||
|
|
||||||
## Testing with Tor Browser
|
|
||||||
|
|
||||||
1. Download Tor Browser from https://www.torproject.org/
|
|
||||||
|
|
||||||
2. Navigate to your `.onion` address:
|
|
||||||
```
|
|
||||||
ws://your-address.onion/
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Or test with curl over Tor:
|
|
||||||
```bash
|
|
||||||
curl --socks5-hostname localhost:9050 -H "Accept: application/nostr+json" http://your-address.onion/
|
|
||||||
```
|
|
||||||
@@ -17,6 +17,11 @@ import (
|
|||||||
"git.mleku.dev/mleku/nostr/utils/units"
|
"git.mleku.dev/mleku/nostr/utils/units"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Flusher interface for HTTP streaming
|
||||||
|
type flusher interface {
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// Export the complete database of stored events to an io.Writer in line structured minified
|
// Export the complete database of stored events to an io.Writer in line structured minified
|
||||||
// JSON. Supports both legacy and compact event formats.
|
// JSON. Supports both legacy and compact event formats.
|
||||||
func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
||||||
@@ -24,11 +29,18 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
|||||||
evB := make([]byte, 0, units.Mb)
|
evB := make([]byte, 0, units.Mb)
|
||||||
evBuf := bytes.NewBuffer(evB)
|
evBuf := bytes.NewBuffer(evB)
|
||||||
|
|
||||||
|
// Get flusher for HTTP streaming if available
|
||||||
|
var f flusher
|
||||||
|
if fl, ok := w.(flusher); ok {
|
||||||
|
f = fl
|
||||||
|
}
|
||||||
|
|
||||||
// Performance tracking
|
// Performance tracking
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
var eventCount, bytesWritten int64
|
var eventCount, bytesWritten int64
|
||||||
lastLogTime := startTime
|
lastLogTime := startTime
|
||||||
const logInterval = 5 * time.Second
|
const logInterval = 5 * time.Second
|
||||||
|
const flushInterval = 100 // Flush every N events
|
||||||
|
|
||||||
log.I.F("export: starting export operation")
|
log.I.F("export: starting export operation")
|
||||||
|
|
||||||
@@ -109,6 +121,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
|||||||
eventCount++
|
eventCount++
|
||||||
ev.Free()
|
ev.Free()
|
||||||
|
|
||||||
|
// Flush periodically for HTTP streaming
|
||||||
|
if f != nil && eventCount%flushInterval == 0 {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// Progress logging every logInterval
|
// Progress logging every logInterval
|
||||||
if time.Since(lastLogTime) >= logInterval {
|
if time.Since(lastLogTime) >= logInterval {
|
||||||
elapsed := time.Since(startTime)
|
elapsed := time.Since(startTime)
|
||||||
@@ -169,6 +186,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
|||||||
eventCount++
|
eventCount++
|
||||||
ev.Free()
|
ev.Free()
|
||||||
|
|
||||||
|
// Flush periodically for HTTP streaming
|
||||||
|
if f != nil && eventCount%flushInterval == 0 {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// Progress logging every logInterval
|
// Progress logging every logInterval
|
||||||
if time.Since(lastLogTime) >= logInterval {
|
if time.Since(lastLogTime) >= logInterval {
|
||||||
elapsed := time.Since(startTime)
|
elapsed := time.Since(startTime)
|
||||||
@@ -186,6 +208,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Final flush
|
||||||
|
if f != nil {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// Final export summary
|
// Final export summary
|
||||||
elapsed := time.Since(startTime)
|
elapsed := time.Since(startTime)
|
||||||
eventsPerSec := float64(eventCount) / elapsed.Seconds()
|
eventsPerSec := float64(eventCount) / elapsed.Seconds()
|
||||||
@@ -244,6 +271,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
|||||||
eventCount++
|
eventCount++
|
||||||
ev.Free()
|
ev.Free()
|
||||||
|
|
||||||
|
// Flush periodically for HTTP streaming
|
||||||
|
if f != nil && eventCount%flushInterval == 0 {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// Progress logging every logInterval
|
// Progress logging every logInterval
|
||||||
if time.Since(lastLogTime) >= logInterval {
|
if time.Since(lastLogTime) >= logInterval {
|
||||||
elapsed := time.Since(startTime)
|
elapsed := time.Since(startTime)
|
||||||
@@ -261,6 +293,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Final flush
|
||||||
|
if f != nil {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// Final export summary for pubkey export
|
// Final export summary for pubkey export
|
||||||
elapsed := time.Since(startTime)
|
elapsed := time.Since(startTime)
|
||||||
eventsPerSec := float64(eventCount) / elapsed.Seconds()
|
eventsPerSec := float64(eventCount) / elapsed.Seconds()
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
// Package tor provides Tor hidden service integration for the ORLY relay.
|
// Package tor provides Tor hidden service integration for the ORLY relay.
|
||||||
// It manages a listener on a dedicated port that receives traffic forwarded
|
// It spawns a tor subprocess with automatic configuration and manages
|
||||||
// from the Tor daemon, and exposes the .onion address for NIP-11 integration.
|
// the hidden service lifecycle.
|
||||||
package tor
|
package tor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -16,25 +22,32 @@ import (
|
|||||||
"lol.mleku.dev/log"
|
"lol.mleku.dev/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config holds Tor hidden service configuration.
|
// Config holds Tor subprocess configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Port is the internal port that Tor forwards .onion traffic to
|
// Port is the internal port for the hidden service
|
||||||
Port int
|
Port int
|
||||||
// HSDir is the Tor HiddenServiceDir path to read .onion hostname from
|
// DataDir is the directory for Tor data (torrc, keys, hostname, etc.)
|
||||||
HSDir string
|
DataDir string
|
||||||
// OnionAddress is an optional manual override for the .onion address
|
// Binary is the path to the tor executable
|
||||||
OnionAddress string
|
Binary string
|
||||||
|
// SOCKSPort is the port for outbound SOCKS connections (0 = disabled)
|
||||||
|
SOCKSPort int
|
||||||
// Handler is the HTTP handler to serve (typically the main relay handler)
|
// Handler is the HTTP handler to serve (typically the main relay handler)
|
||||||
Handler http.Handler
|
Handler http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service manages the Tor hidden service listener.
|
// Service manages the Tor subprocess and hidden service listener.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
cfg *Config
|
cfg *Config
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
server *http.Server
|
server *http.Server
|
||||||
|
|
||||||
// onionAddress is the detected or configured .onion address
|
// Tor subprocess
|
||||||
|
cmd *exec.Cmd
|
||||||
|
stdout io.ReadCloser
|
||||||
|
stderr io.ReadCloser
|
||||||
|
|
||||||
|
// onionAddress is the detected .onion address
|
||||||
onionAddress string
|
onionAddress string
|
||||||
|
|
||||||
// hostname watcher
|
// hostname watcher
|
||||||
@@ -47,11 +60,29 @@ type Service struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Tor service with the given configuration.
|
// New creates a new Tor service with the given configuration.
|
||||||
|
// Returns an error if the tor binary is not found.
|
||||||
func New(cfg *Config) (*Service, error) {
|
func New(cfg *Config) (*Service, error) {
|
||||||
if cfg.Port == 0 {
|
if cfg.Port == 0 {
|
||||||
cfg.Port = 3336
|
cfg.Port = 3336
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find tor binary
|
||||||
|
binary := cfg.Binary
|
||||||
|
if binary == "" {
|
||||||
|
binary = "tor"
|
||||||
|
}
|
||||||
|
|
||||||
|
torPath, err := exec.LookPath(binary)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("tor binary not found: %w (install tor or set ORLY_TOR_ENABLED=false)", err)
|
||||||
|
}
|
||||||
|
cfg.Binary = torPath
|
||||||
|
|
||||||
|
// Ensure data directory exists
|
||||||
|
if err := os.MkdirAll(cfg.DataDir, 0700); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create Tor data directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
s := &Service{
|
s := &Service{
|
||||||
@@ -60,43 +91,117 @@ func New(cfg *Config) (*Service, error) {
|
|||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If manual address provided, use it
|
|
||||||
if cfg.OnionAddress != "" {
|
|
||||||
s.onionAddress = cfg.OnionAddress
|
|
||||||
log.I.F("using configured .onion address: %s", s.onionAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start initializes the Tor listener and hostname watcher.
|
// generateTorrc creates the torrc configuration file.
|
||||||
|
func (s *Service) generateTorrc() (string, error) {
|
||||||
|
torrcPath := filepath.Join(s.cfg.DataDir, "torrc")
|
||||||
|
hsDir := filepath.Join(s.cfg.DataDir, "hidden_service")
|
||||||
|
|
||||||
|
// Ensure hidden service directory exists with correct permissions
|
||||||
|
if err := os.MkdirAll(hsDir, 0700); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create hidden service directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("# ORLY Tor hidden service configuration\n")
|
||||||
|
sb.WriteString("# Auto-generated - do not edit\n\n")
|
||||||
|
|
||||||
|
// Data directory
|
||||||
|
sb.WriteString(fmt.Sprintf("DataDirectory %s/data\n", s.cfg.DataDir))
|
||||||
|
|
||||||
|
// Hidden service configuration
|
||||||
|
sb.WriteString(fmt.Sprintf("HiddenServiceDir %s\n", hsDir))
|
||||||
|
sb.WriteString(fmt.Sprintf("HiddenServicePort 80 127.0.0.1:%d\n", s.cfg.Port))
|
||||||
|
|
||||||
|
// Optional SOCKS port for outbound connections
|
||||||
|
if s.cfg.SOCKSPort > 0 {
|
||||||
|
sb.WriteString(fmt.Sprintf("SocksPort %d\n", s.cfg.SOCKSPort))
|
||||||
|
} else {
|
||||||
|
sb.WriteString("SocksPort 0\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable unused features to reduce resource usage
|
||||||
|
sb.WriteString("ControlPort 0\n")
|
||||||
|
sb.WriteString("Log notice stdout\n")
|
||||||
|
|
||||||
|
// Write torrc
|
||||||
|
if err := os.WriteFile(torrcPath, []byte(sb.String()), 0600); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to write torrc: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create data subdirectory
|
||||||
|
if err := os.MkdirAll(filepath.Join(s.cfg.DataDir, "data"), 0700); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create Tor data subdirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return torrcPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start spawns the Tor subprocess and initializes the listener.
|
||||||
func (s *Service) Start() error {
|
func (s *Service) Start() error {
|
||||||
// Start hostname watcher if HSDir is configured
|
// Generate torrc
|
||||||
if s.cfg.HSDir != "" {
|
torrcPath, err := s.generateTorrc()
|
||||||
s.hostnameWatcher = NewHostnameWatcher(s.cfg.HSDir)
|
if err != nil {
|
||||||
s.hostnameWatcher.OnChange(func(addr string) {
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.I.F("starting Tor subprocess with config: %s", torrcPath)
|
||||||
|
|
||||||
|
// Start tor subprocess
|
||||||
|
s.cmd = exec.CommandContext(s.ctx, s.cfg.Binary, "-f", torrcPath)
|
||||||
|
|
||||||
|
// Capture stdout/stderr for logging
|
||||||
|
s.stdout, err = s.cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get Tor stdout: %w", err)
|
||||||
|
}
|
||||||
|
s.stderr, err = s.cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get Tor stderr: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.cmd.Start(); err != nil {
|
||||||
|
return fmt.Errorf("failed to start Tor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.I.F("Tor subprocess started (PID %d)", s.cmd.Process.Pid)
|
||||||
|
|
||||||
|
// Log Tor output
|
||||||
|
s.wg.Add(2)
|
||||||
|
go s.logOutput("tor", s.stdout)
|
||||||
|
go s.logOutput("tor", s.stderr)
|
||||||
|
|
||||||
|
// Monitor subprocess
|
||||||
|
s.wg.Add(1)
|
||||||
|
go s.monitorProcess()
|
||||||
|
|
||||||
|
// Start hostname watcher
|
||||||
|
hsDir := filepath.Join(s.cfg.DataDir, "hidden_service")
|
||||||
|
s.hostnameWatcher = NewHostnameWatcher(hsDir)
|
||||||
|
s.hostnameWatcher.OnChange(func(addr string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.onionAddress = addr
|
||||||
|
s.mu.Unlock()
|
||||||
|
log.I.F("Tor hidden service address: %s", addr)
|
||||||
|
})
|
||||||
|
if err := s.hostnameWatcher.Start(); err != nil {
|
||||||
|
log.W.F("failed to start hostname watcher: %v", err)
|
||||||
|
} else {
|
||||||
|
// Get initial address
|
||||||
|
if addr := s.hostnameWatcher.Address(); addr != "" {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.onionAddress = addr
|
s.onionAddress = addr
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
log.I.F("detected .onion address: %s", addr)
|
|
||||||
})
|
|
||||||
if err := s.hostnameWatcher.Start(); err != nil {
|
|
||||||
log.W.F("failed to start hostname watcher: %v", err)
|
|
||||||
} else {
|
|
||||||
// Get initial address
|
|
||||||
if addr := s.hostnameWatcher.Address(); addr != "" {
|
|
||||||
s.mu.Lock()
|
|
||||||
s.onionAddress = addr
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create listener
|
// Create listener for the hidden service port
|
||||||
addr := fmt.Sprintf("127.0.0.1:%d", s.cfg.Port)
|
addr := fmt.Sprintf("127.0.0.1:%d", s.cfg.Port)
|
||||||
var err error
|
|
||||||
s.listener, err = net.Listen("tcp", addr)
|
s.listener, err = net.Listen("tcp", addr)
|
||||||
if chk.E(err) {
|
if chk.E(err) {
|
||||||
|
s.Stop()
|
||||||
return fmt.Errorf("failed to listen on %s: %w", addr, err)
|
return fmt.Errorf("failed to listen on %s: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +217,7 @@ func (s *Service) Start() error {
|
|||||||
s.wg.Add(1)
|
s.wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer s.wg.Done()
|
defer s.wg.Done()
|
||||||
log.I.F("Tor listener started on %s", addr)
|
log.I.F("Tor hidden service listener started on %s", addr)
|
||||||
if err := s.server.Serve(s.listener); err != nil && err != http.ErrServerClosed {
|
if err := s.server.Serve(s.listener); err != nil && err != http.ErrServerClosed {
|
||||||
log.E.F("Tor server error: %v", err)
|
log.E.F("Tor server error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -121,6 +226,40 @@ func (s *Service) Start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logOutput reads from a pipe and logs each line.
|
||||||
|
func (s *Service) logOutput(prefix string, r io.ReadCloser) {
|
||||||
|
defer s.wg.Done()
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
// Filter out common noise
|
||||||
|
if strings.Contains(line, "Bootstrapped") {
|
||||||
|
log.I.F("[%s] %s", prefix, line)
|
||||||
|
} else if strings.Contains(line, "[warn]") || strings.Contains(line, "[err]") {
|
||||||
|
log.W.F("[%s] %s", prefix, line)
|
||||||
|
} else {
|
||||||
|
log.D.F("[%s] %s", prefix, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// monitorProcess watches the Tor subprocess and logs when it exits.
|
||||||
|
func (s *Service) monitorProcess() {
|
||||||
|
defer s.wg.Done()
|
||||||
|
err := s.cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
// Expected shutdown
|
||||||
|
log.D.F("Tor subprocess exited (shutdown)")
|
||||||
|
default:
|
||||||
|
log.E.F("Tor subprocess exited unexpectedly: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.I.F("Tor subprocess exited cleanly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop gracefully shuts down the Tor service.
|
// Stop gracefully shuts down the Tor service.
|
||||||
func (s *Service) Stop() error {
|
func (s *Service) Stop() error {
|
||||||
s.cancel()
|
s.cancel()
|
||||||
@@ -135,7 +274,7 @@ func (s *Service) Stop() error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := s.server.Shutdown(ctx); chk.E(err) {
|
if err := s.server.Shutdown(ctx); chk.E(err) {
|
||||||
return err
|
// Continue shutdown anyway
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,12 +283,33 @@ func (s *Service) Stop() error {
|
|||||||
s.listener.Close()
|
s.listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Terminate Tor subprocess
|
||||||
|
if s.cmd != nil && s.cmd.Process != nil {
|
||||||
|
log.D.F("sending SIGTERM to Tor subprocess (PID %d)", s.cmd.Process.Pid)
|
||||||
|
s.cmd.Process.Signal(os.Interrupt)
|
||||||
|
|
||||||
|
// Give it a few seconds to exit gracefully
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
s.cmd.Wait()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
log.D.F("Tor subprocess exited gracefully")
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
log.W.F("Tor subprocess did not exit, killing")
|
||||||
|
s.cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s.wg.Wait()
|
s.wg.Wait()
|
||||||
log.I.F("Tor service stopped")
|
log.I.F("Tor service stopped")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnionAddress returns the current .onion address (without .onion suffix).
|
// OnionAddress returns the current .onion address.
|
||||||
func (s *Service) OnionAddress() string {
|
func (s *Service) OnionAddress() string {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
@@ -172,7 +332,7 @@ func (s *Service) OnionWSAddress() string {
|
|||||||
|
|
||||||
// IsRunning returns whether the Tor service is currently running.
|
// IsRunning returns whether the Tor service is currently running.
|
||||||
func (s *Service) IsRunning() bool {
|
func (s *Service) IsRunning() bool {
|
||||||
return s.listener != nil
|
return s.listener != nil && s.cmd != nil && s.cmd.Process != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrader returns a WebSocket upgrader configured for Tor connections.
|
// Upgrader returns a WebSocket upgrader configured for Tor connections.
|
||||||
@@ -186,3 +346,13 @@ func (s *Service) Upgrader() *websocket.Upgrader {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DataDir returns the Tor data directory path.
|
||||||
|
func (s *Service) DataDir() string {
|
||||||
|
return s.cfg.DataDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// HiddenServiceDir returns the hidden service directory path.
|
||||||
|
func (s *Service) HiddenServiceDir() string {
|
||||||
|
return filepath.Join(s.cfg.DataDir, "hidden_service")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v0.46.0
|
v0.46.2
|
||||||
|
|||||||
@@ -1,217 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# tor-dev-setup.sh - Development Tor hidden service setup for ORLY relay
|
|
||||||
#
|
|
||||||
# This script sets up a user-space Tor hidden service for local development.
|
|
||||||
# No root privileges required (except for initial Tor installation).
|
|
||||||
#
|
|
||||||
# Usage: ./scripts/tor-dev-setup.sh [port]
|
|
||||||
# port: internal port ORLY listens on for Tor traffic (default: 3336)
|
|
||||||
#
|
|
||||||
# After running this script:
|
|
||||||
# 1. Start ORLY with: ORLY_TOR_ENABLED=true ORLY_TOR_HS_DIR=~/.tor/orly-dev ./orly
|
|
||||||
# 2. Connect via Tor Browser to the .onion address
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
TOR_PORT="${1:-3336}"
|
|
||||||
TOR_DATA_DIR="${HOME}/.tor/orly-dev"
|
|
||||||
TOR_CONFIG="${TOR_DATA_DIR}/torrc"
|
|
||||||
TOR_PID_FILE="${TOR_DATA_DIR}/tor.pid"
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
||||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
||||||
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
|
||||||
debug() { echo -e "${BLUE}[DEBUG]${NC} $1"; }
|
|
||||||
|
|
||||||
# Check if Tor is installed
|
|
||||||
check_tor() {
|
|
||||||
if ! command -v tor &> /dev/null; then
|
|
||||||
error "Tor is not installed. Please install it first:
|
|
||||||
Debian/Ubuntu: sudo apt install tor
|
|
||||||
Arch: sudo pacman -S tor
|
|
||||||
macOS: brew install tor
|
|
||||||
Fedora: sudo dnf install tor"
|
|
||||||
fi
|
|
||||||
info "Tor is installed: $(tor --version | head -1)"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create directory structure
|
|
||||||
setup_dirs() {
|
|
||||||
info "Creating directory structure..."
|
|
||||||
|
|
||||||
mkdir -p "${TOR_DATA_DIR}"
|
|
||||||
mkdir -p "${TOR_DATA_DIR}/hidden_service"
|
|
||||||
chmod 700 "${TOR_DATA_DIR}"
|
|
||||||
chmod 700 "${TOR_DATA_DIR}/hidden_service"
|
|
||||||
|
|
||||||
info "Directory created: ${TOR_DATA_DIR}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create Tor configuration
|
|
||||||
create_config() {
|
|
||||||
info "Creating Tor configuration..."
|
|
||||||
|
|
||||||
cat > "$TOR_CONFIG" << EOF
|
|
||||||
# ORLY Development Tor Configuration
|
|
||||||
# Generated by tor-dev-setup.sh on $(date)
|
|
||||||
|
|
||||||
# Data directory
|
|
||||||
DataDirectory ${TOR_DATA_DIR}/data
|
|
||||||
|
|
||||||
# Run in background
|
|
||||||
RunAsDaemon 1
|
|
||||||
PidFile ${TOR_PID_FILE}
|
|
||||||
|
|
||||||
# SOCKS proxy for outgoing connections (optional, for testing)
|
|
||||||
SocksPort 9150
|
|
||||||
|
|
||||||
# Hidden service for ORLY relay
|
|
||||||
HiddenServiceDir ${TOR_DATA_DIR}/hidden_service/
|
|
||||||
HiddenServicePort 80 127.0.0.1:${TOR_PORT}
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
Log notice file ${TOR_DATA_DIR}/tor.log
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod 600 "$TOR_CONFIG"
|
|
||||||
info "Configuration created: ${TOR_CONFIG}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Stop existing Tor instance
|
|
||||||
stop_tor() {
|
|
||||||
if [ -f "$TOR_PID_FILE" ]; then
|
|
||||||
PID=$(cat "$TOR_PID_FILE" 2>/dev/null)
|
|
||||||
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
|
|
||||||
info "Stopping existing Tor instance (PID: $PID)..."
|
|
||||||
kill "$PID" 2>/dev/null || true
|
|
||||||
sleep 2
|
|
||||||
fi
|
|
||||||
rm -f "$TOR_PID_FILE"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Start Tor
|
|
||||||
start_tor() {
|
|
||||||
info "Starting Tor..."
|
|
||||||
|
|
||||||
# Ensure data directory exists
|
|
||||||
mkdir -p "${TOR_DATA_DIR}/data"
|
|
||||||
|
|
||||||
# Start Tor with our config
|
|
||||||
tor -f "$TOR_CONFIG" 2>&1 | head -20 &
|
|
||||||
|
|
||||||
# Wait for Tor to bootstrap
|
|
||||||
info "Waiting for Tor to connect to the network..."
|
|
||||||
|
|
||||||
for i in {1..60}; do
|
|
||||||
if [ -f "${TOR_DATA_DIR}/hidden_service/hostname" ]; then
|
|
||||||
ONION_ADDR=$(cat "${TOR_DATA_DIR}/hidden_service/hostname")
|
|
||||||
if [ -n "$ONION_ADDR" ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if Tor is still running
|
|
||||||
if [ -f "$TOR_PID_FILE" ]; then
|
|
||||||
PID=$(cat "$TOR_PID_FILE")
|
|
||||||
if ! kill -0 "$PID" 2>/dev/null; then
|
|
||||||
error "Tor process died. Check ${TOR_DATA_DIR}/tor.log"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
sleep 1
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [ -f "${TOR_DATA_DIR}/hidden_service/hostname" ]; then
|
|
||||||
ONION_ADDR=$(cat "${TOR_DATA_DIR}/hidden_service/hostname")
|
|
||||||
info "Tor started successfully"
|
|
||||||
echo ""
|
|
||||||
echo -e "${GREEN}======================================${NC}"
|
|
||||||
echo -e "${GREEN}Hidden Service Address:${NC}"
|
|
||||||
echo -e "${YELLOW}${ONION_ADDR}${NC}"
|
|
||||||
echo -e "${GREEN}======================================${NC}"
|
|
||||||
echo ""
|
|
||||||
else
|
|
||||||
warn "Tor started but hidden service not ready yet"
|
|
||||||
warn "Check: tail -f ${TOR_DATA_DIR}/tor.log"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Print usage instructions
|
|
||||||
print_instructions() {
|
|
||||||
echo ""
|
|
||||||
info "Development Tor setup complete!"
|
|
||||||
echo ""
|
|
||||||
echo " To start ORLY with Tor:"
|
|
||||||
echo -e " ${BLUE}ORLY_TOR_ENABLED=true ORLY_TOR_HS_DIR=${TOR_DATA_DIR}/hidden_service ./orly${NC}"
|
|
||||||
echo ""
|
|
||||||
echo " To view the .onion address:"
|
|
||||||
echo -e " ${BLUE}cat ${TOR_DATA_DIR}/hidden_service/hostname${NC}"
|
|
||||||
echo ""
|
|
||||||
echo " To view Tor logs:"
|
|
||||||
echo -e " ${BLUE}tail -f ${TOR_DATA_DIR}/tor.log${NC}"
|
|
||||||
echo ""
|
|
||||||
echo " To stop Tor:"
|
|
||||||
echo -e " ${BLUE}kill \$(cat ${TOR_PID_FILE})${NC}"
|
|
||||||
echo ""
|
|
||||||
echo " To restart Tor:"
|
|
||||||
echo -e " ${BLUE}./scripts/tor-dev-setup.sh${NC}"
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# Status command
|
|
||||||
status() {
|
|
||||||
if [ -f "$TOR_PID_FILE" ]; then
|
|
||||||
PID=$(cat "$TOR_PID_FILE")
|
|
||||||
if kill -0 "$PID" 2>/dev/null; then
|
|
||||||
info "Tor is running (PID: $PID)"
|
|
||||||
if [ -f "${TOR_DATA_DIR}/hidden_service/hostname" ]; then
|
|
||||||
ONION_ADDR=$(cat "${TOR_DATA_DIR}/hidden_service/hostname")
|
|
||||||
echo -e " Address: ${YELLOW}${ONION_ADDR}${NC}"
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
warn "Tor is not running"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main
|
|
||||||
main() {
|
|
||||||
case "${1:-}" in
|
|
||||||
status)
|
|
||||||
status
|
|
||||||
exit $?
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
stop_tor
|
|
||||||
info "Tor stopped"
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
info "ORLY Development Tor Setup"
|
|
||||||
info "Internal port: ${TOR_PORT}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
check_tor
|
|
||||||
setup_dirs
|
|
||||||
stop_tor
|
|
||||||
create_config
|
|
||||||
start_tor
|
|
||||||
print_instructions
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# tor-setup.sh - Production Tor hidden service setup for ORLY relay
|
|
||||||
#
|
|
||||||
# This script installs Tor and configures a hidden service for the relay.
|
|
||||||
# The .onion address will be automatically detected by ORLY.
|
|
||||||
#
|
|
||||||
# Usage: sudo ./scripts/tor-setup.sh [port]
|
|
||||||
# port: internal port ORLY listens on for Tor traffic (default: 3336)
|
|
||||||
#
|
|
||||||
# Requirements:
|
|
||||||
# - Root privileges (for installing packages and configuring Tor)
|
|
||||||
# - Systemd-based Linux distribution
|
|
||||||
#
|
|
||||||
# After running this script:
|
|
||||||
# 1. Start ORLY with: ORLY_TOR_ENABLED=true ORLY_TOR_HS_DIR=/var/lib/tor/orly-relay ./orly
|
|
||||||
# 2. The .onion address will appear in logs and NIP-11
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
TOR_PORT="${1:-3336}"
|
|
||||||
HS_NAME="orly-relay"
|
|
||||||
HS_DIR="/var/lib/tor/${HS_NAME}"
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
||||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
||||||
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
|
||||||
|
|
||||||
# Check if running as root
|
|
||||||
if [ "$EUID" -ne 0 ]; then
|
|
||||||
error "Please run as root: sudo $0"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Detect package manager and install Tor
|
|
||||||
install_tor() {
|
|
||||||
info "Installing Tor..."
|
|
||||||
|
|
||||||
if command -v apt-get &> /dev/null; then
|
|
||||||
# Debian/Ubuntu
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y tor
|
|
||||||
elif command -v dnf &> /dev/null; then
|
|
||||||
# Fedora/RHEL
|
|
||||||
dnf install -y tor
|
|
||||||
elif command -v pacman &> /dev/null; then
|
|
||||||
# Arch Linux
|
|
||||||
pacman -Sy --noconfirm tor
|
|
||||||
elif command -v apk &> /dev/null; then
|
|
||||||
# Alpine
|
|
||||||
apk add tor
|
|
||||||
elif command -v brew &> /dev/null; then
|
|
||||||
# macOS (run as user, not root)
|
|
||||||
brew install tor
|
|
||||||
else
|
|
||||||
error "Unsupported package manager. Please install Tor manually."
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Tor installed successfully"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Configure hidden service
|
|
||||||
configure_tor() {
|
|
||||||
info "Configuring Tor hidden service..."
|
|
||||||
|
|
||||||
TORRC="/etc/tor/torrc"
|
|
||||||
|
|
||||||
# Check if hidden service already configured
|
|
||||||
if grep -q "HiddenServiceDir ${HS_DIR}" "$TORRC" 2>/dev/null; then
|
|
||||||
warn "Hidden service already configured in ${TORRC}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Backup original torrc
|
|
||||||
if [ -f "$TORRC" ]; then
|
|
||||||
cp "$TORRC" "${TORRC}.backup.$(date +%Y%m%d%H%M%S)"
|
|
||||||
info "Backed up original torrc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add hidden service configuration
|
|
||||||
cat >> "$TORRC" << EOF
|
|
||||||
|
|
||||||
# ORLY Relay Hidden Service
|
|
||||||
# Added by tor-setup.sh on $(date)
|
|
||||||
HiddenServiceDir ${HS_DIR}/
|
|
||||||
HiddenServicePort 80 127.0.0.1:${TOR_PORT}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
info "Hidden service configured: ${HS_DIR}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Set permissions
|
|
||||||
set_permissions() {
|
|
||||||
info "Setting directory permissions..."
|
|
||||||
|
|
||||||
# Create hidden service directory if it doesn't exist
|
|
||||||
mkdir -p "$HS_DIR"
|
|
||||||
|
|
||||||
# Set correct ownership (debian-tor on Debian/Ubuntu, tor on others)
|
|
||||||
if id "debian-tor" &>/dev/null; then
|
|
||||||
chown -R debian-tor:debian-tor "$HS_DIR"
|
|
||||||
elif id "tor" &>/dev/null; then
|
|
||||||
chown -R tor:tor "$HS_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
chmod 700 "$HS_DIR"
|
|
||||||
|
|
||||||
info "Permissions set"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Restart Tor service
|
|
||||||
restart_tor() {
|
|
||||||
info "Restarting Tor service..."
|
|
||||||
|
|
||||||
if command -v systemctl &> /dev/null; then
|
|
||||||
systemctl enable tor
|
|
||||||
systemctl restart tor
|
|
||||||
elif command -v service &> /dev/null; then
|
|
||||||
service tor restart
|
|
||||||
else
|
|
||||||
warn "Could not restart Tor. Please restart manually."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Wait for Tor to create the hostname file
|
|
||||||
info "Waiting for hidden service to initialize..."
|
|
||||||
for i in {1..30}; do
|
|
||||||
if [ -f "${HS_DIR}/hostname" ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -f "${HS_DIR}/hostname" ]; then
|
|
||||||
ONION_ADDR=$(cat "${HS_DIR}/hostname")
|
|
||||||
info "Tor service started successfully"
|
|
||||||
echo ""
|
|
||||||
echo -e "${GREEN}======================================${NC}"
|
|
||||||
echo -e "${GREEN}Hidden Service Address:${NC}"
|
|
||||||
echo -e "${YELLOW}${ONION_ADDR}${NC}"
|
|
||||||
echo -e "${GREEN}======================================${NC}"
|
|
||||||
echo ""
|
|
||||||
else
|
|
||||||
warn "Tor started but hostname file not yet created"
|
|
||||||
warn "Check: ls -la ${HS_DIR}/"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Print usage instructions
|
|
||||||
print_instructions() {
|
|
||||||
echo ""
|
|
||||||
info "Setup complete! To enable Tor in ORLY:"
|
|
||||||
echo ""
|
|
||||||
echo " Option 1 - Environment variables:"
|
|
||||||
echo " export ORLY_TOR_ENABLED=true"
|
|
||||||
echo " export ORLY_TOR_HS_DIR=${HS_DIR}"
|
|
||||||
echo " export ORLY_TOR_PORT=${TOR_PORT}"
|
|
||||||
echo " ./orly"
|
|
||||||
echo ""
|
|
||||||
echo " Option 2 - Command line:"
|
|
||||||
echo " ORLY_TOR_ENABLED=true ORLY_TOR_HS_DIR=${HS_DIR} ./orly"
|
|
||||||
echo ""
|
|
||||||
echo " The .onion address will automatically appear in NIP-11 relay info."
|
|
||||||
echo ""
|
|
||||||
echo " To view the .onion address:"
|
|
||||||
echo " cat ${HS_DIR}/hostname"
|
|
||||||
echo ""
|
|
||||||
echo " To check Tor status:"
|
|
||||||
echo " systemctl status tor"
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main
|
|
||||||
main() {
|
|
||||||
info "ORLY Tor Hidden Service Setup"
|
|
||||||
info "Internal port: ${TOR_PORT}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Check if Tor is already installed
|
|
||||||
if ! command -v tor &> /dev/null; then
|
|
||||||
install_tor
|
|
||||||
else
|
|
||||||
info "Tor is already installed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
configure_tor
|
|
||||||
set_permissions
|
|
||||||
restart_tor
|
|
||||||
print_instructions
|
|
||||||
}
|
|
||||||
|
|
||||||
main
|
|
||||||
Reference in New Issue
Block a user