mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
180 lines
5.1 KiB
Go
180 lines
5.1 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package cli
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"quad4/reticulum-go/pkg/common"
|
|
"quad4/reticulum-go/pkg/node"
|
|
"quad4/reticulum-go/pkg/rnsutil"
|
|
)
|
|
|
|
func RunStatus(args []string, opt ...Options) int {
|
|
stdout, stderr := cliIO(opt)
|
|
fs := flag.NewFlagSet("rgostatus", flag.ContinueOnError)
|
|
fs.SetOutput(stderr)
|
|
configDir := fs.String("config", "", "path to config directory")
|
|
jsonOut := fs.Bool("json", false, "emit JSON")
|
|
all := fs.Bool("a", false, "include all interfaces")
|
|
nameFilter := fs.String("n", "", "filter interfaces by name substring")
|
|
links := fs.Bool("l", false, "include link count")
|
|
sortBy := fs.String("s", "", "sort by rate|rx|tx|rxs|txs|traffic|announce|arx|atx|prx|ptx|held")
|
|
sortAsc := fs.Bool("r", false, "sort ascending (default descending)")
|
|
timeout := fs.Duration("timeout", 10*time.Second, "RPC timeout")
|
|
remoteHex := fs.String("R", "", "transport identity hash of remote instance")
|
|
mgmtIdent := fs.String("i", "", "identity file for remote management")
|
|
remoteTimeoutSec := fs.Float64("W", 0, "timeout for remote queries in seconds (0 = adaptive from interface bitrate)")
|
|
quiet := fs.Bool("q", false, "quiet: suppress stderr hints")
|
|
if err := fs.Parse(args); err != nil {
|
|
return 2
|
|
}
|
|
|
|
cfg, err := rnsutil.LoadConfigDir(*configDir)
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "config: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
statusOpts := rnsutil.StatusOptions{
|
|
NameFilter: *nameFilter,
|
|
SortBy: *sortBy,
|
|
SortAsc: *sortAsc,
|
|
ShowAll: *all,
|
|
}
|
|
|
|
if *remoteHex != "" {
|
|
return runStatusRemote(cfg, statusRemoteOpts{
|
|
jsonOut: *jsonOut,
|
|
links: *links,
|
|
remoteHex: *remoteHex,
|
|
identPath: *mgmtIdent,
|
|
timeoutSec: *remoteTimeoutSec,
|
|
statusOpts: statusOpts,
|
|
stdout: stdout,
|
|
stderr: stderr,
|
|
})
|
|
}
|
|
|
|
client, err := rnsutil.DialRPC(cfg, nil)
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "%s: %v\n", errMsg(stderr, "rpc"), err)
|
|
if !*quiet {
|
|
fmt.Fprintln(stderr, warnMsg(stderr, "hint: point -config at the daemon config dir (e.g. ~/.reticulum for rnsd)"))
|
|
fmt.Fprintln(stderr, warnMsg(stderr, "hint: align shared_instance_type (unix on Linux by default, or tcp) and instance_name / ports"))
|
|
}
|
|
return 1
|
|
}
|
|
client.SetTimeout(*timeout)
|
|
|
|
stats, err := client.GetInterfaceStats()
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "%s (%s): %v\n", errMsg(stderr, "interface stats"), client.Addr(), err)
|
|
if !*quiet {
|
|
fmt.Fprintln(stderr, warnMsg(stderr, "hint: start rnsd or reticulum-go first, then retry"))
|
|
fmt.Fprintln(stderr, warnMsg(stderr, "hint: for Python rnsd use: rgostatus -config ~/.reticulum"))
|
|
}
|
|
return 1
|
|
}
|
|
rnsutil.SortInterfaceStats(&stats, *sortBy, *sortAsc)
|
|
|
|
var linkCount *int
|
|
if *links {
|
|
n, err := client.GetLinkCount()
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "link count: %v\n", err)
|
|
return 1
|
|
}
|
|
linkCount = &n
|
|
}
|
|
|
|
if *jsonOut {
|
|
if err := rnsutil.WriteStatusJSON(stdout, stats); err != nil {
|
|
fmt.Fprintf(stderr, "json: %v\n", err)
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
if err := rnsutil.WriteStatusHuman(stdout, stats, linkCount, statusOpts); err != nil {
|
|
fmt.Fprintf(stderr, "write: %v\n", err)
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
type statusRemoteOpts struct {
|
|
jsonOut bool
|
|
links bool
|
|
remoteHex string
|
|
identPath string
|
|
timeoutSec float64
|
|
statusOpts rnsutil.StatusOptions
|
|
stdout io.Writer
|
|
stderr io.Writer
|
|
}
|
|
|
|
func runStatusRemote(cfg *common.ReticulumConfig, opts statusRemoteOpts) int {
|
|
stdout, stderr := opts.stdout, opts.stderr
|
|
tid, err := rnsutil.ParseDestHash(opts.remoteHex)
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "%v\n", err)
|
|
return 20
|
|
}
|
|
auth, err := rnsutil.LoadManagementIdentity(opts.identPath)
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "%v\n", err)
|
|
return 20
|
|
}
|
|
|
|
n, err := node.New(cfg)
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "node: %v\n", err)
|
|
return 1
|
|
}
|
|
if err := n.Start(); err != nil {
|
|
fmt.Fprintf(stderr, "start: %v\n", err)
|
|
return 1
|
|
}
|
|
defer n.Stop()
|
|
|
|
timeout := time.Duration(opts.timeoutSec * float64(time.Second))
|
|
ctx, cancel := rnsutil.CLIWaitContext(timeout)
|
|
defer cancel()
|
|
|
|
fmt.Fprintln(stdout, infoMsg(stdout, "Establishing link with remote transport instance..."))
|
|
l, err := rnsutil.EstablishRemoteManagementLink(ctx, n.Transport(), tid, auth)
|
|
if err != nil {
|
|
fmt.Fprintln(stdout, errMsg(stdout, err.Error()))
|
|
return 12
|
|
}
|
|
defer l.Teardown()
|
|
|
|
fmt.Fprintln(stdout, infoMsg(stdout, "Sending request..."))
|
|
raw, err := rnsutil.RemoteStatusRequest(ctx, l, opts.links)
|
|
if err != nil {
|
|
fmt.Fprintln(stdout, errMsg(stdout, "The remote status request failed. Likely authentication failure."))
|
|
return 10
|
|
}
|
|
stats, linkCount, err := rnsutil.InterfaceStatsFromRemoteStatus(raw)
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "%v\n", err)
|
|
return 1
|
|
}
|
|
rnsutil.SortInterfaceStats(&stats, opts.statusOpts.SortBy, opts.statusOpts.SortAsc)
|
|
if opts.jsonOut {
|
|
if err := rnsutil.WriteStatusJSON(stdout, stats); err != nil {
|
|
fmt.Fprintf(stderr, "json: %v\n", err)
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
if err := rnsutil.WriteStatusHuman(stdout, stats, linkCount, opts.statusOpts); err != nil {
|
|
fmt.Fprintf(stderr, "write: %v\n", err)
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|