diff --git a/cmd/mailcow-admin-aliases/main.go b/cmd/mailcow-admin-aliases/main.go index 42d6a30..0abad2c 100644 --- a/cmd/mailcow-admin-aliases/main.go +++ b/cmd/mailcow-admin-aliases/main.go @@ -54,20 +54,39 @@ func main() { slog.Error("failed to get aliases", "error", err.Error()) os.Exit(1) } - slog.Info(fmt.Sprintf("Found %v domains, %v aliases.", len(domains), len(existingAliases))) + slog.Info(fmt.Sprintf("Found %v existing domains, %v existing aliases", len(domains), len(existingAliases))) for _, domain := range domains { for _, prefix := range cfg.MailPrefixes { address := prefix + "@" + domain.DomainName if address != cfg.AdminEmail && !misc.StringIsInStringSlice(existingAliasesSlice, address) { - slog.Info(fmt.Sprintf("%v: Adding to wanted alias list", address)) + slog.Debug(fmt.Sprintf("%v: Adding to wanted alias list", address)) wantedAliases = append(wantedAliases, address) } else { - slog.Info(fmt.Sprintf("%v: Ignoring alias", address)) + slog.Debug(fmt.Sprintf("%v: Ignoring alias", address)) } } } - // TODO: create aliases + if len(wantedAliases) == 0 { + slog.Info("Found no missing aliases. Nothing to do. Bye") + return + } + slog.Info(fmt.Sprintf("Found %v missing aliases. Will try to create them:", len(wantedAliases))) + + for _, alias := range wantedAliases { + if configuration.DryRun { + slog.Info(fmt.Sprintf("[DRYRUN] Creating alias %v", alias)) + } else { + mailcowAlias := mailcow.NewAlias(alias, cfg.AdminEmail) + err = mailcowAlias.Create(cfg) + + if err != nil { + slog.Error("failed to create alias", "alias", alias, "error", err.Error()) + } else { + slog.Info(fmt.Sprintf("Created alias %v", alias)) + } + } + } } diff --git a/internal/configuration/configuration.go b/internal/configuration/configuration.go index 23ad512..c9414f8 100644 --- a/internal/configuration/configuration.go +++ b/internal/configuration/configuration.go @@ -27,7 +27,7 @@ type Config struct { MailPrefixes []string `yaml:"mail_prefixes"` } -func (c Config) LoadFromDisk() error { +func (c *Config) LoadFromDisk() error { if ConfigFile == "" { slog.Debug("no config file found, looking in working dir") wd, err := os.Getwd() diff --git a/internal/mailcow/aliases.go b/internal/mailcow/aliases.go index 8608de7..4ee5304 100644 --- a/internal/mailcow/aliases.go +++ b/internal/mailcow/aliases.go @@ -1,6 +1,7 @@ package mailcow import ( + "bytes" "encoding/json" "fmt" "io" @@ -18,7 +19,7 @@ type MailcowAlias struct { PrivateComment string `json:"private_comment"` Goto string `json:"goto"` Address string `json:"address"` - IsCatchAll int `json:"is_catch_all"` // skip if 1 + IsCatchAll int `json:"is_catch_all"` Active int `json:"active"` ActiveInt int `json:"active_int"` SogoVisible int `json:"sogo_visible"` @@ -32,6 +33,7 @@ func NewAlias(alias string, destination string) MailcowAlias { a.PrivateComment = "automatically generated by the mail server admin" a.Address = alias a.Goto = destination + a.SogoVisible = 0 return a } @@ -78,3 +80,43 @@ func LoadAliases(cfg configuration.Config) ([]MailcowAlias, []string, error) { return aliases, aliasSlice, nil } + +func (a MailcowAlias) Create(cfg configuration.Config) error { + url := cfg.ApiEndpoint + configuration.AliasAddApiEndpoint + method := "POST" + + payload, err := json.Marshal(a) + if err != nil { + return fmt.Errorf("failed to marshal api body: %w", err) + } + + client := &http.Client{Timeout: 10 * time.Second} + req, err := http.NewRequest(method, url, bytes.NewReader(payload)) + if err != nil { + return fmt.Errorf("failed to create new POST request: %w", err) + } + + slog.Debug("alias create request", "method", method, "url", url) + + req.Header.Add("accept", "application/json") + req.Header.Add("Content-Type", "application/json") + req.Header.Add("X-API-Key", cfg.ApiKey) + + res, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to POST new alias to server: %w", err) + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + err = checkApiResponseOK(body) + if err != nil { + return fmt.Errorf("api returned an error: %w", err) + } + + return nil +} diff --git a/internal/mailcow/api.go b/internal/mailcow/api.go new file mode 100644 index 0000000..806e47a --- /dev/null +++ b/internal/mailcow/api.go @@ -0,0 +1,30 @@ +package mailcow + +import ( + "encoding/json" + "fmt" + "strings" +) + +type MailcowApiResponse struct { + Type string `json:"type"` + Msg []string `json:"msg"` +} + +// checkApiResponseOK checks if we got a positive response from the Mailcow API. +// +// Returns an error detailing why the request failed. +func checkApiResponseOK(body []byte) error { + var response []MailcowApiResponse + + err := json.Unmarshal(body, &response) + if err != nil { + return fmt.Errorf("failed to unmarshal body: %w", err) + } + + if response[0].Type != "success" { + return fmt.Errorf("%v", strings.Join(response[0].Msg, " ")) + } + + return nil +}