Add Tor hidden service support and fallback relay profile fetching (v0.46.0)
Some checks failed
Go / build-and-release (push) Has been cancelled
Some checks failed
Go / build-and-release (push) Has been cancelled
- Add pkg/tor package for Tor hidden service integration - Add Tor config options: ORLY_TOR_ENABLED, ORLY_TOR_PORT, ORLY_TOR_HS_DIR, ORLY_TOR_ONION_ADDRESS - Extend NIP-11 relay info with addresses field for .onion URLs - Add fallback relays (Damus, nos.lol, nostr.band, purplepag.es) for profile lookups - Refactor profile fetching to try local relay first, then fallback relays - Add Tor setup documentation and deployment scripts Files modified: - app/config/config.go: Add Tor configuration options - app/handle-relayinfo.go: Add ExtendedRelayInfo with addresses field - app/main.go: Initialize and manage Tor service lifecycle - app/server.go: Add torService field to Server struct - app/web/src/constants.js: Add FALLBACK_RELAYS - app/web/src/nostr.js: Add fallback relay profile fetching - pkg/tor/: New package for Tor hidden service management - docs/TOR_SETUP.md: Documentation for Tor configuration - deploy/orly-tor.service: Systemd service for Tor integration - scripts/tor-*.sh: Setup scripts for Tor development and production 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
116
pkg/tor/hostname.go
Normal file
116
pkg/tor/hostname.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package tor
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
)
|
||||
|
||||
// HostnameWatcher watches the Tor hidden service hostname file for changes.
|
||||
// When Tor creates or updates a hidden service, it writes the .onion address
|
||||
// to a file called "hostname" in the HiddenServiceDir.
|
||||
type HostnameWatcher struct {
|
||||
hsDir string
|
||||
address string
|
||||
onChange func(string)
|
||||
|
||||
stopCh chan struct{}
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewHostnameWatcher creates a new hostname watcher for the given HiddenServiceDir.
|
||||
func NewHostnameWatcher(hsDir string) *HostnameWatcher {
|
||||
return &HostnameWatcher{
|
||||
hsDir: hsDir,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// OnChange sets a callback function to be called when the hostname changes.
|
||||
func (w *HostnameWatcher) OnChange(fn func(string)) {
|
||||
w.mu.Lock()
|
||||
w.onChange = fn
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
// Start begins watching the hostname file.
|
||||
func (w *HostnameWatcher) Start() error {
|
||||
// Try to read immediately
|
||||
if err := w.readHostname(); err != nil {
|
||||
log.D.F("hostname file not yet available: %v", err)
|
||||
}
|
||||
|
||||
// Start polling goroutine
|
||||
go w.poll()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the hostname watcher.
|
||||
func (w *HostnameWatcher) Stop() {
|
||||
close(w.stopCh)
|
||||
}
|
||||
|
||||
// Address returns the current .onion address.
|
||||
func (w *HostnameWatcher) Address() string {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.address
|
||||
}
|
||||
|
||||
// poll periodically checks the hostname file for changes.
|
||||
func (w *HostnameWatcher) poll() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-w.stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
if err := w.readHostname(); err != nil {
|
||||
// Only log at trace level to avoid spam
|
||||
log.T.F("hostname read: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readHostname reads the hostname file and updates the address if changed.
|
||||
func (w *HostnameWatcher) readHostname() error {
|
||||
path := filepath.Join(w.hsDir, "hostname")
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if chk.T(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the address (file contains "xyz.onion\n")
|
||||
addr := strings.TrimSpace(string(data))
|
||||
if addr == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
oldAddr := w.address
|
||||
w.address = addr
|
||||
onChange := w.onChange
|
||||
w.mu.Unlock()
|
||||
|
||||
// Call callback if address changed
|
||||
if addr != oldAddr && onChange != nil {
|
||||
onChange(addr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HostnameFilePath returns the path to the hostname file.
|
||||
func (w *HostnameWatcher) HostnameFilePath() string {
|
||||
return filepath.Join(w.hsDir, "hostname")
|
||||
}
|
||||
Reference in New Issue
Block a user