mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
264 lines
7.3 KiB
Go
264 lines
7.3 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
// Package cli implements reticulum-go subcommands (daemon tools and pageserver).
|
|
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"quad4/reticulum-go/pkg/term"
|
|
)
|
|
|
|
// Command names accepted as subcommands or argv0 aliases.
|
|
const (
|
|
CmdDaemon = "daemon"
|
|
CmdStatus = "status"
|
|
CmdID = "id"
|
|
CmdProbe = "probe"
|
|
CmdPath = "path"
|
|
CmdCP = "cp"
|
|
CmdX = "x"
|
|
CmdSH = "sh"
|
|
CmdPageserver = "pageserver"
|
|
CmdDebug = "debug"
|
|
CmdSlow = "slow"
|
|
CmdSelfCheck = "self-check"
|
|
CmdSpeedtest = "speedtest"
|
|
CmdDump = "dump"
|
|
CmdSnapshot = "snapshot"
|
|
CmdZen = "zen"
|
|
)
|
|
|
|
// DaemonFunc starts the network daemon. Injected by cmd/reticulum-go to avoid
|
|
// an import cycle with the daemon package main.
|
|
type DaemonFunc func(args []string) int
|
|
|
|
// Options configures the root dispatcher.
|
|
type Options struct {
|
|
Stdout io.Writer
|
|
Stderr io.Writer
|
|
// Argv0 is os.Args[0] (used for symlink / rename aliases).
|
|
Argv0 string
|
|
// VersionLine is printed for -v / --version / version.
|
|
VersionLine string
|
|
// RunDaemon starts the daemon when selected.
|
|
RunDaemon DaemonFunc
|
|
}
|
|
|
|
// Main dispatches based on argv0 aliases or the first argument.
|
|
// Returns a process exit code.
|
|
func Main(args []string, opt Options) int {
|
|
if opt.Stdout == nil {
|
|
opt.Stdout = os.Stdout
|
|
}
|
|
if opt.Stderr == nil {
|
|
opt.Stderr = os.Stderr
|
|
}
|
|
|
|
cmd, rest, ok := resolveCommand(opt.Argv0, args)
|
|
if !ok {
|
|
if len(args) > 0 {
|
|
switch args[0] {
|
|
case "-h", "--help", "help":
|
|
printRootHelp(opt.Stdout)
|
|
return 0
|
|
case "-v", "--version", "version":
|
|
fmt.Fprintln(opt.Stdout, opt.VersionLine)
|
|
return 0
|
|
}
|
|
}
|
|
if len(args) == 0 || strings.HasPrefix(args[0], "-") {
|
|
// Bare binary, or daemon flags without an explicit "daemon" subcommand.
|
|
if opt.RunDaemon == nil {
|
|
fmt.Fprintln(opt.Stderr, "daemon entry not configured")
|
|
return 1
|
|
}
|
|
return opt.RunDaemon(args)
|
|
}
|
|
fmt.Fprintf(opt.Stderr, "%s\n\n", errMsg(opt.Stderr, fmt.Sprintf("unknown command %q", args[0])))
|
|
printRootHelp(opt.Stderr)
|
|
return 2
|
|
}
|
|
|
|
switch cmd {
|
|
case CmdDaemon:
|
|
if opt.RunDaemon == nil {
|
|
fmt.Fprintln(opt.Stderr, "daemon entry not configured")
|
|
return 1
|
|
}
|
|
return opt.RunDaemon(rest)
|
|
case CmdStatus:
|
|
return RunStatus(rest, opt)
|
|
case CmdID:
|
|
return RunID(rest, opt)
|
|
case CmdProbe:
|
|
return RunProbe(rest, opt)
|
|
case CmdPath:
|
|
return RunPath(rest, opt)
|
|
case CmdCP:
|
|
return RunCP(rest, opt)
|
|
case CmdX:
|
|
return RunX(rest, opt)
|
|
case CmdSH:
|
|
return RunSH(rest, opt)
|
|
case CmdPageserver:
|
|
return RunPageserver(rest, opt)
|
|
case CmdDebug:
|
|
return RunDebug(rest, opt)
|
|
case CmdSlow:
|
|
return RunSlow(rest, opt)
|
|
case CmdSelfCheck:
|
|
return RunSelfCheck(rest, opt)
|
|
case CmdSpeedtest:
|
|
return RunSpeedtest(rest, opt)
|
|
case CmdDump:
|
|
return RunDump(rest, opt)
|
|
case CmdSnapshot:
|
|
return RunSnapshot(rest, opt)
|
|
case CmdZen:
|
|
return RunZen(rest, opt)
|
|
default:
|
|
fmt.Fprintf(opt.Stderr, "unknown command %q\n\n", cmd)
|
|
printRootHelp(opt.Stderr)
|
|
return 2
|
|
}
|
|
}
|
|
|
|
func resolveCommand(argv0 string, args []string) (cmd string, rest []string, ok bool) {
|
|
base := strings.ToLower(filepath.Base(argv0))
|
|
base = strings.TrimSuffix(base, ".exe")
|
|
|
|
if alias := aliasFromArgv0(base); alias != "" {
|
|
return alias, args, true
|
|
}
|
|
|
|
if len(args) == 0 {
|
|
return "", nil, false
|
|
}
|
|
|
|
switch args[0] {
|
|
case CmdDaemon, CmdStatus, CmdID, CmdProbe, CmdPath, CmdCP, CmdX, CmdSH, CmdPageserver, CmdDebug, CmdSlow, CmdSelfCheck, CmdSpeedtest, CmdDump, CmdSnapshot, CmdZen:
|
|
return args[0], args[1:], true
|
|
case "selfcheck", "rgoselfcheck":
|
|
return CmdSelfCheck, args[1:], true
|
|
case "rgostatus":
|
|
return CmdStatus, args[1:], true
|
|
case "rgoid":
|
|
return CmdID, args[1:], true
|
|
case "rgoprobe":
|
|
return CmdProbe, args[1:], true
|
|
case "rgopath":
|
|
return CmdPath, args[1:], true
|
|
case "rgocp":
|
|
return CmdCP, args[1:], true
|
|
case "rgox", "rnx":
|
|
return CmdX, args[1:], true
|
|
case "rgosh":
|
|
return CmdSH, args[1:], true
|
|
case "rgoslow":
|
|
return CmdSlow, args[1:], true
|
|
case "rgospeed":
|
|
return CmdSpeedtest, args[1:], true
|
|
case "rgodump":
|
|
return CmdDump, args[1:], true
|
|
case "rgosnap":
|
|
return CmdSnapshot, args[1:], true
|
|
case "rgozen", "reticulum-go-zen":
|
|
return CmdZen, args[1:], true
|
|
default:
|
|
return "", nil, false
|
|
}
|
|
}
|
|
|
|
func aliasFromArgv0(base string) string {
|
|
switch base {
|
|
case "rgostatus", "reticulum-go-status":
|
|
return CmdStatus
|
|
case "rgoid", "reticulum-go-id":
|
|
return CmdID
|
|
case "rgoprobe", "reticulum-go-probe":
|
|
return CmdProbe
|
|
case "rgopath", "reticulum-go-path":
|
|
return CmdPath
|
|
case "rgocp", "reticulum-go-cp":
|
|
return CmdCP
|
|
case "rgox", "rnx", "reticulum-go-x":
|
|
return CmdX
|
|
case "rgosh", "reticulum-go-sh":
|
|
return CmdSH
|
|
case "rgopageserver", "reticulum-go-pageserver":
|
|
return CmdPageserver
|
|
case "rgoslow", "reticulum-go-slow":
|
|
return CmdSlow
|
|
case "rgoselfcheck", "reticulum-go-self-check":
|
|
return CmdSelfCheck
|
|
case "rgospeed", "reticulum-go-speedtest":
|
|
return CmdSpeedtest
|
|
case "rgodump", "reticulum-go-dump":
|
|
return CmdDump
|
|
case "rgosnap", "reticulum-go-snapshot":
|
|
return CmdSnapshot
|
|
case "rgozen", "reticulum-go-zen":
|
|
return CmdZen
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func cliIO(opt []Options) (stdout, stderr io.Writer) {
|
|
stdout, stderr = os.Stdout, os.Stderr
|
|
if len(opt) == 0 {
|
|
return stdout, stderr
|
|
}
|
|
o := opt[0]
|
|
if o.Stdout != nil {
|
|
stdout = o.Stdout
|
|
}
|
|
if o.Stderr != nil {
|
|
stderr = o.Stderr
|
|
}
|
|
return stdout, stderr
|
|
}
|
|
|
|
func okMsg(w io.Writer, s string) string { return term.GreenW(w, s) }
|
|
func errMsg(w io.Writer, s string) string { return term.RedW(w, s) }
|
|
func warnMsg(w io.Writer, s string) string { return term.YellowW(w, s) }
|
|
func infoMsg(w io.Writer, s string) string { return term.CyanW(w, s) }
|
|
|
|
func printRootHelp(w io.Writer) {
|
|
fmt.Fprintf(w, `reticulum-go - Reticulum network stack (Go)
|
|
|
|
Usage:
|
|
reticulum-go run the network daemon
|
|
reticulum-go daemon [flags] run the network daemon
|
|
reticulum-go status [flags] interface and transport status (RPC)
|
|
reticulum-go id [flags] identities, hashes, sign and encrypt
|
|
reticulum-go probe [flags] <name> <hash>
|
|
reticulum-go path [flags] path table, drop, blackhole
|
|
reticulum-go slow [flags] find slow interfaces, paths, transfers
|
|
reticulum-go speedtest [flags] loopback link throughput smoke
|
|
reticulum-go dump [flags] decode RNS packets from hex or pcap
|
|
reticulum-go snapshot [flags] path table, links, and health JSON (RPC)
|
|
reticulum-go cp [flags] file transfer over links
|
|
reticulum-go x [flags] remote command execution (rnx)
|
|
reticulum-go sh [flags] interactive remote shell (rgosh)
|
|
reticulum-go pageserver [flags] NomadNet-style page and file server
|
|
reticulum-go debug [flags] effective config, rate table, RPC dump
|
|
reticulum-go self-check [flags] host OS preflight checklist
|
|
reticulum-go zen [flags] [packages] scan for path/link footguns (go fix style)
|
|
|
|
Global:
|
|
-h, --help print this help
|
|
-v, --version print version
|
|
|
|
Legacy tool names (rgostatus, rgoid, rgoprobe, rgopath, rgocp, rgox, rnx, rgosh, rgoslow, rgospeed, rgodump, rgosnap, rgozen) work as
|
|
subcommands or when the binary is installed under those names (symlinks).
|
|
|
|
Configuration defaults to ~/.reticulum-go/config.
|
|
`)
|
|
}
|