aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/mirumd/main.go')
-rw-r--r--cmd/mirumd/main.go266+261 −5
1 files changed, 261 insertions, 5 deletions
diff --git a/cmd/mirumd/main.go b/cmd/mirumd/main.go
index 32dc09c..b7210e6 100644
--- a/cmd/mirumd/main.go
+++ b/cmd/mirumd/main.go
@@ -14,12 +14,15 @@ import (
"net"
"net/http"
"os"
+ "os/user"
+ "strconv"
"time"
- "connectrpc.com/connect"
"dimidiumlabs/mirum/internal/database"
"dimidiumlabs/mirum/internal/forges"
+ "connectrpc.com/connect"
+
"dimidiumlabs/mirum/internal/protocol/pb"
"dimidiumlabs/mirum/internal/protocol/pb/pbconnect"
"dimidiumlabs/mirum/internal/supervisor"
@@ -127,6 +130,132 @@ func main() {
}
workerCmd.AddCommand(workerListCmd)
+ orgCmd := &cobra.Command{Use: "org", Short: "Manage organizations"}
+ root.AddCommand(orgCmd)
+
+ orgCreateCmd := &cobra.Command{
+ Use: "create",
+ Short: "Create an organization",
+ Run: func(cmd *cobra.Command, args []string) {
+ name, _ := cmd.Flags().GetString("name")
+ slug, _ := cmd.Flags().GetString("slug")
+ public, _ := cmd.Flags().GetBool("public")
+ owner, _ := cmd.Flags().GetString("owner")
+ orgCreate(socketPath, name, slug, public, owner)
+ },
+ }
+ orgCmd.AddCommand(orgCreateCmd)
+ orgCreateCmd.Flags().String("name", "", "display name")
+ orgCreateCmd.Flags().String("slug", "", "URL slug")
+ orgCreateCmd.Flags().Bool("public", false, "public visibility")
+ orgCreateCmd.Flags().String("owner", "", "owner email")
+ _ = orgCreateCmd.MarkFlagRequired("name")
+ _ = orgCreateCmd.MarkFlagRequired("slug")
+ _ = orgCreateCmd.MarkFlagRequired("owner")
+
+ orgDeleteCmd := &cobra.Command{
+ Use: "delete",
+ Short: "Delete an organization",
+ Run: func(cmd *cobra.Command, args []string) {
+ slug, _ := cmd.Flags().GetString("slug")
+ orgDelete(socketPath, slug)
+ },
+ }
+ orgCmd.AddCommand(orgDeleteCmd)
+ orgDeleteCmd.Flags().String("slug", "", "org slug")
+ _ = orgDeleteCmd.MarkFlagRequired("slug")
+
+ orgRenameCmd := &cobra.Command{
+ Use: "rename",
+ Short: "Rename an organization",
+ Run: func(cmd *cobra.Command, args []string) {
+ slug, _ := cmd.Flags().GetString("slug")
+ newName, _ := cmd.Flags().GetString("name")
+ newSlug, _ := cmd.Flags().GetString("new-slug")
+ orgRename(socketPath, slug, newName, newSlug)
+ },
+ }
+ orgCmd.AddCommand(orgRenameCmd)
+ orgRenameCmd.Flags().String("slug", "", "current slug")
+ orgRenameCmd.Flags().String("name", "", "new display name")
+ orgRenameCmd.Flags().String("new-slug", "", "new slug")
+ _ = orgRenameCmd.MarkFlagRequired("slug")
+ _ = orgRenameCmd.MarkFlagRequired("name")
+ _ = orgRenameCmd.MarkFlagRequired("new-slug")
+
+ orgListCmd := &cobra.Command{
+ Use: "list",
+ Short: "List organizations",
+ Run: func(cmd *cobra.Command, args []string) {
+ email, _ := cmd.Flags().GetString("user")
+ orgList(socketPath, email)
+ },
+ }
+ orgCmd.AddCommand(orgListCmd)
+ orgListCmd.Flags().String("user", "", "filter by user email (optional)")
+
+ orgMemberAddCmd := &cobra.Command{
+ Use: "add-member",
+ Short: "Add a member to an organization",
+ Run: func(cmd *cobra.Command, args []string) {
+ org, _ := cmd.Flags().GetString("org")
+ email, _ := cmd.Flags().GetString("email")
+ role, _ := cmd.Flags().GetString("role")
+ orgMemberAdd(socketPath, org, email, role)
+ },
+ }
+ orgCmd.AddCommand(orgMemberAddCmd)
+ orgMemberAddCmd.Flags().String("org", "", "org slug")
+ orgMemberAddCmd.Flags().String("email", "", "user email")
+ orgMemberAddCmd.Flags().String("role", "member", "role (owner, admin, member)")
+ _ = orgMemberAddCmd.MarkFlagRequired("org")
+ _ = orgMemberAddCmd.MarkFlagRequired("email")
+
+ orgMemberRemoveCmd := &cobra.Command{
+ Use: "remove-member",
+ Short: "Remove a member from an organization",
+ Run: func(cmd *cobra.Command, args []string) {
+ org, _ := cmd.Flags().GetString("org")
+ email, _ := cmd.Flags().GetString("email")
+ orgMemberRemove(socketPath, org, email)
+ },
+ }
+ orgCmd.AddCommand(orgMemberRemoveCmd)
+ orgMemberRemoveCmd.Flags().String("org", "", "org slug")
+ orgMemberRemoveCmd.Flags().String("email", "", "user email")
+ _ = orgMemberRemoveCmd.MarkFlagRequired("org")
+ _ = orgMemberRemoveCmd.MarkFlagRequired("email")
+
+ orgSetRoleCmd := &cobra.Command{
+ Use: "set-role",
+ Short: "Change a member's role",
+ Run: func(cmd *cobra.Command, args []string) {
+ org, _ := cmd.Flags().GetString("org")
+ email, _ := cmd.Flags().GetString("email")
+ role, _ := cmd.Flags().GetString("role")
+ orgMemberSetRole(socketPath, org, email, role)
+ },
+ }
+ orgCmd.AddCommand(orgSetRoleCmd)
+ orgSetRoleCmd.Flags().String("org", "", "org slug")
+ orgSetRoleCmd.Flags().String("email", "", "user email")
+ orgSetRoleCmd.Flags().String("role", "", "new role (owner, admin, member)")
+ _ = orgSetRoleCmd.MarkFlagRequired("org")
+ _ = orgSetRoleCmd.MarkFlagRequired("email")
+ _ = orgSetRoleCmd.MarkFlagRequired("role")
+
+ orgMemberListCmd := &cobra.Command{
+ Use: "list-members",
+ Short: "List members of an organization",
+ Run: func(cmd *cobra.Command, args []string) {
+ org, _ := cmd.Flags().GetString("org")
+ orgMemberList(socketPath, org)
+ },
+ }
+ orgCmd.AddCommand(orgMemberListCmd)
+ orgMemberListCmd.Flags().String("org", "", "org slug")
+ _ = orgMemberListCmd.MarkFlagRequired("org")
+
if err := root.Execute(); err != nil {
os.Exit(1)
}
@@ -237,9 +366,9 @@ func daemon(configFile, socketFlag string) {
srv.Close()
- adminSrv.Shutdown(context.Background())
wwwSrv.Shutdown(context.Background())
grpcSrv.Shutdown(context.Background())
+ adminSrv.Shutdown(context.Background())
}
// listeners returns gRPC, web, and admin listeners.
@@ -273,6 +402,7 @@ func listeners(cfg *config) (grpcLn, webLn, adminLn net.Listener, err error) {
}
}()
+ _ = os.Remove(cfg.AdminSocket)
if adminLn, err = net.Listen("unix", cfg.AdminSocket); err != nil {
return nil, nil, nil, err
}
@@ -282,6 +412,24 @@ func listeners(cfg *config) (grpcLn, webLn, adminLn net.Listener, err error) {
}
}()
+ grp, err := user.LookupGroup("workerd")
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("lookup group workerd: %w", err)
+ }
+
+ gid, err := strconv.Atoi(grp.Gid)
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("parse gid: %w", err)
+ }
+
+ if err = os.Chown(cfg.AdminSocket, 0, gid); err != nil {
+ return nil, nil, nil, fmt.Errorf("chown admin socket: %w", err)
+ }
+
+ if err = os.Chmod(cfg.AdminSocket, 0660); err != nil {
+ return nil, nil, nil, fmt.Errorf("chmod admin socket: %w", err)
+ }
+
return grpcLn, webLn, adminLn, nil
}
@@ -302,7 +450,7 @@ func adminClient(socketPath string) pbconnect.AdminClient {
}
func userCreate(socketPath, email, password string) {
- resp, err := adminClient(socketPath).CreateUser(context.Background(), connect.NewRequest(&pb.CreateUserRequest{
+ resp, err := adminClient(socketPath).UserCreate(context.Background(), connect.NewRequest(&pb.UserCreateRequest{
Email: email,
Password: password,
}))
@@ -314,7 +462,7 @@ func userCreate(socketPath, email, password string) {
}
func userSetPassword(socketPath, email, password string) {
- _, err := adminClient(socketPath).SetPassword(context.Background(), connect.NewRequest(&pb.SetPasswordRequest{
+ _, err := adminClient(socketPath).UserSetPassword(context.Background(), connect.NewRequest(&pb.UserSetPasswordRequest{
Email: email,
Password: password,
}))
@@ -326,7 +474,7 @@ func userSetPassword(socketPath, email, password string) {
}
func userDelete(socketPath, email string) {
- _, err := adminClient(socketPath).DeleteUser(context.Background(), connect.NewRequest(&pb.DeleteUserRequest{
+ _, err := adminClient(socketPath).UserDelete(context.Background(), connect.NewRequest(&pb.UserDeleteRequest{
Email: email,
}))
if err != nil {
@@ -384,3 +532,111 @@ func workerList(socketPath string) {
fmt.Printf("%s\t%s\t%s\n", w.Id, base64.StdEncoding.EncodeToString(w.PublicKey), created)
}
}
+
+func orgCreate(socketPath, name, slug string, public bool, ownerEmail string) {
+ resp, err := adminClient(socketPath).OrgCreate(context.Background(), connect.NewRequest(&pb.OrgCreateRequest{
+ Name: name,
+ Slug: slug,
+ Public: public,
+ OwnerEmail: ownerEmail,
+ }))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ fmt.Println(resp.Msg.Id)
+}
+
+func orgDelete(socketPath, slug string) {
+ _, err := adminClient(socketPath).OrgDelete(context.Background(), connect.NewRequest(&pb.OrgDeleteRequest{
+ Slug: slug,
+ }))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ fmt.Println("ok")
+}
+
+func orgRename(socketPath, slug, newName, newSlug string) {
+ _, err := adminClient(socketPath).OrgRename(context.Background(), connect.NewRequest(&pb.OrgRenameRequest{
+ Slug: slug,
+ NewName: newName,
+ NewSlug: newSlug,
+ }))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ fmt.Println("ok")
+}
+
+func orgList(socketPath, email string) {
+ req := &pb.OrgListRequest{}
+ if email != "" {
+ req.UserEmail = &email
+ }
+ resp, err := adminClient(socketPath).OrgList(context.Background(), connect.NewRequest(req))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ for _, o := range resp.Msg.Organizations {
+ visibility := "private"
+ if o.Public {
+ visibility = "public"
+ }
+ fmt.Printf("%s\t%s\t%s\t%s\n", o.Slug, o.Name, visibility, o.CreatedAt.AsTime().Format(time.DateOnly))
+ }
+}
+
+func orgMemberAdd(socketPath, orgSlug, email, role string) {
+ _, err := adminClient(socketPath).OrgMemberAdd(context.Background(), connect.NewRequest(&pb.OrgMemberAddRequest{
+ OrgSlug: orgSlug,
+ Email: email,
+ Role: role,
+ }))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ fmt.Println("ok")
+}
+
+func orgMemberRemove(socketPath, orgSlug, email string) {
+ _, err := adminClient(socketPath).OrgMemberRemove(context.Background(), connect.NewRequest(&pb.OrgMemberRemoveRequest{
+ OrgSlug: orgSlug,
+ Email: email,
+ }))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ fmt.Println("ok")
+}
+
+func orgMemberSetRole(socketPath, orgSlug, email, role string) {
+ _, err := adminClient(socketPath).OrgMemberSetRole(context.Background(), connect.NewRequest(&pb.OrgMemberSetRoleRequest{
+ OrgSlug: orgSlug,
+ Email: email,
+ Role: role,
+ }))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ fmt.Println("ok")
+}
+
+func orgMemberList(socketPath, orgSlug string) {
+ resp, err := adminClient(socketPath).OrgMemberList(context.Background(), connect.NewRequest(&pb.OrgMemberListRequest{
+ OrgSlug: orgSlug,
+ }))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ for _, m := range resp.Msg.Members {
+ fmt.Printf("%s\t%s\t%s\n", m.Email, m.Role, m.JoinedAt.AsTime().Format(time.DateOnly))
+ }
+}