mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
161 lines
5.1 KiB
Go
161 lines
5.1 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package cli
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"syscall"
|
|
"time"
|
|
|
|
"quad4/reticulum-go/pkg/common"
|
|
"quad4/reticulum-go/pkg/debug"
|
|
"quad4/reticulum-go/pkg/pageserver"
|
|
"quad4/reticulum-go/pkg/reticulumconfig"
|
|
)
|
|
|
|
// RunPageserver serves NomadNet-style pages and files over Reticulum.
|
|
func RunPageserver(args []string, opt ...Options) int {
|
|
_, stderr := cliIO(opt)
|
|
fs := flag.NewFlagSet("pageserver", flag.ContinueOnError)
|
|
fs.SetOutput(stderr)
|
|
|
|
var (
|
|
configPath string
|
|
pagesDir string
|
|
filesDir string
|
|
nodeName string
|
|
announceIntervalMin int
|
|
pageRefreshSec int
|
|
fileRefreshSec int
|
|
logLevel int
|
|
identityOverride string
|
|
disablePageStats bool
|
|
)
|
|
|
|
fs.StringVar(&configPath, "config", "", "Reticulum config file (default ~/.reticulum-go/config)")
|
|
fs.StringVar(&configPath, "c", "", "same as -config")
|
|
fs.StringVar(&pagesDir, "pages-dir", "pages", "pages directory for /page/")
|
|
fs.StringVar(&pagesDir, "p", "pages", "same as -pages-dir")
|
|
fs.StringVar(&filesDir, "files-dir", "files", "files directory for /file/")
|
|
fs.StringVar(&filesDir, "f", "files", "same as -files-dir")
|
|
fs.StringVar(&nodeName, "node-name", pageserver.AppName, "node display name in announces")
|
|
fs.StringVar(&nodeName, "n", pageserver.AppName, "same as -node-name")
|
|
fs.IntVar(&announceIntervalMin, "announce-interval", 360, "periodic announces every N minutes (0 = once)")
|
|
fs.IntVar(&announceIntervalMin, "a", 360, "same as -announce-interval")
|
|
fs.IntVar(&pageRefreshSec, "page-refresh", 0, "rescan pages every N seconds (0 = startup only)")
|
|
fs.IntVar(&pageRefreshSec, "pages-refresh-interval", 0, "same as -page-refresh")
|
|
fs.IntVar(&fileRefreshSec, "file-refresh", 0, "rescan files every N seconds (0 = startup only)")
|
|
fs.IntVar(&fileRefreshSec, "files-refresh-interval", 0, "same as -file-refresh")
|
|
fs.IntVar(&logLevel, "log-level", -1, "log verbosity 1-7 (-1 = from config)")
|
|
fs.StringVar(&identityOverride, "identity", "", "identity file path")
|
|
fs.StringVar(&identityOverride, "identity-path", "", "same as -identity")
|
|
fs.BoolVar(&disablePageStats, "no-page-stats", false, "disable built-in page view stats")
|
|
|
|
if err := fs.Parse(args); err != nil {
|
|
return 2
|
|
}
|
|
|
|
cfg, err := loadOrInitConfig(configPath)
|
|
if err != nil {
|
|
applyPageserverLogLevel(fs, logLevel, nil)
|
|
debug.Init()
|
|
debug.GetLogger().Error("Failed to load configuration", "error", err)
|
|
return 1
|
|
}
|
|
applyPageserverLogLevel(fs, logLevel, cfg)
|
|
debug.Init()
|
|
|
|
opts := pageserver.Options{
|
|
PagesDir: pagesDir,
|
|
FilesDir: filesDir,
|
|
PageRefreshInterval: time.Duration(pageRefreshSec) * time.Second,
|
|
FileRefreshInterval: time.Duration(fileRefreshSec) * time.Second,
|
|
AnnounceIntervalMinutes: announceIntervalMin,
|
|
IdentityFileOverride: identityOverride,
|
|
NodeDisplayName: pageserver.ClampDisplayName(nodeName, pageserver.AppName),
|
|
DisablePageStats: disablePageStats,
|
|
}
|
|
|
|
r, err := pageserver.NewReticulum(cfg, opts)
|
|
if err != nil {
|
|
debug.GetLogger().Error("Failed to create Reticulum instance", "error", err)
|
|
return 1
|
|
}
|
|
|
|
go r.MonitorInterfaces()
|
|
|
|
handler := pageserver.NewAnnounceHandler(r, []string{"*"})
|
|
r.Transport().RegisterAnnounceHandler(handler)
|
|
|
|
if err := r.Start(); err != nil {
|
|
debug.GetLogger().Error("Failed to start Reticulum", "error", err)
|
|
return 1
|
|
}
|
|
|
|
pageserver.PrintStartupSummary(r)
|
|
|
|
sigChan := make(chan os.Signal, 1)
|
|
sigs := []os.Signal{os.Interrupt}
|
|
if runtime.GOOS != "windows" {
|
|
sigs = append(sigs, syscall.SIGTERM)
|
|
}
|
|
signal.Notify(sigChan, sigs...)
|
|
<-sigChan
|
|
|
|
debug.Log(debug.DebugCritical, "Shutting down...")
|
|
if err := r.Stop(); err != nil {
|
|
debug.Log(debug.DebugCritical, "Error during shutdown", "error", err)
|
|
}
|
|
debug.Log(debug.DebugCritical, "Goodbye!")
|
|
return 0
|
|
}
|
|
|
|
func loadOrInitConfig(override string) (*common.ReticulumConfig, error) {
|
|
path := override
|
|
if path == "" {
|
|
p, err := reticulumconfig.GetConfigPath()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("resolve default config path: %w", err)
|
|
}
|
|
path = p
|
|
}
|
|
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
if err := reticulumconfig.CreateDefaultConfig(path); err != nil {
|
|
return nil, fmt.Errorf("create default config %q: %w", path, err)
|
|
}
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("stat config %q: %w", path, err)
|
|
}
|
|
|
|
return reticulumconfig.LoadConfig(path)
|
|
}
|
|
|
|
func applyPageserverLogLevel(fs *flag.FlagSet, pageserverLogLevel int, cfg *common.ReticulumConfig) {
|
|
logLevelSet := false
|
|
fs.Visit(func(f *flag.Flag) {
|
|
if f.Name == "log-level" {
|
|
logLevelSet = true
|
|
}
|
|
})
|
|
switch {
|
|
case logLevelSet && pageserverLogLevel >= debug.DebugCritical:
|
|
debug.SetDebugLevel(pageserverLogLevel)
|
|
case logLevelSet && pageserverLogLevel != -1 && pageserverLogLevel < debug.DebugCritical:
|
|
debug.SetDebugLevel(debug.DebugCritical)
|
|
default:
|
|
if cfg != nil {
|
|
l := cfg.LogLevel
|
|
if l >= debug.DebugCritical && l <= debug.DebugAll {
|
|
debug.SetDebugLevel(l)
|
|
return
|
|
}
|
|
}
|
|
debug.SetDebugLevel(debug.DebugCritical)
|
|
}
|
|
}
|