// Command nidusctl is a small administrative CLI for the nidus DAV // server. It currently manages calendar and address-book sharing grants // stored in the SQLite database at /nidus.db; it doesn't talk // to a running server, so the server should be restarted (or, in the // future, will pick up changes automatically) after granting/revoking // shares — the caldav/carddav backends read shares on every request, so // no cache invalidation is needed, only an already-open DB connection. package main import ( "flag" "fmt" "os" "path/filepath" "github.com/yourusername/caldav-server/internal/config" "github.com/yourusername/caldav-server/internal/db" ) func main() { os.Exit(run(os.Args[1:])) } func run(args []string) int { fs := flag.NewFlagSet("nidusctl", flag.ContinueOnError) cfgPath := fs.String("config", "config.yaml", "path to configuration file") if err := fs.Parse(args); err != nil { return 2 } rest := fs.Args() if len(rest) < 1 { usage() return 2 } cfg, err := config.Load(*cfgPath) if err != nil { fmt.Fprintf(os.Stderr, "error loading config: %v\n", err) return 1 } dbPath := filepath.Join(cfg.Storage.DataDir, "nidus.db") dbase, err := db.Open(dbPath) if err != nil { fmt.Fprintf(os.Stderr, "error opening database %q: %v\n", dbPath, err) return 1 } defer dbase.Close() switch rest[0] { case "calendar", "cal": return runCalendar(cfg, dbase, rest[1:]) case "addressbook", "card": return runAddressBook(cfg, dbase, rest[1:]) case "help", "-h", "--help": usage() return 0 default: fmt.Fprintf(os.Stderr, "unknown command %q\n\n", rest[0]) usage() return 2 } } func usage() { fmt.Fprint(os.Stderr, `nidusctl - manage nidus DAV server sharing grants Usage: nidusctl [-config config.yaml] calendar share nidusctl [-config config.yaml] calendar unshare nidusctl [-config config.yaml] calendar shares nidusctl [-config config.yaml] addressbook share nidusctl [-config config.yaml] addressbook unshare nidusctl [-config config.yaml] addressbook shares Examples: nidusctl calendar share alice work bob write nidusctl calendar shares alice work nidusctl calendar unshare alice work bob `) } // userExists reports whether username is a configured user, printing a // warning (not a hard error) if not — the share is still recorded, since // config.yaml and the share database are independent sources of truth and // a user added after the fact shouldn't require re-running share commands. func warnIfUnknownUser(cfg *config.Config, username string) { if _, ok := cfg.Users[username]; !ok { fmt.Fprintf(os.Stderr, "warning: %q is not a user in config.yaml (continuing anyway)\n", username) } } func runCalendar(cfg *config.Config, dbase *db.DB, args []string) int { if len(args) < 1 { usage() return 2 } switch args[0] { case "share": if len(args) != 5 { fmt.Fprintln(os.Stderr, "usage: nidusctl calendar share ") return 2 } owner, calName, user, permStr := args[1], args[2], args[3], args[4] perm := db.Permission(permStr) if perm != db.PermRead && perm != db.PermWrite { fmt.Fprintf(os.Stderr, "invalid permission %q: must be %q or %q\n", permStr, db.PermRead, db.PermWrite) return 2 } warnIfUnknownUser(cfg, owner) warnIfUnknownUser(cfg, user) if err := dbase.ShareCalendar(owner, calName, user, perm); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } fmt.Printf("shared %s's calendar %q with %s (%s)\n", owner, calName, user, perm) return 0 case "unshare": if len(args) != 4 { fmt.Fprintln(os.Stderr, "usage: nidusctl calendar unshare ") return 2 } owner, calName, user := args[1], args[2], args[3] if err := dbase.UnshareCalendar(owner, calName, user); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } fmt.Printf("unshared %s's calendar %q from %s\n", owner, calName, user) return 0 case "shares": if len(args) != 3 { fmt.Fprintln(os.Stderr, "usage: nidusctl calendar shares ") return 2 } owner, calName := args[1], args[2] shares, err := dbase.SharesOfCalendar(owner, calName) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } if len(shares) == 0 { fmt.Printf("%s's calendar %q is not shared with anyone\n", owner, calName) return 0 } for _, s := range shares { fmt.Printf("%s\t%s\n", s.SharedWith, s.Permission) } return 0 default: fmt.Fprintf(os.Stderr, "unknown calendar subcommand %q\n", args[0]) return 2 } } func runAddressBook(cfg *config.Config, dbase *db.DB, args []string) int { if len(args) < 1 { usage() return 2 } switch args[0] { case "share": if len(args) != 5 { fmt.Fprintln(os.Stderr, "usage: nidusctl addressbook share ") return 2 } owner, bookName, user, permStr := args[1], args[2], args[3], args[4] perm := db.Permission(permStr) if perm != db.PermRead && perm != db.PermWrite { fmt.Fprintf(os.Stderr, "invalid permission %q: must be %q or %q\n", permStr, db.PermRead, db.PermWrite) return 2 } warnIfUnknownUser(cfg, owner) warnIfUnknownUser(cfg, user) if err := dbase.ShareAddressBook(owner, bookName, user, perm); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } fmt.Printf("shared %s's address book %q with %s (%s)\n", owner, bookName, user, perm) return 0 case "unshare": if len(args) != 4 { fmt.Fprintln(os.Stderr, "usage: nidusctl addressbook unshare ") return 2 } owner, bookName, user := args[1], args[2], args[3] if err := dbase.UnshareAddressBook(owner, bookName, user); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } fmt.Printf("unshared %s's address book %q from %s\n", owner, bookName, user) return 0 case "shares": if len(args) != 3 { fmt.Fprintln(os.Stderr, "usage: nidusctl addressbook shares ") return 2 } owner, bookName := args[1], args[2] shares, err := dbase.SharesOfAddressBook(owner, bookName) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } if len(shares) == 0 { fmt.Printf("%s's address book %q is not shared with anyone\n", owner, bookName) return 0 } for _, s := range shares { fmt.Printf("%s\t%s\n", s.SharedWith, s.Permission) } return 0 default: fmt.Fprintf(os.Stderr, "unknown addressbook subcommand %q\n", args[0]) return 2 } }