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>
117 lines
2.4 KiB
Go
117 lines
2.4 KiB
Go
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")
|
|
}
|