Compare commits
6 commits
b3273545b5
...
e7c7a6bf30
Author | SHA1 | Date | |
---|---|---|---|
e7c7a6bf30 | |||
e625620b40 | |||
e8b4faec3b | |||
8a965c588a | |||
d2748259cb | |||
6a0644d9af |
9 changed files with 298 additions and 22 deletions
|
@ -17,13 +17,14 @@ steps:
|
||||||
- APP_NAME=mailcow-admin-aliases
|
- APP_NAME=mailcow-admin-aliases
|
||||||
- FORGE=https://code.lila.network
|
- FORGE=https://code.lila.network
|
||||||
commands:
|
commands:
|
||||||
- apk add --update --no-cache xz curl jq
|
- apk add --update --no-cache xz curl jq make
|
||||||
- go mod download
|
- go mod download
|
||||||
- go build -o output/$APP_NAME-${CI_COMMIT_TAG##v}-${GOOS}-${GOARCH}${GOARM} main.go
|
- make build
|
||||||
- cd output
|
- cd bin
|
||||||
- xz --keep --compress $APP_NAME-${CI_COMMIT_TAG##v}-${GOOS}-${GOARCH}${GOARM}
|
- mv $APP_NAME $APP_NAME-${GOOS}-${GOARCH}${GOARM}
|
||||||
- sha256sum $APP_NAME-${CI_COMMIT_TAG##v}-${GOOS}-${GOARCH}${GOARM} >> $APP_NAME-${CI_COMMIT_TAG##v}-${GOOS}-${GOARCH}${GOARM}.sha256
|
- xz --keep --compress $APP_NAME-${GOOS}-${GOARCH}${GOARM}
|
||||||
- sha256sum $APP_NAME-${CI_COMMIT_TAG##v}-${GOOS}-${GOARCH}${GOARM}.xz >> $APP_NAME-${CI_COMMIT_TAG##v}-${GOOS}-${GOARCH}${GOARM}.xz.sha256
|
- sha256sum $APP_NAME-${GOOS}-${GOARCH}${GOARM} >> $APP_NAME-${GOOS}-${GOARCH}${GOARM}.sha256
|
||||||
|
- sha256sum $APP_NAME-${GOOS}-${GOARCH}${GOARM}.xz >> $APP_NAME-${GOOS}-${GOARCH}${GOARM}.xz.sha256
|
||||||
- |-
|
- |-
|
||||||
export RELEASE_ID=`curl --location "$FORGE/api/v1/repos/$CI_REPO/releases?limit=10" \
|
export RELEASE_ID=`curl --location "$FORGE/api/v1/repos/$CI_REPO/releases?limit=10" \
|
||||||
--header 'Accept: application/json' -s -S \
|
--header 'Accept: application/json' -s -S \
|
||||||
|
@ -32,23 +33,23 @@ steps:
|
||||||
curl --location "$FORGE/api/v1/repos/$CI_REPO/releases/$RELEASE_ID/assets" \
|
curl --location "$FORGE/api/v1/repos/$CI_REPO/releases/$RELEASE_ID/assets" \
|
||||||
--header "Authorization: token $FORGEJO_APIKEY" \
|
--header "Authorization: token $FORGEJO_APIKEY" \
|
||||||
--header 'Content-Type: multipart/form-data' -s -S \
|
--header 'Content-Type: multipart/form-data' -s -S \
|
||||||
--form "attachment=@$APP_NAME-${CI_COMMIT_TAG##v}-${GOOS}-${GOARCH}${GOARM};type=application/octet-stream" \
|
--form "attachment=@$APP_NAME-${GOOS}-${GOARCH}${GOARM};type=application/octet-stream" \
|
||||||
--fail-with-body
|
--fail-with-body
|
||||||
- |-
|
- |-
|
||||||
curl --location "$FORGE/api/v1/repos/$CI_REPO/releases/$RELEASE_ID/assets" \
|
curl --location "$FORGE/api/v1/repos/$CI_REPO/releases/$RELEASE_ID/assets" \
|
||||||
--header "Authorization: token $FORGEJO_APIKEY" \
|
--header "Authorization: token $FORGEJO_APIKEY" \
|
||||||
--header 'Content-Type: multipart/form-data' -s -S \
|
--header 'Content-Type: multipart/form-data' -s -S \
|
||||||
--form "attachment=@$APP_NAME-${CI_COMMIT_TAG##v}-${GOOS}-${GOARCH}${GOARM}.xz;type=application/octet-stream" \
|
--form "attachment=@$APP_NAME-${GOOS}-${GOARCH}${GOARM}.xz;type=application/octet-stream" \
|
||||||
--fail-with-body
|
--fail-with-body
|
||||||
- |-
|
- |-
|
||||||
curl --location "$FORGE/api/v1/repos/$CI_REPO/releases/$RELEASE_ID/assets" \
|
curl --location "$FORGE/api/v1/repos/$CI_REPO/releases/$RELEASE_ID/assets" \
|
||||||
--header "Authorization: token $FORGEJO_APIKEY" \
|
--header "Authorization: token $FORGEJO_APIKEY" \
|
||||||
--header 'Content-Type: multipart/form-data' -s -S \
|
--header 'Content-Type: multipart/form-data' -s -S \
|
||||||
--form "attachment=@$APP_NAME-${CI_COMMIT_TAG##v}-${GOOS}-${GOARCH}${GOARM}.sha256;type=application/octet-stream" \
|
--form "attachment=@$APP_NAME-${GOOS}-${GOARCH}${GOARM}.sha256;type=application/octet-stream" \
|
||||||
--fail-with-body
|
--fail-with-body
|
||||||
- |-
|
- |-
|
||||||
curl --location "$FORGE/api/v1/repos/$CI_REPO/releases/$RELEASE_ID/assets" \
|
curl --location "$FORGE/api/v1/repos/$CI_REPO/releases/$RELEASE_ID/assets" \
|
||||||
--header "Authorization: token $FORGEJO_APIKEY" \
|
--header "Authorization: token $FORGEJO_APIKEY" \
|
||||||
--header 'Content-Type: multipart/form-data' -s -S \
|
--header 'Content-Type: multipart/form-data' -s -S \
|
||||||
--form "attachment=@$APP_NAME-${CI_COMMIT_TAG##v}-${GOOS}-${GOARCH}${GOARM}.xz.sha256;type=application/octet-stream" \
|
--form "attachment=@$APP_NAME-${GOOS}-${GOARCH}${GOARM}.xz.sha256;type=application/octet-stream" \
|
||||||
--fail-with-body
|
--fail-with-body
|
||||||
|
|
15
CHANGELOG.md
Normal file
15
CHANGELOG.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.0.0] - 2024-07-26
|
||||||
|
First release of `mailcow-admin-aliases`
|
||||||
|
|
||||||
|
[unreleased]: https://code.lila.network/adoralaura/mailcow-admin-aliases/compare/1.0.0...HEAD
|
||||||
|
[1.0.0]: https://code.lila.network/adoralaura/mailcow-admin-aliases/releases/tag/1.0.0
|
17
Makefile
Normal file
17
Makefile
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Set the default Go build flags
|
||||||
|
GOFLAGS = -ldflags='-w -s -X main.Version=$(VERSION)'
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
build:
|
||||||
|
go build $(GOFLAGS) -o bin/mailcow-admin-aliases cmd/mailcow-admin-aliases/main.go
|
||||||
|
|
||||||
|
# Clean the build artifacts
|
||||||
|
clean:
|
||||||
|
rm -rf bin
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
run: build
|
||||||
|
./bin/mailcow-admin-aliases
|
||||||
|
|
||||||
|
# Set a version for the build
|
||||||
|
VERSION := $(shell git describe --tags --always)
|
101
README.md
Normal file
101
README.md
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
# mailcow-admin-aliases
|
||||||
|
![status-badge](https://ci.lila.network/api/badges/24/status.svg)
|
||||||
|
[![Please don't upload to GitHub](https://nogithub.codeberg.page/badge.svg)](https://nogithub.codeberg.page)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
Many mail administrators (me included) want to have some specific email addresses (like postmaster@..., security@..., abuse@..., etc.) always point to a dedicated administrator mailbox, so that e.g. mails regarding abuse or security issues will always be at one central management point.
|
||||||
|
|
||||||
|
If you are using [Mailcow](https://docs.mailcow.email/) as your mail server, you'd have to manually add those mail addresses as _aliases_ by hand. I'm also hosting my own Mailcow instance, and by now I have enough (sub)domains that manually adding aliases gets tedious _very quickly_. Thats why I created `mailcow-admin-aliases`.
|
||||||
|
|
||||||
|
With `mailcow-admin-aliases` you can (automatically) create the mail aliases you want, e.g. if you configure a new domain. In the [examples](examples/) folder you can find _systemd_ service and timer files to automate the creation of your aliases. `mailcow-admin-aliases` will check which ones already exist and will skip those.
|
||||||
|
|
||||||
|
This application is successfully tested with mailcow `2023-03` to `2024-06c`, but it should work with older versions too.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Installation of the required Mailcow instance is way out of scope of this documentation. For detailed instructions regarding Mailcow, please visit [it's documentation](https://docs.mailcow.email/)
|
||||||
|
|
||||||
|
|
||||||
|
To quickly get started with `mailcow-admin-aliases`, just download the binary from [the mailcow-admin-aliases Release page](https://code.lila.network/adoralaura/mailcow-admin-aliases/releases)...
|
||||||
|
|
||||||
|
... fill out the config file...
|
||||||
|
```shell
|
||||||
|
vi config.yaml
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
# Base-Url of your Mailcow instance.
|
||||||
|
# format: "https://mail.example.com"
|
||||||
|
api_endpoint:
|
||||||
|
|
||||||
|
# Read/Write API Key from your Mailcow instance
|
||||||
|
# See the [+] API Section at <your-instance>/admin
|
||||||
|
# format: "XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX"
|
||||||
|
api_key:
|
||||||
|
|
||||||
|
# The mail address the aliases should point to
|
||||||
|
# Should be a mailbox on your Mailcow instance
|
||||||
|
# format: "admin-mail@your-domain.com"
|
||||||
|
admin_email:
|
||||||
|
|
||||||
|
# Local part of your important aliases you want created
|
||||||
|
# Example: if you want security@<your-domains.tld>, one entry should be - security
|
||||||
|
mail_prefixes:
|
||||||
|
- abuse
|
||||||
|
- admin
|
||||||
|
- postmaster
|
||||||
|
- security
|
||||||
|
- webadmin
|
||||||
|
- webmaster
|
||||||
|
```
|
||||||
|
|
||||||
|
... and run it!
|
||||||
|
```shell
|
||||||
|
mailcow-admin-aliases --config /path/to/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## `mailcow-admin-aliases` CLI flags
|
||||||
|
when using `mailcow-admin-aliases`, you have the following CLI flag options:
|
||||||
|
```
|
||||||
|
-c, --config string Path to config file (default: $WorkDir/config.yaml)
|
||||||
|
--debug Enable debug logging (beats --quiet)
|
||||||
|
-d, --dry-run Show what this application *would* do
|
||||||
|
-q, --quiet Disable logging
|
||||||
|
-v, --version Show version information
|
||||||
|
-h, --help Show this overview
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also see the list of CLI options with `--help`
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
`mailcow-admin-aliases` queries the API of the configured Mailcow instance to
|
||||||
|
- get the currently configured Mailcow domains
|
||||||
|
- get the list of currently configured aliases
|
||||||
|
- create missing aliases
|
||||||
|
|
||||||
|
## Scaling
|
||||||
|
As this is a Go application, performance shsould generally be pretty good. **However**, I currently do not have access to a big Mailcow test instance. Should you run into scaling/performance issues, please don't hesitate to contact me!
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
I use my own [Forgejo Instance](https://code.lila.network) to manage issues and pull requests.
|
||||||
|
|
||||||
|
* If you have a trivial fix or improvement, go ahead and create a pull request,
|
||||||
|
addressing (with `@...`) the maintainer of this repository (see
|
||||||
|
[MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request.
|
||||||
|
|
||||||
|
* If you plan to do something more involved, first please [send me a mail]( mailto:dev@lauka.net?subject=%5Bmailcow-admin-aliases%5D).
|
||||||
|
|
||||||
|
### What to contribute
|
||||||
|
|
||||||
|
The best way to help without speaking a lot of Go would be to share your
|
||||||
|
configuration, alerts, dashboards, and recording rules. If you have something
|
||||||
|
that works and is not in the repository, please pay it forward and
|
||||||
|
share what works.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
You can find the Changelog here: [Changelog](https://code.lila.network/adoralaura/mailcow-admin-aliases/src/branch/main/CHANGELOG.md)
|
||||||
|
|
||||||
|
## License
|
||||||
|
`mailcow-admin-aliases` is available under the MIT license. See the [LICENSE](https://code.lila.network/adoralaura/mailcow-admin-aliases/src/branch/main/LICENSE) file for more info.
|
|
@ -17,14 +17,23 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var Version string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
quiet := pflag.BoolP("quiet", "q", false, "Disable logging")
|
quietFlag := pflag.BoolP("quiet", "q", false, "Disable logging")
|
||||||
verbose := pflag.Bool("debug", false, "Enable debug logging (beats --quiet)")
|
verboseFlag := pflag.Bool("debug", false, "Enable debug logging (beats --quiet)")
|
||||||
|
versionFlag := pflag.BoolP("version", "v", false, "Show version information")
|
||||||
pflag.BoolVarP(&configuration.DryRun, "dry-run", "d", false, "Show what this application *would* do")
|
pflag.BoolVarP(&configuration.DryRun, "dry-run", "d", false, "Show what this application *would* do")
|
||||||
pflag.StringVarP(&configuration.ConfigFile, "config", "c", "", "Path to config file (default: $WorkDir/config.yaml)")
|
pflag.StringVarP(&configuration.ConfigFile, "config", "c", "", "Path to config file (default: $WorkDir/config.yaml)")
|
||||||
|
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
logging.NewSlogLogger(*quiet, *verbose)
|
|
||||||
|
if *versionFlag {
|
||||||
|
fmt.Println("mailcow-admin-aliases " + Version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.NewSlogLogger(*quietFlag, *verboseFlag)
|
||||||
|
|
||||||
var cfg configuration.Config
|
var cfg configuration.Config
|
||||||
err := cfg.LoadFromDisk()
|
err := cfg.LoadFromDisk()
|
||||||
|
@ -45,20 +54,39 @@ func main() {
|
||||||
slog.Error("failed to get aliases", "error", err.Error())
|
slog.Error("failed to get aliases", "error", err.Error())
|
||||||
os.Exit(1)
|
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 _, domain := range domains {
|
||||||
for _, prefix := range cfg.MailPrefixes {
|
for _, prefix := range cfg.MailPrefixes {
|
||||||
address := prefix + "@" + domain.DomainName
|
address := prefix + "@" + domain.DomainName
|
||||||
if address != cfg.AdminEmail && !misc.StringIsInStringSlice(existingAliasesSlice, address) {
|
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)
|
wantedAliases = append(wantedAliases, address)
|
||||||
} else {
|
} 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,20 @@
|
||||||
---
|
---
|
||||||
api_endpoint: https://mailcow.example.com
|
# Base-Url of your Mailcow instance.
|
||||||
api_key: aaaaa-bbbbb-ccccc-ddddd-eeeee
|
# format: "https://mail.example.com"
|
||||||
admin_email: admin@example.com
|
api_endpoint:
|
||||||
|
|
||||||
|
# Read/Write API Key from your Mailcow instance
|
||||||
|
# See the [+] API Section at <your-instance>/admin
|
||||||
|
# format: "XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX"
|
||||||
|
api_key:
|
||||||
|
|
||||||
|
# The mail address the aliases should point to
|
||||||
|
# Should be a mailbox on your Mailcow instance
|
||||||
|
# format: "admin-mail@your-domain.com"
|
||||||
|
admin_email:
|
||||||
|
|
||||||
|
# Local part of your important aliases you want created
|
||||||
|
# Example: if you want security@<your-domains.tld>, one entry should be - security
|
||||||
mail_prefixes:
|
mail_prefixes:
|
||||||
- abuse
|
- abuse
|
||||||
- admin
|
- admin
|
||||||
|
|
|
@ -27,7 +27,7 @@ type Config struct {
|
||||||
MailPrefixes []string `yaml:"mail_prefixes"`
|
MailPrefixes []string `yaml:"mail_prefixes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) LoadFromDisk() error {
|
func (c *Config) LoadFromDisk() error {
|
||||||
if ConfigFile == "" {
|
if ConfigFile == "" {
|
||||||
slog.Debug("no config file found, looking in working dir")
|
slog.Debug("no config file found, looking in working dir")
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
|
@ -48,8 +48,38 @@ func (c Config) LoadFromDisk() error {
|
||||||
return fmt.Errorf("failed to unmarshal config file: %w", err)
|
return fmt.Errorf("failed to unmarshal config file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.checkConfig()
|
||||||
|
|
||||||
slog.Debug("successfully read config file", "path", ConfigFile)
|
slog.Debug("successfully read config file", "path", ConfigFile)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Config) checkConfig() {
|
||||||
|
failed := false
|
||||||
|
if c.ApiEndpoint == "" {
|
||||||
|
slog.Error("[CONFIG] api_endpoint must not be empty")
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ApiKey == "" {
|
||||||
|
slog.Error("[CONFIG] api_key must not be empty")
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.AdminEmail == "" {
|
||||||
|
slog.Error("[CONFIG] admin_email must not be empty")
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.MailPrefixes) < 1 {
|
||||||
|
slog.Error("[CONFIG] mail_prefixes must have at least one entry")
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if failed {
|
||||||
|
slog.Error("[CONFIG] Application can't start until above errors are fixed.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package mailcow
|
package mailcow
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -18,7 +19,7 @@ type MailcowAlias struct {
|
||||||
PrivateComment string `json:"private_comment"`
|
PrivateComment string `json:"private_comment"`
|
||||||
Goto string `json:"goto"`
|
Goto string `json:"goto"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
IsCatchAll int `json:"is_catch_all"` // skip if 1
|
IsCatchAll int `json:"is_catch_all"`
|
||||||
Active int `json:"active"`
|
Active int `json:"active"`
|
||||||
ActiveInt int `json:"active_int"`
|
ActiveInt int `json:"active_int"`
|
||||||
SogoVisible int `json:"sogo_visible"`
|
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.PrivateComment = "automatically generated by the mail server admin"
|
||||||
a.Address = alias
|
a.Address = alias
|
||||||
a.Goto = destination
|
a.Goto = destination
|
||||||
|
a.SogoVisible = 0
|
||||||
|
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
@ -78,3 +80,43 @@ func LoadAliases(cfg configuration.Config) ([]MailcowAlias, []string, error) {
|
||||||
|
|
||||||
return aliases, aliasSlice, nil
|
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
|
||||||
|
}
|
||||||
|
|
30
internal/mailcow/api.go
Normal file
30
internal/mailcow/api.go
Normal file
|
@ -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
|
||||||
|
}
|
Loading…
Reference in a new issue