package main import ( "bufio" "bytes" "encoding/json" "flag" "fmt" "io" "mime/multipart" "net/http" "os" "os/exec" "path/filepath" "strings" "time" ) var version = "dev" // sätts av CI via -ldflags var exec_LookPath = exec.LookPath func main() { if len(os.Args) < 2 { usage() os.Exit(2) } switch os.Args[1] { case "serve": fs := flag.NewFlagSet("serve", flag.ExitOnError) listen := fs.String("listen", "", "override listen-adress (t.ex. 0.0.0.0:8788)") confPath := fs.String("config", configPath(), "sökväg till config.json") fs.Parse(os.Args[2:]) cfg, err := loadConfig(*confPath) if err != nil { fatal(err) } if *listen != "" { cfg.Listen = *listen } if err := serve(cfg); err != nil { fatal(err) } case "token": cfg, err := loadConfig(configPath()) if err != nil { fatal(err) } fmt.Println(cfg.Token) case "share": fs := flag.NewFlagSet("share", flag.ExitOnError) server := fs.String("server", "http://127.0.0.1:8788", "helmd-serverns adress") note := fs.String("note", "", "beskrivning som visas i klienten") session := fs.String("session", "", "session som delningen hör till") fs.Parse(os.Args[2:]) if fs.NArg() != 1 { fatal(fmt.Errorf("användning: helmd share [flaggor] ")) } cfg, err := loadConfig(configPath()) if err != nil { fatal(err) } id, err := shareFile(*server, cfg.Token, fs.Arg(0), *note, *session) if err != nil { fatal(err) } fmt.Printf("delad: %s (id %s)\n%s/api/shares/%s/raw\n", filepath.Base(fs.Arg(0)), id, *server, id) case "hub": if err := runHub(os.Args[2:]); err != nil { fatal(err) } case "ctl": if err := runCtl(os.Args[2:]); err != nil { fatal(err) } case "version", "--version", "-v": fmt.Println("helmd", version) default: usage() os.Exit(2) } } // runHub hanterar hub-subkommandona: starta hubben eller administrera // dess konton/agent-nyckel. func runHub(args []string) error { if len(args) > 0 && !strings.HasPrefix(args[0], "-") { switch args[0] { case "setpass": fs := flag.NewFlagSet("hub setpass", flag.ExitOnError) confPath := fs.String("config", hubConfigPath(), "sökväg till hub.json") password := fs.String("password", "", "lösenord (utelämna för att läsa från stdin)") fs.Parse(args[1:]) if fs.NArg() != 1 { return fmt.Errorf("användning: helmd hub setpass [--password PW]") } cfg, err := loadHubConfig(*confPath) if err != nil { return err } pw := *password if pw == "" { fmt.Fprintf(os.Stderr, "nytt lösenord för %s (syns när du skriver): ", fs.Arg(0)) sc := bufio.NewScanner(os.Stdin) if sc.Scan() { pw = strings.TrimSpace(sc.Text()) } } if err := cfg.SetPassword(fs.Arg(0), pw); err != nil { return err } if err := cfg.Save(); err != nil { return err } fmt.Printf("lösenord satt för %s — starta om hubben så att det börjar gälla\n", fs.Arg(0)) return nil case "users": fs := flag.NewFlagSet("hub users", flag.ExitOnError) confPath := fs.String("config", hubConfigPath(), "sökväg till hub.json") fs.Parse(args[1:]) cfg, err := loadHubConfig(*confPath) if err != nil { return err } for _, u := range cfg.Users { fmt.Println(u.Username) } return nil case "agentkey": fs := flag.NewFlagSet("hub agentkey", flag.ExitOnError) confPath := fs.String("config", hubConfigPath(), "sökväg till hub.json") fs.Parse(args[1:]) cfg, err := loadHubConfig(*confPath) if err != nil { return err } fmt.Println(cfg.AgentKey) return nil default: return fmt.Errorf("okänt hub-kommando %q (setpass, users, agentkey — eller flaggor för att starta)", args[0]) } } fs := flag.NewFlagSet("hub", flag.ExitOnError) listen := fs.String("listen", "", "override listen-adress (t.ex. 0.0.0.0:8790)") confPath := fs.String("config", hubConfigPath(), "sökväg till hub.json") fs.Parse(args) cfg, err := loadHubConfig(*confPath) if err != nil { return err } if *listen != "" { cfg.Listen = *listen } return hubServe(cfg) } func usage() { fmt.Fprintln(os.Stderr, `helmd — agent-helm: styr CLI-agenter i tmux via REST-API + hub användning: helmd serve [--listen host:port] [--config fil] starta agent-servern helmd share [--note text] [--session namn] dela en fil till klienterna helmd token skriv ut API-token helmd hub [--listen host:port] [--config fil] starta hubben (Pi5) helmd hub setpass [--password PW] sätt/skapa hub-konto helmd hub users lista hub-konton helmd hub agentkey skriv ut agent-nyckeln helmd ctl CLI-styrklient mot hubben helmd version`) } func fatal(err error) { fmt.Fprintln(os.Stderr, "helmd:", err) os.Exit(1) } // shareFile uploads a file to a running helmd and returns the share id. func shareFile(server, token, path, note, session string) (string, error) { f, err := os.Open(path) if err != nil { return "", err } defer f.Close() var buf bytes.Buffer mw := multipart.NewWriter(&buf) fw, err := mw.CreateFormFile("file", filepath.Base(path)) if err != nil { return "", err } if _, err := io.Copy(fw, f); err != nil { return "", err } mw.WriteField("note", note) mw.WriteField("session", session) mw.Close() req, err := http.NewRequest("POST", server+"/api/shares", &buf) if err != nil { return "", err } req.Header.Set("Content-Type", mw.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{Timeout: 60 * time.Second} resp, err := client.Do(req) if err != nil { return "", err } defer resp.Body.Close() body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) if resp.StatusCode >= 300 { return "", fmt.Errorf("servern svarade %s: %s", resp.Status, bytes.TrimSpace(body)) } var out struct { ID string `json:"id"` } if err := json.Unmarshal(body, &out); err != nil { return "", err } return out.ID, nil }