diff --git a/README.md b/README.md index 8cb1be20..9cea9d5f 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ Every stack starts from the same [standard setup](documentation/standard-setup.m | **Karakeep** | Bookmark manager that archives links, notes, and images, with full-text search and AI tagging. | [Details](services/karakeep) | | **LanguageTool** | Grammar and spelling checker server for browser extensions, with no web interface. | [Details](services/languagetool) | | **linkding** | Bookmark manager to save, tag, and search links. | [Details](services/linkding) | +| **Mail Archiver** | Archives incoming mail and makes it searchable from the browser. | [Details](services/mail-archiver) | | **Mattermost** | Team chat platform with channels, direct messages, and integrations. | [Details](services/mattermost) | | **Memos** | Note-taking app for short Markdown notes, shown on a timeline. | [Details](services/memos) | | **Nanote** | Note-taking app that stores notes as Markdown files in folders. | [Details](services/nanote) | diff --git a/services/mail-archiver/.env b/services/mail-archiver/.env new file mode 100644 index 00000000..0aed982b --- /dev/null +++ b/services/mail-archiver/.env @@ -0,0 +1,47 @@ +#URL=https://github.com/tailscale-dev/ScaleTail + +# Service Configuration +# Required: Service name (e.g., adguard). Used as hostname in Tailscale and for container naming (app-${SERVICE}). +SERVICE=mail-archiver +# Required: Docker image URL from container registry (e.g., adguard/adguard-home). +IMAGE_URL=s1t5/mailarchiver:latest + +# Network Configuration +# Required: Port to expose to local network. Uncomment the "ports:" section in compose.yaml to enable. +# Preferred DNS server for Tailscale. Uncomment the "dns:" section in compose.yaml to enable. +SERVICEPORT=5000 +DNS_SERVER=9.9.9.9 + +# Tailscale Configuration +# Required: Auth key from https://tailscale.com/admin/authkeys. +# See: https://tailscale.com/kb/1085/auth-keys#generate-an-auth-key for instructions. +TS_AUTHKEY= + +# Optional Service variables +# PUID=1000 + +# Time Zone setting for containers +# See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +TZ=Europe/Amsterdam + +# Any Container environment variables are declared below. +# See https://docs.docker.com/compose/how-tos/environment-variables/ + +# First admin user. +APP_USER=admin +# Required: set a password before the first start. Generate it with: openssl rand -hex 16 +APP_PW= + +# Database user. +# PostgreSQL applies it only when the database is first created. Existing installations must keep their current password. +DB_USER=mailuser +# Required: set a password before the first start. Use letters and digits only, because it is part of the connection string. +DB_PW= + +# OAuth/OIDC login (optional). Set OAUTH_SETUP to true and fill in the values from your identity provider. +OAUTH_SETUP=false +OAUTH_URL= +OAUTH_CLIENTID= +OAUTH_SECRET= +# Name shown on the login button ("Login with ..."). +OAUTH_DISPLAYNAME=SSO diff --git a/services/mail-archiver/README.md b/services/mail-archiver/README.md new file mode 100644 index 00000000..1a9b0e76 --- /dev/null +++ b/services/mail-archiver/README.md @@ -0,0 +1,38 @@ +# MAIL ARCHIVER + +[Mail Archiver](https://mail-archiver.org/) is an open-source, self-hosted email archiver. It collects mail from IMAP accounts, Microsoft 365, and imported files, and offers full-text search across message metadata and content. Pairing it with Tailscale keeps your email archive off the public internet and reachable only from devices on your Tailnet. + +This stack runs Mail Archiver with a Tailscale sidecar, as described in [the standard setup](../../documentation/standard-setup.md). + +## At a glance + +| Item | Value | +| ------------- | ---------------------------------- | +| Web interface | `https://mail-archiver..ts.net` | +| Service port | `5000` | +| Image | `s1t5/mailarchiver:latest`
`postgres:17-alpine` | +| Data | `./mail-archiver-data/` | + +## Before you start + +Go through the [Quick Start](../../README.md#quick-start). + +In addition: + +- Set `TS_AUTHKEY`, `APP_PW`, and `DB_PW` in `.env`. Only APP_PW and DB_PW are required and Compose stops with an error until they are set. TS_AUTHKEY is only needed for the first start. +- Use only letters and digits in `DB_PW`. The password is inserted into the database connection string, so characters such as `;` or `=` break it. +- PostgreSQL applies `DB_USER` and `DB_PW` only when it creates the database on the first start. Changing them later in `.env` does not change the existing database user. +- `APP_USER` and `APP_PW` define the first admin account. Log in with them after the first start. + +## Deviations from the standard setup + +The stack adds a `db` service (`app-mail-archiver-db`) running PostgreSQL. Mail Archiver keeps its data in a database, so it cannot run as a single container. The database listens on localhost only, at `127.0.0.1`. The database is not exposed outside the stack. + +## First run + +The first run can take a few seconds while Tailscale issues the certificate. Open the web interface and log in with the initial credentials, a password change will be forced for added security. + +## Links + +- [Mail Archiver documentation](https://github.com/s1t5/mail-archiver/blob/main/doc/index.md) +- [Mail Archiver source code](https://github.com/s1t5/mail-archiver/) diff --git a/services/mail-archiver/compose.yaml b/services/mail-archiver/compose.yaml new file mode 100644 index 00000000..20ec52e8 --- /dev/null +++ b/services/mail-archiver/compose.yaml @@ -0,0 +1,108 @@ +configs: + ts-serve: + content: | + {"TCP":{"443":{"HTTPS":true}}, + "Web":{"$${TS_CERT_DOMAIN}:443": + {"Handlers":{"/": + {"Proxy":"http://127.0.0.1:5000"}}}}, + "AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}} + +services: + # Make sure you have updated/checked the .env file with the correct variables. + # Every variable used in this file must be defined there, unless it has a default such as ${VAR:-value}. + # Tailscale Sidecar Configuration + tailscale: + image: tailscale/tailscale:latest # Image to be used + container_name: tailscale-${SERVICE} # Name for local container management + hostname: ${SERVICE} # Name used within your Tailscale environment + environment: + - TS_AUTHKEY=${TS_AUTHKEY} + - TS_STATE_DIR=/var/lib/tailscale + - TS_SERVE_CONFIG=/config/serve.json # Tailscale Serve configuration to expose the web interface on your local Tailnet - remove this line if not required + - TS_USERSPACE=false + - TS_ENABLE_HEALTH_CHECK=true # Enable healthcheck endpoint: "/healthz" + - TS_LOCAL_ADDR_PORT=127.0.0.1:41234 # The : for the healthz endpoint + #- TS_ACCEPT_DNS=true # Uncomment only if the service must resolve MagicDNS names - this replaces Docker DNS, so Compose service names no longer resolve + - TS_AUTH_ONCE=true + configs: + - source: ts-serve + target: /config/serve.json + volumes: + - ./config:/config # Config folder used to store Tailscale files - you may need to change the path + - ./ts/state:/var/lib/tailscale # Tailscale requirement - you may need to change the path + devices: + - /dev/net/tun:/dev/net/tun # Network configuration for Tailscale to work + cap_add: + - net_admin # Tailscale requirement + #ports: + # - 0.0.0.0:${SERVICEPORT}:${SERVICEPORT} # Binding ports to the local network - may be removed if only exposure to your Tailnet is required + # If any DNS issues arise, use your preferred DNS provider by uncommenting the config below + #dns: + # - ${DNS_SERVER} + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] # Check Tailscale has a Tailnet IP and is operational + interval: 1m # How often to perform the check + timeout: 10s # Time to wait for the check to succeed + retries: 3 # Number of retries before marking as unhealthy + start_period: 10s # Time to wait before starting health checks + restart: always + + # Application + application: + image: ${IMAGE_URL} # Image to be used + network_mode: service:tailscale # Sidecar configuration to route the service through Tailscale + container_name: app-${SERVICE} # Name for local container management + environment: # Variables are declared in .env file. + # Database connection + - ConnectionStrings__DefaultConnection=Host=127.0.0.1;Database=MailArchiver;Username=${DB_USER};Password=${DB_PW}; + # First admin user + - Authentication__Username=${APP_USER} + - Authentication__Password=${APP_PW:?Set APP_PW in .env} + # Time zone + - TZ=${TZ} + - TimeZone__DisplayTimeZoneId=${TZ} # Time zone used for dates in the web interface + # OAuth/OIDC login + - OAuth__Enabled=${OAUTH_SETUP} + - OAuth__Authority=${OAUTH_URL} + - OAuth__ClientId=${OAUTH_CLIENTID} + - OAuth__ClientSecret=${OAUTH_SECRET} + - OAuth__DisplayName=${OAUTH_DISPLAYNAME} + - OAuth__ClientScopes__0=openid + - OAuth__ClientScopes__1=profile + - OAuth__ClientScopes__2=email + - OAuth__DisablePasswordLogin=false + - OAuth__AutoApproveUsers=false + volumes: + - ./${SERVICE}-data/data-protection-keys:/app/DataProtection-Keys + depends_on: + tailscale: + condition: service_healthy + db: + condition: service_healthy + healthcheck: + test: ["CMD", "pgrep", "-f", "MailArchiver.dll"] # Check if the Mail Archiver process is running; the image has no HTTP client + interval: 1m # How often to perform the check + timeout: 10s # Time to wait for the check to succeed + retries: 3 # Number of retries before marking as unhealthy + start_period: 30s # Time to wait before starting health checks + restart: always + + # Database + db: + image: postgres:17-alpine # Image to be used + network_mode: service:tailscale # Join the same network namespace to be accessible via localhost + container_name: app-${SERVICE}-db # Name for local container management + environment: + - POSTGRES_DB=MailArchiver + - POSTGRES_USER=${DB_USER} + - POSTGRES_PASSWORD=${DB_PW:?Set DB_PW in .env} + volumes: + - ./${SERVICE}-data/db:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U ${DB_USER} -d MailArchiver"] # Check if PostgreSQL accepts connections + interval: 10s # How often to perform the check + timeout: 5s # Time to wait for the check to succeed + retries: 5 # Number of retries before marking as unhealthy + start_period: 30s # Time to wait before starting health checks + restart: always + command: ["postgres", "-c", "listen_addresses=localhost"]