// Bamboo is the site agent: run it inside a private network to expose services from there. package main import ( "context" "flag" "log" "os" "os/signal" "path/filepath" "strconv" "strings" "git.veillax.com/Veillax/bamboo" "git.veillax.com/Veillax/bamboo/discover" "git.veillax.com/Veillax/bamboo/sshd" "git.veillax.com/Veillax/reef/dotenv" "git.veillax.com/Veillax/reef/selfupdate" ) // version is set at build time (deploy/build.sh). var version = "dev" func env(k, def string) string { if v := os.Getenv(k); v != "" { return v } return def } func envFloat(k string, def float64) float64 { if f, err := strconv.ParseFloat(os.Getenv(k), 64); err == nil && f > 0 { return f } return def } func main() { // secrets (tokens, DNS-provider credentials) can live in a .env file instead of the command line envFile := dotenv.ArgPath(os.Args[1:], ".env") if _, err := dotenv.Load(envFile); err != nil { log.Fatalf("read %s: %v", envFile, err) } hh := flag.String("hammerhead", env("BAMBOO_HAMMERHEAD", ""), "URL of hammerhead") token := flag.String("token", env("BAMBOO_TOKEN", ""), "site token from the hammerhead UI") data := flag.String("data", env("BAMBOO_DATA", "./bamboo-data"), "directory for this agent's WireGuard key") dockerOn := flag.Bool("docker", os.Getenv("BAMBOO_DOCKER") == "1", "report local Docker containers so they can be picked as targets in the UI") discoverOn := flag.Bool("discover", os.Getenv("BAMBOO_DISCOVER") == "1", "report services listening on this machine as suggested targets in the UI (Linux)") discoverSubnets := flag.String("discover-subnets", os.Getenv("BAMBOO_DISCOVER_SUBNETS"), "comma-separated private subnets to scan for services, e.g. 192.168.1.0/24 (at most /22 each; off by default)") discoverPorts := flag.String("discover-ports", os.Getenv("BAMBOO_DISCOVER_PORTS"), "TCP ports to try on scanned addresses, e.g. 22,80,443,8000-8010 (default: a short list of common ones)") discoverRate := flag.Float64("discover-rate", envFloat("BAMBOO_DISCOVER_RATE", discover.DefaultRate), "most connection attempts per second discovery may make") sshOn := flag.Bool("ssh", os.Getenv("BAMBOO_SSH") == "1", "run a built-in SSH server (public keys only) so this machine can be reached through a tunnelled port") sshListen := flag.String("ssh-listen", env("BAMBOO_SSH_LISTEN", sshd.DefaultListen), "address the SSH server listens on; point a Hammerhead port at it from this site") sshKeys := flag.String("ssh-keys", os.Getenv("BAMBOO_SSH_KEYS"), "authorized_keys file listing who may sign in (default: authorized_keys in the data folder)") flag.String("env", ".env", "file to read secrets and settings from") autoUpdate := flag.Bool("auto-update", os.Getenv("BAMBOO_AUTO_UPDATE") == "1", "check for new releases every few hours and update and restart by itself (Linux and macOS)") showVersion := flag.Bool("version", false, "print the version and exit") flag.Parse() if *showVersion { os.Stdout.WriteString("bamboo " + version + "\n") return } if *hh == "" || *token == "" { flag.Usage() os.Exit(2) } log.Printf("bamboo %s starting", version) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() upd := selfupdate.Config{Server: "https://git.veillax.com", Repo: "Veillax/bamboo", App: "bamboo", Current: version, Logf: log.Printf} if *autoUpdate { go selfupdate.Run(ctx, upd) } dc := discover.Config{Local: *discoverOn || *discoverSubnets != "", Rate: *discoverRate} var err error if dc.Subnets, err = discover.ParseSubnets(*discoverSubnets); err != nil { log.Fatalf("-discover-subnets: %v", err) } if dc.Ports, err = discover.ParsePorts(*discoverPorts); err != nil { log.Fatalf("-discover-ports: %v", err) } sshAddr := "" // reported to Hammerhead, so its terminal can reach the server through this agent if *sshOn { if !sshd.Supported { log.Fatal("-ssh: the SSH server is not available on this platform") } keys := *sshKeys if keys == "" { keys = filepath.Join(*data, "authorized_keys") } srv, err := sshd.New(sshd.Config{Listen: *sshListen, AuthorizedKeys: keys, HostKey: filepath.Join(*data, "ssh_host_ed25519_key"), Logf: log.Printf}) if err != nil { log.Fatalf("-ssh: %v", err) } sshAddr = *sshListen log.Printf("ssh: listening on %s, host key %s", *sshListen, srv.Fingerprint()) go func() { if err := srv.Run(ctx); err != nil { log.Printf("ssh: stopped: %v", err) } }() } b, err := bamboo.New(bamboo.Config{Hammerhead: strings.TrimRight(*hh, "/"), Token: *token, DataDir: *data, Docker: *dockerOn, Discover: dc, SSH: sshAddr, Version: version, AutoUpdate: *autoUpdate, UpdateNow: func(ctx context.Context, progress func(string, int, string)) (string, error) { c := upd c.Progress = func(e selfupdate.Event) { progress(e.Stage, e.Pct, e.Line) } return selfupdate.CheckOnce(ctx, c) }}) if err != nil { log.Fatal(err) } b.Run(ctx) }