Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
47 changes: 47 additions & 0 deletions services/mail-archiver/.env
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions services/mail-archiver/README.md
Original file line number Diff line number Diff line change
@@ -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.<tailnet>.ts.net` |
| Service port | `5000` |
| Image | `s1t5/mailarchiver:latest`<br>`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/)
108 changes: 108 additions & 0 deletions services/mail-archiver/compose.yaml
Original file line number Diff line number Diff line change
@@ -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 <addr>:<port> 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"]
Loading