added support for anyone relay with rewards

This commit is contained in:
anonpenguin23 2026-01-28 08:36:57 +02:00
parent c827651245
commit 468ca06398
14 changed files with 885 additions and 29 deletions

4
.gitignore vendored
View File

@ -90,4 +90,6 @@ scripts/remote-nodes.conf
orama-cli-linux
rnd/
rnd/
keys_backup/

View File

@ -324,10 +324,56 @@ curl -X DELETE http://localhost:6001/v1/functions/hello-world?namespace=default
- 5001 - RQLite HTTP API
- 6001 - Unified Gateway
- 8080 - IPFS Gateway
- 9050 - Anyone Client SOCKS5 proxy
- 9050 - Anyone SOCKS5 proxy
- 9094 - IPFS Cluster API
- 3320/3322 - Olric Cache
**Anyone Relay Mode (optional, for earning rewards):**
- 9001 - Anyone ORPort (relay traffic, must be open externally)
### Anyone Network Integration
Orama Network integrates with the [Anyone Protocol](https://anyone.io) for anonymous routing. By default, nodes run as **clients** (consuming the network). Optionally, you can run as a **relay operator** to earn rewards.
**Client Mode (Default):**
- Routes traffic through Anyone network for anonymity
- SOCKS5 proxy on localhost:9050
- No rewards, just consumes network
**Relay Mode (Earn Rewards):**
- Provide bandwidth to the Anyone network
- Earn $ANYONE tokens as a relay operator
- Requires 100 $ANYONE tokens in your wallet
- Requires ORPort (9001) open to the internet
```bash
# Install as relay operator (earn rewards)
sudo orama install --vps-ip <IP> --domain <domain> \
--anyone-relay \
--anyone-nickname "MyRelay" \
--anyone-contact "operator@email.com" \
--anyone-wallet "0x1234...abcd"
# With exit relay (legal implications apply)
sudo orama install --vps-ip <IP> --domain <domain> \
--anyone-relay \
--anyone-exit \
--anyone-nickname "MyExitRelay" \
--anyone-contact "operator@email.com" \
--anyone-wallet "0x1234...abcd"
# Migrate existing Anyone installation
sudo orama install --vps-ip <IP> --domain <domain> \
--anyone-relay \
--anyone-migrate \
--anyone-nickname "MyRelay" \
--anyone-contact "operator@email.com" \
--anyone-wallet "0x1234...abcd"
```
**Important:** After installation, register your relay at [dashboard.anyone.io](https://dashboard.anyone.io) to start earning rewards.
### Installation
**macOS (Homebrew):**

View File

@ -52,6 +52,13 @@ The system follows a clean, layered architecture with clear separation of concer
│ │ │ │
│ Port 9094 │ │ In-Process │
└─────────────────┘ └──────────────┘
┌─────────────────┐
│ Anyone │
│ (Anonymity) │
│ │
│ Port 9050 │
└─────────────────┘
```
## Core Components
@ -226,7 +233,38 @@ pkg/config/
└── gateway.go
```
### 6. Shared Utilities
### 6. Anyone Integration (`pkg/anyoneproxy/`)
Integration with the Anyone Protocol for anonymous routing.
**Modes:**
| Mode | Purpose | Port | Rewards |
|------|---------|------|---------|
| Client | Route traffic anonymously | 9050 (SOCKS5) | No |
| Relay | Provide bandwidth to network | 9001 (ORPort) + 9050 | Yes ($ANYONE) |
**Key Files:**
- `pkg/anyoneproxy/socks.go` - SOCKS5 proxy client interface
- `pkg/gateway/anon_proxy_handler.go` - Anonymous proxy API endpoint
- `pkg/environments/production/installers/anyone_relay.go` - Relay installation
**Features:**
- Smart routing (bypasses proxy for local/private addresses)
- Automatic detection of existing Anyone installations
- Migration support for existing relay operators
- Exit relay mode with legal warnings
**API Endpoint:**
- `POST /v1/proxy/anon` - Route HTTP requests through Anyone network
**Relay Requirements:**
- Linux OS (Debian/Ubuntu)
- 100 $ANYONE tokens in wallet
- ORPort accessible from internet
- Registration at dashboard.anyone.io
### 7. Shared Utilities
**HTTP Utilities (`pkg/httputil/`):**
- Request parsing and validation

View File

@ -39,6 +39,12 @@ func Handle(args []string) {
os.Exit(1)
}
// Validate Anyone relay configuration if enabled
if err := orchestrator.validator.ValidateAnyoneRelayFlags(); err != nil {
fmt.Fprintf(os.Stderr, "❌ %v\n", err)
os.Exit(1)
}
// Execute installation
if err := orchestrator.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "❌ %v\n", err)

View File

@ -27,6 +27,16 @@ type Flags struct {
IPFSAddrs string
IPFSClusterPeerID string
IPFSClusterAddrs string
// Anyone relay operator flags
AnyoneRelay bool // Run as relay operator instead of client
AnyoneExit bool // Run as exit relay (legal implications)
AnyoneMigrate bool // Migrate existing Anyone installation
AnyoneNickname string // Relay nickname (1-19 alphanumeric)
AnyoneContact string // Contact info (email or @telegram)
AnyoneWallet string // Ethereum wallet for rewards
AnyoneORPort int // ORPort for relay (default 9001)
AnyoneFamily string // Comma-separated fingerprints of other relays you operate
}
// ParseFlags parses install command flags
@ -58,6 +68,16 @@ func ParseFlags(args []string) (*Flags, error) {
fs.StringVar(&flags.IPFSClusterPeerID, "ipfs-cluster-peer", "", "Peer ID of existing IPFS Cluster node")
fs.StringVar(&flags.IPFSClusterAddrs, "ipfs-cluster-addrs", "", "Comma-separated multiaddrs of existing IPFS Cluster node")
// Anyone relay operator flags
fs.BoolVar(&flags.AnyoneRelay, "anyone-relay", false, "Run as Anyone relay operator (earn rewards)")
fs.BoolVar(&flags.AnyoneExit, "anyone-exit", false, "Run as exit relay (requires --anyone-relay, legal implications)")
fs.BoolVar(&flags.AnyoneMigrate, "anyone-migrate", false, "Migrate existing Anyone installation into Orama Network")
fs.StringVar(&flags.AnyoneNickname, "anyone-nickname", "", "Relay nickname (1-19 alphanumeric chars)")
fs.StringVar(&flags.AnyoneContact, "anyone-contact", "", "Contact info (email or @telegram)")
fs.StringVar(&flags.AnyoneWallet, "anyone-wallet", "", "Ethereum wallet address for rewards")
fs.IntVar(&flags.AnyoneORPort, "anyone-orport", 9001, "ORPort for relay (default 9001)")
fs.StringVar(&flags.AnyoneFamily, "anyone-family", "", "Comma-separated fingerprints of other relays you operate")
if err := fs.Parse(args); err != nil {
if err == flag.ErrHelp {
return nil, err

View File

@ -33,6 +33,21 @@ func NewOrchestrator(flags *Flags) (*Orchestrator, error) {
setup := production.NewProductionSetup(oramaHome, os.Stdout, flags.Force, flags.Branch, flags.NoPull, flags.SkipChecks)
setup.SetNameserver(flags.Nameserver)
// Configure Anyone relay if enabled
if flags.AnyoneRelay {
setup.SetAnyoneRelayConfig(&production.AnyoneRelayConfig{
Enabled: true,
Exit: flags.AnyoneExit,
Migrate: flags.AnyoneMigrate,
Nickname: flags.AnyoneNickname,
Contact: flags.AnyoneContact,
Wallet: flags.AnyoneWallet,
ORPort: flags.AnyoneORPort,
MyFamily: flags.AnyoneFamily,
})
}
validator := NewValidator(flags, oramaDir)
return &Orchestrator{
@ -60,7 +75,18 @@ func (o *Orchestrator) Execute() error {
// Dry-run mode: show what would be done and exit
if o.flags.DryRun {
utils.ShowDryRunSummary(o.flags.VpsIP, o.flags.Domain, o.flags.Branch, o.peers, o.flags.JoinAddress, o.validator.IsFirstNode(), o.oramaDir)
var relayInfo *utils.AnyoneRelayDryRunInfo
if o.flags.AnyoneRelay {
relayInfo = &utils.AnyoneRelayDryRunInfo{
Enabled: true,
Exit: o.flags.AnyoneExit,
Nickname: o.flags.AnyoneNickname,
Contact: o.flags.AnyoneContact,
Wallet: o.flags.AnyoneWallet,
ORPort: o.flags.AnyoneORPort,
}
}
utils.ShowDryRunSummaryWithRelay(o.flags.VpsIP, o.flags.Domain, o.flags.Branch, o.peers, o.flags.JoinAddress, o.validator.IsFirstNode(), o.oramaDir, relayInfo)
return nil
}

View File

@ -7,6 +7,7 @@ import (
"strings"
"github.com/DeBrosOfficial/network/pkg/cli/utils"
"github.com/DeBrosOfficial/network/pkg/environments/production/installers"
)
// Validator validates install command inputs
@ -43,7 +44,17 @@ func (v *Validator) ValidateRootPrivileges() error {
// ValidatePorts validates port availability
func (v *Validator) ValidatePorts() error {
if err := utils.EnsurePortsAvailable("install", utils.DefaultPorts()); err != nil {
ports := utils.DefaultPorts()
// Add ORPort check for relay mode (skip if migrating existing installation)
if v.flags.AnyoneRelay && !v.flags.AnyoneMigrate {
ports = append(ports, utils.PortSpec{
Name: "Anyone ORPort",
Port: v.flags.AnyoneORPort,
})
}
if err := utils.EnsurePortsAvailable("install", ports); err != nil {
return err
}
return nil
@ -104,3 +115,107 @@ func (v *Validator) SaveSecrets() error {
func (v *Validator) IsFirstNode() bool {
return v.isFirstNode
}
// ValidateAnyoneRelayFlags validates Anyone relay configuration and displays warnings
func (v *Validator) ValidateAnyoneRelayFlags() error {
// Skip validation if not running as relay
if !v.flags.AnyoneRelay {
return nil
}
fmt.Printf("\n🔗 Anyone Relay Configuration\n")
// Check for existing Anyone installation
existing, err := installers.DetectExistingAnyoneInstallation()
if err != nil {
fmt.Printf(" ⚠️ Warning: failed to detect existing installation: %v\n", err)
}
if existing != nil {
fmt.Printf(" ⚠️ Existing Anyone relay detected:\n")
if existing.Fingerprint != "" {
fmt.Printf(" Fingerprint: %s\n", existing.Fingerprint)
}
if existing.Nickname != "" {
fmt.Printf(" Nickname: %s\n", existing.Nickname)
}
if existing.Wallet != "" {
fmt.Printf(" Wallet: %s\n", existing.Wallet)
}
if existing.MyFamily != "" {
familyCount := len(strings.Split(existing.MyFamily, ","))
fmt.Printf(" MyFamily: %d relays\n", familyCount)
}
fmt.Printf(" Keys: %s\n", existing.KeysPath)
fmt.Printf(" Config: %s\n", existing.ConfigPath)
if existing.IsRunning {
fmt.Printf(" Status: Running\n")
}
if !v.flags.AnyoneMigrate {
fmt.Printf("\n 💡 Use --anyone-migrate to preserve existing keys and fingerprint\n")
} else {
fmt.Printf("\n ✓ Will migrate existing installation (keys preserved)\n")
// Auto-populate missing values from existing installation
if v.flags.AnyoneNickname == "" && existing.Nickname != "" {
v.flags.AnyoneNickname = existing.Nickname
fmt.Printf(" ✓ Using existing nickname: %s\n", existing.Nickname)
}
if v.flags.AnyoneWallet == "" && existing.Wallet != "" {
v.flags.AnyoneWallet = existing.Wallet
fmt.Printf(" ✓ Using existing wallet: %s\n", existing.Wallet)
}
}
fmt.Println()
}
// Validate required fields for relay mode
if v.flags.AnyoneNickname == "" {
return fmt.Errorf("--anyone-nickname is required for relay mode")
}
if err := installers.ValidateNickname(v.flags.AnyoneNickname); err != nil {
return fmt.Errorf("invalid --anyone-nickname: %w", err)
}
if v.flags.AnyoneWallet == "" {
return fmt.Errorf("--anyone-wallet is required for relay mode (for rewards)")
}
if err := installers.ValidateWallet(v.flags.AnyoneWallet); err != nil {
return fmt.Errorf("invalid --anyone-wallet: %w", err)
}
if v.flags.AnyoneContact == "" {
return fmt.Errorf("--anyone-contact is required for relay mode")
}
// Validate ORPort
if v.flags.AnyoneORPort < 1 || v.flags.AnyoneORPort > 65535 {
return fmt.Errorf("--anyone-orport must be between 1 and 65535")
}
// Display configuration summary
fmt.Printf(" Nickname: %s\n", v.flags.AnyoneNickname)
fmt.Printf(" Contact: %s\n", v.flags.AnyoneContact)
fmt.Printf(" Wallet: %s\n", v.flags.AnyoneWallet)
fmt.Printf(" ORPort: %d\n", v.flags.AnyoneORPort)
if v.flags.AnyoneExit {
fmt.Printf(" Mode: Exit Relay\n")
} else {
fmt.Printf(" Mode: Non-exit Relay\n")
}
// Warning about token requirement
fmt.Printf("\n ⚠️ IMPORTANT: Relay operators must hold 100 $ANYONE tokens\n")
fmt.Printf(" in wallet %s to receive rewards.\n", v.flags.AnyoneWallet)
fmt.Printf(" Register at: https://dashboard.anyone.io\n")
// Exit relay warning
if v.flags.AnyoneExit {
fmt.Printf("\n ⚠️ EXIT RELAY WARNING:\n")
fmt.Printf(" Running an exit relay may expose you to legal liability\n")
fmt.Printf(" for traffic that exits through your node.\n")
fmt.Printf(" Ensure you understand the implications before proceeding.\n")
}
fmt.Println()
return nil
}

View File

@ -13,6 +13,16 @@ type Flags struct {
NoPull bool
Branch string
Nameserver *bool // Pointer so we can detect if explicitly set vs default
// Anyone relay operator flags
AnyoneRelay bool
AnyoneExit bool
AnyoneMigrate bool
AnyoneNickname string
AnyoneContact string
AnyoneWallet string
AnyoneORPort int
AnyoneFamily string
}
// ParseFlags parses upgrade command flags
@ -30,6 +40,16 @@ func ParseFlags(args []string) (*Flags, error) {
// Nameserver flag - use pointer to detect if explicitly set
nameserver := fs.Bool("nameserver", false, "Make this node a nameserver (uses saved preference if not specified)")
// Anyone relay operator flags
fs.BoolVar(&flags.AnyoneRelay, "anyone-relay", false, "Run as Anyone relay operator (earn rewards)")
fs.BoolVar(&flags.AnyoneExit, "anyone-exit", false, "Run as exit relay (requires --anyone-relay, legal implications)")
fs.BoolVar(&flags.AnyoneMigrate, "anyone-migrate", false, "Migrate existing Anyone installation into Orama Network")
fs.StringVar(&flags.AnyoneNickname, "anyone-nickname", "", "Relay nickname (1-19 alphanumeric chars)")
fs.StringVar(&flags.AnyoneContact, "anyone-contact", "", "Contact info (email or @telegram)")
fs.StringVar(&flags.AnyoneWallet, "anyone-wallet", "", "Ethereum wallet address for rewards")
fs.IntVar(&flags.AnyoneORPort, "anyone-orport", 9001, "ORPort for relay (default 9001)")
fs.StringVar(&flags.AnyoneFamily, "anyone-family", "", "Comma-separated fingerprints of other relays you operate")
// Support legacy flags for backwards compatibility
nightly := fs.Bool("nightly", false, "Use nightly branch (deprecated, use --branch nightly)")
main := fs.Bool("main", false, "Use main branch (deprecated, use --branch main)")

View File

@ -44,6 +44,20 @@ func NewOrchestrator(flags *Flags) *Orchestrator {
setup := production.NewProductionSetup(oramaHome, os.Stdout, flags.Force, branch, flags.NoPull, false)
setup.SetNameserver(isNameserver)
// Configure Anyone relay if enabled
if flags.AnyoneRelay {
setup.SetAnyoneRelayConfig(&production.AnyoneRelayConfig{
Enabled: true,
Exit: flags.AnyoneExit,
Migrate: flags.AnyoneMigrate,
Nickname: flags.AnyoneNickname,
Contact: flags.AnyoneContact,
Wallet: flags.AnyoneWallet,
ORPort: flags.AnyoneORPort,
MyFamily: flags.AnyoneFamily,
})
}
return &Orchestrator{
oramaHome: oramaHome,
oramaDir: oramaDir,
@ -192,7 +206,8 @@ func (o *Orchestrator) stopServices() error {
"debros-ipfs-cluster.service", // Depends on IPFS
"debros-ipfs.service", // Base IPFS
"debros-olric.service", // Independent
"debros-anyone-client.service", // Independent
"debros-anyone-client.service", // Client mode
"debros-anyone-relay.service", // Relay mode
}
for _, svc := range services {
unitPath := filepath.Join("/etc/systemd/system", svc)

View File

@ -17,8 +17,23 @@ type IPFSClusterPeerInfo struct {
Addrs []string
}
// AnyoneRelayDryRunInfo contains Anyone relay info for dry-run summary
type AnyoneRelayDryRunInfo struct {
Enabled bool
Exit bool
Nickname string
Contact string
Wallet string
ORPort int
}
// ShowDryRunSummary displays what would be done during installation without making changes
func ShowDryRunSummary(vpsIP, domain, branch string, peers []string, joinAddress string, isFirstNode bool, oramaDir string) {
ShowDryRunSummaryWithRelay(vpsIP, domain, branch, peers, joinAddress, isFirstNode, oramaDir, nil)
}
// ShowDryRunSummaryWithRelay displays what would be done during installation with optional relay info
func ShowDryRunSummaryWithRelay(vpsIP, domain, branch string, peers []string, joinAddress string, isFirstNode bool, oramaDir string, relayInfo *AnyoneRelayDryRunInfo) {
fmt.Print("\n" + strings.Repeat("=", 70) + "\n")
fmt.Printf("DRY RUN - No changes will be made\n")
fmt.Print(strings.Repeat("=", 70) + "\n\n")
@ -57,7 +72,11 @@ func ShowDryRunSummary(vpsIP, domain, branch string, peers []string, joinAddress
fmt.Printf(" - IPFS/Kubo 0.38.2\n")
fmt.Printf(" - IPFS Cluster (latest)\n")
fmt.Printf(" - Olric 0.7.0\n")
fmt.Printf(" - anyone-client (npm)\n")
if relayInfo != nil && relayInfo.Enabled {
fmt.Printf(" - anon (relay binary via apt)\n")
} else {
fmt.Printf(" - anyone-client (npm)\n")
}
fmt.Printf(" - DeBros binaries (built from %s branch)\n", branch)
fmt.Printf("\n🔐 Secrets that would be generated:\n")
@ -74,7 +93,11 @@ func ShowDryRunSummary(vpsIP, domain, branch string, peers []string, joinAddress
fmt.Printf(" - debros-ipfs-cluster.service\n")
fmt.Printf(" - debros-olric.service\n")
fmt.Printf(" - debros-node.service (includes embedded gateway + RQLite)\n")
fmt.Printf(" - debros-anyone-client.service\n")
if relayInfo != nil && relayInfo.Enabled {
fmt.Printf(" - debros-anyone-relay.service (relay operator mode)\n")
} else {
fmt.Printf(" - debros-anyone-client.service\n")
}
fmt.Printf("\n🌐 Ports that would be used:\n")
fmt.Printf(" External (must be open in firewall):\n")
@ -82,6 +105,9 @@ func ShowDryRunSummary(vpsIP, domain, branch string, peers []string, joinAddress
fmt.Printf(" - 443 (HTTPS gateway)\n")
fmt.Printf(" - 4101 (IPFS swarm)\n")
fmt.Printf(" - 7001 (RQLite Raft)\n")
if relayInfo != nil && relayInfo.Enabled {
fmt.Printf(" - %d (Anyone ORPort - relay traffic)\n", relayInfo.ORPort)
}
fmt.Printf(" Internal (localhost only):\n")
fmt.Printf(" - 4501 (IPFS API)\n")
fmt.Printf(" - 5001 (RQLite HTTP)\n")
@ -91,6 +117,23 @@ func ShowDryRunSummary(vpsIP, domain, branch string, peers []string, joinAddress
fmt.Printf(" - 9094 (IPFS Cluster API)\n")
fmt.Printf(" - 3320/3322 (Olric)\n")
// Show relay-specific configuration
if relayInfo != nil && relayInfo.Enabled {
fmt.Printf("\n🔗 Anyone Relay Configuration:\n")
fmt.Printf(" Mode: Relay Operator\n")
fmt.Printf(" Nickname: %s\n", relayInfo.Nickname)
fmt.Printf(" Contact: %s\n", relayInfo.Contact)
fmt.Printf(" Wallet: %s\n", relayInfo.Wallet)
fmt.Printf(" ORPort: %d\n", relayInfo.ORPort)
if relayInfo.Exit {
fmt.Printf(" Exit: Yes (legal implications apply)\n")
} else {
fmt.Printf(" Exit: No (non-exit relay)\n")
}
fmt.Printf("\n ⚠️ IMPORTANT: You need 100 $ANYONE tokens in wallet to receive rewards\n")
fmt.Printf(" Register at: https://dashboard.anyone.io\n")
}
fmt.Print("\n" + strings.Repeat("=", 70) + "\n")
fmt.Printf("To proceed with installation, run without --dry-run\n")
fmt.Print(strings.Repeat("=", 70) + "\n\n")

View File

@ -160,6 +160,7 @@ func GetProductionServices() []string {
"debros-ipfs-cluster",
"debros-ipfs",
"debros-anyone-client",
"debros-anyone-relay",
}
// Filter to only existing services by checking if unit file exists

View File

@ -0,0 +1,385 @@
package installers
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
// AnyoneRelayConfig holds configuration for the Anyone relay
type AnyoneRelayConfig struct {
Nickname string // Relay nickname (1-19 alphanumeric)
Contact string // Contact info (email or @telegram)
Wallet string // Ethereum wallet for rewards
ORPort int // ORPort for relay (default 9001)
ExitRelay bool // Whether to run as exit relay
Migrate bool // Whether to migrate existing installation
MyFamily string // Comma-separated list of family fingerprints (for multi-relay operators)
}
// ExistingAnyoneInfo contains information about an existing Anyone installation
type ExistingAnyoneInfo struct {
HasKeys bool
HasConfig bool
IsRunning bool
Fingerprint string
Wallet string
Nickname string
MyFamily string // Existing MyFamily setting (important to preserve!)
ConfigPath string
KeysPath string
}
// AnyoneRelayInstaller handles Anyone relay installation
type AnyoneRelayInstaller struct {
*BaseInstaller
config AnyoneRelayConfig
}
// NewAnyoneRelayInstaller creates a new Anyone relay installer
func NewAnyoneRelayInstaller(arch string, logWriter io.Writer, config AnyoneRelayConfig) *AnyoneRelayInstaller {
return &AnyoneRelayInstaller{
BaseInstaller: NewBaseInstaller(arch, logWriter),
config: config,
}
}
// DetectExistingAnyoneInstallation checks for an existing Anyone relay installation
func DetectExistingAnyoneInstallation() (*ExistingAnyoneInfo, error) {
info := &ExistingAnyoneInfo{
ConfigPath: "/etc/anon/anonrc",
KeysPath: "/var/lib/anon/keys",
}
// Check for existing keys
if _, err := os.Stat(info.KeysPath); err == nil {
info.HasKeys = true
}
// Check for existing config
if _, err := os.Stat(info.ConfigPath); err == nil {
info.HasConfig = true
// Parse existing config for fingerprint/wallet/nickname
if file, err := os.Open(info.ConfigPath); err == nil {
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "#") {
continue
}
// Parse Nickname
if strings.HasPrefix(line, "Nickname ") {
info.Nickname = strings.TrimPrefix(line, "Nickname ")
}
// Parse ContactInfo for wallet (format: ... @anon:0x... or @anon: 0x...)
if strings.HasPrefix(line, "ContactInfo ") {
contact := strings.TrimPrefix(line, "ContactInfo ")
// Extract wallet address from @anon: prefix (handle space after colon)
if idx := strings.Index(contact, "@anon:"); idx != -1 {
wallet := strings.TrimSpace(contact[idx+6:])
info.Wallet = wallet
}
}
// Parse MyFamily (critical to preserve for multi-relay operators)
if strings.HasPrefix(line, "MyFamily ") {
info.MyFamily = strings.TrimPrefix(line, "MyFamily ")
}
}
}
}
// Check if anon service is running
cmd := exec.Command("systemctl", "is-active", "--quiet", "anon")
if cmd.Run() == nil {
info.IsRunning = true
}
// Try to get fingerprint from data directory (it's in /var/lib/anon/, not keys/)
fingerprintFile := "/var/lib/anon/fingerprint"
if data, err := os.ReadFile(fingerprintFile); err == nil {
info.Fingerprint = strings.TrimSpace(string(data))
}
// Return nil if no installation detected
if !info.HasKeys && !info.HasConfig && !info.IsRunning {
return nil, nil
}
return info, nil
}
// IsInstalled checks if the anon relay binary is installed
func (ari *AnyoneRelayInstaller) IsInstalled() bool {
// Check if anon binary exists
if _, err := exec.LookPath("anon"); err == nil {
return true
}
// Check common installation path
if _, err := os.Stat("/usr/bin/anon"); err == nil {
return true
}
return false
}
// Install downloads and installs the Anyone relay using the official install script
func (ari *AnyoneRelayInstaller) Install() error {
fmt.Fprintf(ari.logWriter, " Installing Anyone relay...\n")
// Create required directories
dirs := []string{
"/etc/anon",
"/var/lib/anon",
"/var/log/anon",
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %w", dir, err)
}
}
// Download the official install script
installScript := "/tmp/anon-install.sh"
scriptURL := "https://raw.githubusercontent.com/anyone-protocol/anon-install/refs/heads/main/install.sh"
fmt.Fprintf(ari.logWriter, " Downloading install script...\n")
if err := DownloadFile(scriptURL, installScript); err != nil {
return fmt.Errorf("failed to download install script: %w", err)
}
// Make script executable
if err := os.Chmod(installScript, 0755); err != nil {
return fmt.Errorf("failed to chmod install script: %w", err)
}
// The official script is interactive, so we need to provide answers via stdin
// or install the package directly
fmt.Fprintf(ari.logWriter, " Installing anon package...\n")
// Add the Anyone repository and install the package directly
// This is more reliable than running the interactive script
if err := ari.addAnyoneRepository(); err != nil {
return fmt.Errorf("failed to add Anyone repository: %w", err)
}
// Install the anon package
cmd := exec.Command("apt-get", "install", "-y", "anon")
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to install anon package: %w\n%s", err, string(output))
}
// Clean up
os.Remove(installScript)
fmt.Fprintf(ari.logWriter, " ✓ Anyone relay binary installed\n")
return nil
}
// addAnyoneRepository adds the Anyone apt repository
func (ari *AnyoneRelayInstaller) addAnyoneRepository() error {
// Add GPG key using wget (as per official install script)
fmt.Fprintf(ari.logWriter, " Adding Anyone repository key...\n")
// Download and add the GPG key using the official method
keyPath := "/etc/apt/trusted.gpg.d/anon.asc"
cmd := exec.Command("bash", "-c", "wget -qO- https://deb.en.anyone.tech/anon.asc | tee "+keyPath)
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to download GPG key: %w\n%s", err, string(output))
}
// Add repository
fmt.Fprintf(ari.logWriter, " Adding Anyone repository...\n")
// Determine distribution codename
codename := "stable"
if data, err := exec.Command("lsb_release", "-cs").Output(); err == nil {
codename = strings.TrimSpace(string(data))
}
// Create sources.list entry using the official format: anon-live-$VERSION_CODENAME
repoLine := fmt.Sprintf("deb [signed-by=%s] https://deb.en.anyone.tech anon-live-%s main\n", keyPath, codename)
if err := os.WriteFile("/etc/apt/sources.list.d/anon.list", []byte(repoLine), 0644); err != nil {
return fmt.Errorf("failed to write repository file: %w", err)
}
// Update apt
cmd = exec.Command("apt-get", "update", "--yes")
if output, err := cmd.CombinedOutput(); err != nil {
fmt.Fprintf(ari.logWriter, " ⚠️ Warning: apt update failed: %s\n", string(output))
}
return nil
}
// Configure generates the anonrc configuration file
func (ari *AnyoneRelayInstaller) Configure() error {
fmt.Fprintf(ari.logWriter, " Configuring Anyone relay...\n")
configPath := "/etc/anon/anonrc"
// Backup existing config if it exists
if _, err := os.Stat(configPath); err == nil {
backupPath := configPath + ".bak"
if err := exec.Command("cp", configPath, backupPath).Run(); err != nil {
fmt.Fprintf(ari.logWriter, " ⚠️ Warning: failed to backup existing config: %v\n", err)
} else {
fmt.Fprintf(ari.logWriter, " Backed up existing config to %s\n", backupPath)
}
}
// Generate configuration
config := ari.generateAnonrc()
// Write configuration
if err := os.WriteFile(configPath, []byte(config), 0644); err != nil {
return fmt.Errorf("failed to write anonrc: %w", err)
}
fmt.Fprintf(ari.logWriter, " ✓ Anyone relay configured\n")
return nil
}
// generateAnonrc creates the anonrc configuration content
func (ari *AnyoneRelayInstaller) generateAnonrc() string {
var sb strings.Builder
sb.WriteString("# Anyone Relay Configuration (Managed by Orama Network)\n")
sb.WriteString("# Generated automatically - manual edits may be overwritten\n\n")
// Nickname
sb.WriteString(fmt.Sprintf("Nickname %s\n", ari.config.Nickname))
// Contact info with wallet
if ari.config.Wallet != "" {
sb.WriteString(fmt.Sprintf("ContactInfo %s @anon:%s\n", ari.config.Contact, ari.config.Wallet))
} else {
sb.WriteString(fmt.Sprintf("ContactInfo %s\n", ari.config.Contact))
}
sb.WriteString("\n")
// ORPort
sb.WriteString(fmt.Sprintf("ORPort %d\n", ari.config.ORPort))
// SOCKS port for local use
sb.WriteString("SocksPort 9050\n")
sb.WriteString("\n")
// Exit relay configuration
if ari.config.ExitRelay {
sb.WriteString("ExitRelay 1\n")
sb.WriteString("# Exit policy - allow common ports\n")
sb.WriteString("ExitPolicy accept *:80\n")
sb.WriteString("ExitPolicy accept *:443\n")
sb.WriteString("ExitPolicy reject *:*\n")
} else {
sb.WriteString("ExitRelay 0\n")
sb.WriteString("ExitPolicy reject *:*\n")
}
sb.WriteString("\n")
// Logging
sb.WriteString("Log notice file /var/log/anon/notices.log\n")
// Data directory
sb.WriteString("DataDirectory /var/lib/anon\n")
// Control port for monitoring
sb.WriteString("ControlPort 9051\n")
// MyFamily for multi-relay operators (preserve from existing config)
if ari.config.MyFamily != "" {
sb.WriteString("\n")
sb.WriteString(fmt.Sprintf("MyFamily %s\n", ari.config.MyFamily))
}
return sb.String()
}
// MigrateExistingInstallation migrates an existing Anyone installation into Orama Network
func (ari *AnyoneRelayInstaller) MigrateExistingInstallation(existing *ExistingAnyoneInfo, backupDir string) error {
fmt.Fprintf(ari.logWriter, " Migrating existing Anyone installation...\n")
// Create backup directory
backupAnonDir := filepath.Join(backupDir, "anon-backup")
if err := os.MkdirAll(backupAnonDir, 0755); err != nil {
return fmt.Errorf("failed to create backup directory: %w", err)
}
// Stop existing anon service if running
if existing.IsRunning {
fmt.Fprintf(ari.logWriter, " Stopping existing anon service...\n")
exec.Command("systemctl", "stop", "anon").Run()
}
// Backup keys
if existing.HasKeys {
fmt.Fprintf(ari.logWriter, " Backing up keys...\n")
keysBackup := filepath.Join(backupAnonDir, "keys")
if err := exec.Command("cp", "-r", existing.KeysPath, keysBackup).Run(); err != nil {
return fmt.Errorf("failed to backup keys: %w", err)
}
}
// Backup config
if existing.HasConfig {
fmt.Fprintf(ari.logWriter, " Backing up config...\n")
configBackup := filepath.Join(backupAnonDir, "anonrc")
if err := exec.Command("cp", existing.ConfigPath, configBackup).Run(); err != nil {
return fmt.Errorf("failed to backup config: %w", err)
}
}
// Preserve nickname from existing installation if not provided
if ari.config.Nickname == "" && existing.Nickname != "" {
fmt.Fprintf(ari.logWriter, " Using existing nickname: %s\n", existing.Nickname)
ari.config.Nickname = existing.Nickname
}
// Preserve wallet from existing installation if not provided
if ari.config.Wallet == "" && existing.Wallet != "" {
fmt.Fprintf(ari.logWriter, " Using existing wallet: %s\n", existing.Wallet)
ari.config.Wallet = existing.Wallet
}
// Preserve MyFamily from existing installation (critical for multi-relay operators)
if existing.MyFamily != "" {
fmt.Fprintf(ari.logWriter, " Preserving MyFamily configuration (%d relays)\n", len(strings.Split(existing.MyFamily, ",")))
ari.config.MyFamily = existing.MyFamily
}
fmt.Fprintf(ari.logWriter, " ✓ Backup created at %s\n", backupAnonDir)
fmt.Fprintf(ari.logWriter, " ✓ Migration complete - keys and fingerprint preserved\n")
return nil
}
// ValidateNickname validates the relay nickname (1-19 alphanumeric chars)
func ValidateNickname(nickname string) error {
if len(nickname) < 1 || len(nickname) > 19 {
return fmt.Errorf("nickname must be 1-19 characters")
}
if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(nickname) {
return fmt.Errorf("nickname must be alphanumeric only")
}
return nil
}
// ValidateWallet validates an Ethereum wallet address
func ValidateWallet(wallet string) error {
if !regexp.MustCompile(`^0x[a-fA-F0-9]{40}$`).MatchString(wallet) {
return fmt.Errorf("invalid Ethereum wallet address (must be 0x followed by 40 hex characters)")
}
return nil
}

View File

@ -8,8 +8,22 @@ import (
"path/filepath"
"strings"
"time"
"github.com/DeBrosOfficial/network/pkg/environments/production/installers"
)
// AnyoneRelayConfig holds configuration for Anyone relay mode
type AnyoneRelayConfig struct {
Enabled bool // Whether to run as relay operator
Exit bool // Whether to run as exit relay
Migrate bool // Whether to migrate existing installation
Nickname string // Relay nickname (1-19 alphanumeric)
Contact string // Contact info (email or @telegram)
Wallet string // Ethereum wallet for rewards
ORPort int // ORPort for relay (default 9001)
MyFamily string // Comma-separated fingerprints of other relays (for multi-relay operators)
}
// ProductionSetup orchestrates the entire production deployment
type ProductionSetup struct {
osInfo *OSInfo
@ -21,6 +35,7 @@ type ProductionSetup struct {
skipOptionalDeps bool
skipResourceChecks bool
isNameserver bool // Whether this node is a nameserver (runs CoreDNS + Caddy)
anyoneRelayConfig *AnyoneRelayConfig // Configuration for Anyone relay mode
privChecker *PrivilegeChecker
osDetector *OSDetector
archDetector *ArchitectureDetector
@ -123,6 +138,16 @@ func (ps *ProductionSetup) IsNameserver() bool {
return ps.isNameserver
}
// SetAnyoneRelayConfig sets the Anyone relay configuration
func (ps *ProductionSetup) SetAnyoneRelayConfig(config *AnyoneRelayConfig) {
ps.anyoneRelayConfig = config
}
// IsAnyoneRelay returns whether this node is configured as an Anyone relay operator
func (ps *ProductionSetup) IsAnyoneRelay() bool {
return ps.anyoneRelayConfig != nil && ps.anyoneRelayConfig.Enabled
}
// Phase1CheckPrerequisites performs initial environment validation
func (ps *ProductionSetup) Phase1CheckPrerequisites() error {
ps.logf("Phase 1: Checking prerequisites...")
@ -275,9 +300,47 @@ func (ps *ProductionSetup) Phase2bInstallBinaries() error {
ps.logf(" ⚠️ Olric install warning: %v", err)
}
// Install anyone-client for SOCKS5 proxy
if err := ps.binaryInstaller.InstallAnyoneClient(); err != nil {
ps.logf(" ⚠️ anyone-client install warning: %v", err)
// Install Anyone (client or relay based on configuration)
if ps.IsAnyoneRelay() {
ps.logf(" Installing Anyone relay (operator mode)...")
relayConfig := installers.AnyoneRelayConfig{
Nickname: ps.anyoneRelayConfig.Nickname,
Contact: ps.anyoneRelayConfig.Contact,
Wallet: ps.anyoneRelayConfig.Wallet,
ORPort: ps.anyoneRelayConfig.ORPort,
ExitRelay: ps.anyoneRelayConfig.Exit,
Migrate: ps.anyoneRelayConfig.Migrate,
MyFamily: ps.anyoneRelayConfig.MyFamily,
}
relayInstaller := installers.NewAnyoneRelayInstaller(ps.arch, ps.logWriter, relayConfig)
// Check for existing installation if migration is requested
if relayConfig.Migrate {
existing, err := installers.DetectExistingAnyoneInstallation()
if err != nil {
ps.logf(" ⚠️ Failed to detect existing installation: %v", err)
} else if existing != nil {
backupDir := filepath.Join(ps.oramaDir, "backups")
if err := relayInstaller.MigrateExistingInstallation(existing, backupDir); err != nil {
ps.logf(" ⚠️ Migration warning: %v", err)
}
}
}
// Install the relay
if err := relayInstaller.Install(); err != nil {
ps.logf(" ⚠️ Anyone relay install warning: %v", err)
}
// Configure the relay
if err := relayInstaller.Configure(); err != nil {
ps.logf(" ⚠️ Anyone relay config warning: %v", err)
}
} else {
// Install anyone-client for SOCKS5 proxy (default client mode)
if err := ps.binaryInstaller.InstallAnyoneClient(); err != nil {
ps.logf(" ⚠️ anyone-client install warning: %v", err)
}
}
// Install DeBros binaries (must be done before CoreDNS since we need the RQLite plugin source)
@ -551,12 +614,20 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices(enableHTTPS bool) error {
}
ps.logf(" ✓ Node service created: debros-node.service (with embedded gateway)")
// Anyone Client service (SOCKS5 proxy)
anyoneUnit := ps.serviceGenerator.GenerateAnyoneClientService()
if err := ps.serviceController.WriteServiceUnit("debros-anyone-client.service", anyoneUnit); err != nil {
return fmt.Errorf("failed to write Anyone Client service: %w", err)
// Anyone service (Client or Relay based on configuration)
if ps.IsAnyoneRelay() {
anyoneUnit := ps.serviceGenerator.GenerateAnyoneRelayService()
if err := ps.serviceController.WriteServiceUnit("debros-anyone-relay.service", anyoneUnit); err != nil {
return fmt.Errorf("failed to write Anyone Relay service: %w", err)
}
ps.logf(" ✓ Anyone Relay service created (operator mode, ORPort: %d)", ps.anyoneRelayConfig.ORPort)
} else {
anyoneUnit := ps.serviceGenerator.GenerateAnyoneClientService()
if err := ps.serviceController.WriteServiceUnit("debros-anyone-client.service", anyoneUnit); err != nil {
return fmt.Errorf("failed to write Anyone Client service: %w", err)
}
ps.logf(" ✓ Anyone Client service created")
}
ps.logf(" ✓ Anyone Client service created")
// CoreDNS and Caddy services (only for nameserver nodes)
if ps.isNameserver {
@ -595,7 +666,14 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices(enableHTTPS bool) error {
// Enable services (unified names - no bootstrap/node distinction)
// Note: debros-gateway.service is no longer needed - each node has an embedded gateway
// Note: debros-rqlite.service is NOT created - RQLite is managed by each node internally
services := []string{"debros-ipfs.service", "debros-ipfs-cluster.service", "debros-olric.service", "debros-node.service", "debros-anyone-client.service"}
services := []string{"debros-ipfs.service", "debros-ipfs-cluster.service", "debros-olric.service", "debros-node.service"}
// Add appropriate Anyone service based on mode
if ps.IsAnyoneRelay() {
services = append(services, "debros-anyone-relay.service")
} else {
services = append(services, "debros-anyone-client.service")
}
// Add CoreDNS and Caddy only for nameserver nodes
if ps.isNameserver {
@ -617,15 +695,30 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices(enableHTTPS bool) error {
// Start services in dependency order
ps.logf(" Starting services...")
// Start infrastructure first (IPFS, Olric, Anyone Client) - RQLite is managed internally by each node
// Start infrastructure first (IPFS, Olric, Anyone) - RQLite is managed internally by each node
infraServices := []string{"debros-ipfs.service", "debros-olric.service"}
// Check if port 9050 is already in use (e.g., another anyone-client or similar service)
if ps.portChecker.IsPortInUse(9050) {
ps.logf(" Port 9050 is already in use (anyone-client or similar service running)")
ps.logf(" Skipping debros-anyone-client startup - using existing service")
// Add appropriate Anyone service based on mode
if ps.IsAnyoneRelay() {
// For relay mode, check if ORPort is already in use
orPort := 9001
if ps.anyoneRelayConfig != nil && ps.anyoneRelayConfig.ORPort > 0 {
orPort = ps.anyoneRelayConfig.ORPort
}
if ps.portChecker.IsPortInUse(orPort) {
ps.logf(" ORPort %d is already in use (existing anon relay running)", orPort)
ps.logf(" Skipping debros-anyone-relay startup - using existing service")
} else {
infraServices = append(infraServices, "debros-anyone-relay.service")
}
} else {
infraServices = append(infraServices, "debros-anyone-client.service")
// For client mode, check if SOCKS port 9050 is already in use
if ps.portChecker.IsPortInUse(9050) {
ps.logf(" Port 9050 is already in use (anyone-client or similar service running)")
ps.logf(" Skipping debros-anyone-client startup - using existing service")
} else {
infraServices = append(infraServices, "debros-anyone-client.service")
}
}
for _, svc := range infraServices {
@ -720,11 +813,27 @@ func (ps *ProductionSetup) LogSetupComplete(peerID string) {
ps.logf(" %s/logs/olric.log", ps.oramaDir)
ps.logf(" %s/logs/node.log", ps.oramaDir)
ps.logf(" %s/logs/gateway.log", ps.oramaDir)
ps.logf(" %s/logs/anyone-client.log", ps.oramaDir)
ps.logf("\nStart All Services:")
ps.logf(" systemctl start debros-ipfs debros-ipfs-cluster debros-olric debros-anyone-client debros-node")
// Anyone mode-specific logs and commands
if ps.IsAnyoneRelay() {
ps.logf(" /var/log/anon/notices.log (Anyone Relay)")
ps.logf("\nStart All Services:")
ps.logf(" systemctl start debros-ipfs debros-ipfs-cluster debros-olric debros-anyone-relay debros-node")
ps.logf("\nAnyone Relay Operator:")
ps.logf(" ORPort: %d", ps.anyoneRelayConfig.ORPort)
ps.logf(" Wallet: %s", ps.anyoneRelayConfig.Wallet)
ps.logf(" Config: /etc/anon/anonrc")
ps.logf(" Register at: https://dashboard.anyone.io")
ps.logf(" IMPORTANT: You need 100 $ANYONE tokens in your wallet to receive rewards")
} else {
ps.logf(" %s/logs/anyone-client.log", ps.oramaDir)
ps.logf("\nStart All Services:")
ps.logf(" systemctl start debros-ipfs debros-ipfs-cluster debros-olric debros-anyone-client debros-node")
ps.logf("\nAnyone Client:")
ps.logf(" # SOCKS5 proxy on localhost:9050")
}
ps.logf("\nVerify Installation:")
ps.logf(" curl http://localhost:6001/health")
ps.logf(" curl http://localhost:5001/status")
ps.logf(" # Anyone Client SOCKS5 proxy on localhost:9050\n")
ps.logf(" curl http://localhost:5001/status\n")
}

View File

@ -324,6 +324,36 @@ WantedBy=multi-user.target
`, ssg.oramaHome, logFile, ssg.oramaDir)
}
// GenerateAnyoneRelayService generates the Anyone Relay operator systemd unit
// Uses debian-anon user created by the anon apt package
func (ssg *SystemdServiceGenerator) GenerateAnyoneRelayService() string {
return `[Unit]
Description=Anyone Relay (Orama Network)
Documentation=https://docs.anyone.io
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=debian-anon
Group=debian-anon
ExecStart=/usr/bin/anon --agree-to-terms
Restart=always
RestartSec=10
SyslogIdentifier=anon-relay
# Security hardening
NoNewPrivileges=yes
ProtectSystem=full
ProtectHome=read-only
PrivateTmp=yes
ReadWritePaths=/var/lib/anon /var/log/anon /etc/anon
[Install]
WantedBy=multi-user.target
`
}
// GenerateCoreDNSService generates the CoreDNS systemd unit
func (ssg *SystemdServiceGenerator) GenerateCoreDNSService() string {
return `[Unit]