mirror of
https://github.com/DeBrosOfficial/network.git
synced 2026-01-30 08:33:04 +00:00
added support for anyone relay with rewards
This commit is contained in:
parent
c827651245
commit
468ca06398
2
.gitignore
vendored
2
.gitignore
vendored
@ -91,3 +91,5 @@ scripts/remote-nodes.conf
|
|||||||
orama-cli-linux
|
orama-cli-linux
|
||||||
|
|
||||||
rnd/
|
rnd/
|
||||||
|
|
||||||
|
keys_backup/
|
||||||
48
README.md
48
README.md
@ -324,10 +324,56 @@ curl -X DELETE http://localhost:6001/v1/functions/hello-world?namespace=default
|
|||||||
- 5001 - RQLite HTTP API
|
- 5001 - RQLite HTTP API
|
||||||
- 6001 - Unified Gateway
|
- 6001 - Unified Gateway
|
||||||
- 8080 - IPFS Gateway
|
- 8080 - IPFS Gateway
|
||||||
- 9050 - Anyone Client SOCKS5 proxy
|
- 9050 - Anyone SOCKS5 proxy
|
||||||
- 9094 - IPFS Cluster API
|
- 9094 - IPFS Cluster API
|
||||||
- 3320/3322 - Olric Cache
|
- 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
|
### Installation
|
||||||
|
|
||||||
**macOS (Homebrew):**
|
**macOS (Homebrew):**
|
||||||
|
|||||||
@ -52,6 +52,13 @@ The system follows a clean, layered architecture with clear separation of concer
|
|||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ Port 9094 │ │ In-Process │
|
│ Port 9094 │ │ In-Process │
|
||||||
└─────────────────┘ └──────────────┘
|
└─────────────────┘ └──────────────┘
|
||||||
|
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Anyone │
|
||||||
|
│ (Anonymity) │
|
||||||
|
│ │
|
||||||
|
│ Port 9050 │
|
||||||
|
└─────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
## Core Components
|
## Core Components
|
||||||
@ -226,7 +233,38 @@ pkg/config/
|
|||||||
└── gateway.go
|
└── 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/`):**
|
**HTTP Utilities (`pkg/httputil/`):**
|
||||||
- Request parsing and validation
|
- Request parsing and validation
|
||||||
|
|||||||
@ -39,6 +39,12 @@ func Handle(args []string) {
|
|||||||
os.Exit(1)
|
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
|
// Execute installation
|
||||||
if err := orchestrator.Execute(); err != nil {
|
if err := orchestrator.Execute(); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "❌ %v\n", err)
|
fmt.Fprintf(os.Stderr, "❌ %v\n", err)
|
||||||
|
|||||||
@ -27,6 +27,16 @@ type Flags struct {
|
|||||||
IPFSAddrs string
|
IPFSAddrs string
|
||||||
IPFSClusterPeerID string
|
IPFSClusterPeerID string
|
||||||
IPFSClusterAddrs 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
|
// 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.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")
|
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 := fs.Parse(args); err != nil {
|
||||||
if err == flag.ErrHelp {
|
if err == flag.ErrHelp {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -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 := production.NewProductionSetup(oramaHome, os.Stdout, flags.Force, flags.Branch, flags.NoPull, flags.SkipChecks)
|
||||||
setup.SetNameserver(flags.Nameserver)
|
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)
|
validator := NewValidator(flags, oramaDir)
|
||||||
|
|
||||||
return &Orchestrator{
|
return &Orchestrator{
|
||||||
@ -60,7 +75,18 @@ func (o *Orchestrator) Execute() error {
|
|||||||
|
|
||||||
// Dry-run mode: show what would be done and exit
|
// Dry-run mode: show what would be done and exit
|
||||||
if o.flags.DryRun {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/DeBrosOfficial/network/pkg/cli/utils"
|
"github.com/DeBrosOfficial/network/pkg/cli/utils"
|
||||||
|
"github.com/DeBrosOfficial/network/pkg/environments/production/installers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validator validates install command inputs
|
// Validator validates install command inputs
|
||||||
@ -43,7 +44,17 @@ func (v *Validator) ValidateRootPrivileges() error {
|
|||||||
|
|
||||||
// ValidatePorts validates port availability
|
// ValidatePorts validates port availability
|
||||||
func (v *Validator) ValidatePorts() error {
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -104,3 +115,107 @@ func (v *Validator) SaveSecrets() error {
|
|||||||
func (v *Validator) IsFirstNode() bool {
|
func (v *Validator) IsFirstNode() bool {
|
||||||
return v.isFirstNode
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -13,6 +13,16 @@ type Flags struct {
|
|||||||
NoPull bool
|
NoPull bool
|
||||||
Branch string
|
Branch string
|
||||||
Nameserver *bool // Pointer so we can detect if explicitly set vs default
|
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
|
// 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 flag - use pointer to detect if explicitly set
|
||||||
nameserver := fs.Bool("nameserver", false, "Make this node a nameserver (uses saved preference if not specified)")
|
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
|
// Support legacy flags for backwards compatibility
|
||||||
nightly := fs.Bool("nightly", false, "Use nightly branch (deprecated, use --branch nightly)")
|
nightly := fs.Bool("nightly", false, "Use nightly branch (deprecated, use --branch nightly)")
|
||||||
main := fs.Bool("main", false, "Use main branch (deprecated, use --branch main)")
|
main := fs.Bool("main", false, "Use main branch (deprecated, use --branch main)")
|
||||||
|
|||||||
@ -44,6 +44,20 @@ func NewOrchestrator(flags *Flags) *Orchestrator {
|
|||||||
setup := production.NewProductionSetup(oramaHome, os.Stdout, flags.Force, branch, flags.NoPull, false)
|
setup := production.NewProductionSetup(oramaHome, os.Stdout, flags.Force, branch, flags.NoPull, false)
|
||||||
setup.SetNameserver(isNameserver)
|
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{
|
return &Orchestrator{
|
||||||
oramaHome: oramaHome,
|
oramaHome: oramaHome,
|
||||||
oramaDir: oramaDir,
|
oramaDir: oramaDir,
|
||||||
@ -192,7 +206,8 @@ func (o *Orchestrator) stopServices() error {
|
|||||||
"debros-ipfs-cluster.service", // Depends on IPFS
|
"debros-ipfs-cluster.service", // Depends on IPFS
|
||||||
"debros-ipfs.service", // Base IPFS
|
"debros-ipfs.service", // Base IPFS
|
||||||
"debros-olric.service", // Independent
|
"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 {
|
for _, svc := range services {
|
||||||
unitPath := filepath.Join("/etc/systemd/system", svc)
|
unitPath := filepath.Join("/etc/systemd/system", svc)
|
||||||
|
|||||||
@ -17,8 +17,23 @@ type IPFSClusterPeerInfo struct {
|
|||||||
Addrs []string
|
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
|
// 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) {
|
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.Print("\n" + strings.Repeat("=", 70) + "\n")
|
||||||
fmt.Printf("DRY RUN - No changes will be made\n")
|
fmt.Printf("DRY RUN - No changes will be made\n")
|
||||||
fmt.Print(strings.Repeat("=", 70) + "\n\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/Kubo 0.38.2\n")
|
||||||
fmt.Printf(" - IPFS Cluster (latest)\n")
|
fmt.Printf(" - IPFS Cluster (latest)\n")
|
||||||
fmt.Printf(" - Olric 0.7.0\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(" - DeBros binaries (built from %s branch)\n", branch)
|
||||||
|
|
||||||
fmt.Printf("\n🔐 Secrets that would be generated:\n")
|
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-ipfs-cluster.service\n")
|
||||||
fmt.Printf(" - debros-olric.service\n")
|
fmt.Printf(" - debros-olric.service\n")
|
||||||
fmt.Printf(" - debros-node.service (includes embedded gateway + RQLite)\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("\n🌐 Ports that would be used:\n")
|
||||||
fmt.Printf(" External (must be open in firewall):\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(" - 443 (HTTPS gateway)\n")
|
||||||
fmt.Printf(" - 4101 (IPFS swarm)\n")
|
fmt.Printf(" - 4101 (IPFS swarm)\n")
|
||||||
fmt.Printf(" - 7001 (RQLite Raft)\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(" Internal (localhost only):\n")
|
||||||
fmt.Printf(" - 4501 (IPFS API)\n")
|
fmt.Printf(" - 4501 (IPFS API)\n")
|
||||||
fmt.Printf(" - 5001 (RQLite HTTP)\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(" - 9094 (IPFS Cluster API)\n")
|
||||||
fmt.Printf(" - 3320/3322 (Olric)\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.Print("\n" + strings.Repeat("=", 70) + "\n")
|
||||||
fmt.Printf("To proceed with installation, run without --dry-run\n")
|
fmt.Printf("To proceed with installation, run without --dry-run\n")
|
||||||
fmt.Print(strings.Repeat("=", 70) + "\n\n")
|
fmt.Print(strings.Repeat("=", 70) + "\n\n")
|
||||||
|
|||||||
@ -160,6 +160,7 @@ func GetProductionServices() []string {
|
|||||||
"debros-ipfs-cluster",
|
"debros-ipfs-cluster",
|
||||||
"debros-ipfs",
|
"debros-ipfs",
|
||||||
"debros-anyone-client",
|
"debros-anyone-client",
|
||||||
|
"debros-anyone-relay",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter to only existing services by checking if unit file exists
|
// Filter to only existing services by checking if unit file exists
|
||||||
|
|||||||
385
pkg/environments/production/installers/anyone_relay.go
Normal file
385
pkg/environments/production/installers/anyone_relay.go
Normal 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
|
||||||
|
}
|
||||||
@ -8,8 +8,22 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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
|
// ProductionSetup orchestrates the entire production deployment
|
||||||
type ProductionSetup struct {
|
type ProductionSetup struct {
|
||||||
osInfo *OSInfo
|
osInfo *OSInfo
|
||||||
@ -21,6 +35,7 @@ type ProductionSetup struct {
|
|||||||
skipOptionalDeps bool
|
skipOptionalDeps bool
|
||||||
skipResourceChecks bool
|
skipResourceChecks bool
|
||||||
isNameserver bool // Whether this node is a nameserver (runs CoreDNS + Caddy)
|
isNameserver bool // Whether this node is a nameserver (runs CoreDNS + Caddy)
|
||||||
|
anyoneRelayConfig *AnyoneRelayConfig // Configuration for Anyone relay mode
|
||||||
privChecker *PrivilegeChecker
|
privChecker *PrivilegeChecker
|
||||||
osDetector *OSDetector
|
osDetector *OSDetector
|
||||||
archDetector *ArchitectureDetector
|
archDetector *ArchitectureDetector
|
||||||
@ -123,6 +138,16 @@ func (ps *ProductionSetup) IsNameserver() bool {
|
|||||||
return ps.isNameserver
|
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
|
// Phase1CheckPrerequisites performs initial environment validation
|
||||||
func (ps *ProductionSetup) Phase1CheckPrerequisites() error {
|
func (ps *ProductionSetup) Phase1CheckPrerequisites() error {
|
||||||
ps.logf("Phase 1: Checking prerequisites...")
|
ps.logf("Phase 1: Checking prerequisites...")
|
||||||
@ -275,9 +300,47 @@ func (ps *ProductionSetup) Phase2bInstallBinaries() error {
|
|||||||
ps.logf(" ⚠️ Olric install warning: %v", err)
|
ps.logf(" ⚠️ Olric install warning: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install anyone-client for SOCKS5 proxy
|
// Install Anyone (client or relay based on configuration)
|
||||||
if err := ps.binaryInstaller.InstallAnyoneClient(); err != nil {
|
if ps.IsAnyoneRelay() {
|
||||||
ps.logf(" ⚠️ anyone-client install warning: %v", err)
|
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)
|
// 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)")
|
ps.logf(" ✓ Node service created: debros-node.service (with embedded gateway)")
|
||||||
|
|
||||||
// Anyone Client service (SOCKS5 proxy)
|
// Anyone service (Client or Relay based on configuration)
|
||||||
anyoneUnit := ps.serviceGenerator.GenerateAnyoneClientService()
|
if ps.IsAnyoneRelay() {
|
||||||
if err := ps.serviceController.WriteServiceUnit("debros-anyone-client.service", anyoneUnit); err != nil {
|
anyoneUnit := ps.serviceGenerator.GenerateAnyoneRelayService()
|
||||||
return fmt.Errorf("failed to write Anyone Client service: %w", err)
|
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)
|
// CoreDNS and Caddy services (only for nameserver nodes)
|
||||||
if ps.isNameserver {
|
if ps.isNameserver {
|
||||||
@ -595,7 +666,14 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices(enableHTTPS bool) error {
|
|||||||
// Enable services (unified names - no bootstrap/node distinction)
|
// Enable services (unified names - no bootstrap/node distinction)
|
||||||
// Note: debros-gateway.service is no longer needed - each node has an embedded gateway
|
// 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
|
// 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
|
// Add CoreDNS and Caddy only for nameserver nodes
|
||||||
if ps.isNameserver {
|
if ps.isNameserver {
|
||||||
@ -617,15 +695,30 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices(enableHTTPS bool) error {
|
|||||||
// Start services in dependency order
|
// Start services in dependency order
|
||||||
ps.logf(" Starting services...")
|
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"}
|
infraServices := []string{"debros-ipfs.service", "debros-olric.service"}
|
||||||
|
|
||||||
// Check if port 9050 is already in use (e.g., another anyone-client or similar service)
|
// Add appropriate Anyone service based on mode
|
||||||
if ps.portChecker.IsPortInUse(9050) {
|
if ps.IsAnyoneRelay() {
|
||||||
ps.logf(" ℹ️ Port 9050 is already in use (anyone-client or similar service running)")
|
// For relay mode, check if ORPort is already in use
|
||||||
ps.logf(" ℹ️ Skipping debros-anyone-client startup - using existing service")
|
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 {
|
} 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 {
|
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/olric.log", ps.oramaDir)
|
||||||
ps.logf(" %s/logs/node.log", ps.oramaDir)
|
ps.logf(" %s/logs/node.log", ps.oramaDir)
|
||||||
ps.logf(" %s/logs/gateway.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:")
|
// Anyone mode-specific logs and commands
|
||||||
ps.logf(" systemctl start debros-ipfs debros-ipfs-cluster debros-olric debros-anyone-client debros-node")
|
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("\nVerify Installation:")
|
||||||
ps.logf(" curl http://localhost:6001/health")
|
ps.logf(" curl http://localhost:6001/health")
|
||||||
ps.logf(" curl http://localhost:5001/status")
|
ps.logf(" curl http://localhost:5001/status\n")
|
||||||
ps.logf(" # Anyone Client SOCKS5 proxy on localhost:9050\n")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -324,6 +324,36 @@ WantedBy=multi-user.target
|
|||||||
`, ssg.oramaHome, logFile, ssg.oramaDir)
|
`, 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
|
// GenerateCoreDNSService generates the CoreDNS systemd unit
|
||||||
func (ssg *SystemdServiceGenerator) GenerateCoreDNSService() string {
|
func (ssg *SystemdServiceGenerator) GenerateCoreDNSService() string {
|
||||||
return `[Unit]
|
return `[Unit]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user