mirror of
https://github.com/DeBrosOfficial/network.git
synced 2026-01-30 01:53:02 +00:00
- Updated the API gateway documentation to reflect changes in architecture and functionality, emphasizing its role as a multi-functional entry point for decentralized services. - Refactored CLI commands to utilize utility functions for better code organization and maintainability. - Introduced new utility functions for handling peer normalization, service management, and port validation, enhancing the overall CLI experience. - Added a new production installation script to streamline the setup process for users, including detailed dry-run summaries for better visibility. - Enhanced validation mechanisms for configuration files and swarm keys, ensuring robust error handling and user feedback during setup.
218 lines
5.9 KiB
Go
218 lines
5.9 KiB
Go
package utils
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
var ErrServiceNotFound = errors.New("service not found")
|
|
|
|
// PortSpec defines a port and its name for checking availability
|
|
type PortSpec struct {
|
|
Name string
|
|
Port int
|
|
}
|
|
|
|
var ServicePorts = map[string][]PortSpec{
|
|
"debros-gateway": {
|
|
{Name: "Gateway API", Port: 6001},
|
|
},
|
|
"debros-olric": {
|
|
{Name: "Olric HTTP", Port: 3320},
|
|
{Name: "Olric Memberlist", Port: 3322},
|
|
},
|
|
"debros-node": {
|
|
{Name: "RQLite HTTP", Port: 5001},
|
|
{Name: "RQLite Raft", Port: 7001},
|
|
},
|
|
"debros-ipfs": {
|
|
{Name: "IPFS API", Port: 4501},
|
|
{Name: "IPFS Gateway", Port: 8080},
|
|
{Name: "IPFS Swarm", Port: 4101},
|
|
},
|
|
"debros-ipfs-cluster": {
|
|
{Name: "IPFS Cluster API", Port: 9094},
|
|
},
|
|
}
|
|
|
|
// DefaultPorts is used for fresh installs/upgrades before unit files exist.
|
|
func DefaultPorts() []PortSpec {
|
|
return []PortSpec{
|
|
{Name: "IPFS Swarm", Port: 4001},
|
|
{Name: "IPFS API", Port: 4501},
|
|
{Name: "IPFS Gateway", Port: 8080},
|
|
{Name: "Gateway API", Port: 6001},
|
|
{Name: "RQLite HTTP", Port: 5001},
|
|
{Name: "RQLite Raft", Port: 7001},
|
|
{Name: "IPFS Cluster API", Port: 9094},
|
|
{Name: "Olric HTTP", Port: 3320},
|
|
{Name: "Olric Memberlist", Port: 3322},
|
|
}
|
|
}
|
|
|
|
// ResolveServiceName resolves service aliases to actual systemd service names
|
|
func ResolveServiceName(alias string) ([]string, error) {
|
|
// Service alias mapping (unified - no bootstrap/node distinction)
|
|
aliases := map[string][]string{
|
|
"node": {"debros-node"},
|
|
"ipfs": {"debros-ipfs"},
|
|
"cluster": {"debros-ipfs-cluster"},
|
|
"ipfs-cluster": {"debros-ipfs-cluster"},
|
|
"gateway": {"debros-gateway"},
|
|
"olric": {"debros-olric"},
|
|
"rqlite": {"debros-node"}, // RQLite logs are in node logs
|
|
}
|
|
|
|
// Check if it's an alias
|
|
if serviceNames, ok := aliases[strings.ToLower(alias)]; ok {
|
|
// Filter to only existing services
|
|
var existing []string
|
|
for _, svc := range serviceNames {
|
|
unitPath := filepath.Join("/etc/systemd/system", svc+".service")
|
|
if _, err := os.Stat(unitPath); err == nil {
|
|
existing = append(existing, svc)
|
|
}
|
|
}
|
|
if len(existing) == 0 {
|
|
return nil, fmt.Errorf("no services found for alias %q", alias)
|
|
}
|
|
return existing, nil
|
|
}
|
|
|
|
// Check if it's already a full service name
|
|
unitPath := filepath.Join("/etc/systemd/system", alias+".service")
|
|
if _, err := os.Stat(unitPath); err == nil {
|
|
return []string{alias}, nil
|
|
}
|
|
|
|
// Try without .service suffix
|
|
if !strings.HasSuffix(alias, ".service") {
|
|
unitPath = filepath.Join("/etc/systemd/system", alias+".service")
|
|
if _, err := os.Stat(unitPath); err == nil {
|
|
return []string{alias}, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("service %q not found. Use: node, ipfs, cluster, gateway, olric, or full service name", alias)
|
|
}
|
|
|
|
// IsServiceActive checks if a systemd service is currently active (running)
|
|
func IsServiceActive(service string) (bool, error) {
|
|
cmd := exec.Command("systemctl", "is-active", "--quiet", service)
|
|
if err := cmd.Run(); err != nil {
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
switch exitErr.ExitCode() {
|
|
case 3:
|
|
return false, nil
|
|
case 4:
|
|
return false, ErrServiceNotFound
|
|
}
|
|
}
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// IsServiceEnabled checks if a systemd service is enabled to start on boot
|
|
func IsServiceEnabled(service string) (bool, error) {
|
|
cmd := exec.Command("systemctl", "is-enabled", "--quiet", service)
|
|
if err := cmd.Run(); err != nil {
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
switch exitErr.ExitCode() {
|
|
case 1:
|
|
return false, nil // Service is disabled
|
|
case 4:
|
|
return false, ErrServiceNotFound
|
|
}
|
|
}
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// IsServiceMasked checks if a systemd service is masked
|
|
func IsServiceMasked(service string) (bool, error) {
|
|
cmd := exec.Command("systemctl", "is-enabled", service)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
outputStr := string(output)
|
|
if strings.Contains(outputStr, "masked") {
|
|
return true, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// GetProductionServices returns a list of all DeBros production service names that exist
|
|
func GetProductionServices() []string {
|
|
// Unified service names (no bootstrap/node distinction)
|
|
allServices := []string{
|
|
"debros-gateway",
|
|
"debros-node",
|
|
"debros-olric",
|
|
"debros-ipfs-cluster",
|
|
"debros-ipfs",
|
|
"debros-anyone-client",
|
|
}
|
|
|
|
// Filter to only existing services by checking if unit file exists
|
|
var existing []string
|
|
for _, svc := range allServices {
|
|
unitPath := filepath.Join("/etc/systemd/system", svc+".service")
|
|
if _, err := os.Stat(unitPath); err == nil {
|
|
existing = append(existing, svc)
|
|
}
|
|
}
|
|
|
|
return existing
|
|
}
|
|
|
|
// CollectPortsForServices returns a list of ports used by the specified services
|
|
func CollectPortsForServices(services []string, skipActive bool) ([]PortSpec, error) {
|
|
seen := make(map[int]PortSpec)
|
|
for _, svc := range services {
|
|
if skipActive {
|
|
active, err := IsServiceActive(svc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to check %s: %w", svc, err)
|
|
}
|
|
if active {
|
|
continue
|
|
}
|
|
}
|
|
for _, spec := range ServicePorts[svc] {
|
|
if _, ok := seen[spec.Port]; !ok {
|
|
seen[spec.Port] = spec
|
|
}
|
|
}
|
|
}
|
|
ports := make([]PortSpec, 0, len(seen))
|
|
for _, spec := range seen {
|
|
ports = append(ports, spec)
|
|
}
|
|
return ports, nil
|
|
}
|
|
|
|
// EnsurePortsAvailable checks if the specified ports are available
|
|
func EnsurePortsAvailable(action string, ports []PortSpec) error {
|
|
for _, spec := range ports {
|
|
ln, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", spec.Port))
|
|
if err != nil {
|
|
if errors.Is(err, syscall.EADDRINUSE) || strings.Contains(err.Error(), "address already in use") {
|
|
return fmt.Errorf("%s cannot continue: %s (port %d) is already in use", action, spec.Name, spec.Port)
|
|
}
|
|
return fmt.Errorf("%s cannot continue: failed to inspect %s (port %d): %w", action, spec.Name, spec.Port, err)
|
|
}
|
|
_ = ln.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|