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)"`
|
||||
BunkerPort int `env:"ORLY_BUNKER_PORT" default:"3335" usage:"internal port for bunker WebSocket (only accessible via WireGuard)"`
|
||||
|
||||
// Tor hidden service configuration
|
||||
TorEnabled bool `env:"ORLY_TOR_ENABLED" default:"false" usage:"enable Tor hidden service integration (requires external Tor daemon)"`
|
||||
TorPort int `env:"ORLY_TOR_PORT" default:"3336" usage:"internal port that Tor forwards .onion traffic to"`
|
||||
TorHSDir string `env:"ORLY_TOR_HS_DIR" usage:"Tor HiddenServiceDir path to read .onion hostname (e.g., /var/lib/tor/orly-relay)"`
|
||||
TorOnionAddress string `env:"ORLY_TOR_ONION_ADDRESS" usage:"manual .onion address override (optional, auto-detected from TorHSDir if empty)"`
|
||||
// Tor hidden service configuration (subprocess mode - runs tor binary automatically)
|
||||
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 for Tor hidden service traffic"`
|
||||
TorDataDir string `env:"ORLY_TOR_DATA_DIR" usage:"Tor data directory (default: $ORLY_DATA_DIR/tor)"`
|
||||
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)
|
||||
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() (
|
||||
enabled bool,
|
||||
port int,
|
||||
hsDir string,
|
||||
onionAddress string,
|
||||
dataDir string,
|
||||
binary string,
|
||||
socksPort int,
|
||||
) {
|
||||
dataDir = cfg.TorDataDir
|
||||
if dataDir == "" {
|
||||
dataDir = filepath.Join(cfg.DataDir, "tor")
|
||||
}
|
||||
return cfg.TorEnabled,
|
||||
cfg.TorPort,
|
||||
cfg.TorHSDir,
|
||||
cfg.TorOnionAddress
|
||||
dataDir,
|
||||
cfg.TorBinary,
|
||||
cfg.TorSOCKS
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ type ExtendedRelayInfo struct {
|
||||
// Informer interface implementation or predefined server configuration. It
|
||||
// returns this document as a JSON response to the client.
|
||||
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")
|
||||
var info *relayinfo.T
|
||||
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))
|
||||
}
|
||||
|
||||
// Initialize Tor hidden service if enabled
|
||||
torEnabled, torPort, torHSDir, torOnionAddr := cfg.GetTorConfigValues()
|
||||
// Initialize Tor hidden service if enabled (spawns tor subprocess)
|
||||
torEnabled, torPort, torDataDir, torBinary, torSOCKSPort := cfg.GetTorConfigValues()
|
||||
if torEnabled {
|
||||
torCfg := &tor.Config{
|
||||
Port: torPort,
|
||||
HSDir: torHSDir,
|
||||
OnionAddress: torOnionAddr,
|
||||
Handler: l,
|
||||
Port: torPort,
|
||||
DataDir: torDataDir,
|
||||
Binary: torBinary,
|
||||
SOCKSPort: torSOCKSPort,
|
||||
Handler: l,
|
||||
}
|
||||
var err error
|
||||
l.torService, err = tor.New(torCfg)
|
||||
if err != nil {
|
||||
log.W.F("failed to create Tor service: %v", err)
|
||||
log.W.F("Tor disabled: %v", err)
|
||||
} else {
|
||||
if err = l.torService.Start(); err != nil {
|
||||
log.W.F("failed to start Tor service: %v", err)
|
||||
l.torService = nil
|
||||
} else {
|
||||
if addr := l.torService.OnionWSAddress(); 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(
|
||||
"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
|
||||
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"
|
||||
)
|
||||
|
||||
// Flusher interface for HTTP streaming
|
||||
type flusher interface {
|
||||
Flush()
|
||||
}
|
||||
|
||||
// Export the complete database of stored events to an io.Writer in line structured minified
|
||||
// JSON. Supports both legacy and compact event formats.
|
||||
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)
|
||||
evBuf := bytes.NewBuffer(evB)
|
||||
|
||||
// Get flusher for HTTP streaming if available
|
||||
var f flusher
|
||||
if fl, ok := w.(flusher); ok {
|
||||
f = fl
|
||||
}
|
||||
|
||||
// Performance tracking
|
||||
startTime := time.Now()
|
||||
var eventCount, bytesWritten int64
|
||||
lastLogTime := startTime
|
||||
const logInterval = 5 * time.Second
|
||||
const flushInterval = 100 // Flush every N events
|
||||
|
||||
log.I.F("export: starting export operation")
|
||||
|
||||
@@ -109,6 +121,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
||||
eventCount++
|
||||
ev.Free()
|
||||
|
||||
// Flush periodically for HTTP streaming
|
||||
if f != nil && eventCount%flushInterval == 0 {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
// Progress logging every logInterval
|
||||
if time.Since(lastLogTime) >= logInterval {
|
||||
elapsed := time.Since(startTime)
|
||||
@@ -169,6 +186,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
||||
eventCount++
|
||||
ev.Free()
|
||||
|
||||
// Flush periodically for HTTP streaming
|
||||
if f != nil && eventCount%flushInterval == 0 {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
// Progress logging every logInterval
|
||||
if time.Since(lastLogTime) >= logInterval {
|
||||
elapsed := time.Since(startTime)
|
||||
@@ -186,6 +208,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
||||
return
|
||||
}
|
||||
|
||||
// Final flush
|
||||
if f != nil {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
// Final export summary
|
||||
elapsed := time.Since(startTime)
|
||||
eventsPerSec := float64(eventCount) / elapsed.Seconds()
|
||||
@@ -244,6 +271,11 @@ func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
|
||||
eventCount++
|
||||
ev.Free()
|
||||
|
||||
// Flush periodically for HTTP streaming
|
||||
if f != nil && eventCount%flushInterval == 0 {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
// Progress logging every logInterval
|
||||
if time.Since(lastLogTime) >= logInterval {
|
||||
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
|
||||
elapsed := time.Since(startTime)
|
||||
eventsPerSec := float64(eventCount) / elapsed.Seconds()
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
// Package tor provides Tor hidden service integration for the ORLY relay.
|
||||
// It manages a listener on a dedicated port that receives traffic forwarded
|
||||
// from the Tor daemon, and exposes the .onion address for NIP-11 integration.
|
||||
// It spawns a tor subprocess with automatic configuration and manages
|
||||
// the hidden service lifecycle.
|
||||
package tor
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -16,25 +22,32 @@ import (
|
||||
"lol.mleku.dev/log"
|
||||
)
|
||||
|
||||
// Config holds Tor hidden service configuration.
|
||||
// Config holds Tor subprocess configuration.
|
||||
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
|
||||
// HSDir is the Tor HiddenServiceDir path to read .onion hostname from
|
||||
HSDir string
|
||||
// OnionAddress is an optional manual override for the .onion address
|
||||
OnionAddress string
|
||||
// DataDir is the directory for Tor data (torrc, keys, hostname, etc.)
|
||||
DataDir string
|
||||
// Binary is the path to the tor executable
|
||||
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 http.Handler
|
||||
}
|
||||
|
||||
// Service manages the Tor hidden service listener.
|
||||
// Service manages the Tor subprocess and hidden service listener.
|
||||
type Service struct {
|
||||
cfg *Config
|
||||
listener net.Listener
|
||||
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
|
||||
|
||||
// hostname watcher
|
||||
@@ -47,11 +60,29 @@ type Service struct {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if cfg.Port == 0 {
|
||||
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())
|
||||
|
||||
s := &Service{
|
||||
@@ -60,43 +91,117 @@ func New(cfg *Config) (*Service, error) {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Start hostname watcher if HSDir is configured
|
||||
if s.cfg.HSDir != "" {
|
||||
s.hostnameWatcher = NewHostnameWatcher(s.cfg.HSDir)
|
||||
s.hostnameWatcher.OnChange(func(addr string) {
|
||||
// Generate torrc
|
||||
torrcPath, err := s.generateTorrc()
|
||||
if err != nil {
|
||||
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.onionAddress = addr
|
||||
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)
|
||||
var err error
|
||||
s.listener, err = net.Listen("tcp", addr)
|
||||
if chk.E(err) {
|
||||
s.Stop()
|
||||
return fmt.Errorf("failed to listen on %s: %w", addr, err)
|
||||
}
|
||||
|
||||
@@ -112,7 +217,7 @@ func (s *Service) Start() error {
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
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 {
|
||||
log.E.F("Tor server error: %v", err)
|
||||
}
|
||||
@@ -121,6 +226,40 @@ func (s *Service) Start() error {
|
||||
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.
|
||||
func (s *Service) Stop() error {
|
||||
s.cancel()
|
||||
@@ -135,7 +274,7 @@ func (s *Service) Stop() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
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()
|
||||
}
|
||||
|
||||
// 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()
|
||||
log.I.F("Tor service stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnionAddress returns the current .onion address (without .onion suffix).
|
||||
// OnionAddress returns the current .onion address.
|
||||
func (s *Service) OnionAddress() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
@@ -172,7 +332,7 @@ func (s *Service) OnionWSAddress() string {
|
||||
|
||||
// IsRunning returns whether the Tor service is currently running.
|
||||
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.
|
||||
@@ -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