forked from retoor/devplacepy
Zo doen wijdat in oost
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
var Version = "dev"
|
||||
|
||||
const (
|
||||
exitOK = 0
|
||||
exitError = 1
|
||||
exitUsage = 2
|
||||
exitFatal = 3
|
||||
usageText = `usage: devplace-node <command> [flags]
|
||||
|
||||
commands:
|
||||
login store the server and device secret (--server URL --secret dpn_...)
|
||||
status show the stored configuration, today's usage and whether the server accepts this device
|
||||
run connect and serve jobs in the foreground
|
||||
version print the version
|
||||
|
||||
flags for every command: --config PATH (default: <user config dir>/devplace-node/config.json)
|
||||
`
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Exit(execute(os.Args[1:], os.Stdout, os.Stderr))
|
||||
}
|
||||
|
||||
func execute(args []string, stdout, stderr io.Writer) int {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprint(stderr, usageText)
|
||||
return exitUsage
|
||||
}
|
||||
command, rest := args[0], args[1:]
|
||||
switch command {
|
||||
case "version":
|
||||
fmt.Fprintln(stdout, "devplace-node", Version)
|
||||
return exitOK
|
||||
case "login":
|
||||
return runLogin(rest, stdout, stderr)
|
||||
case "status":
|
||||
return runStatus(rest, stdout, stderr)
|
||||
case "run":
|
||||
return runRun(rest, stderr)
|
||||
case "help", "-h", "--help":
|
||||
fmt.Fprint(stdout, usageText)
|
||||
return exitOK
|
||||
}
|
||||
fmt.Fprintf(stderr, "unknown command %q\n%s", command, usageText)
|
||||
return exitUsage
|
||||
}
|
||||
|
||||
func resolveConfigPath(flagValue string) (string, error) {
|
||||
if flagValue != "" {
|
||||
return flagValue, nil
|
||||
}
|
||||
return DefaultConfigPath()
|
||||
}
|
||||
|
||||
func newFlags(name string, stderr io.Writer) (*flag.FlagSet, *string) {
|
||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
set.SetOutput(stderr)
|
||||
path := set.String("config", "", "config file path")
|
||||
return set, path
|
||||
}
|
||||
|
||||
func runLogin(args []string, stdout, stderr io.Writer) int {
|
||||
set, pathFlag := newFlags("login", stderr)
|
||||
server := set.String("server", "", "server url, for example https://devplace.net")
|
||||
secret := set.String("secret", "", "device secret (or set DEVPLACE_NODE_SECRET)")
|
||||
bandwidth := set.Int("bandwidth-mb", DefaultBandwidthMB, "daily bandwidth cap in MB")
|
||||
concurrent := set.Int("concurrency", DefaultMaxConcurrent, "maximum parallel jobs")
|
||||
tunnels := set.Int("tunnels", DefaultMaxTunnels, "maximum parallel tunnels")
|
||||
from := set.String("from", "", "active hours start HH:MM")
|
||||
to := set.String("to", "", "active hours end HH:MM")
|
||||
zone := set.String("timezone", "", "IANA timezone for the active hours (default: system)")
|
||||
skip := set.Bool("no-verify", false, "store without asking the server to accept the secret")
|
||||
if err := set.Parse(args); err != nil {
|
||||
return exitUsage
|
||||
}
|
||||
if *secret == "" {
|
||||
*secret = os.Getenv("DEVPLACE_NODE_SECRET")
|
||||
}
|
||||
path, err := resolveConfigPath(*pathFlag)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return exitError
|
||||
}
|
||||
config := Config{
|
||||
Server: *server, Secret: *secret, BandwidthMBDay: *bandwidth, MaxConcurrent: *concurrent,
|
||||
MaxTunnels: *tunnels, ActiveFrom: *from, ActiveTo: *to, Timezone: *zone,
|
||||
}
|
||||
if err := config.Validate(); err != nil {
|
||||
fmt.Fprintln(stderr, "login refused:", err)
|
||||
return exitUsage
|
||||
}
|
||||
if !*skip {
|
||||
if code := verifyDevice(config, stderr); code != exitOK {
|
||||
return code
|
||||
}
|
||||
}
|
||||
if err := SaveConfig(path, config); err != nil {
|
||||
fmt.Fprintln(stderr, "config could not be written:", err)
|
||||
return exitError
|
||||
}
|
||||
fmt.Fprintf(stdout, "stored %s (mode 0600)\n", path)
|
||||
return exitOK
|
||||
}
|
||||
|
||||
func verifyDevice(config Config, stderr io.Writer) int {
|
||||
session := NewSession(config, NewPolicy(), nil, nil, nil, NewLogger(stderr, false))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(TicketTimeoutSeconds+5)*time.Second)
|
||||
defer cancel()
|
||||
_, result := session.fetchTicket(ctx)
|
||||
if result.fatal != "" {
|
||||
fmt.Fprintln(stderr, "server refused this device:", result.fatal)
|
||||
return exitFatal
|
||||
}
|
||||
if result.reason != "" {
|
||||
fmt.Fprintln(stderr, "server not verified:", result.reason, "(use --no-verify to store anyway)")
|
||||
return exitError
|
||||
}
|
||||
return exitOK
|
||||
}
|
||||
|
||||
func runStatus(args []string, stdout, stderr io.Writer) int {
|
||||
set, pathFlag := newFlags("status", stderr)
|
||||
if err := set.Parse(args); err != nil {
|
||||
return exitUsage
|
||||
}
|
||||
path, err := resolveConfigPath(*pathFlag)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return exitError
|
||||
}
|
||||
config, err := LoadConfig(path)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return exitError
|
||||
}
|
||||
location, _ := config.Location()
|
||||
used := ReadUsage(filepath.Join(filepath.Dir(path), UsageFileName), location, time.Now())
|
||||
fmt.Fprintf(stdout, "version: %s\nconfig: %s\nserver: %s\nsecret: stored\n", Version, path, config.Server)
|
||||
fmt.Fprintf(stdout, "bandwidth: %d MB/day (used today: %.1f MB)\n", config.BandwidthMBDay, float64(used)/mebibyte)
|
||||
fmt.Fprintf(stdout, "concurrency: %d jobs, %d tunnels\n", config.MaxConcurrent, config.MaxTunnels)
|
||||
hours := "always"
|
||||
if config.ActiveFrom != "" {
|
||||
hours = config.ActiveFrom + "-" + config.ActiveTo
|
||||
}
|
||||
fmt.Fprintf(stdout, "active hours: %s (%s)\n", hours, location)
|
||||
if code := verifyDevice(config, stderr); code != exitOK {
|
||||
fmt.Fprintln(stdout, "server: not accepting this device")
|
||||
return code
|
||||
}
|
||||
fmt.Fprintln(stdout, "server: accepts this device")
|
||||
return exitOK
|
||||
}
|
||||
|
||||
func runRun(args []string, stderr io.Writer) int {
|
||||
set, pathFlag := newFlags("run", stderr)
|
||||
verbose := set.Bool("v", false, "verbose logging")
|
||||
if err := set.Parse(args); err != nil {
|
||||
return exitUsage
|
||||
}
|
||||
path, err := resolveConfigPath(*pathFlag)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return exitError
|
||||
}
|
||||
config, err := LoadConfig(path)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return exitError
|
||||
}
|
||||
logger := NewLogger(stderr, *verbose)
|
||||
limiter, err := NewLimiter(config, filepath.Join(filepath.Dir(path), UsageFileName), time.Now)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return exitError
|
||||
}
|
||||
policy := NewPolicy()
|
||||
stats := NewStats(time.Now())
|
||||
session := NewSession(config, policy, limiter, NewFetcher(policy), stats, logger)
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
logger.Printf("started version %s, server %s", Version, config.Server)
|
||||
err = session.Run(ctx)
|
||||
limiter.Flush()
|
||||
logger.Printf("%s", stats.Line())
|
||||
var fatal *FatalError
|
||||
if errors.As(err, &fatal) {
|
||||
logger.Printf("%s", fatal.Error())
|
||||
return exitFatal
|
||||
}
|
||||
if err != nil {
|
||||
logger.Printf("stopped: %v", err)
|
||||
return exitError
|
||||
}
|
||||
return exitOK
|
||||
}
|
||||
Reference in New Issue
Block a user