cmd

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 9, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var CreateAliasTargetsCmd = &cobra.Command{
	Use:     "alias-target <alias-email> <target-email> [target-email...]",
	Aliases: []string{"target"},
	Short:   "Create a new alias target",
	Long:    `Add a target to an alias. Both emails must be in the format name@domain.`,
	Args:    cobra.MinimumNArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagForward, _ := cmd.Flags().GetBool("forward")
		flagSend, _ := cmd.Flags().GetBool("send")

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		argAliasEmail := argEmails[0]
		argTargetEmails := argEmails[1:]

		options := db.AliasesTargetsCreateOptions{
			ForwardEnabled: flagForward,
		}
		if cmd.Flags().Changed("send") {
			options.SendEnabled = &flagSend
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argTargetEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.AliasesTargets(tx).Create(argAliasEmail, item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return argAliasEmail.String() + " -> " + item.String() },
			FailureMessage: "failed to create alias target",
			SuccessMessage: "Successfully created alias target",
		}

		runner.Run()
		return nil
	},
}
View Source
var CreateAliasesCmd = &cobra.Command{
	Use:     "aliases <email> <target> [target...]",
	Aliases: []string{"alias"},
	Short:   "Create a new alias",
	Long:    "Creates a new alias with the specified target addresses.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.MinimumNArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDisabled, _ := cmd.Flags().GetBool("disabled")

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		options := db.AliasesCreateOptions{
			Disabled: flagDisabled,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.Aliases(tx).Create(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to create alias",
			SuccessMessage: "Successfully created alias",
		}

		runner.Run()
		return nil
	},
}
View Source
var CreateCmd = &cobra.Command{
	Use:   "create",
	Short: "Create mail system objects",
}
View Source
var CreateDomainCatchallTargetsCmd = &cobra.Command{
	Use:     "catchall-targets <domain> <target-email> [target-email...]",
	Aliases: []string{"catchall-target", "catchall"},
	Short:   "Create a new catch-all target for a domain",
	Long:    "Add a catch-all target to a domain.",
	Args:    cobra.MinimumNArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagForward, _ := cmd.Flags().GetBool("forward")
		flagFallbackOnly, _ := cmd.Flags().GetBool("fallback-only")

		argDomain := args[0]
		argEmails := ParseEmailArgs(args[1:])
		if len(argEmails) != len(args)-1 {
			return fmt.Errorf("invalid email arguments")
		}

		options := db.DomainsCatchallTargetsCreateOptions{
			ForwardEnabled: flagForward,
			FallbackOnly:   flagFallbackOnly,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.DomainsCatchallTargets(tx).Create(argDomain, item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return "@" + argDomain + " -> " + item.String() },
			FailureMessage: "failed to create catchall target",
			SuccessMessage: "Successfully created catchall target",
		}

		runner.Run()
		return nil
	},
}
View Source
var CreateDomainsCmd = &cobra.Command{
	Use:     "domains <fqdn> [fqdn...]",
	Aliases: []string{"domain"},
	Short:   "Create a new domain",
	Long:    "Creates a new domain of the specified type.\nFQDNs must be valid domain names.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDisabled, _ := cmd.Flags().GetBool("disabled")
		flagType, _ := cmd.Flags().GetString("type")
		flagTransport, _ := cmd.Flags().GetString("transport")
		flagTargetDomain, _ := cmd.Flags().GetString("target-domain")

		domainType := strings.ToLower(flagType)
		switch domainType {
		case "managed", "relayed":
			if flagTransport == "" {
				return fmt.Errorf("transport name is required for %s domains", domainType)
			}
		case "canonical":
			if flagTargetDomain == "" {
				return fmt.Errorf("target domain FQDN is required for canonical domains")
			}
		case "alias":

		default:
			return fmt.Errorf("invalid domain type: %s (must be 'managed', 'relayed', 'alias' or 'canonical')", flagType)
		}

		argDomains := ParseDomainFQDNArgs(args)
		if len(argDomains) != len(args) {
			return fmt.Errorf("invalid domain arguments")
		}

		options := db.DomainsCreateOptions{
			DomainType:       domainType,
			TransportName:    flagTransport,
			TargetDomainFQDN: flagTargetDomain,
			Enabled:          !flagDisabled,
		}

		runner := db.TxForEachRunner[string]{
			Items: argDomains,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Domains(tx).Create(item, options)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "failed to create domain",
			SuccessMessage: "Successfully created domain",
		}

		runner.Run()
		return nil
	},
}
View Source
var CreateMailboxesCmd = &cobra.Command{
	Use:     "mailboxes <email> [email...]",
	Aliases: []string{"mailbox"},
	Short:   "Create a new mailbox",
	Long:    "Creates a new mailbox with the specified email address.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPassword, _ := cmd.Flags().GetBool("password")
		flagPasswordStdin, _ := cmd.Flags().GetBool("password-stdin")
		flagPasswordMethod, _ := cmd.Flags().GetString("password-method")
		flagQuota, _ := cmd.Flags().GetInt32("quota")
		flagTransportName, _ := cmd.Flags().GetString("transport")
		flagLoginDisabled, _ := cmd.Flags().GetBool("login-disabled")
		flagReceivingDisabled, _ := cmd.Flags().GetBool("receiving-disabled")
		flagSendingDisabled, _ := cmd.Flags().GetBool("sending-disabled")

		if flagPassword && flagPasswordStdin {
			return fmt.Errorf("cannot use both --password and --password-stdin")
		}

		if len(args) > 0 && (flagPassword || flagPasswordStdin) {
			return fmt.Errorf("cannot set password while creating multiple mailboxes")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		options := db.MailboxesCreateOptions{
			LoginEnabled:     !flagLoginDisabled,
			ReceivingEnabled: !flagReceivingDisabled,
			SendingEnabled:   !flagSendingDisabled,
		}

		if flagPassword || flagPasswordStdin {
			passwordHash, err := ReadPasswordHashed(flagPasswordMethod, flagPasswordStdin)
			if err != nil {
				utils.PrintErrorWithMessage("failed to read password", err)
				return nil
			}
			options.PasswordHash.Valid = true
			options.PasswordHash.String = passwordHash
		}

		if flagQuota > 0 {
			options.Quota.Valid = true
			options.Quota.Int32 = flagQuota
		}

		if flagTransportName != "" {
			options.TransportName.Valid = true
			options.TransportName.String = flagTransportName
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.Mailboxes(tx).Create(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to create mailbox",
			SuccessMessage: "Successfully created mailbox",
		}

		runner.Run()
		return nil
	},
}
View Source
var CreateRecipientsRelayedCmd = &cobra.Command{
	Use:     "recipients-relayed <email> [email...]",
	Aliases: []string{"recipient-relayed", "relayed-recipient", "relayed-recipients", "relayed"},
	Short:   "Create a new relayed recipient",
	Long:    "Creates a new relayed recipient.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDisabled, _ := cmd.Flags().GetBool("disabled")

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		options := db.RecipientsRelayedCreateOptions{
			Enabled: !flagDisabled,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.RecipientsRelayed(tx).Create(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to create relayed recipient",
			SuccessMessage: "Successfully created relayed recipient",
		}

		runner.Run()
		return nil
	},
}
View Source
var CreateRemoteSendGrantsCmd = &cobra.Command{
	Use:   "send-grant <remote> <email|domain>",
	Short: "Create a new send grant",
	Args:  cobra.ExactArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		argRemoteName := args[0]
		argEmails := ParseEmailOrWildcardArgs(args[1:])
		if len(argEmails) != len(args[1:]) {
			return fmt.Errorf("invalid email or domain argument")
		}
		argEmail := argEmails[0]

		options := db.RemotesSendGrantsCreateOptions{}

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.RemotesSendGrants(tx).Create(argRemoteName, argEmail, options)
			},
			FailureMessage: "failed to create send grant",
			SuccessMessage: "Successfully created send grant",
		}

		runner.Run()
		return nil
	},
}
View Source
var CreateRemotesCmd = &cobra.Command{
	Use:     "remotes <hostname> [hostname...]",
	Aliases: []string{"remote"},
	Short:   "Create a new remote",
	Long:    "Creates a new remote SMTP relay server configuration.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPassword, _ := cmd.Flags().GetBool("password")
		flagPasswordStdin, _ := cmd.Flags().GetBool("password-stdin")
		flagPasswordMethod, _ := cmd.Flags().GetString("password-method")
		flagDisabled, _ := cmd.Flags().GetBool("disabled")

		if flagPassword && flagPasswordStdin {
			return fmt.Errorf("cannot use both --password and --password-stdin")
		}

		if len(args) > 1 && (flagPassword || flagPasswordStdin) {
			return fmt.Errorf("cannot set password while creating multiple mailboxes")
		}

		options := db.RemotesCreateOptions{
			Enabled: !flagDisabled,
		}

		if flagPassword || flagPasswordStdin {
			passwordHash, err := ReadPasswordHashed(flagPasswordMethod, flagPasswordStdin)
			if err != nil {
				utils.PrintErrorWithMessage("failed to read password", err)
				return nil
			}
			options.PasswordHash = sql.NullString{
				String: passwordHash,
				Valid:  true,
			}
		}

		runner := db.TxForEachRunner[string]{
			Items: args,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Remotes(tx).Create(item, options)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "failed to create remote",
			SuccessMessage: "Successfully created remote",
		}

		runner.Run()
		return nil
	},
}
View Source
var CreateTransportsCmd = &cobra.Command{
	Use:   "transport <name> [name...]",
	Short: "Create a new transport",
	Long:  "Creates a new mail transport configuration.",
	Args:  cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		name := args[0]
		flagMethod, _ := cmd.Flags().GetString("method")
		flagHost, _ := cmd.Flags().GetString("host")
		flagPort, _ := cmd.Flags().GetUint16("port")
		flagMxLookup, _ := cmd.Flags().GetBool("mx-lookup")

		options := db.TransportsCreateOptions{
			Method:   flagMethod,
			Host:     flagHost,
			MxLookup: flagMxLookup,
		}
		if flagPort > 0 {
			options.Port = sql.NullInt32{Int32: int32(flagPort), Valid: true}
		}

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.Transports(tx).Create(name, options)
			},
			ItemString:     name,
			FailureMessage: "failed to create transport",
			SuccessMessage: "Successfully created transport",
		}

		runner.Run()
		return nil
	},
}
View Source
var DeleteAliasTargetsCmd = &cobra.Command{
	Use:     "alias-target <alias-email> <target-email> [target-email...]",
	Aliases: []string{"target"},
	Short:   "Delete an alias target",
	Long:    `Delete a target from an alias. By default performs a soft delete. Use --permanent --force for hard delete.`,
	Args:    cobra.MinimumNArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPermanent, _ := cmd.Flags().GetBool("permanent")
		flagForce, _ := cmd.Flags().GetBool("force")

		if flagPermanent && flagForce {
			return fmt.Errorf("cannot use --permanent and --force flags together")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		argAliasEmail := argEmails[0]
		argTargetEmails := argEmails[1:]

		options := db.DeleteOptions{
			Permanent: flagPermanent,
			Force:     flagForce,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argTargetEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.AliasesTargets(tx).Delete(argAliasEmail, item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return argAliasEmail.String() + " -> " + item.String() },
			FailureMessage: "failed to delete alias target",
			SuccessMessage: "Successfully deleted alias target",
		}

		if flagPermanent {
			runner.SuccessMessage += " permanently"
		}

		runner.Run()
		return nil
	},
}
View Source
var DeleteAliasesCmd = &cobra.Command{
	Use:     "aliases <email> [email...]",
	Aliases: []string{"alias"},
	Short:   "Deletes aliases",
	Long:    "Deletes aliases. By default performs a soft delete. Use --permanent for hard delete.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPermanent, _ := cmd.Flags().GetBool("permanent")
		flagForce, _ := cmd.Flags().GetBool("force")

		if flagPermanent && flagForce {
			return fmt.Errorf("cannot use --permanent and --force flags together")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		options := db.DeleteOptions{
			Permanent: flagPermanent,
			Force:     flagForce,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.Aliases(tx).Delete(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to delete alias",
			SuccessMessage: "Successfully deleted alias",
		}

		if flagPermanent {
			runner.SuccessMessage += " permanently"
		}

		runner.Run()
		return nil
	},
}
View Source
var DeleteCmd = &cobra.Command{
	Use:   "delete",
	Short: "Delete mail system objects",
}
View Source
var DeleteDomainCatchallTargetsCmd = &cobra.Command{
	Use:     "catchall-target <domain> <target-email> [target-email...]",
	Aliases: []string{"catchall"},
	Short:   "Delete a domain catch-all target",
	Long:    `Delete a catch-all target from a domain. By default performs a soft delete. Use --permanent --force for hard delete.`,
	Args:    cobra.MinimumNArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPermanent, _ := cmd.Flags().GetBool("permanent")
		flagForce, _ := cmd.Flags().GetBool("force")

		if flagPermanent && flagForce {
			return fmt.Errorf("cannot use --permanent and --force flags together")
		}

		argDomain := args[0]
		argEmails := ParseEmailArgs(args[1:])
		if len(argEmails) != len(args)-1 {
			return fmt.Errorf("invalid email arguments")
		}

		options := db.DeleteOptions{
			Permanent: flagPermanent,
			Force:     flagForce,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.DomainsCatchallTargets(tx).Delete(argDomain, item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return "@" + argDomain + " -> " + item.String() },
			FailureMessage: "failed to delete catchall target",
			SuccessMessage: "Successfully deleted catchall target",
		}

		if flagPermanent {
			runner.SuccessMessage += " permanently"
		}

		runner.Run()
		return nil
	},
}
View Source
var DeleteDomainsCmd = &cobra.Command{
	Use:     "domains <fqdn> [fqdn...]",
	Aliases: []string{"domain"},
	Short:   "Deletes domains",
	Long:    "Deletes domains. By default performs a soft delete. Use --permanent for hard delete.\nFQDNs must be valid domain names.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPermanent, _ := cmd.Flags().GetBool("permanent")
		flagForce, _ := cmd.Flags().GetBool("force")

		if flagPermanent && flagForce {
			return fmt.Errorf("cannot use --permanent and --force flags together")
		}

		argDomains := ParseDomainFQDNArgs(args)
		if len(argDomains) != len(args) {
			return fmt.Errorf("invalid domain arguments")
		}

		options := db.DeleteOptions{
			Permanent: flagPermanent,
			Force:     flagForce,
		}

		runner := db.TxForEachRunner[string]{
			Items: argDomains,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Domains(tx).Delete(item, options)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "failed to delete domain",
			SuccessMessage: "Successfully deleted domain",
		}

		if flagPermanent {
			runner.SuccessMessage += " permanently"
		}

		runner.Run()
		return nil
	},
}
View Source
var DeleteMailboxesCmd = &cobra.Command{
	Use:     "mailboxes <email> [email...]",
	Aliases: []string{"mailbox"},
	Short:   "Deletes mailboxes",
	Long:    "Deletes mailboxes. By default performs a soft delete. Use --permanent for hard delete.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPermanent, _ := cmd.Flags().GetBool("permanent")
		flagForce, _ := cmd.Flags().GetBool("force")

		if flagPermanent && flagForce {
			return fmt.Errorf("cannot use --permanent and --force flags together")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		options := db.DeleteOptions{
			Permanent: flagPermanent,
			Force:     flagForce,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.Mailboxes(tx).Delete(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to delete mailbox",
			SuccessMessage: "Successfully deleted mailbox",
		}

		if flagPermanent {
			runner.SuccessMessage += " permanently"
		}

		runner.Run()
		return nil
	},
}
View Source
var DeleteRecipientsRelayedCmd = &cobra.Command{
	Use:     "recipients-relayed <email> [email...]",
	Aliases: []string{"recipient-relayed", "relayed-recipient", "relayed-recipients", "relayed"},
	Short:   "Deletes relayed recipients",
	Long:    "Deletes relayed recipients. By default performs a soft delete. Use --permanent for hard delete.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPermanent, _ := cmd.Flags().GetBool("permanent")
		flagForce, _ := cmd.Flags().GetBool("force")

		if flagPermanent && flagForce {
			return fmt.Errorf("cannot use --permanent and --force flags together")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		options := db.DeleteOptions{
			Permanent: flagPermanent,
			Force:     flagForce,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.RecipientsRelayed(tx).Delete(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to delete relayed recipient",
			SuccessMessage: "Successfully deleted relayed recipient",
		}

		if flagPermanent {
			runner.SuccessMessage += " permanently"
		}

		runner.Run()
		return nil
	},
}
View Source
var DeleteRemoteSendGrantsCmd = &cobra.Command{
	Use:   "send-grant <remote> <email|domain>",
	Short: "Delete a send grant",
	Long:  "Delete a send grant from a remote. By default performs a soft delete. Use --permanent --force for hard delete.",
	Args:  cobra.ExactArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPermanent, _ := cmd.Flags().GetBool("permanent")
		flagForce, _ := cmd.Flags().GetBool("force")

		if flagPermanent && flagForce {
			return fmt.Errorf("cannot use --permanent and --force flags together")
		}

		argRemoteName := args[0]
		argEmails := ParseEmailOrWildcardArgs(args[1:])
		if len(argEmails) != len(args[1:]) {
			return fmt.Errorf("invalid email or domain argument")
		}
		argEmail := argEmails[0]

		options := db.DeleteOptions{
			Permanent: flagPermanent,
			Force:     flagForce,
		}

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.RemotesSendGrants(tx).Delete(argRemoteName, argEmail, options)
			},
			FailureMessage: "failed to delete send grant",
			SuccessMessage: "Successfully deleted send grant",
		}

		runner.Run()
		return nil
	},
}
View Source
var DeleteRemotesCmd = &cobra.Command{
	Use:   "remotes <name> [<name>...]",
	Short: "Delete one or more remotes (soft delete by default)",
	Args:  cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPermanent, _ := cmd.Flags().GetBool("permanent")
		flagForce, _ := cmd.Flags().GetBool("force")

		options := db.DeleteOptions{
			Permanent: flagPermanent,
			Force:     flagForce,
		}

		runner := db.TxForEachRunner[string]{
			Items: args,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Remotes(tx).Delete(item, options)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "failed to delete remote",
			SuccessMessage: "Successfully deleted remote",
		}

		runner.Run()
		return nil
	},
}
View Source
var DeleteTransportsCmd = &cobra.Command{
	Use:     "transports <name> [name...]",
	Aliases: []string{"transport"},
	Short:   "Delete a transport",
	Long:    "Delete a transport. By default performs a soft delete.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPermanent, _ := cmd.Flags().GetBool("permanent")
		flagForce, _ := cmd.Flags().GetBool("force")

		options := db.DeleteOptions{
			Permanent: flagPermanent,
			Force:     flagForce,
		}

		runner := db.TxForEachRunner[string]{
			Items: args,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Transports(tx).Delete(item, options)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "failed to delete transport",
			SuccessMessage: "Successfully deleted transport",
		}

		if flagPermanent {
			runner.SuccessMessage += " permanently"
		}

		runner.Run()
		return nil
	},
}
View Source
var DescribeCmd = &cobra.Command{
	Use:   "describe",
	Short: "Describe mail system objects",
	Args:  cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		dbConn, err := db.Connect()
		if err != nil {
			utils.PrintErrorWithMessage("failed to connect to database", err)
			return nil
		}
		defer func() {
			if err := dbConn.Close(); err != nil {
				utils.PrintErrorWithMessage("failed to close database connection", err)
			}
		}()

		if strings.Contains(args[0], "@") {

			emailOrWildcard, err := utils.ParseEmailAddressOrWildcard(args[0])
			if err != nil {
				utils.PrintError(fmt.Errorf("failed to parse email: %w", err))
				return nil
			}

			if emailOrWildcard.IsWildcard() {

				if described, err := describeDomainscatchalltargets(dbConn, emailOrWildcard.DomainFQDN); described || err != nil {
					if err != nil {
						utils.PrintError(err)
					}
					return nil
				}
			} else {

				email := utils.EmailAddress{
					DomainFQDN: emailOrWildcard.DomainFQDN,
					LocalPart:  *emailOrWildcard.LocalPart,
				}

				if described, err := describeAliases(dbConn, email); described || err != nil {
					if err != nil {
						utils.PrintError(err)
					}
					return nil
				}

				if described, err := DescribeCanonicalAddress(dbConn, email); described || err != nil {
					if err != nil {
						utils.PrintError(err)
					}
					return nil
				}

				if described, err := describeMailboxes(dbConn, email); described || err != nil {
					if err != nil {
						utils.PrintError(err)
					}
					return nil
				}

				if described, err := describeRecipientsrelayed(dbConn, email); described || err != nil {
					if err != nil {
						utils.PrintError(err)
					}
					return nil
				}
			}

			describeUnknownEmail(dbConn, emailOrWildcard)
			return nil
		} else {

			if described, err := describeDomains(dbConn, args[0]); described || err != nil {
				if err != nil {
					utils.PrintError(err)
				}
				return nil
			}

			if described, err := describeTransports(dbConn, args[0]); described || err != nil {
				if err != nil {
					utils.PrintError(err)
				}
				return nil
			}

			if described, err := describeRemotes(dbConn, args[0]); described || err != nil {
				if err != nil {
					utils.PrintError(err)
				}
				return nil
			}

			describeUnknown(dbConn, args[0])
			return nil
		}
	},
}
View Source
var DisableAliasTargetsCmd = &cobra.Command{
	Use:     "alias-target <alias-email> <target-email>",
	Aliases: []string{"target"},
	Short:   "Disable properties on an alias target",
	Long:    `Disable forwarding and/or sending on an alias target. Use flags to select which property to disable.`,
	Args:    cobra.MinimumNArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagForward, _ := cmd.Flags().GetBool("forward")
		flagSend, _ := cmd.Flags().GetBool("send")

		if !flagForward && !flagSend {
			return fmt.Errorf("at least one of --forward or --send flags must be specified")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		argAliasEmail := argEmails[0]
		argTargetEmails := argEmails[1:]

		disabled := false
		options := db.AliasesTargetsPatchOptions{}
		if flagForward {
			options.ForwardingToTargetEnabled = &disabled
		}
		if flagSend {
			options.SendingFromTargetEnabled = &disabled
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argTargetEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.AliasesTargets(tx).Patch(argAliasEmail, item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return argAliasEmail.String() + " -> " + item.String() },
			FailureMessage: "failed to disable alias target",
			SuccessMessage: "Successfully disable alias target",
		}

		runner.Run()
		return nil
	},
}
View Source
var DisableAliasesCmd = &cobra.Command{
	Use:     "aliases <email> [email...]",
	Aliases: []string{"alias"},
	Short:   "Disables aliases",
	Long:    "Disables aliases.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		enabled := false
		options := db.AliasesPatchOptions{
			Enabled: &enabled,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.Aliases(tx).Patch(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to disable alias",
			SuccessMessage: "Successfully disabled alias",
		}

		runner.Run()
		return nil
	},
}
View Source
var DisableCmd = &cobra.Command{
	Use:   "disable",
	Short: "Disable mail system objects",
}
View Source
var DisableDomainCatchallTargetsCmd = &cobra.Command{
	Use:     "catchall-target <domain> <target-email> [target-email...]",
	Aliases: []string{"catchall"},
	Short:   "Disable forwarding on a domain catch-all target",
	Long:    `Disable forwarding on a domain catch-all target.`,
	Args:    cobra.MinimumNArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		argDomain := args[0]
		argEmails := ParseEmailArgs(args[1:])
		if len(argEmails) != len(args)-1 {
			return fmt.Errorf("invalid email arguments")
		}

		enabled := false
		options := db.DomainsCatchallTargetsPatchOptions{
			ForwardingToTargetEnabled: &enabled,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.DomainsCatchallTargets(tx).Patch(argDomain, item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return "@" + argDomain + " -> " + item.String() },
			FailureMessage: "failed to disable catchall target",
			SuccessMessage: "Successfully disable catchall target",
		}

		runner.Run()
		return nil
	},
}
View Source
var DisableDomainsCmd = &cobra.Command{
	Use:     "domains <fqdn> [fqdn...]",
	Aliases: []string{"domain"},
	Short:   "Disables domains",
	Long:    "Disables domains.\nFQDNs must be valid domain names.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		argDomains := ParseDomainFQDNArgs(args)
		if len(argDomains) != len(args) {
			return fmt.Errorf("invalid domain arguments")
		}

		enabled := false
		options := db.DomainsPatchOptions{
			Enabled: &enabled,
		}

		runner := db.TxForEachRunner[string]{
			Items: argDomains,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Domains(tx).Patch(item, options)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "failed to disable domain",
			SuccessMessage: "Successfully disabled domain",
		}

		runner.Run()
		return nil
	},
}
View Source
var DisableMailboxesCmd = &cobra.Command{
	Use:     "mailboxes <email> [email...]",
	Aliases: []string{"mailbox"},
	Short:   "Disables mailboxes",
	Long:    "Disables login, receiving, and/or sending for mailboxes.\nEmails must be in the format \"name@example.com\".\nIf no specific flags are provided, all three (login, receiving, sending) will be disabled.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagLogin, _ := cmd.Flags().GetBool("login")
		flagReceiving, _ := cmd.Flags().GetBool("receiving")
		flagSending, _ := cmd.Flags().GetBool("sending")

		if !flagLogin && !flagReceiving && !flagSending {
			return fmt.Errorf("at least one of --login, --receiving, or --sending must be true when specified")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		disabled := false
		options := db.MailboxesPatchOptions{}
		if flagLogin {
			options.Login = &disabled
		}
		if flagReceiving {
			options.Receiving = &disabled
		}
		if flagSending {
			options.Sending = &disabled
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.Mailboxes(tx).Patch(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to patch mailbox",
			SuccessMessage: "Successfully patched mailbox",
		}

		runner.Run()
		return nil
	},
}
View Source
var DisableRecipientsRelayed = &cobra.Command{
	Use:     "recipients-relayed <email> [email...]",
	Aliases: []string{"recipient-relayed", "relayed-recipient", "relayed-recipients", "relayed"},
	Short:   "Disables relayed recipients",
	Long:    "Disables relayed recipients.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		enabled := false
		options := db.RecipientsRelayedPatchOptions{
			Enabled: &enabled,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.RecipientsRelayed(tx).Patch(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to disable relayed recipient",
			SuccessMessage: "Successfully disabled relayed recipient",
		}

		runner.Run()
		return nil
	},
}
View Source
var DisableRemotesCmd = &cobra.Command{
	Use:   "remotes <name> [<name>...]",
	Short: "Disable one or more remotes",
	Args:  cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		enabled := false
		options := db.RemotesPatchOptions{
			Enabled: &enabled,
		}

		runner := db.TxForEachRunner[string]{
			Items: args,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Remotes(tx).Patch(item, options)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "Failed to disable remote",
			SuccessMessage: "Successfully disabled remote",
		}

		runner.Run()
		return nil
	},
}
View Source
var EnableAliasTargetsCmd = &cobra.Command{
	Use:     "alias-target <alias-email> <target-email>",
	Aliases: []string{"target"},
	Short:   "Enable properties on an alias target",
	Long:    `Enable forwarding and/or sending on an alias target. Use flags to select which property to enable.`,
	Args:    cobra.MinimumNArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagForward, _ := cmd.Flags().GetBool("forward")
		flagSend, _ := cmd.Flags().GetBool("send")

		if !flagForward && !flagSend {
			return fmt.Errorf("at least one of --forward or --send flags must be specified")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		argAliasEmail := argEmails[0]
		argTargetEmails := argEmails[1:]

		enabled := true
		options := db.AliasesTargetsPatchOptions{}
		if flagForward {
			options.ForwardingToTargetEnabled = &enabled
		}
		if flagSend {
			options.SendingFromTargetEnabled = &enabled
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argTargetEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.AliasesTargets(tx).Patch(argAliasEmail, item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return argAliasEmail.String() + " -> " + item.String() },
			FailureMessage: "failed to enable alias target",
			SuccessMessage: "Successfully enable alias target",
		}

		runner.Run()
		return nil
	},
}
View Source
var EnableAliasesCmd = &cobra.Command{
	Use:     "aliases <email> [email...]",
	Aliases: []string{"alias"},
	Short:   "Enables aliases",
	Long:    "Enables aliases.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		enabled := true
		options := db.AliasesPatchOptions{
			Enabled: &enabled,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.Aliases(tx).Patch(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to enable alias",
			SuccessMessage: "Successfully enabled alias",
		}

		runner.Run()
		return nil
	},
}
View Source
var EnableCmd = &cobra.Command{
	Use:   "enable",
	Short: "Enable mail system objects",
}
View Source
var EnableDomainCatchallTargetsCmd = &cobra.Command{
	Use:     "catchall-targets <domain> <target-email> [target-email...]",
	Aliases: []string{"catchall-target", "catchall"},
	Short:   "Enable forwarding on a domain catch-all target",
	Long:    `Enable forwarding on a domain catch-all target.`,
	Args:    cobra.MinimumNArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		argDomain := args[0]
		argEmails := ParseEmailArgs(args[1:])
		if len(argEmails) != len(args)-1 {
			return fmt.Errorf("invalid email arguments")
		}

		enabled := true
		options := db.DomainsCatchallTargetsPatchOptions{
			ForwardingToTargetEnabled: &enabled,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.DomainsCatchallTargets(tx).Patch(argDomain, item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return "@" + argDomain + " -> " + item.String() },
			FailureMessage: "failed to enable catchall target",
			SuccessMessage: "Successfully enabled catchall target",
		}

		runner.Run()
		return nil
	},
}
View Source
var EnableDomainsCmd = &cobra.Command{
	Use:     "domains <fqdn> [fqdn...]",
	Aliases: []string{"domain"},
	Short:   "Enables domains",
	Long:    "Enables domains.\nFQDNs must be valid domain names.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		argDomains := ParseDomainFQDNArgs(args)
		if len(argDomains) != len(args) {
			return fmt.Errorf("invalid domain arguments")
		}

		enabled := true
		options := db.DomainsPatchOptions{
			Enabled: &enabled,
		}

		runner := db.TxForEachRunner[string]{
			Items: argDomains,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Domains(tx).Patch(item, options)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "failed to enable domain",
			SuccessMessage: "Successfully enabled domain",
		}

		runner.Run()
		return nil
	},
}
View Source
var EnableMailboxesCmd = &cobra.Command{
	Use:     "mailboxes <email> [email...]",
	Aliases: []string{"mailbox"},
	Short:   "Enables mailboxes",
	Long:    "Enables login, receiving, and/or sending for mailboxes.\nEmails must be in the format \"name@example.com\".\nIf no specific flags are provided, all three (login, receiving, sending) will be enabled.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagLogin, _ := cmd.Flags().GetBool("login")
		flagReceiving, _ := cmd.Flags().GetBool("receiving")
		flagSending, _ := cmd.Flags().GetBool("sending")

		if !flagLogin && !flagReceiving && !flagSending {
			return fmt.Errorf("at least one of --login, --receiving, or --sending must be true when specified")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		enabled := true
		options := db.MailboxesPatchOptions{}
		if flagLogin {
			options.Login = &enabled
		}
		if flagReceiving {
			options.Receiving = &enabled
		}
		if flagSending {
			options.Sending = &enabled
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.Mailboxes(tx).Patch(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to enable mailbox",
			SuccessMessage: "Successfully enabled mailbox",
		}

		runner.Run()
		return nil
	},
}
View Source
var EnableRecipientsRelayedCmd = &cobra.Command{
	Use:     "recipients-relayed <email> [email...]",
	Aliases: []string{"recipient-relayed", "relayed-recipient", "relayed-recipients", "relayed"},
	Short:   "Enables relayed recipients",
	Long:    "Enables relayed recipients.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		enabled := true
		options := db.RecipientsRelayedPatchOptions{
			Enabled: &enabled,
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.RecipientsRelayed(tx).Patch(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to enable relayed recipient",
			SuccessMessage: "Successfully enabled relayed recipient",
		}

		runner.Run()
		return nil
	},
}
View Source
var EnableRemotesCmd = &cobra.Command{
	Use:   "remotes <name> [<name>...]",
	Short: "Enable one or more remotes",
	Args:  cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		enabled := true
		options := db.RemotesPatchOptions{
			Enabled: &enabled,
		}

		runner := db.TxForEachRunner[string]{
			Items: args,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Remotes(tx).Patch(item, options)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "Failed to enable remote",
			SuccessMessage: "Successfully enabled remote",
		}

		runner.Run()
		return nil
	},
}
View Source
var (
	ErrNoValueProvided = errors.New("no value provided")
)
View Source
var ListAliasTargetsCmd = &cobra.Command{
	Use:     "alias-targets [alias-email...]",
	Aliases: []string{"alias-target", "targets", "target"},
	Short:   "List all targets for an alias",
	Args:    cobra.ArbitraryArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDeleted, _ := cmd.Flags().GetBool("deleted")
		flagAll, _ := cmd.Flags().GetBool("all")
		flagJSON, _ := cmd.Flags().GetBool("json")
		flagVerbose, _ := cmd.Flags().GetBool("verbose")

		var filterEmails []utils.EmailAddress
		if len(args) > 0 {
			filterEmails = ParseEmailArgs(args)
			if len(filterEmails) != len(args) {
				return fmt.Errorf("invalid email arguments")
			}
		}

		targets, err := listAliasTargets(db.AliasesTargetsListOptions{
			FilterAliasEmails: filterEmails,
			IncludeDeleted:    flagDeleted,
			IncludeAll:        flagAll,
		})
		if err != nil {
			return nil
		}

		if len(targets) == 0 {
			fmt.Println("No targets found")
			return nil
		}

		if flagJSON {
			output, err := json.MarshalIndent(targets, "", "  ")
			if err != nil {
				utils.PrintErrorWithMessage("Failed to marshal targets", err)
				return nil
			}
			fmt.Println(string(output))
			return nil
		}

		headers := []string{"Alias", "Target", "Foreign", "Forward", "Send"}
		if flagVerbose {
			headers = append(headers, "Created", "Updated")
		}
		if flagAll || flagDeleted {
			headers = append(headers, "Deleted")
		}

		t := table.New().
			Border(lipgloss.RoundedBorder()).
			BorderStyle(utils.BlackStyle).
			StyleFunc(func(row, col int) lipgloss.Style {
				if row == table.HeaderRow {
					return utils.TableHeaderStyle
				}
				cellStyle := utils.TableRowStyle
				switch col {
				case 2, 3, 4:
					cellStyle = cellStyle.Align(lipgloss.Center)
				}
				return cellStyle
			}).
			Headers(headers...)

		for _, target := range targets {
			row := []string{
				target.AliasEmail,
				target.TargetEmail,
				utils.MaybeEnabledTableStyle.Render(target.IsForeign),
				utils.MaybeEnabledTableStyle.Render(target.ForwardingToTargetEnabled, target.AliasEnabled, target.DomainEnabled),
				utils.MaybeEnabledTableStyle.Render(target.SendingFromTargetEnabled, target.AliasEnabled, target.DomainEnabled),
			}
			if flagVerbose {
				row = append(row,
					utils.MaybeTimeStyle.Render(target.CreatedAt),
					utils.MaybeTimeStyle.Render(target.UpdatedAt),
				)
			}
			if flagAll || flagDeleted {
				row = append(row,
					utils.MaybeTimeStyle.Render(target.DeletedAt),
				)
			}

			t.Row(row...)
		}

		fmt.Println(t)
		return nil
	},
}
View Source
var ListAliasesCmd = &cobra.Command{
	Use:   "aliases [domain...]",
	Short: "List aliases",
	Long:  "List aliases.\nIf a domains are provided, only aliases for these domains are listed.",
	Args:  cobra.MinimumNArgs(0),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDeleted, _ := cmd.Flags().GetBool("deleted")
		flagAll, _ := cmd.Flags().GetBool("all")
		flagJSON, _ := cmd.Flags().GetBool("json")
		flagVerbose, _ := cmd.Flags().GetBool("verbose")

		if flagDeleted && flagAll {
			return fmt.Errorf("cannot use --deleted and --all flags together")
		}

		filterDomains := ParseDomainFQDNArgs(args)
		if len(filterDomains) != len(args) {
			return fmt.Errorf("invalid domain arguments")
		}

		aliases, err := listAliases(db.AliasesListOptions{
			FilterDomains:  filterDomains,
			IncludeDeleted: flagDeleted,
			IncludeAll:     flagAll,
		})
		if err != nil {
			return nil
		}

		if flagJSON {
			json, err := json.Marshal(aliases)
			if err != nil {
				utils.PrintErrorWithMessage("Failed to marshal aliases to JSON", err)
			}
			fmt.Println(string(json))
			return nil
		}

		headers := []string{"Domain", "Name", "Enabled", "Targets"}
		if flagVerbose {
			headers = append(headers, "Created", "Last Updated")
		}
		if flagDeleted || flagAll {
			headers = append(headers, "Deleted")
		}

		t := table.New().
			Border(lipgloss.RoundedBorder()).
			BorderStyle(utils.BlackStyle).
			StyleFunc(func(row, col int) lipgloss.Style {
				cellStyle := utils.TableRowStyle
				if row == table.HeaderRow {
					cellStyle = utils.TableHeaderStyle
				}
				switch col {
				case 2:
					return cellStyle.Align(lipgloss.Center)
				case 3:
					return cellStyle.Align(lipgloss.Right)
				default:
					return cellStyle.Align(lipgloss.Left)
				}
			}).
			Headers(headers...)

		for _, a := range aliases {
			row := []string{
				a.DomainFQDN,
				utils.MaybeWildcardNameStyle.Render(a.Name),
				utils.MaybeEnabledTableStyle.Render(a.Enabled, a.DomainEnabled),
				utils.MaybeZeroStyle.Render(a.TargetCount),
			}
			if flagVerbose {
				row = append(row,
					utils.MaybeTimeStyle.Render(a.CreatedAt),
					utils.MaybeTimeStyle.Render(a.UpdatedAt),
				)
			}
			if flagDeleted || flagAll {
				row = append(row,
					utils.MaybeTimeStyle.Render(a.DeletedAt),
				)
			}

			t.Row(row...)
		}

		fmt.Println(t.Render())
		return nil
	},
}
View Source
var ListCmd = &cobra.Command{
	Use:   "list",
	Short: "List mail system objects",
}
View Source
var ListDomainCatchallTargetsCmd = &cobra.Command{
	Use:     "catchall-targets [domain...]",
	Aliases: []string{"catchall-target", "catchall"},
	Short:   "List catch-all targets.",
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDeleted, _ := cmd.Flags().GetBool("deleted")
		flagAll, _ := cmd.Flags().GetBool("all")
		flagJSON, _ := cmd.Flags().GetBool("json")
		flagVerbose, _ := cmd.Flags().GetBool("verbose")

		argDomains := args

		options := db.DomainsCatchallTargetsListOptions{
			FilterDomains:  argDomains,
			IncludeDeleted: flagDeleted,
			IncludeAll:     flagAll,
		}

		targets, err := listCatchallTargets(options)
		if err != nil {
			return nil
		}

		if len(targets) == 0 {
			fmt.Println("No catchall targets found for this domain")
			return nil
		}

		if flagJSON {
			out, err := json.MarshalIndent(targets, "", "  ")
			if err != nil {
				utils.PrintErrorWithMessage("Failed to marshal targets", err)
				return nil
			}
			fmt.Println(string(out))
			return nil
		}

		headers := []string{"Domain", "Target", "Forward", "Fallback Only"}
		if flagVerbose {
			headers = append(headers, "Created", "Updated")
		}
		if flagAll || flagDeleted {
			headers = append(headers, "Deleted")
		}

		t := table.New().
			Border(lipgloss.RoundedBorder()).
			BorderStyle(utils.BlackStyle).
			StyleFunc(func(row, col int) lipgloss.Style {
				if row == table.HeaderRow {
					return utils.TableHeaderStyle
				}
				cellStyle := utils.TableRowStyle
				if col == 2 || col == 3 {
					cellStyle = cellStyle.Align(lipgloss.Center)
				}
				return cellStyle
			}).
			Headers(headers...)

		for _, tgt := range targets {
			row := []string{
				tgt.DomainFQDN,
				tgt.TargetEmail,
				utils.MaybeEnabledTableStyle.Render(tgt.ForwardingToTargetEnabled),
				utils.MaybeEnabledTableStyle.Render(tgt.FallbackOnly),
			}
			if flagVerbose {
				row = append(row, utils.MaybeTimeStyle.Render(tgt.CreatedAt), utils.MaybeTimeStyle.Render(tgt.UpdatedAt))
			}
			if flagAll || flagDeleted {
				row = append(row, utils.MaybeTimeStyle.Render(tgt.DeletedAt))
			}
			t.Row(row...)
		}

		fmt.Println(t)
		return nil
	},
}
View Source
var ListDomainsCmd = &cobra.Command{
	Use:   "domains",
	Short: "List domains",
	Long:  "List domains.",
	Args:  cobra.MinimumNArgs(0),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDeleted, _ := cmd.Flags().GetBool("deleted")
		flagAll, _ := cmd.Flags().GetBool("all")
		flagJSON, _ := cmd.Flags().GetBool("json")
		flagVerbose, _ := cmd.Flags().GetBool("verbose")

		if flagDeleted && flagAll {
			return fmt.Errorf("cannot use --deleted and --all flags together")
		}

		options := db.DomainsListOptions{
			IncludeDeleted: flagDeleted,
			IncludeAll:     flagAll,
		}

		domains, err := listDomains(options)
		if err != nil {
			return nil
		}

		if flagJSON {
			jsonData, err := json.Marshal(domains)
			if err != nil {
				utils.PrintErrorWithMessage("failed to marshal domains to JSON", err)
				return nil
			}
			fmt.Println(string(jsonData))
			return nil
		}

		headers := []string{"FQDN", "Type", "Enabled", "Transport / Target Domain"}
		if flagVerbose {
			headers = append(headers, "Created", "Last Updated")
		}
		if flagDeleted || flagAll {
			headers = append(headers, "Deleted")
		}

		t := table.New().
			Border(lipgloss.RoundedBorder()).
			BorderStyle(utils.BlackStyle).
			StyleFunc(func(row, col int) lipgloss.Style {
				cellStyle := utils.TableRowStyle
				if row == table.HeaderRow {
					cellStyle = utils.TableHeaderStyle
				}
				switch col {
				case 2:
					return cellStyle.Align(lipgloss.Center)
				default:
					return cellStyle.Align(lipgloss.Left)
				}
			}).
			Headers(headers...)

		for _, domain := range domains {
			row := []string{
				domain.FQDN,
				domain.Type,
			}
			switch domain.Type {
			case "canonical":
				row = append(row,
					utils.MaybeEnabledTableStyle.Render(domain.Enabled, domain.TargetDomainEnabled),
					utils.MaybeEmptyStyle.Render(domain.TargetDomainFQDN),
				)
			case "alias":
				row = append(row,
					utils.MaybeEnabledTableStyle.Render(domain.Enabled),
					utils.BlackStyle.Render("-"),
				)
			default:
				row = append(row,
					utils.MaybeEnabledTableStyle.Render(domain.Enabled),
					utils.MaybeIDSuffixStyle.Render(domain.Transport, domain.TransportName),
				)
			}
			if flagVerbose {
				row = append(row,
					utils.MaybeTimeStyle.Render(domain.CreatedAt),
					utils.MaybeTimeStyle.Render(domain.UpdatedAt),
				)
			}
			if flagDeleted || flagAll {
				row = append(row,
					utils.MaybeTimeStyle.Render(domain.DeletedAt),
				)
			}

			t.Row(row...)
		}

		fmt.Println(t.Render())
		return nil
	},
}
View Source
var ListMailboxesCmd = &cobra.Command{
	Use:   "mailboxes [domain...]",
	Short: "List mailboxes",
	Long:  "List mailboxes.\nIf domains are provided, only mailboxes for these domains are listed.",
	Args:  cobra.MinimumNArgs(0),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDeleted, _ := cmd.Flags().GetBool("deleted")
		flagAll, _ := cmd.Flags().GetBool("all")
		flagJSON, _ := cmd.Flags().GetBool("json")
		flagVerbose, _ := cmd.Flags().GetBool("verbose")

		if flagDeleted && flagAll {
			return fmt.Errorf("cannot use --deleted and --all flags together")
		}

		filterDomains := ParseDomainFQDNArgs(args)
		if len(filterDomains) != len(args) {
			return fmt.Errorf("invalid domain arguments")
		}

		options := db.MailboxesListOptions{
			FilterDomains:  filterDomains,
			IncludeDeleted: flagDeleted,
			IncludeAll:     flagAll,
		}

		mailboxes, err := listMailboxes(options)
		if err != nil {
			return nil
		}

		if flagJSON {
			out, err := json.Marshal(mailboxes)
			if err != nil {
				utils.PrintErrorWithMessage("Failed to marshal mailboxes to JSON", err)
				return nil
			}
			fmt.Println(string(out))
			return nil
		}

		headers := []string{"Domain", "Name", "Login", "Receive", "Send", "Pwd", "Quota", "Transport"}
		if flagVerbose {
			headers = append(headers, "Created", "Last Updated")
		}
		if flagDeleted || flagAll {
			headers = append(headers, "Deleted")
		}

		t := table.New().
			Border(lipgloss.RoundedBorder()).
			BorderStyle(utils.BlackStyle).
			StyleFunc(func(row, col int) lipgloss.Style {
				cellStyle := utils.TableRowStyle
				if row == table.HeaderRow {
					cellStyle = utils.TableHeaderStyle
				}
				switch col {
				case 2, 3, 4, 5:
					return cellStyle.Align(lipgloss.Center)
				case 6:
					return cellStyle.Align(lipgloss.Right)
				default:
					return cellStyle.Align(lipgloss.Left)
				}
			}).
			Headers(headers...)

		for _, m := range mailboxes {
			row := []string{
				m.DomainFQDN,
				m.Name,
				utils.MaybeEnabledTableStyle.Render(m.LoginEnabled, m.DomainEnabled),
				utils.MaybeEnabledTableStyle.Render(m.ReceivingEnabled, m.DomainEnabled),
				utils.MaybeEnabledTableStyle.Render(m.SendingEnabled, m.DomainEnabled),
				utils.MaybePasswordStyle.Render(m.PasswordSet),
				utils.MaybeQuotaStyle.Render(m.StorageQuota, 1024*1024),
				utils.MaybeIDSuffixStyle.Render(m.Transport, m.TransportName),
			}

			if flagVerbose {
				row = append(row,
					utils.MaybeTimeStyle.Render(m.CreatedAt),
					utils.MaybeTimeStyle.Render(m.UpdatedAt),
				)
			}
			if flagDeleted || flagAll {
				row = append(row,
					utils.MaybeTimeStyle.Render(m.DeletedAt),
				)
			}

			t.Row(row...)
		}

		fmt.Println(t.Render())
		return nil
	},
}
View Source
var ListRecipientsRelayedCmd = &cobra.Command{
	Use:   "relayed-recipients [domain...]",
	Short: "List relayed recipients",
	Long:  "List relayed recipients.\nIf domains are provided, only recipients for these domains are listed.",
	Args:  cobra.MinimumNArgs(0),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDeleted, _ := cmd.Flags().GetBool("deleted")
		flagAll, _ := cmd.Flags().GetBool("all")
		flagJSON, _ := cmd.Flags().GetBool("json")
		flagVerbose, _ := cmd.Flags().GetBool("verbose")

		if flagDeleted && flagAll {
			return fmt.Errorf("cannot use --deleted and --all flags together")
		}

		filterDomains := ParseDomainFQDNArgs(args)
		if len(filterDomains) != len(args) {
			return fmt.Errorf("invalid domain arguments")
		}

		options := db.RecipientsRelayedListOptions{
			FilterDomains:  filterDomains,
			IncludeDeleted: flagDeleted,
			IncludeAll:     flagAll,
		}

		recipients, err := listRelayedRecipients(options)
		if err != nil {
			return nil
		}

		if flagJSON {
			out, err := json.Marshal(recipients)
			if err != nil {
				utils.PrintErrorWithMessage("Failed to marshal recipients to JSON", err)
				return nil
			}
			fmt.Println(string(out))
			return nil
		}

		if len(recipients) == 0 {
			fmt.Println("No relayed recipients found")
			return nil
		}

		headers := []string{"Domain", "Name", "Enabled"}
		if flagVerbose {
			headers = append(headers, "Created", "Last Updated")
		}
		if flagDeleted || flagAll {
			headers = append(headers, "Deleted")
		}

		t := table.New().
			Border(lipgloss.RoundedBorder()).
			BorderStyle(utils.BlackStyle).
			StyleFunc(func(row, col int) lipgloss.Style {
				cellStyle := utils.TableRowStyle
				if row == table.HeaderRow {
					cellStyle = utils.TableHeaderStyle
				}
				switch col {
				case 2:
					return cellStyle.Align(lipgloss.Center)
				default:
					return cellStyle.Align(lipgloss.Left)
				}
			}).
			Headers(headers...)

		for _, r := range recipients {
			row := []string{
				r.DomainFQDN,
				r.Name,
				utils.MaybeEnabledTableStyle.Render(r.Enabled, r.DomainEnabled),
			}
			if flagVerbose {
				row = append(row,
					utils.MaybeTimeStyle.Render(r.CreatedAt),
					utils.MaybeTimeStyle.Render(r.UpdatedAt),
				)
			}
			if flagDeleted || flagAll {
				row = append(row,
					utils.MaybeTimeStyle.Render(r.DeletedAt),
				)
			}

			t.Row(row...)
		}

		fmt.Println(t.Render())
		return nil
	},
}
View Source
var ListRemoteSendGrantsCmd = &cobra.Command{
	Use:   "send-grants [name...]",
	Short: "List send grants for remotes",
	Args:  cobra.ArbitraryArgs,
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDeleted, _ := cmd.Flags().GetBool("deleted")
		flagAll, _ := cmd.Flags().GetBool("all")
		flagJSON, _ := cmd.Flags().GetBool("json")
		flagVerbose, _ := cmd.Flags().GetBool("verbose")

		if flagDeleted && flagAll {
			return fmt.Errorf("cannot use --deleted and --all flags together")
		}

		var filterNames []string
		if len(args) > 0 {
			filterNames = args
		}

		grants, err := listGrants(db.RemotesSendGrantsListOptions{
			FilterRemoteNames: filterNames,
			IncludeDeleted:    flagDeleted,
			IncludeAll:        flagAll,
		})
		if err != nil {
			return nil
		}

		if flagJSON {
			out, err := json.Marshal(grants)
			if err != nil {
				utils.PrintErrorWithMessage("Failed to marshal send grants to JSON", err)
				return nil
			}
			fmt.Println(string(out))
			return nil
		}

		if len(grants) == 0 {
			fmt.Println("No send grants found")
			return nil
		}

		headers := []string{"Remote", "Domain", "Name"}
		if flagVerbose {
			headers = append(headers, "Created", "Last Updated")
		}
		if flagDeleted || flagAll {
			headers = append(headers, "Deleted")
		}

		t := table.New().
			Border(lipgloss.RoundedBorder()).
			BorderStyle(utils.BlackStyle).
			StyleFunc(func(row, col int) lipgloss.Style {
				cellStyle := utils.TableRowStyle
				if row == table.HeaderRow {
					cellStyle = utils.TableHeaderStyle
				}
				switch col {
				default:
					return cellStyle.Align(lipgloss.Left)
				}
			}).
			Headers(headers...)

		for _, g := range grants {
			row := []string{
				g.RemoteName,
				g.DomainFQDN,
				utils.SQLLikeStyle.Render(g.Name),
			}

			if flagVerbose {
				row = append(row,
					utils.MaybeTimeStyle.Render(g.CreatedAt),
					utils.MaybeTimeStyle.Render(g.UpdatedAt),
				)
			}
			if flagDeleted || flagAll {
				row = append(row,
					utils.MaybeTimeStyle.Render(g.DeletedAt),
				)
			}

			t.Row(row...)
		}

		fmt.Println(t.Render())
		return nil
	},
}
View Source
var ListRemotesCmd = &cobra.Command{
	Use:   "remotes",
	Short: "List all remotes",
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDeleted, _ := cmd.Flags().GetBool("deleted")
		flagAll, _ := cmd.Flags().GetBool("all")
		flagJSON, _ := cmd.Flags().GetBool("json")
		flagVerbose, _ := cmd.Flags().GetBool("verbose")

		if flagDeleted && flagAll {
			return fmt.Errorf("cannot use --deleted and --all flags together")
		}

		options := db.RemotesListOptions{
			IncludeDeleted: flagDeleted,
			IncludeAll:     flagAll,
		}

		remotes, err := listRemotes(options)
		if err != nil {
			return nil
		}

		if flagJSON {
			out, err := json.Marshal(remotes)
			if err != nil {
				utils.PrintErrorWithMessage("Failed to marshal remotes to JSON", err)
				return nil
			}
			fmt.Println(string(out))
			return nil
		}

		if len(remotes) == 0 {
			fmt.Println("No remotes found")
			return nil
		}

		headers := []string{"Name", "Enabled", "Pwd"}
		if flagVerbose {
			headers = append(headers, "Created", "Last Updated")
		}
		if flagDeleted || flagAll {
			headers = append(headers, "Deleted")
		}

		t := table.New().
			Border(lipgloss.RoundedBorder()).
			BorderStyle(utils.BlackStyle).
			StyleFunc(func(row, col int) lipgloss.Style {
				cellStyle := utils.TableRowStyle
				if row == table.HeaderRow {
					cellStyle = utils.TableHeaderStyle
				}
				switch col {
				case 1, 2:
					return cellStyle.Align(lipgloss.Center)
				default:
					return cellStyle.Align(lipgloss.Left)
				}
			}).
			Headers(headers...)

		for _, r := range remotes {
			row := []string{
				r.Name,
				utils.MaybeEnabledTableStyle.Render(r.Enabled),
				utils.MaybePasswordStyle.Render(r.PasswordSet),
			}

			if flagVerbose {
				row = append(row,
					utils.MaybeTimeStyle.Render(r.CreatedAt),
					utils.MaybeTimeStyle.Render(r.UpdatedAt),
				)
			}
			if flagDeleted || flagAll {
				row = append(row,
					utils.MaybeTimeStyle.Render(r.DeletedAt),
				)
			}

			t.Row(row...)
		}

		fmt.Println(t.Render())
		return nil
	},
}
View Source
var ListTransportsCmd = &cobra.Command{
	Use:   "transports",
	Short: "List transports",
	Long:  "List all transports. Use --deleted to show only deleted transports, --all to show both active and deleted.",
	RunE: func(cmd *cobra.Command, args []string) error {
		flagDeleted, _ := cmd.Flags().GetBool("deleted")
		flagAll, _ := cmd.Flags().GetBool("all")
		flagJSON, _ := cmd.Flags().GetBool("json")
		flagVerbose, _ := cmd.Flags().GetBool("verbose")

		transports, err := listTransports(db.TransportsListOptions{IncludeDeleted: flagDeleted, IncludeAll: flagAll})
		if err != nil {
			return nil
		}

		if flagJSON {
			encoder := json.NewEncoder(os.Stdout)
			if err := encoder.Encode(transports); err != nil {
				utils.PrintErrorWithMessage("Failed to encode JSON", err)
				return nil
			}
			return nil
		}

		headers := []string{"Name", "Method", "Host", "Port", "MX Lookup"}
		if flagVerbose {
			headers = append(headers, "Created", "Last Updated")
		}
		if flagDeleted || flagAll {
			headers = append(headers, "Deleted")
		}

		t := table.New().
			Border(lipgloss.RoundedBorder()).
			BorderStyle(utils.BlackStyle).
			Headers(headers...).
			StyleFunc(func(row, col int) lipgloss.Style {
				if row == table.HeaderRow {
					return utils.TableHeaderStyle
				}
				cellStyle := utils.TableRowStyle
				switch col {
				case 3:
					return cellStyle.Align(lipgloss.Right)
				case 4:
					return cellStyle.Align(lipgloss.Center)
				}
				return cellStyle
			})

		mxLookupStyle := utils.MaybeEnabledTableStyle
		mxLookupStyle.TrueStyle = utils.BlueStyle.Bold(true)
		mxLookupStyle.FalseStyle = utils.BlackStyle.Bold(true)

		for _, transport := range transports {
			row := []string{
				transport.Name,
				transport.Method,
				transport.Host,
				utils.MaybeEmptyStyle.Render(transport.Port),
				mxLookupStyle.Render(transport.MXLookup),
			}

			if flagVerbose {
				row = append(row,
					utils.MaybeTimeStyle.Render(transport.CreatedAt),
					utils.MaybeTimeStyle.Render(transport.UpdatedAt),
				)
			}

			if flagAll || flagDeleted {
				row = append(row,
					utils.MaybeTimeStyle.Render(transport.DeletedAt),
				)
			}

			t.Row(row...)
		}

		fmt.Println(t.Render())
		return nil
	},
}
View Source
var PatchAliasTargetsCmd = &cobra.Command{
	Use:     "alias-target <email> [flags]",
	Aliases: []string{"target"},
	Short:   "Update an existing alias target",
	Long:    "Updates properties of an existing alias target.",
	Args:    cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		if !cmd.Flags().Changed("forward") && !cmd.Flags().Changed("send") {
			return fmt.Errorf("at least one of --forward or --send flags must be specified")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		argAliasEmail := argEmails[0]
		argTargetEmails := argEmails[1:]

		options := db.AliasesTargetsPatchOptions{}
		if cmd.Flags().Changed("forward") {
			flagForward, _ := cmd.Flags().GetBool("forward")
			options.ForwardingToTargetEnabled = &flagForward
		}
		if cmd.Flags().Changed("send") {
			flagSend, _ := cmd.Flags().GetBool("send")
			options.SendingFromTargetEnabled = &flagSend
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argTargetEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.AliasesTargets(tx).Patch(argAliasEmail, item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return argAliasEmail.String() + " -> " + item.String() },
			FailureMessage: "failed to patch alias target",
			SuccessMessage: "Successfully patch alias target",
		}

		runner.Run()
		return nil
	},
}
View Source
var PatchAliasesCmd = &cobra.Command{
	Use:     "aliases <email> [flags]",
	Aliases: []string{"alias"},
	Short:   "Update an existing alias",
	Long:    "Updates properties of an existing alias. Emails must be in the format \"name@example.com\".",
	Args:    cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagEnabled, _ := cmd.Flags().GetBool("enabled")

		if !cmd.Flags().Changed("enabled") {
			return fmt.Errorf("no changes specified")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		options := db.AliasesPatchOptions{}

		if cmd.Flags().Changed("enabled") {
			options.Enabled = &flagEnabled
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.Aliases(tx).Patch(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to patch alias",
			SuccessMessage: "Successfully patched alias",
		}

		runner.Run()
		return nil
	},
}
View Source
var PatchCmd = &cobra.Command{
	Use:   "patch",
	Short: "Patch mail system objects",
}
View Source
var PatchDomainCatchallTargetsCmd = &cobra.Command{
	Use:     "catchall-targets <domain> <target> [flags]",
	Aliases: []string{"catchall-target", "catchall"},
	Short:   "Update an existing catchall target",
	Long:    "Updates properties of an existing catchall target.",
	Args:    cobra.ExactArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		if !cmd.Flags().Changed("forward") && !cmd.Flags().Changed("fallback-only") {
			return fmt.Errorf("at least one of --forward or --fallback-only flags must be specified")
		}

		argDomain := args[0]
		argEmails := ParseEmailArgs(args[1:])
		if len(argEmails) != len(args)-1 {
			return fmt.Errorf("invalid email arguments")
		}

		options := db.DomainsCatchallTargetsPatchOptions{}
		if cmd.Flags().Changed("forward") {
			flagForward, _ := cmd.Flags().GetBool("forward")
			options.ForwardingToTargetEnabled = &flagForward
		}
		if cmd.Flags().Changed("fallback-only") {
			flagOnly, _ := cmd.Flags().GetBool("fallback-only")
			options.FallbackOnly = &flagOnly
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.DomainsCatchallTargets(tx).Patch(argDomain, item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return "@" + argDomain + " -> " + item.String() },
			FailureMessage: "failed to patch catchall target",
			SuccessMessage: "Successfully patched catchall target",
		}

		runner.Run()
		return nil
	},
}
View Source
var PatchDomainsCmd = &cobra.Command{
	Use:     "domains <fqdn> [fqdn...]",
	Aliases: []string{"domain"},
	Short:   "Update an existing domain",
	Long:    "Updates properties of an existing domain.\nFQDNs must be valid domain names.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagEnabled, _ := cmd.Flags().GetBool("enabled")
		flagTransport, _ := cmd.Flags().GetString("transport")
		flagTargetDomain, _ := cmd.Flags().GetString("target-domain")

		if !cmd.Flags().Changed("enabled") && !cmd.Flags().Changed("transport") && !cmd.Flags().Changed("target-domain") {
			return fmt.Errorf("no changes specified")
		}

		argDomains := ParseDomainFQDNArgs(args)
		if len(argDomains) != len(args) {
			return fmt.Errorf("invalid domain arguments")
		}

		var options db.DomainsPatchOptions
		if cmd.Flags().Changed("enabled") {
			options.Enabled = &flagEnabled
		}
		if cmd.Flags().Changed("transport") {
			options.TransportName = &flagTransport
		}
		if cmd.Flags().Changed("target-domain") {
			options.TargetDomainFQDN = &flagTargetDomain
		}

		runner := db.TxForEachRunner[string]{
			Items: argDomains,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Domains(tx).Patch(item, options)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "failed to patch domain",
			SuccessMessage: "Successfully patched domain",
		}

		runner.Run()
		return nil
	},
}
View Source
var PatchMailboxesCmd = &cobra.Command{
	Use:   "mailbox <email>",
	Short: "Update an existing mailbox",
	Long:  "Updates properties of an existing mailbox.",
	Args:  cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPassword, _ := cmd.Flags().GetBool("password")
		flagPasswordStdin, _ := cmd.Flags().GetBool("password-stdin")
		flagPasswordMethod, _ := cmd.Flags().GetString("password-method")
		flagPasswordNo, _ := cmd.Flags().GetBool("no-password")

		if (flagPassword || flagPasswordStdin) && flagPasswordNo {
			return fmt.Errorf("cannot use --no-password with --password or --password-stdin")
		}

		if flagPassword && flagPasswordStdin {
			return fmt.Errorf("cannot use both --password and --password-stdin")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		argEmail := argEmails[0]

		options := db.MailboxesPatchOptions{}
		if flagPassword || flagPasswordStdin {
			passwordHash, err := ReadPasswordHashed(flagPasswordMethod, flagPasswordStdin)
			if err != nil {
				utils.PrintErrorWithMessage("failed to read password", err)
				return nil
			}
			options.PasswordHash = &sql.NullString{Valid: true, String: passwordHash}
		}
		if flagPasswordNo {
			options.PasswordHash = &sql.NullString{Valid: false}
		}
		if cmd.Flags().Changed("quota") {
			q, _ := cmd.Flags().GetInt32("quota")
			if q <= 0 {
				options.Quota = &sql.NullInt32{Valid: false}
			} else {
				options.Quota = &sql.NullInt32{Valid: true, Int32: q}
			}
		}
		if cmd.Flags().Changed("transport") {
			transportName, _ := cmd.Flags().GetString("transport")
			if transportName == "-" {
				options.TransportName = &sql.NullString{Valid: false}
			} else {
				options.TransportName = &sql.NullString{Valid: true, String: transportName}
			}
		}
		if cmd.Flags().Changed("login") {
			v, _ := cmd.Flags().GetBool("login")
			options.Login = &v
		}
		if cmd.Flags().Changed("receiving") {
			v, _ := cmd.Flags().GetBool("receiving")
			options.Receiving = &v
		}
		if cmd.Flags().Changed("sending") {
			v, _ := cmd.Flags().GetBool("sending")
			options.Sending = &v
		}

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.Mailboxes(tx).Patch(argEmail, options)
			},
			ItemString:     argEmail.String(),
			FailureMessage: "failed to patch mailbox",
			SuccessMessage: "Successfully patched mailbox",
		}

		runner.Run()
		return nil
	},
}
View Source
var PatchRecipientsRelayedCmd = &cobra.Command{
	Use:     "recipients-relayed <email> [flags]",
	Aliases: []string{"recipient-relayed", "relayed-recipient", "relayed-recipients", "relayed"},
	Short:   "Update an existing relayed recipient",
	Long:    "Updates properties of an existing relayed recipient. Emails must be in the format \"name@example.com\".",
	Args:    cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagEnabled, _ := cmd.Flags().GetBool("enabled")

		if !cmd.Flags().Changed("enabled") {
			return fmt.Errorf("no changes specified")
		}

		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		options := db.RecipientsRelayedPatchOptions{}
		if cmd.Flags().Changed("enabled") {
			options.Enabled = &flagEnabled
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.RecipientsRelayed(tx).Patch(item, options)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to patch relayed recipient",
			SuccessMessage: "Successfully patched relayed recipient",
		}

		runner.Run()
		return nil
	},
}
View Source
var PatchRemotesCmd = &cobra.Command{
	Use:   "remote <hostname> [flags]",
	Short: "Update an existing remote",
	Long:  "Updates properties of an existing remote.",
	Args:  cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagPassword, _ := cmd.Flags().GetBool("password")
		flagPasswordStdin, _ := cmd.Flags().GetBool("password-stdin")
		flagPasswordMethod, _ := cmd.Flags().GetString("password-method")
		flagPasswordNo, _ := cmd.Flags().GetBool("no-password")

		if (flagPassword || flagPasswordStdin) && flagPasswordNo {
			return fmt.Errorf("cannot use --no-password with --password or --password-stdin")
		}

		if flagPassword && flagPasswordStdin {
			return fmt.Errorf("cannot use both --password and --password-stdin")
		}

		if !flagPassword && !flagPasswordStdin && !flagPasswordNo && !cmd.Flags().Changed("enabled") {
			return fmt.Errorf("no changes specified. Use --password, --no-password, or --enabled flags")
		}

		name := args[0]

		options := db.RemotesPatchOptions{}

		if cmd.Flags().Changed("enabled") {
			flagEnabled, _ := cmd.Flags().GetBool("enabled")
			options.Enabled = &flagEnabled
		}

		if flagPassword || flagPasswordStdin {
			passwordHash, err := ReadPasswordHashed(flagPasswordMethod, flagPasswordStdin)
			if err != nil {
				utils.PrintErrorWithMessage("failed to read password", err)
				return nil
			}
			options.PasswordHash = &sql.NullString{
				String: passwordHash,
				Valid:  true,
			}
		}
		if flagPasswordNo {
			options.PasswordHash = &sql.NullString{
				Valid: false,
			}
		}

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.Remotes(tx).Patch(name, options)
			},
			ItemString:     name,
			FailureMessage: "failed to patch remote",
			SuccessMessage: "Successfully patched remote",
		}

		runner.Run()
		return nil
	},
}
View Source
var PatchTransportsCmd = &cobra.Command{
	Use:     "transports <name> [flags]",
	Aliases: []string{"transport"},
	Short:   "Update an existing transport",
	Long:    "Updates properties of an existing transport.",
	Args:    cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagMethod, _ := cmd.Flags().GetString("method")
		flagHost, _ := cmd.Flags().GetString("host")
		flagPort, _ := cmd.Flags().GetUint16("port")
		flagMxLookup, _ := cmd.Flags().GetBool("mx-lookup")

		if !cmd.Flags().Changed("method") && !cmd.Flags().Changed("host") && !cmd.Flags().Changed("port") && !cmd.Flags().Changed("mx-lookup") {
			return fmt.Errorf("no changes specified")
		}

		options := db.TransportsPatchOptions{}
		if cmd.Flags().Changed("method") {
			options.Method = &flagMethod
		}
		if cmd.Flags().Changed("host") {
			options.Host = &flagHost
		}
		if cmd.Flags().Changed("port") {
			if flagPort > 0 {
				options.Port = &sql.NullInt32{Int32: int32(flagPort), Valid: true}
			} else {
				options.Port = &sql.NullInt32{Valid: false}
			}
		}
		if cmd.Flags().Changed("mx-lookup") {
			options.MxLookup = &flagMxLookup
		}

		runner := db.TxForEachRunner[string]{
			Items: args,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Transports(tx).Patch(item, options)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "failed to patch transport",
			SuccessMessage: "Successfully patched transport",
		}

		runner.Run()
		return nil
	},
}
View Source
var RenameAliasesCmd = &cobra.Command{
	Use:     "alias <email> <new-email>",
	Aliases: []string{"aliases"},
	Short:   "Rename an alias",
	Long:    "Rename an alias.\nEmail must be in the format \"name@example.com\".",
	Args:    cobra.ExactArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}
		argOldEmail := argEmails[0]
		argNewEmail := argEmails[1]

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.Aliases(tx).Rename(argOldEmail, argNewEmail)
			},
			ItemString:     fmt.Sprintf("%s -> %s", argOldEmail.String(), argNewEmail.String()),
			FailureMessage: "failed to rename alias",
			SuccessMessage: "Successfully renamed alias",
		}

		runner.Run()
		return nil
	},
}
View Source
var RenameCmd = &cobra.Command{
	Use:   "rename",
	Short: "Rename mail system objects",
}
View Source
var RenameDomainsCmd = &cobra.Command{
	Use:     "domain <old-fqdn> <new-fqdn>",
	Aliases: []string{"domains"},
	Short:   "Rename a domain",
	Long:    "Rename a domain by changing its FQDN.\nFQDNs must be valid domain names.",
	Args:    cobra.ExactArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		argDomains := ParseDomainFQDNArgs(args)
		if len(argDomains) != len(args) {
			return fmt.Errorf("invalid domain arguments")
		}

		oldDomain := argDomains[0]
		newDomain := argDomains[1]

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.Domains(tx).Rename(oldDomain, newDomain)
			},
			ItemString:     fmt.Sprintf("%s -> %s", oldDomain, newDomain),
			FailureMessage: "failed to rename domain",
			SuccessMessage: "Successfully renamed domain",
		}

		runner.Run()
		return nil
	},
}
View Source
var RenameMailboxesCmd = &cobra.Command{
	Use:     "mailbox <email> <new-email>",
	Aliases: []string{"mailboxes"},
	Short:   "Rename a mailbox",
	Long:    "Rename a mailbox.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.ExactArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		oldEmail := argEmails[0]
		newEmail := argEmails[1]

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.Mailboxes(tx).Rename(oldEmail, newEmail)
			},
			ItemString:     fmt.Sprintf("%s -> %s", oldEmail, newEmail),
			FailureMessage: "failed to rename mailbox",
			SuccessMessage: "Successfully renamed mailbox",
		}

		runner.Run()
		return nil
	},
}
View Source
var RenameRecipientsRelayedCmd = &cobra.Command{
	Use:     "recipient-relayed <email> <new-email>",
	Aliases: []string{"relayed-recipient", "relayed"},
	Short:   "Rename a relayed recipient",
	Long:    "Rename a relayed recipient.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.ExactArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}
		oldEmail := argEmails[0]
		newEmail := argEmails[1]

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.RecipientsRelayed(tx).Rename(oldEmail, newEmail)
			},
			ItemString:     fmt.Sprintf("%s -> %s", oldEmail.String(), newEmail.String()),
			FailureMessage: "failed to rename relayed recipient",
			SuccessMessage: "Successfully renamed relayed recipient",
		}

		runner.Run()
		return nil
	},
}
View Source
var RenameRemotesCmd = &cobra.Command{
	Use:   "remote <old-name> <new-name>",
	Short: "Rename a remote",
	Args:  cobra.ExactArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		oldName := args[0]
		newName := args[1]

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.Remotes(tx).Rename(oldName, newName)
			},
			ItemString:     fmt.Sprintf("%s -> %s", oldName, newName),
			FailureMessage: "failed to rename remote",
			SuccessMessage: "Successfully renamed remote",
		}

		runner.Run()
		return nil
	},
}
View Source
var RenameTransportsCmd = &cobra.Command{
	Use:     "transport <old-name> <new-name>",
	Aliases: []string{"transports"},
	Short:   "Rename a transport",
	Long:    "Rename a transport by changing its name.",
	Args:    cobra.ExactArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		oldName := args[0]
		newName := args[1]

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.Transports(tx).Rename(oldName, newName)
			},
			ItemString:     fmt.Sprintf("%s -> %s", oldName, newName),
			FailureMessage: "failed to rename transport",
			SuccessMessage: "Successfully renamed transport",
		}

		runner.Run()
		return nil
	},
}
View Source
var RestoreAliasTargetsCmd = &cobra.Command{
	Use:     "alias-target <alias-email> <target-email>",
	Aliases: []string{"target"},
	Short:   "Restore a soft-deleted alias target",
	Long:    `Restore a soft-deleted target to an alias. Both emails must be in the format name@domain.`,
	Args:    cobra.MinimumNArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		argAliasEmail := argEmails[0]
		argTargetEmails := argEmails[1:]

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argTargetEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.AliasesTargets(tx).Restore(argAliasEmail, item)
			},
			ItemString:     func(item utils.EmailAddress) string { return argAliasEmail.String() + " -> " + item.String() },
			FailureMessage: "failed to restore alias target",
			SuccessMessage: "Successfully restore alias target",
		}

		runner.Run()
		return nil
	},
}
View Source
var RestoreAliasesCmd = &cobra.Command{
	Use:     "aliases <email> [email...]",
	Aliases: []string{"alias"},
	Short:   "Restores soft-deleted aliases",
	Long:    "Restores soft-deleted aliases.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.Aliases(tx).Restore(item)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to restore alias",
			SuccessMessage: "Successfully restored alias",
		}

		runner.Run()
		return nil
	},
}
View Source
var RestoreCmd = &cobra.Command{
	Use:   "restore",
	Short: "Restore soft-deleted mail system objects",
}
View Source
var RestoreDomainCatchallTargetsCmd = &cobra.Command{
	Use:     "catchall-targets <domain> <target-email> [target-email...]",
	Aliases: []string{"catchall-target", "catchall"},
	Short:   "Restore a soft-deleted domain catch-all target",
	Long:    `Restore a soft-deleted domain catch-all target.`,
	Args:    cobra.MinimumNArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		argDomain := args[0]
		argEmails := ParseEmailArgs(args[1:])
		if len(argEmails) != len(args)-1 {
			return fmt.Errorf("invalid email arguments")
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.DomainsCatchallTargets(tx).Restore(argDomain, item)
			},
			ItemString:     func(item utils.EmailAddress) string { return "@" + argDomain + " -> " + item.String() },
			FailureMessage: "failed to restore catchall target",
			SuccessMessage: "Successfully restored catchall target",
		}

		runner.Run()
		return nil
	},
}
View Source
var RestoreDomainsCmd = &cobra.Command{
	Use:     "domains <fqdn> [fqdn...]",
	Aliases: []string{"domain"},
	Short:   "Restores soft-deleted domains",
	Long:    "Restores soft-deleted domains.\nFQDNs must be valid domain names.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		argDomains := ParseDomainFQDNArgs(args)
		if len(argDomains) != len(args) {
			return fmt.Errorf("invalid domain arguments")
		}

		runner := db.TxForEachRunner[string]{
			Items: argDomains,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Domains(tx).Restore(item)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "failed to restore domain",
			SuccessMessage: "Successfully restored domain",
		}

		runner.Run()
		return nil
	},
}
View Source
var RestoreMailboxesCmd = &cobra.Command{
	Use:     "mailboxes <email> [email...]",
	Aliases: []string{"mailbox"},
	Short:   "Restores soft-deleted mailboxes",
	Long:    "Restores soft-deleted mailboxes.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.Mailboxes(tx).Restore(item)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to restore mailbox",
			SuccessMessage: "Successfully restored mailbox",
		}

		runner.Run()
		return nil
	},
}
View Source
var RestoreRecipientsRelayedCmd = &cobra.Command{
	Use:     "recipients-relayed <email> [email...]",
	Aliases: []string{"recipient-relayed", "relayed-recipient", "relayed-recipients", "relayed"},
	Short:   "Restores soft-deleted relayed recipients",
	Long:    "Restores soft-deleted relayed recipients.\nEmails must be in the format \"name@example.com\".",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		argEmails := ParseEmailArgs(args)
		if len(argEmails) != len(args) {
			return fmt.Errorf("invalid email arguments")
		}

		runner := db.TxForEachRunner[utils.EmailAddress]{
			Items: argEmails,
			Exec: func(tx *sql.Tx, item utils.EmailAddress) error {
				return db.RecipientsRelayed(tx).Restore(item)
			},
			ItemString:     func(item utils.EmailAddress) string { return item.String() },
			FailureMessage: "failed to restore relayed recipient",
			SuccessMessage: "Successfully restored relayed recipient",
		}

		runner.Run()
		return nil
	},
}
View Source
var RestoreRemoteSendGrantsCmd = &cobra.Command{
	Use:   "send-grant <remote> <email|domain>",
	Short: "Restore a soft-deleted send grant",
	Args:  cobra.ExactArgs(2),
	RunE: func(cmd *cobra.Command, args []string) error {
		argRemoteName := args[0]
		argEmails := ParseEmailOrWildcardArgs(args[1:])
		if len(argEmails) != len(args[1:]) {
			return fmt.Errorf("invalid email or domain argument")
		}
		argEmail := argEmails[0]

		runner := db.TxRunner{
			Exec: func(tx *sql.Tx) error {
				return db.RemotesSendGrants(tx).Restore(argRemoteName, argEmail)
			},
			FailureMessage: "failed to restore send grant",
			SuccessMessage: "Successfully restored send grant",
		}

		runner.Run()
		return nil
	},
}
View Source
var RestoreRemotesCmd = &cobra.Command{
	Use:   "remotes <name> [<name>...]",
	Short: "Restore one or more soft-deleted remotes",
	Args:  cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		runner := db.TxForEachRunner[string]{
			Items: args,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Remotes(tx).Restore(item)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "failed to restore remote",
			SuccessMessage: "Successfully restored remote",
		}

		runner.Run()
		return nil
	},
}
View Source
var RestoreTransportsCmd = &cobra.Command{
	Use:     "transports <name> [name...]",
	Aliases: []string{"transport"},
	Short:   "Restore a soft-deleted transport",
	Long:    "Restore a soft-deleted transport.",
	Args:    cobra.MinimumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		runner := db.TxForEachRunner[string]{
			Items: args,
			Exec: func(tx *sql.Tx, item string) error {
				return db.Transports(tx).Restore(item)
			},
			ItemString:     func(item string) string { return item },
			FailureMessage: "Failed to restore transport",
			SuccessMessage: "Successfully restored transport",
		}

		runner.Run()
		return nil
	},
}
View Source
var SchemaCmd = &cobra.Command{
	Use:   "schema",
	Short: "Manage database schema",
	Long:  `Manage database schema setup and upgrades`,
}
View Source
var SchemaDropUserCmd = &cobra.Command{
	Use:   "drop-user [username]",
	Short: "Drop an application-specific database user",
	Args:  cobra.MaximumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagNameFile, _ := cmd.Flags().GetString("name-file")
		flagEnvPrefix, _ := cmd.Flags().GetString("env-prefix")

		argUsername := ""
		if len(args) > 0 {
			argUsername = args[0]
		}

		userName, err := ResolveDynamicSourceArg(argUsername, flagNameFile, fmt.Sprintf("%s_NAME", flagEnvPrefix))
		if err != nil {
			utils.PrintError(fmt.Errorf("failed to resolve username: %w", err))
			return nil
		}

		dbConn, err := db.Connect()
		if err != nil {
			utils.PrintErrorWithMessage("Failed to connect to database", err)
			return nil
		}
		defer dbConn.Close()

		dbConfig := db.GetConfig()

		err = schema.DropUser(dbConn, dbConfig.DBName, dbConfig.User, userName)
		if err != nil {
			utils.PrintErrorWithMessage("Failed to drop database user", err)
			return nil
		}

		utils.PrintSuccess("Database user dropped successfully")
		return nil
	},
}
View Source
var SchemaEnsureUserCmd = &cobra.Command{
	Use:   "ensure-user [username]",
	Short: "Create/sync application-specific database user with limited permissions",
	Args:  cobra.MaximumNArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		flagType, _ := cmd.Flags().GetString("type")
		flagPasswordPrompt, _ := cmd.Flags().GetBool("password")
		flagPasswordStdin, _ := cmd.Flags().GetBool("password-stdin")
		flagTypeFile, _ := cmd.Flags().GetString("type-file")
		flagNameFile, _ := cmd.Flags().GetString("name-file")
		flagPasswordFile, _ := cmd.Flags().GetString("password-file")
		flagEnvPrefix, _ := cmd.Flags().GetString("env-prefix")

		argUsername := ""
		if len(args) > 0 {
			argUsername = args[0]
		}

		userType, err := ResolveDynamicSourceArg(flagType, flagTypeFile, fmt.Sprintf("%s_TYPE", flagEnvPrefix))
		if err != nil {
			utils.PrintError(fmt.Errorf("failed to retreive user type: %w", err))
			return nil
		}

		userName, err := ResolveDynamicSourceArg(argUsername, flagNameFile, fmt.Sprintf("%s_NAME", flagEnvPrefix))
		if err != nil {
			utils.PrintError(fmt.Errorf("failed to retreive username: %w", err))
			return nil
		}

		var password string
		if flagPasswordPrompt || flagPasswordStdin {
			password, err = ReadPassword(flagPasswordStdin)
			if err != nil {
				utils.PrintErrorWithMessage("failed to read password", err)
				return nil
			}
		} else {
			password, err = ResolveDynamicSourceArg("", flagPasswordFile, fmt.Sprintf("%s_PASSWORD", flagEnvPrefix))
			if err != nil {
				utils.PrintError(fmt.Errorf("failed to retreive password: %w", err))
				return nil
			}
		}

		dbConn, err := db.Connect()
		if err != nil {
			utils.PrintErrorWithMessage("Failed to connect to database", err)
			return nil
		}
		defer dbConn.Close()

		dbConfig := db.GetConfig()

		err = schema.EnsureUser(dbConn, dbConfig.DBName, userType, schema.User{
			Name:     userName,
			Password: password,
		})
		if err != nil {
			utils.PrintErrorWithMessage("Failed to sync database user", err)
			return nil
		}

		utils.PrintSuccess("Database user synced successfully")
		return nil
	},
}
View Source
var SchemaPurgeCmd = &cobra.Command{
	Use:   "purge",
	Short: "Purge all schema and data from the database",
	Long:  "Purge the entire database by dropping all tables, sequences, functions, and types.",
	RunE: func(cmd *cobra.Command, args []string) error {

		confirm, err := cmd.Flags().GetBool("confirm")
		if err != nil {
			utils.PrintErrorWithMessage("Failed to read confirmation flag", err)
			return nil
		}
		if !confirm {
			fmt.Printf("This will %s:\n", utils.RedStyle.Render("PERMANENTLY DELETE"))
			fmt.Println("  • All tables and their data")
			fmt.Println("  • All sequences")
			fmt.Println("  • All functions")
			fmt.Println("  • All custom types")
			fmt.Println("To proceed, use '--confirm'")
			os.Exit(1)
		}

		dbConn, err := db.Connect()
		if err != nil {
			utils.PrintErrorWithMessage("Failed to connect to database", err)
			return nil
		}
		defer dbConn.Close()

		fmt.Println("Purging database...")

		err = schema.Purge(dbConn)
		if err != nil {
			utils.PrintErrorWithMessage("Failed to purge database", err)
			return nil
		}

		utils.PrintSuccess("Schema purge completed successfully")
		return nil
	},
}
View Source
var SchemaStatusCmd = &cobra.Command{
	Use:   "status",
	Short: "Show current schema version",
	RunE: func(cmd *cobra.Command, args []string) error {
		dbConn, err := db.Connect()
		if err != nil {
			utils.PrintErrorWithMessage("failed to connect to database", err)
			return nil
		}
		defer dbConn.Close()

		currentVersion, err := schema.GetCurrentVersion(dbConn)
		if err != nil {
			utils.PrintErrorWithMessage("Failed to get current schema version", err)
			return nil
		}

		latestVersion, err := schema.GetLatestAvailableVersion()
		if err != nil {
			utils.PrintErrorWithMessage("Failed to get latest available schema version", err)
			return nil
		}

		if currentVersion > 0 && currentVersion < latestVersion {
			fmt.Println(utils.YellowStyle.Bold(true).Render("Schema upgrade available"))
			fmt.Printf("Installed schema: v%d\n", currentVersion)
			fmt.Printf("Available schema: v%d\n", latestVersion)
		} else if currentVersion > 0 && currentVersion == latestVersion {
			utils.PrintSuccess("Schema is up to date")
			fmt.Printf("Installed schema: v%d\n", currentVersion)
		} else {
			fmt.Println(utils.YellowStyle.Bold(true).Render("Schema is not installed"))
			fmt.Printf("Available schema: v%d\n", latestVersion)
		}
		return nil
	},
}
View Source
var SchemaUpgradeCmd = &cobra.Command{
	Use:   "upgrade",
	Short: "Upgrade database schema to latest version",
	Long:  `Upgrade the database schema to the latest available version by applying pending migrations.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		latestVersion, err := schema.GetLatestAvailableVersion()
		if err != nil {
			utils.PrintErrorWithMessage("Failed to get latest available schema version", err)
			return nil
		}

		dbConn, err := db.Connect()
		if err != nil {
			utils.PrintErrorWithMessage("Failed to connect to database", err)
			return nil
		}
		defer dbConn.Close()

		currentVersion, err := schema.GetCurrentVersion(dbConn)
		if err != nil {
			utils.PrintErrorWithMessage("Failed to get current schema version", err)
			return nil
		}

		if currentVersion >= latestVersion {
			utils.PrintSuccess("Schema is already up to date")
			return nil
		}

		if currentVersion == 0 {
			fmt.Printf("Installing schema v%d...\n", latestVersion)
		} else {
			fmt.Printf("Upgrading schema: v%d -> v%d...\n", currentVersion, latestVersion)
		}

		err = schema.Upgrade(dbConn, latestVersion)
		if err != nil {
			utils.PrintErrorWithMessage("Failed to upgrade schema", err)
			return nil
		}

		utils.PrintSuccess("Schema upgrade completed successfully")
		return nil
	},
}

Functions

func DescribeCanonicalAddress

func DescribeCanonicalAddress(r sq.BaseRunner, email utils.EmailAddress) (bool, error)

func Execute

func Execute()

func ParseDomainFQDNArgs

func ParseDomainFQDNArgs(args []string) []string

func ParseEmailArgs

func ParseEmailArgs(args []string) []utils.EmailAddress

func ParseEmailOrWildcardArgs

func ParseEmailOrWildcardArgs(args []string) []utils.EmailAddressOrWildcard

func ReadFileValue

func ReadFileValue(path string) (string, error)

func ReadPassword

func ReadPassword(fromStdin bool) (string, error)

func ReadPasswordHashed

func ReadPasswordHashed(method string, fromStdin bool) (string, error)

func RenderMetaSection

func RenderMetaSection(createdAt time.Time, updatedAt time.Time, deletedAt *time.Time) string

RenderMetaSection returns a standardized table showing created/updated/deleted timestamps.

func ResolveDynamicSourceArg

func ResolveDynamicSourceArg(directValue, filePath, envVarName string) (string, error)

Types

This section is empty.

Source Files

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL