mirror of
https://github.com/tiredofit/docker-postal.git
synced 2025-09-07 13:04:14 +08:00
Release 2.1.0 - See CHANGELOG.md
This commit is contained in:
parent
587ab2b7bd
commit
15dded5b7c
12 changed files with 601 additions and 608 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -1,3 +1,23 @@
|
|||
## 2.1.0 2020-06-09 <dave at tiredofit dot ca>
|
||||
|
||||
### Added
|
||||
- Update to support tiredofit alpine 5.0.0 base image
|
||||
|
||||
|
||||
## 2.0.1 2020-06-05 <dave at tiredofit dot ca>
|
||||
|
||||
### Changed
|
||||
- Move /etc/s6/services to /etc/services.d
|
||||
|
||||
|
||||
## 2.0.0 2020-05-21 <dave at tiredofit dot ca>
|
||||
|
||||
### Added
|
||||
- Refactor Configuration Building
|
||||
- Allow Custom Configuration File
|
||||
- New Environment Variables - See README
|
||||
|
||||
|
||||
## 1.3.0 2020-01-13 <dave at tiredofit dot ca>
|
||||
|
||||
### Added
|
||||
|
|
26
Dockerfile
26
Dockerfile
|
@ -1,50 +1,50 @@
|
|||
FROM tiredofit/ruby:2.6-alpine
|
||||
FROM registry.selfdesign.org/docker/ruby/2.6/alpine:latest
|
||||
LABEL maintainer="Dave Conroy (dave at tiredofit dot ca)"
|
||||
|
||||
ENV ENABLE_CRON=FALSE \
|
||||
ENABLE_SMTP=FALSE
|
||||
ENV POSTAL_CONFIG_ROOT=/app/config \
|
||||
ENABLE_SMTP=FALSE \
|
||||
ZABBIX_HOSTNAME=postal-app
|
||||
|
||||
RUN set -x && \
|
||||
# Create User
|
||||
addgroup -g 2525 postal && \
|
||||
adduser -S -D -G postal -u 2525 -h /opt/postal/ postal && \
|
||||
adduser -S -D -G postal -u 2525 -h /app/ postal && \
|
||||
\
|
||||
# Build Dependencies
|
||||
apk update && \
|
||||
apk add --no-cache --virtual .postal-build-deps \
|
||||
apk upgrade && \
|
||||
apk add -t .postal-build-deps \
|
||||
build-base \
|
||||
git \
|
||||
mariadb-dev \
|
||||
&& \
|
||||
\
|
||||
apk add --no-cache --virtual .postal-run-deps \
|
||||
apk add -t .postal-run-deps \
|
||||
expect \
|
||||
nodejs \
|
||||
mariadb-client \
|
||||
mariadb-connector-c \
|
||||
sudo \
|
||||
openssl \
|
||||
&& \
|
||||
\
|
||||
### Fetch Source and install Ruby Dependencies
|
||||
gem install bundler && \
|
||||
gem install procodile && \
|
||||
git clone https://github.com/atech/postal /opt/postal && \
|
||||
git clone https://github.com/postalhq/postal /app/ && \
|
||||
\
|
||||
### Install Ruby Gems and dependencies
|
||||
/opt/postal/bin/postal bundle /opt/postal/vendor/bundle && \
|
||||
/app/bin/postal bundle /app/vendor/bundle && \
|
||||
\
|
||||
### Housekeeping
|
||||
ln -s /usr/local/bundle/bin/procodile /usr/sbin && \
|
||||
mkdir -p /opt/postal/certs && \
|
||||
\
|
||||
# Cleanup
|
||||
chown -R postal. /opt/postal && \
|
||||
rm -rf && \
|
||||
chown -R postal. /app/ && \
|
||||
apk del .postal-build-deps && \
|
||||
rm -rf /tmp/* /var/cache/apk/*
|
||||
|
||||
### Networking Setup
|
||||
EXPOSE 80 5000
|
||||
EXPOSE 25 5000
|
||||
|
||||
### Add Files and Assets
|
||||
ADD install /
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Dave Conroy
|
||||
Copyright (c) 2020 Dave Conroy
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -44,7 +44,7 @@ management.
|
|||
|
||||
# Installation
|
||||
|
||||
Automated builds of the image are available on [Registry](https://hub.docker.com/r/tiredofit/postal) and is the recommended method of
|
||||
Automated builds of the image are available on [Docker Hub](https://hub.docker.com/r/tiredofit/postal) and is the recommended method of
|
||||
installation.
|
||||
|
||||
|
||||
|
@ -100,7 +100,6 @@ available options that can be used to customize your installation.
|
|||
| Port | Description |
|
||||
|-----------|---------------|
|
||||
| `25` | SMTP |
|
||||
| `587` | Submission |
|
||||
| `5000` | Procodile |
|
||||
|
||||
# Maintenance
|
||||
|
|
|
@ -5,119 +5,141 @@ services:
|
|||
image: tiredofit/postal
|
||||
container_name: postal-app
|
||||
ports:
|
||||
- 25:2525
|
||||
- 25:2525
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.frontend.rule=Host:postal.example.com
|
||||
- traefik.port=5000
|
||||
- traefik.protocol=http
|
||||
- traefik.docker.network=proxy
|
||||
- traefik.backend=postal-app
|
||||
- traefik.enable=true
|
||||
- traefik.frontend.rule=Host:postal.example.com
|
||||
- traefik.port=5000
|
||||
- traefik.protocol=http
|
||||
- traefik.docker.network=proxy
|
||||
- traefik.backend=postal-app
|
||||
volumes:
|
||||
- ./data/postal:/opt/postal/config
|
||||
- ./logs/postal:/logs
|
||||
environment:
|
||||
- ZABBIX_HOSTNAME=postal-app
|
||||
- ZABBIX_HOSTNAME=postal-app
|
||||
|
||||
- DB_HOST=postal-db
|
||||
- DB_PORT=3306
|
||||
- DB_NAME=postal
|
||||
- DB_USER=postal
|
||||
- DB_PASS=postalpass
|
||||
- DB_HOST=postal-db
|
||||
- DB_PORT=3306
|
||||
- DB_NAME=postal
|
||||
- DB_USER=postal
|
||||
- DB_PASS=postalpass
|
||||
|
||||
## A great idea is to delete this block after first install.
|
||||
- DB_ROOT_PASS=rootpassword
|
||||
- ADMIN_EMAIL=example@hostname.com
|
||||
- ADMIN_PASS=password
|
||||
- ADMIN_FNAME=Example
|
||||
- ADMIN_LNAME=Admin
|
||||
- DB_ROOT_PASS=rootpassword
|
||||
- ADMIN_EMAIL=example@hostname.com
|
||||
- ADMIN_PASS=password
|
||||
- ADMIN_FNAME=Example
|
||||
- ADMIN_LNAME=Admin
|
||||
##
|
||||
|
||||
- RABBITMQ_HOST=postal-rabbitmq
|
||||
- RABBITMQ_USER=postal
|
||||
- RABBITMQ_PASS=password
|
||||
- RABBITMQ_VHOST=/postal
|
||||
- RABBITMQ_HOST=postal-rabbitmq
|
||||
- RABBITMQ_USER=postal
|
||||
- RABBITMQ_PASS=password
|
||||
- RABBITMQ_VHOST=/postal
|
||||
|
||||
- ENABLE_SPAMASSASSIN=false
|
||||
- SPAMASSASSIN_HOST=postal-spamassassin
|
||||
|
||||
- ENABLE_CLAMAV=false
|
||||
- CLAMAV_HOST=postal-clamav
|
||||
|
||||
- DNS_HOSTNAME=postal.example.com
|
||||
- DNS_MX=mx.example.com
|
||||
- DNS_RETURN_PATH=rp.example.com
|
||||
- DNS_ROUTE_DOMAIN=routes.postal.example.com
|
||||
- DNS_TRACK_DOMAIN=track.postal.example.com
|
||||
- DNS_SPF=spf.postal.example.com
|
||||
- DNS_DKIM_IDENTIFIER=postal
|
||||
- DNS_DOMAIN_VERIFY_PREFIX=example-verification
|
||||
- SMTP_SERVER_ENABLE_TLS=false
|
||||
|
||||
- SMTP_FROM_ADDRESS=noreply@example.com
|
||||
- SMTP_FROM_NAME=Postal
|
||||
- SMTP_HOST=127.0.0.1
|
||||
- SMTP_USER= ## Fill these in after you have a working installation and server to be able to invite users
|
||||
- SMTP_PASS= ##
|
||||
|
||||
- WEB_HOST=mx.example.com
|
||||
- WEB_PROTOCOL=https
|
||||
|
||||
- ENABLE_SPAMASSASSIN=false
|
||||
- ENABLE_CLAMAV=false
|
||||
- CLAMAV_HOST=postal-clamav
|
||||
- SPAMASSASSIN_HOST=postal-spamassassin
|
||||
networks:
|
||||
- proxy
|
||||
- services
|
||||
- proxy
|
||||
- services
|
||||
restart: always
|
||||
|
||||
postal-db:
|
||||
image: tiredofit/mariadb
|
||||
container_name: postal-db
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
- ./db:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=rootpassword
|
||||
- MYSQL_DATABASE=postal
|
||||
- MYSQL_USER=postal
|
||||
- MYSQL_PASSWORD=postalpass
|
||||
- ZABBIX_HOSTNAME=postal-db
|
||||
- MYSQL_ROOT_PASSWORD=rootpassword
|
||||
- MYSQL_DATABASE=postal
|
||||
- MYSQL_USER=postal
|
||||
- MYSQL_PASSWORD=postalpass
|
||||
- ZABBIX_HOSTNAME=postal-db
|
||||
networks:
|
||||
- services
|
||||
- services
|
||||
restart: always
|
||||
|
||||
postal-db-backup:
|
||||
image: tiredofit/db-backup
|
||||
container_name: postal-db-backup
|
||||
links:
|
||||
- postal-db
|
||||
- postal-db
|
||||
volumes:
|
||||
- ./dbbackup:/backup
|
||||
- ./dbbackup:/backup
|
||||
environment:
|
||||
- ZABBIX_HOSTNAME=postal-db-backup
|
||||
- DB_HOST=postal-db
|
||||
- DB_TYPE=mariadb
|
||||
- DB_NAME=postal
|
||||
- DB_USER=postal
|
||||
- DB_PASS=postalpass
|
||||
- DB_DUMP_FREQ=1440
|
||||
- DB_DUMP_BEGIN=0000
|
||||
- DB_CLEANUP_TIME=8640
|
||||
- COMPRESSION=BZ
|
||||
- MD5=TRUE
|
||||
- ZABBIX_HOSTNAME=postal-db-backup
|
||||
- DB_HOST=postal-db
|
||||
- DB_TYPE=mariadb
|
||||
- DB_NAME=postal
|
||||
- DB_USER=postal
|
||||
- DB_PASS=postalpass
|
||||
- DB_DUMP_FREQ=1440
|
||||
- DB_DUMP_BEGIN=0000
|
||||
- DB_CLEANUP_TIME=8640
|
||||
- COMPRESSION=BZ
|
||||
- MD5=TRUE
|
||||
networks:
|
||||
- services
|
||||
- services
|
||||
restart: always
|
||||
|
||||
postal-rabbitmq:
|
||||
container_name: postal-rabbitmq
|
||||
image: tiredofit/rabbitmq
|
||||
environment:
|
||||
- RABBITMQ_DEFAULT_USER=postal
|
||||
- RABBITMQ_DEFAULT_PASS=password
|
||||
- RABBITMQ_DEFAULT_VHOST=/postal
|
||||
- ZABBIX_HOSTNAME=postal-rabbitmq
|
||||
- RABBITMQ_DEFAULT_USER=postal
|
||||
- RABBITMQ_DEFAULT_PASS=password
|
||||
- RABBITMQ_DEFAULT_VHOST=/postal
|
||||
- ZABBIX_HOSTNAME=postal-rabbitmq
|
||||
networks:
|
||||
- services
|
||||
- services
|
||||
restart: always
|
||||
|
||||
postal-clamav:
|
||||
container_name: postal-clamav
|
||||
image: tiredofit/clamav
|
||||
volumes:
|
||||
- ./data/clamav:/data
|
||||
- ./logs/clamav:/var/log/clamav
|
||||
- ./data/clamav:/data
|
||||
- ./logs/clamav:/var/log/clamav
|
||||
environment:
|
||||
- ZABBIX_HOSTNAME=postal-clamav
|
||||
- ZABBIX_HOSTNAME=postal-clamav
|
||||
networks:
|
||||
- services
|
||||
- services
|
||||
restart: always
|
||||
|
||||
postal-spamassassin:
|
||||
image: tiredofit/spamassassin
|
||||
container_name: postal-spamassassin-app
|
||||
volumes:
|
||||
- ./logs/spamassassin:/var/log/spamassassin
|
||||
- ./data/spamassassin/conf:/etc/mail/spamassassin
|
||||
- ./data/spamassassin/data:/var/lib/spamassassin
|
||||
- ./logs/spamassassin:/var/log/spamassassin
|
||||
- ./data/spamassassin/conf:/etc/mail/spamassassin
|
||||
- ./data/spamassassin/data:/var/lib/spamassassin
|
||||
environment:
|
||||
- ZABBIX_HOSTNAME=postal-spamassassin
|
||||
- ZABBIX_HOSTNAME=postal-spamassassin
|
||||
networks:
|
||||
- services
|
||||
- services
|
||||
restart: always
|
||||
|
||||
networks:
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
# These are the default configuration options that will be used if they aren't overriden
|
||||
# in your postal.yml configuration file. No changes should be made to this file for
|
||||
# your installation.
|
||||
|
||||
# You can refer to this for a complete listing all available configuration options.
|
||||
|
||||
web:
|
||||
host: postal.example.com
|
||||
protocol: http
|
||||
|
||||
general:
|
||||
use_ip_pools: false
|
||||
exception_url:
|
||||
maximum_delivery_attempts: 18
|
||||
use_local_ns_for_domains: false
|
||||
|
||||
web_server:
|
||||
bind_address: 0.0.0.0
|
||||
port: 5000
|
||||
max_threads: 5
|
||||
|
||||
fast_server:
|
||||
enabled: false
|
||||
bind_address:
|
||||
# Set appropriate IP addresses to listen on. These should be dedicated IP
|
||||
# addresses just used for this server. You should list IPv4 and IPv6 addresses
|
||||
# as appropriate.
|
||||
# - 1.2.3.4
|
||||
# - abcd:a:b:c:d::1
|
||||
port: 80
|
||||
ssl_port: 443
|
||||
proxy_protocol: false
|
||||
default_private_key_path: # Defaults to config/fast_server.key
|
||||
default_tls_certificate_path: # Defaults to config/fast_server.cert
|
||||
|
||||
main_db:
|
||||
host: 127.0.0.1
|
||||
port: 3306
|
||||
username: postal
|
||||
password:
|
||||
database: postal
|
||||
pool_size: 5
|
||||
|
||||
logging:
|
||||
stdout: false
|
||||
root: # Automatically determined based on config root
|
||||
max_log_file_size: 20
|
||||
max_log_files: 10
|
||||
greylog:
|
||||
host:
|
||||
port: 12201
|
||||
|
||||
message_db:
|
||||
host: 127.0.0.1
|
||||
port: 3306
|
||||
username: postal
|
||||
password:
|
||||
prefix: postal
|
||||
|
||||
rabbitmq:
|
||||
host: 127.0.0.1
|
||||
port: 5672
|
||||
username: postal
|
||||
password:
|
||||
vhost: /postal
|
||||
|
||||
workers:
|
||||
quantity: 1
|
||||
threads: 4
|
||||
|
||||
smtp_server:
|
||||
port: 25
|
||||
tls_enabled: false
|
||||
tls_certificate_path: # Defaults to config/smtp.cert
|
||||
tls_private_key_path: # Defaults to config/smtp.key
|
||||
tls_ciphers:
|
||||
ssl_version: SSLv23
|
||||
proxy_protocol: false
|
||||
log_connect: true
|
||||
strip_received_headers: false
|
||||
max_message_size: 14 # size in Megabytes
|
||||
|
||||
smtp_relays:
|
||||
-
|
||||
hostname:
|
||||
port: 25
|
||||
ssl_mode: Auto
|
||||
|
||||
dns:
|
||||
mx_records:
|
||||
- mx.postal.example.com
|
||||
smtp_server_hostname: postal.example.com
|
||||
spf_include: spf.postal.example.com
|
||||
return_path: rp.postal.example.com
|
||||
route_domain: routes.postal.example.com
|
||||
track_domain: track.postal.example.com
|
||||
helo_hostname: # By default, this will be the same as the `smtp_server_hostname`
|
||||
dkim_identifier: postal
|
||||
domain_verify_prefix: postal-verification
|
||||
custom_return_path_prefix: psrp
|
||||
|
||||
smtp:
|
||||
host: 127.0.0.1
|
||||
port: 25
|
||||
username: # Complete when Postal is running and you can
|
||||
password: # generate the credentials within the interface.
|
||||
from_name: Postal
|
||||
from_address: postal@yourdomain.com
|
||||
|
||||
rails:
|
||||
environment: production
|
||||
secret_key:
|
||||
|
||||
spamd:
|
||||
enabled: false
|
||||
host: 127.0.0.1
|
||||
port: 783
|
||||
|
||||
clamav:
|
||||
enabled: false
|
||||
host: 127.0.0.1
|
||||
port: 2000
|
||||
|
||||
smtp_client:
|
||||
open_timeout: 30
|
||||
read_timeout: 60
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
# These are the default configuration options that will be used if they aren't overriden
|
||||
# in your postal.yml configuration file. No changes should be made to this file for
|
||||
# your installation.
|
||||
|
||||
# You can refer to this for a complete listing all available configuration options.
|
||||
|
||||
web:
|
||||
host: postal.example.com
|
||||
protocol: http
|
||||
|
||||
general:
|
||||
use_ip_pools: false
|
||||
exception_url:
|
||||
maximum_delivery_attempts: 18
|
||||
use_local_ns_for_domains: false
|
||||
|
||||
web_server:
|
||||
bind_address: 0.0.0.0
|
||||
port: 5000
|
||||
max_threads: 5
|
||||
|
||||
fast_server:
|
||||
enabled: false
|
||||
bind_address:
|
||||
# Set appropriate IP addresses to listen on. These should be dedicated IP
|
||||
# addresses just used for this server. You should list IPv4 and IPv6 addresses
|
||||
# as appropriate.
|
||||
# - 1.2.3.4
|
||||
# - abcd:a:b:c:d::1
|
||||
port: 80
|
||||
ssl_port: 443
|
||||
proxy_protocol: false
|
||||
default_private_key_path: # Defaults to config/fast_server.key
|
||||
default_tls_certificate_path: # Defaults to config/fast_server.cert
|
||||
|
||||
main_db:
|
||||
host: 127.0.0.1
|
||||
port: 3306
|
||||
username: postal
|
||||
password:
|
||||
database: postal
|
||||
pool_size: 5
|
||||
|
||||
logging:
|
||||
stdout: false
|
||||
root: # Automatically determined based on config root
|
||||
max_log_file_size: 20
|
||||
max_log_files: 10
|
||||
greylog:
|
||||
host:
|
||||
port: 12201
|
||||
|
||||
message_db:
|
||||
host: 127.0.0.1
|
||||
port: 3306
|
||||
username: postal
|
||||
password:
|
||||
prefix: postal
|
||||
|
||||
rabbitmq:
|
||||
host: 127.0.0.1
|
||||
port: 5672
|
||||
username: postal
|
||||
password:
|
||||
vhost: /postal
|
||||
|
||||
workers:
|
||||
quantity: 1
|
||||
threads: 4
|
||||
|
||||
smtp_server:
|
||||
port: 25
|
||||
tls_enabled: false
|
||||
tls_certificate_path: # Defaults to config/smtp.cert
|
||||
tls_private_key_path: # Defaults to config/smtp.key
|
||||
tls_ciphers:
|
||||
ssl_version: SSLv23
|
||||
proxy_protocol: false
|
||||
log_connect: true
|
||||
strip_received_headers: false
|
||||
max_message_size: 14 # size in Megabytes
|
||||
|
||||
smtp_relays:
|
||||
-
|
||||
hostname:
|
||||
port: 25
|
||||
ssl_mode: Auto
|
||||
|
||||
dns:
|
||||
mx_records:
|
||||
- mx.postal.example.com
|
||||
smtp_server_hostname: postal.example.com
|
||||
spf_include: spf.postal.example.com
|
||||
return_path: rp.postal.example.com
|
||||
route_domain: routes.postal.example.com
|
||||
track_domain: track.postal.example.com
|
||||
helo_hostname: # By default, this will be the same as the `smtp_server_hostname`
|
||||
dkim_identifier: postal
|
||||
domain_verify_prefix: postal-verification
|
||||
custom_return_path_prefix: psrp
|
||||
|
||||
smtp:
|
||||
host: 127.0.0.1
|
||||
port: 25
|
||||
username: # Complete when Postal is running and you can
|
||||
password: # generate the credentials within the interface.
|
||||
from_name: Postal
|
||||
from_address: postal@yourdomain.com
|
||||
|
||||
rails:
|
||||
environment: production
|
||||
secret_key:
|
||||
|
||||
spamd:
|
||||
enabled: false
|
||||
host: 127.0.0.1
|
||||
port: 783
|
||||
|
||||
clamav:
|
||||
enabled: false
|
||||
host: 127.0.0.1
|
||||
port: 2000
|
||||
|
||||
smtp_client:
|
||||
open_timeout: 30
|
||||
read_timeout: 60
|
||||
|
52
install/assets/defaults/10-postal
Executable file
52
install/assets/defaults/10-postal
Executable file
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/with-contenv /bin/bash
|
||||
|
||||
CLAMAV_PORT=${CLAMAV_PORT:-3310}
|
||||
CONFIG_LOCATION=${CONFIG_LOCATION:-"/app/config/postal.yml"}
|
||||
DB_PORT=${DB_PORT:-3306}
|
||||
DNS_DKIM_IDENTIFIER=${DNS_DKIM_IDENTIFIER:-"postal"}
|
||||
DNS_DOMAIN_VERIFY_PREFIX=${DNS_DOMAIN_VERIFY_PREFIX:-"postal-verification"}
|
||||
DNS_HOSTNAME=${DNS_HOSTNAME:-"postal.example.com"}
|
||||
DNS_MX=${DNS_MX:-"mx.$DNS_HOSTNAME"}
|
||||
DNS_RETURN_PATH=${DNS_RETURN_PATH:-"rp."$DNS_HOSTNAME}
|
||||
DNS_RETURN_PATH_PREFIX=${DNS_RETURN_PATH_PREFIX:-"psrp"}
|
||||
DNS_ROUTE_DOMAIN=${DNS_ROUTE_DOMAIN:-"routes."$DNS_HOSTNAME}
|
||||
DNS_SPF=${DNS_SPF:-"spf."$DNS_HOSTNAME}
|
||||
DNS_TRACK_DOMAIN=${DNS_TRACK_DOMAIN:-"track."$DNS_HOSTNAME}
|
||||
ENABLE_CLAMAV=${ENABLE_CLAMAV:-false}
|
||||
ENABLE_SPAMASSASSIN=${ENABLE_SPAMASSASSIN:-false}
|
||||
LOG_CONSOLE=${LOG_CONSOLE:-true}
|
||||
RABBITMQ_PORT=${RABBITMQ_PORT:-5672}
|
||||
SETUP_TYPE=${SETUP_TYPE:-"AUTO"}
|
||||
SMTP_CLIENT_OPEN_TIMEOUT=${SMTP_CLIENT_OPEN_TIMEOUT:-30}
|
||||
SMTP_CLIENT_READ_TIMEOUT=${SMTP_CLIENT_READ_TIMEOUT:-60}
|
||||
SMTP_FROM_ADDRESS=${SMTP_FROM_ADDRESS:-"postal@yourdomain.com"}
|
||||
SMTP_FROM_NAME=${SMTP_FROM_NAME:-"Postal"}
|
||||
SMTP_HOST=${SMTP_HOST:-127.0.0.1}
|
||||
SMTP_PORT=${SMTP_PORT:-25}
|
||||
SMTP_RELAY_HOST=${SMTP_RELAY_HOST:-""}
|
||||
SMTP_RELAY_PORT=${SMTP_RELAY_PORT:-25}
|
||||
SMTP_RELAY_SSL_MODE=${SMTP_RELAY_SSL_MODE:-"Auto"}
|
||||
SMTP_SERVER_ENABLE_TLS=${SMTP_SERVER_ENABLE_TLS:-"false"}
|
||||
SMTP_SERVER_HELO_HOSTNAME=${SMTP_SERVER_HELO_HOSTNAME:-$DNS_HOSTNAME}
|
||||
SMTP_SERVER_LOG_CONNECTIONS=${SMTP_SERVER_LOG_CONNECTIONS:-"true"}
|
||||
SMTP_SERVER_MAX_MESSAGE_SIZE=${SMTP_SERVER_MAX_MESSAGE_SIZE:-14}
|
||||
SMTP_SERVER_PORT=${SMTP_SERVER_PORT:-"25"}
|
||||
SMTP_SERVER_PROXY_PROTOCOL=${SMTP_SERVER_PROXY_PROTOCOL:-"false"}
|
||||
SMTP_SERVER_SSL_VERSION=${SMTP_SERVER_SSL_VERSION:-"SSLv23"}
|
||||
SMTP_SERVER_STRIP_RECEIVED_HEADERS=${SMTP_SERVER_STRIP_RECEIVED_HEADERS:-"false"}
|
||||
SMTP_SERVER_TLS_CERT=${SMTP_SERVER_TLS_CERT:-"/certs/cert.pem"}
|
||||
SMTP_SERVER_TLS_CIPHERS=${SMTP_SERVER_TLS_CIPHERS:-""}
|
||||
SMTP_SERVER_TLS_KEY=${SMTP_SERVER_TLS_KEY:-"/certs/key.pem"}
|
||||
SPAMASSASSIN_PORT=${SPAMASSASSIN_PORT:-737}
|
||||
WEB_HOST=${WEB_HOST:-"postal.example.com"}
|
||||
WEB_PROTOCOL=${WEB_PROTOCOL:-"http"}
|
||||
LOG_SIZE_MAX=${LOG_SIZE_MAX:-9999}
|
||||
MAX_DELIVERY_ATTEMPTS=${MAX_DELIVERY_ATTEMPTS:-18}
|
||||
MAX_HOLD_EXPIRY_DAYS=${MAX_HOLD_EXPIRY_DAYS:-7}
|
||||
SUPPRESSION_LIST_EXPIRY=${SUPPRESSION_LIST_EXPIRY:-30}
|
||||
WEB_BIND_IP=${WEB_BIND_IP:-"0.0.0.0"}
|
||||
WEB_BIND_PORT=${WEB_BIND_PORT:-5000}
|
||||
WEB_MAX_THREADS=${WEB_MAX_THREADS:-5}
|
||||
LOG_LOCATION=${LOG_LOCATION:-"/logs/"}
|
||||
WORKERS_AMOUNT=${WORKERS_AMOUNT:-1}
|
||||
WORKERS_THREADS=${WORKERS_THREADS:-4}
|
398
install/assets/functions/10-postal
Executable file
398
install/assets/functions/10-postal
Executable file
|
@ -0,0 +1,398 @@
|
|||
#!/usr/bin/with-contenv /bin/bash
|
||||
|
||||
certificates() {
|
||||
CA_NAME=postal-selfsigned-ca
|
||||
CA_SUBJECT=${CA_SUBJECT:-"/C=XX/ST=Postal/L=Postal/O=Postal/CN="}
|
||||
CA_CERT_SUBJECT=${CA_CERT_SUBJECT:-${CA_SUBJECT}${CA_NAME}}
|
||||
CA_CERT_FILE=${CA_CERT_FILE:-"/certs/${CA_NAME}/${CA_NAME}.crt"}
|
||||
CA_KEY_FILE=${CA_KEY_FILE:-"`dirname ${CA_CERT_FILE}`/${CA_NAME}.key"}
|
||||
CREATE_CA=${CREATE_CA:-"TRUE"}
|
||||
|
||||
certificates_create_certificate_authority() {
|
||||
if [ ! -f ${CA_CERT_FILE} ] ; then
|
||||
|
||||
print_debug "Certificates: Creating Self Signed Certificate Authority"
|
||||
mkdir -p `dirname ${CA_CERT_FILE}`
|
||||
echo "000a" > `dirname ${CA_CERT_FILE}`/serial
|
||||
touch `dirname ${CA_CERT_FILE}`/certindex
|
||||
|
||||
silent eval "openssl req \
|
||||
-newkey rsa:4096 -keyout ${CA_KEY_FILE} \
|
||||
-x509 -days 3650 -nodes -out ${CA_CERT_FILE} \
|
||||
-subj \"${CA_CERT_SUBJECT}\""
|
||||
|
||||
cat > `dirname ${CA_CERT_FILE}`/$CA_NAME.conf << EOF
|
||||
[ ca ]
|
||||
default_ca = $CA_NAME
|
||||
|
||||
[ $CA_NAME ]
|
||||
unique_subject = no
|
||||
new_certs_dir = .
|
||||
certificate = ${CA_CERT_FILE}
|
||||
database = `dirname ${CA_CERT_FILE}`/certindex
|
||||
private_key = ${CA_KEY_FILE}
|
||||
serial = `dirname ${CA_CERT_FILE}`/serial
|
||||
default_days = 3650
|
||||
default_md = default
|
||||
policy = ${CA_NAME}_policy
|
||||
x509_extensions = ${CA_NAME}_extensions
|
||||
|
||||
[ ${CA_NAME}_policy ]
|
||||
commonName = supplied
|
||||
stateOrProvinceName = supplied
|
||||
countryName = supplied
|
||||
emailAddress = optional
|
||||
organizationName = supplied
|
||||
organizationalUnitName = optional
|
||||
|
||||
[ ${CA_NAME}_extensions ]
|
||||
basicConstraints = CA:false
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always
|
||||
keyUsage = digitalSignature,keyEncipherment
|
||||
extendedKeyUsage = serverAuth,clientAuth
|
||||
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
|
||||
[default_conf]
|
||||
ssl_conf = ssl_sect
|
||||
|
||||
[ssl_sect]
|
||||
system_default = system_default_sect
|
||||
|
||||
[system_default_sect]
|
||||
MinProtocol = TLSv1.1
|
||||
MaxProtocol = TLSv1.2
|
||||
CipherString = DEFAULT
|
||||
EOF
|
||||
else
|
||||
print_debug "Certificates: CA Already Exists"
|
||||
fi
|
||||
}
|
||||
|
||||
certificates_create_certificate() {
|
||||
if [ "$1" != "" ] ; then
|
||||
|
||||
if var_true $CREATE_CA ; then
|
||||
if [ ! -f ${CA_CERT_FILE} ] || [ ! -f ${CA_KEY_FILE} ] ; then
|
||||
print_debug "Certificates: No CA Found - Creating before generating certificates"
|
||||
certificates_create_certificate_authority
|
||||
fi
|
||||
fi
|
||||
CERT_SUBJECT="${CA_SUBJECT}$(basename ${1%%.*})"
|
||||
|
||||
if var_true $CREATE_CA ; then
|
||||
if [ ! -f ${1%%.*}.key ] ; then
|
||||
print_debug "Certificates: Creating Certificate: ${1}"
|
||||
silent eval "openssl req \
|
||||
-newkey rsa:2048 -keyout ${1%%.*}.key \
|
||||
-nodes -out ${1%%.*}.csr \
|
||||
-subj \"${CERT_SUBJECT}\""
|
||||
fi
|
||||
else
|
||||
if [ ! -f ${1%%.*}.key ] ; then
|
||||
print_debug "Certificates: Creating Certificate: ${1}"
|
||||
silent eval "openssl req \
|
||||
-new -x509 -nodes -days 3650
|
||||
-config /tmp/openssl.cnf
|
||||
-out ${1%%.*}.crt \
|
||||
-keyout ${1%%.*}.key"
|
||||
fi
|
||||
fi
|
||||
|
||||
if var_true $CREATE_CA ; then
|
||||
if [ ! -f ${1%%.*}.cert ] ; then
|
||||
print_debug "Certificates: Signing Certificate: ${1}"
|
||||
silent eval "openssl ca -batch -config `dirname ${CA_CERT_FILE}`/${CA_NAME}.conf -notext -in ${1%%.*}.csr -out ${1%%.*}.crt"
|
||||
rm -rf $(tail -n 1 $(dirname ${CA_CERT_FILE})/certindex | awk '{print $3}').pem
|
||||
rm -rf ${1%%.*}.csr
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f ${1%%.*}.crt ] && [ -f ${1%%.*}.key ] ; then
|
||||
print_debug "Certificates: Merging Certificate and Key for: ${1}"
|
||||
cat ${1%%.*}.crt ${1%%.*}.key > ${1%%.*}.pem
|
||||
fi
|
||||
|
||||
if [ ! -f ${1%%.*}-public.pem ] ; then
|
||||
print_debug "Certificates: Creating Public Key for: ${1}"
|
||||
silent openssl rsa -in ${1%%.*}.pem -out ${1%%.*}-public.pem -outform PEM -pubout
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
certificates_check_permissions() {
|
||||
if sudo -u postal [ ! -r "${1}" ] ; then
|
||||
print_debug "Certificates: Setting Read Permissions on ${1}"
|
||||
chmod 444 ${1}
|
||||
fi
|
||||
}
|
||||
|
||||
certificates_check_certificates() {
|
||||
print_debug "Certificates: Checking Existence of ${1}"
|
||||
if [ ! -f ${1} ] ; then
|
||||
mkdir -p `dirname ${1}`
|
||||
certificates_create_certificate ${1}
|
||||
fi
|
||||
}
|
||||
|
||||
certificates_trust_ca() {
|
||||
if var_true $CREATE_CA ; then
|
||||
if [ -f ${CA_CERT_FILE} ]; then
|
||||
if [ ! -L /usr/local/share/ca-certificates/`basename ${CA_CERT_FILE}` ] ; then
|
||||
print_debug "Certificates: Trusting CA ${CA_NAME}"
|
||||
ln -sf ${CA_CERT_FILE} /usr/local/share/ca-certificates/
|
||||
silent update-ca-certificates
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
*)
|
||||
certificates_check_certificates ${1}
|
||||
certificates_check_permissions ${1}
|
||||
certificates_trust_ca
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
check_clamav(){
|
||||
print_debug "Checking Clam Antivirus"
|
||||
if var_true $ENABLE_CLAMAV ; then
|
||||
sanity_var CLAMAV_HOST "ClamAV Host"
|
||||
COUNTER=0
|
||||
while ! (silent nc -z ${CLAMAV_HOST} ${CLAMAV_PORT}) ; do
|
||||
sleep 5
|
||||
let COUNTER+=5
|
||||
print_warn "ClamAV Host '${CLAMAV_HOST}' is not accessible, retrying.. ($COUNTER seconds so far)"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
check_spamassassin() {
|
||||
print_debug "Checking Spamassassin"
|
||||
if var_true $ENABLE_SPAMASSASSIN ; then
|
||||
sanity_var SPAMASSASSIN_HOST "SpamAssassin Host"
|
||||
COUNTER=0
|
||||
while ! (silent nc -z ${SPAMASSASSIN_HOST} ${SPAMASSASSIN_PORT}) ; do
|
||||
sleep 5
|
||||
let COUNTER+=5
|
||||
print_warn "SpamAssassin Host '${SPAMASSASSIN_HOST}' is not accessible, retrying.. ($COUNTER seconds so far)"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
configure_logging() {
|
||||
print_debug "Configuring Logging"
|
||||
mkdir -p ${LOG_LOCATION}
|
||||
chown -R postal: ${LOG_LOCATION}
|
||||
}
|
||||
|
||||
configure_postal() {
|
||||
|
||||
silent /app/bin/postal initialize-config
|
||||
### Create Configuration
|
||||
if [ "$SETUP_TYPE" = "AUTO" ]; then
|
||||
print_notice "Configuring Postal"
|
||||
mx_array=$(echo $DNS_MX | tr "," "\n")
|
||||
for mx in ${mx_array}
|
||||
do
|
||||
mx_record=${mx_record}$(cat <<EOF
|
||||
- ${mx}
|
||||
|
||||
EOF
|
||||
)
|
||||
done
|
||||
print_debug "Writing Configuration to ${CONFIG_LOCATION}"
|
||||
cat <<EOF > ${CONFIG_LOCATION}
|
||||
web:
|
||||
host: ${WEB_HOST}
|
||||
protocol: ${WEB_PROTOCOL}
|
||||
|
||||
general:
|
||||
use_ip_pools: false
|
||||
exception_url:
|
||||
maximum_delivery_attempts: ${MAX_DELIVERY_ATTEMPTS}
|
||||
maximum_hold_expiry_days: ${MAX_HOLD_EXPIRY_DAYS}
|
||||
suppression_list_removal_delay: ${SUPPRESSION_LIST_EXPIRY}
|
||||
use_local_ns_for_domains: false
|
||||
|
||||
fast_server:
|
||||
enabled: false
|
||||
bind_address:
|
||||
port: 80
|
||||
ssl_port: 443
|
||||
proxy_protocol: false
|
||||
default_private_key_path: # Defaults to config/fast_server.key
|
||||
default_tls_certificate_path: # Defaults to config/fast_server.cert
|
||||
|
||||
web_server:
|
||||
bind_address: ${WEB_BIND_IP}
|
||||
port: ${WEB_BIND_PORT}
|
||||
max_threads: ${WEB_MAX_THREADS}
|
||||
|
||||
main_db:
|
||||
host: ${DB_HOST}
|
||||
port: ${DB_PORT}
|
||||
username: ${DB_USER}
|
||||
password: ${DB_PASS}
|
||||
database: ${DB_NAME}
|
||||
pool_size: ${DB_POOL_SIZE}
|
||||
|
||||
logging:
|
||||
stdout: ${LOG_CONSOLE}
|
||||
root: ${LOG_LOCATION}
|
||||
max_log_file_size: ${LOG_SIZE_MAX}
|
||||
|
||||
message_db:
|
||||
host: ${DB_HOST}
|
||||
port: ${DB_PORT}
|
||||
username: ${DB_USER}
|
||||
password: ${DB_PASS}
|
||||
prefix: ${DB_NAME}
|
||||
|
||||
rabbitmq:
|
||||
host: ${RABBITMQ_HOST}
|
||||
port: ${RABBITMQ_PORT}
|
||||
username: ${RABBITMQ_USER}
|
||||
password: ${RABBITMQ_PASS}
|
||||
vhost: ${RABBITMQ_VHOST}
|
||||
|
||||
workers:
|
||||
quantity: ${WORKERS_AMOUNT}
|
||||
threads: ${WORKERS_THREADS}
|
||||
|
||||
smtp_server:
|
||||
port: ${SMTP_SERVER_PORT}
|
||||
tls_enabled: ${SMTP_SERVER_ENABLE_TLS}
|
||||
tls_certificate_path: ${SMTP_SERVER_TLS_CERT}
|
||||
tls_private_key_path: ${SMTP_SERVER_TLS_KEY}
|
||||
tls_ciphers: ${SMTP_SERVER_TLS_CIPHERS}
|
||||
ssl_version: ${SMTP_SERVER_SSL_VERSION}
|
||||
proxy_protocol: ${SMTP_SERVER_PROXY_PROTOCOL}
|
||||
log_connect: ${SMTP_SERVER_LOG_CONNECTIONS}
|
||||
strip_received_headers: ${SMTP_SERVER_STRIP_RECEIVED_HEADERS}
|
||||
max_message_size: ${SMTP_SERVER_MAX_MESSAGE_SIZE}
|
||||
|
||||
smtp_relays:
|
||||
-
|
||||
hostname: ${SMTP_RELAY_HOST}
|
||||
port: ${SMTP_RELAY_PORT}
|
||||
ssl_mode: ${SMTP_RELAY_SSL_MODE}
|
||||
|
||||
dns:
|
||||
mx_records:
|
||||
${mx_record}
|
||||
smtp_server_hostname: ${DNS_HOSTNAME}
|
||||
spf_include: ${DNS_SPF}
|
||||
return_path: ${DNS_RETURN_PATH}
|
||||
route_domain: ${DNS_ROUTE_DOMAIN}
|
||||
track_domain: ${DNS_TRACK_DOMAIN}
|
||||
dkim_identifier: ${DNS_DKIM_IDENTIFIER}
|
||||
domain_verify_prefix: ${DNS_DOMAIN_VERIFY_PREFIX}
|
||||
custom_return_path_prefix: ${DNS_RETURN_PATH_PREFIX}
|
||||
|
||||
smtp:
|
||||
host: ${SMTP_HOST}
|
||||
port: ${SMTP_PORT}
|
||||
username: ${SMTP_USER}
|
||||
password: ${SMTP_PASS}
|
||||
from_name: ${SMTP_FROM_NAME}
|
||||
from_address: ${SMTP_FROM_ADDRESS}
|
||||
|
||||
rails:
|
||||
environment: production
|
||||
secret_key:
|
||||
|
||||
spamd:
|
||||
enabled: ${ENABLE_SPAMASSASSIN}
|
||||
host: ${SPAMASSASSIN_HOST}
|
||||
port: ${SPAMASSASSIN_PORT}
|
||||
|
||||
clamav:
|
||||
enabled: ${ENABLE_CLAMAV}
|
||||
host: ${CLAMAV_HOST}
|
||||
port: ${CLAMAV_PORT}
|
||||
|
||||
smtp_client:
|
||||
open_timeout: ${SMTP_CLIENT_OPEN_TIMEOUT}
|
||||
read_timeout: ${SMTP_CLIENT_READ_TIMEOUT}
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [ "${CONFIG_LOCATION}" != "/app/config/postal.yml" ] ; then
|
||||
ln -sf ${CONFIG_LOCATION} /app/config/postal.yml
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
check_rabbitmq() {
|
||||
print_debug "Checking RabbitMQ"
|
||||
sanity_var RABBITMQ_HOST "RabbitMQ Host"
|
||||
sanity_var RABBITMQ_VHOST "RabbitMQ VHost"
|
||||
sanity_var RABBITMQ_PASS "RabbitMQ Password"
|
||||
|
||||
print_debug "Checking to see if RabbitMQ Host: ${RABBITMQ_HOST} is accessible"
|
||||
COUNTER=0
|
||||
while ! (silent nc -z ${RABBITMQ_HOST} ${RABBITMQ_PORT}) ; do
|
||||
sleep 5
|
||||
let COUNTER+=5
|
||||
print_warn "RabbitMQ Host '${RABBITMQ_HOST}' is not accessible, retrying.. ($COUNTER seconds so far)"
|
||||
done
|
||||
}
|
||||
|
||||
configure_tls() {
|
||||
print_debug "Configuring TLS"
|
||||
if var_true $SMTP_SERVER_ENABLE_TLS ; then
|
||||
certificates ${SMTP_SERVER_TLS_CERT}
|
||||
fi
|
||||
}
|
||||
|
||||
initialize_database() {
|
||||
print_debug "Initializing Database"
|
||||
silent /app/bin/postal initialize
|
||||
|
||||
mysql -uroot -p$DB_ROOT_PASS -h$DB_HOST -e 'GRANT ALL PRIVILEGES ON `'$DB_NAME'-%` . * to `'$DB_USER'`@`%` IDENTIFIED BY "'$DB_PASS'";'
|
||||
print_notice "Creating Administrative User"
|
||||
if [ ! -n "${ADMIN_EMAIL}" ]; then
|
||||
print_warn "No Admin Email entered, setting default email login to 'postal@example.com'"
|
||||
ADMIN_EMAIL="postal@example.com"
|
||||
fi
|
||||
if [ ! -n "${ADMIN_PASS}" ]; then
|
||||
print_warn "WARNING: No Admin Pass entered, setting default password to 'postal'"
|
||||
ADMIN_PASS="postal"
|
||||
fi
|
||||
if [ ! -n "${ADMIN_FNAME}" ]; then
|
||||
print_warn "No Admin First Name entered, setting default to 'Postal'"
|
||||
ADMIN_FNAME="Postal"
|
||||
fi
|
||||
if [ ! -n "${ADMIN_LNAME}" ]; then
|
||||
print_warn "No Admin Last Name entered, setting default to 'Admin'"
|
||||
ADMIN_LNAME="Admin"
|
||||
fi
|
||||
|
||||
cat <<EOF > /tmp/create-pass.sh
|
||||
#!/usr/bin/expect
|
||||
|
||||
spawn /usr/bin/with-contenv bash /app/bin/postal make-user
|
||||
expect "E-Mail Address :"
|
||||
send -- "$ADMIN_EMAIL\n"
|
||||
expect "First Name :"
|
||||
send -- "$ADMIN_FNAME\n"
|
||||
expect "Last Name :"
|
||||
send -- "$ADMIN_LNAME\n"
|
||||
expect "Initial Password: :"
|
||||
send -- "$ADMIN_PASS\n"
|
||||
expect "User has been created"
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x /tmp/create-pass.sh
|
||||
print_debug "Creating User"
|
||||
silent /tmp/create-pass.sh
|
||||
rm -rf /tmp/create-pass.sh
|
||||
}
|
|
@ -1,276 +1,28 @@
|
|||
#!/usr/bin/with-contenv /bin/bash
|
||||
|
||||
for s in /assets/functions/*; do source $s; done
|
||||
PROCESS_NAME="postal"
|
||||
source /assets/functions/00-container
|
||||
prepare_service
|
||||
|
||||
### Set Defaults
|
||||
CLAMAV_PORT=${CLAMAV_PORT:-3310}
|
||||
DB_PORT=${DB_PORT:-3306}
|
||||
DNS_DKIM_IDENTIFIER=${DNS_DKIM_IDENTIFIER:-"postal"}
|
||||
DNS_DOMAIN_VERIFY_PREFIX=${DNS_DOMAIN_VERIFY_PREFIX:-"postal-verification"}
|
||||
DNS_HOSTNAME=${DNS_HOSTNAME:-"postal.example.com"}
|
||||
DNS_RETURN_PATH=${DNS_RETURN_PATH:-"rp."$DNS_HOSTNAME}
|
||||
DNS_RETURN_PATH_PREFIX=${DNS_RETURN_PATH_PREFIX:-"psrp"}
|
||||
DNS_ROUTE_DOMAIN=${DNS_ROUTE_DOMAIN:-"routes."$DNS_HOSTNAME}
|
||||
DNS_SPF=${DNS_SPF:-"spf."$DNS_HOSTNAME}
|
||||
DNS_TRACK_DOMAIN=${DNS_TRACK_DOMAIN:-"track."$DNS_HOSTNAME}
|
||||
ENABLE_CLAMAV=${ENABLE_CLAMAV:-false}
|
||||
ENABLE_SPAMASSASSIN=${ENABLE_SPAMASSASSIN:-false}
|
||||
LOG_CONSOLE=${LOG_CONSOLE:-true}
|
||||
RABBITMQ_PORT=${RABBITMQ_PORT:5672}
|
||||
SMTP_CLIENT_OPEN_TIMEOUT=${SMTP_CLIENT_OPEN_TIMEOUT:-30}
|
||||
SMTP_CLIENT_READ_TIMEOUT=${SMTP_CLIENT_READ_TIMEOUT:-60}
|
||||
SMTP_FROM_ADDRESS=${SMTP_FROM_ADDRESS:-"postal@yourdomain.com"}
|
||||
SMTP_FROM_NAME=${SMTP_FROM_NAME:-"Postal"}
|
||||
SMTP_HOST=${SMTP_HOST:-127.0.0.1}
|
||||
SMTP_PORT=${SMTP_PORT:-25}
|
||||
SMTP_RELAY_HOST=${SMTP_RELAY_HOST:-""}
|
||||
SMTP_RELAY_PORT=${SMTP_RELAY_PORT:-25}
|
||||
SMTP_RELAY_SSL_MODE=${SMTP_RELAY_SSL_MODE:-"Auto"}
|
||||
SMTP_SERVER_ENABLE_TLS=${SMTP_SERVER_ENABLE_TLS:-"false"}
|
||||
SMTP_SERVER_HELO_HOSTNAME=${SMTP_SERVER_HELO_HOSTNAME:-$DNS_HOSTNAME}
|
||||
SMTP_SERVER_LOG_CONNECTIONS=${SMTP_SERVER_LOG_CONNECTIONS:-"true"}
|
||||
SMTP_SERVER_MAX_MESSAGE_SIZE=${SMTP_SERVER_MAX_MESSAGE_SIZE:-14}
|
||||
SMTP_SERVER_PORT=${SMTP_SERVER_PORT:-"25"}
|
||||
SMTP_SERVER_PROXY_PROTOCOL=${SMTP_SERVER_PROXY_PROTOCOL:-"false"}
|
||||
SMTP_SERVER_SSL_VERSION=${SMTP_SERVER_SSL_VERSION:-"SSLv23"}
|
||||
SMTP_SERVER_STRIP_RECEIVED_HEADERS=${SMTP_SERVER_STRIP_RECEIVED_HEADERS:-"false"}
|
||||
SMTP_SERVER_TLS_CERT=${SMTP_SERVER_TLS_CERT:-"cert.pem"}
|
||||
SMTP_SERVER_TLS_CIPHERS=${SMTP_SERVER_TLS_CIPHERS:-""}
|
||||
SMTP_SERVER_TLS_KEY=${SMTP_SERVER_TLS_KEY:-"key.pem"}
|
||||
SPAMASSASSIN_PORT=${SPAMASSASSIN_PORT:-737}
|
||||
WEB_PROTOCOL=${WEB_PROTOCOL:-"http"}
|
||||
WEB_HOST=${WEB_HOST:-"postal.example.com"}
|
||||
|
||||
### Set Debug Mode
|
||||
sanity_db
|
||||
sanity_var RABBITMQ_HOST "RabbitMQ Host"
|
||||
sanity_var RABBITMQ_VHOST "RabbitMQ VHost"
|
||||
sanity_var RABBITMQ_PASS "RabbitMQ Password"
|
||||
|
||||
if [ "$ENABLE_CLAMAV" = "true" ]; then
|
||||
sanity_var CLAMAV_HOST "ClamAV Host"
|
||||
fi
|
||||
|
||||
if [ "$ENABLE_SPAMASSASSIN" = "true" ]; then
|
||||
sanity_var SPAMASSASSIN_HOST "SpamAssassin Host"
|
||||
fi
|
||||
|
||||
### SSL Configuration
|
||||
if [ "$SMTP_SERVER_ENABLE_TLS" = "true" ]; then
|
||||
if [ ! -f "/opt/postal/certs/$SMTP_SERVER_TLS_CERT" ] || [ ! -f "/opt/postal/certs/$SMTP_SERVER_TLS_KEY" ]; then
|
||||
print_warn "TLS Key or certificate not found. Generating self-signed certificates"
|
||||
cat <<EOF > /tmp/openssl.cnf
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
encrypt_key = yes
|
||||
distinguished_name = req_dn
|
||||
x509_extensions = cert_type
|
||||
prompt = no
|
||||
|
||||
[ req_dn ]
|
||||
C=XX
|
||||
ST=XX
|
||||
L=Test Server
|
||||
O=Postal
|
||||
|
||||
# Organizational Unit Name (eg. section)
|
||||
OU=SMTP server
|
||||
|
||||
# Common Name (*.example.com is also possible)
|
||||
CN=$DNS_HOSTNAME
|
||||
|
||||
# E-mail contact
|
||||
emailAddress=postmaster@$DNS_HOSTNAME
|
||||
|
||||
[ cert_type ]
|
||||
nsCertType = server
|
||||
EOF
|
||||
|
||||
openssl req -new -x509 -nodes -days 3650 \
|
||||
-config /tmp/openssl.cnf \
|
||||
-out /opt/postal/certs/$SMTP_SERVER_TLS_CERT \
|
||||
-keyout /opt/postal/certs/$SMTP_SERVER_TLS_KEY
|
||||
chmod 0600 /opt/postal/certs/$SMTP_SERVER_TLS_KEY
|
||||
rm -rf /tmp/openssl.cnf
|
||||
chown -R postal. /opt/postal/certs
|
||||
fi
|
||||
fi
|
||||
|
||||
### Create Configuration
|
||||
cp -R /assets/config/*.yml /opt/postal/config
|
||||
chown -R postal. /opt/postal/config
|
||||
/opt/postal/bin/postal initialize-config
|
||||
|
||||
## Modify Configuration file
|
||||
### Web Hosting
|
||||
sed -i -e '/web:/!b' -e ':a' -e "s/host.*/host: $WEB_HOST/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/web:/!b' -e ':a' -e "s/protocol.*/protocol: $WEB_PROTOCOL/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
|
||||
### Clam Antivirus
|
||||
sed -i -e '/clamav:/!b' -e ':a' -e "s/enabled.*/enabled: $ENABLE_CLAMAV/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/clamav:/!b' -e ':a' -e "s/host.*/host: $CLAMAV_HOST/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/clamav:/!b' -e ':a' -e "s/port.*/port: $CLAMAV_PORT/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
|
||||
### DNS
|
||||
sed -i -e '/dns:/!b' -e ':a' -e "s/smtp_server_hostname.*/smtp_server_hostname: $DNS_HOSTNAME/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/dns:/!b' -e ':a' -e "s/spf_include.*/spf_include: $DNS_SPF/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/dns:/!b' -e ':a' -e "s/return_path.*/return_path: $DNS_RETURN_PATH/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/dns:/!b' -e ':a' -e "s/route_domain.*/route_domain: $DNS_ROUTE_DOMAIN/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/dns:/!b' -e ':a' -e "s/track_domain.*/track_domain: $DNS_TRACK_DOMAIN/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/dns:/!b' -e ':a' -e "s/dkim_identifier.*/dkim_identifier: $DNS_DKIM_IDENTIFIER/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/dns:/!b' -e ':a' -e "s/domain_verify_prefix.*/domain_verify_prefix: $DNS_DOMAIN_VERIFY_PREFIX/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/dns:/!b' -e ':a' -e "s/custom_return_path_prefix.*/custom_return_path_prefix: $DNS_RETURN_PATH_PREFIX/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
|
||||
### Logging
|
||||
sed -i -e '/logging:/!b' -e ':a' -e "s/stdout.*/stdout: $LOG_CONSOLE/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
|
||||
### MySQL Main DB
|
||||
sed -i -e '/main_db:/!b' -e ':a' -e "s/host.*/host: $DB_HOST/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/main_db:/!b' -e ':a' -e "s/username.*/username: $DB_USER/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/main_db:/!b' -e ':a' -e "s/password.*/password: $DB_PASS/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/main_db:/!b' -e ':a' -e "s/database.*/database: $DB_NAME/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/main_db:/!b' -e ':a' -e "s/port.*/port: $DB_PORT/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
|
||||
### MySQL Message DB
|
||||
sed -i -e '/message_db:/!b' -e ':a' -e "s/host.*/host: $DB_HOST/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/message_db:/!b' -e ':a' -e "s/username.*/username: $DB_USER/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/message_db:/!b' -e ':a' -e "s/password.*/password: $DB_PASS/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/message_db:/!b' -e ':a' -e "s/port.*/port: $DB_PORT/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
|
||||
### RabbitMQ
|
||||
sed -i -e '/rabbitmq:/!b' -e ':a' -e "s/host.*/host: $RABBITMQ_HOST/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/rabbitmq:/!b' -e ':a' -e "s/username.*/username: $RABBITMQ_USER/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/rabbitmq:/!b' -e ':a' -e "s/password.*/password: $RABBITMQ_PASS/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/rabbitmq:/!b' -e ':a' -e "s/vhost.*/vhost: \/$RABBITMQ_VHOST/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/rabbitmq:/!b' -e ':a' -e "s/port.*/port: $RABBITMQ_PORT/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
|
||||
### SMTP Server
|
||||
sed -i -e '/smtp_server:/!b' -e ':a' -e "s/port.*/port: $SMTP_SERVER_PORT/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_server:/!b' -e ':a' -e "s/tls_enabled.*/tls_enabled: $SMTP_SERVER_ENABLE_TLS/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_server:/!b' -e ':a' -e "s/tls_certificate_path.*/tls_certificate_path: /opt/postal/certs/$SMTP_SERVER_TLS_CERT/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_server:/!b' -e ':a' -e "s/tls_private_key_path.*/tls_private_key_path: /opt/postal/certs/$SMTP_SERVER_TLS_KEY/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_server:/!b' -e ':a' -e "s/tls_ciphers.*/tls_ciphers: $SMTP_SERVER_TLS_CIPHERS/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_server:/!b' -e ':a' -e "s/ssl_version.*/ssl_version: $SMTP_SERVER_SSL_VERSION/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_server:/!b' -e ':a' -e "s/proxy_protocol.*/proxy_protocol: $SMTP_SERVER_PROXY_PROTOCOL/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_server:/!b' -e ':a' -e "s/log_connect.*/log_connect: $SMTP_SERVER_LOG_CONNECTIONS/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_server:/!b' -e ':a' -e "s/strip_received_headers.*/strip_received_headers: $SMTP_SERVER_STRIP_RECEIVED_HEADERS/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_server:/!b' -e ':a' -e "s/max_message_size.*/max_message_size: $SMTP_SERVER_MAX_MESSAGE_SIZE/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
|
||||
### SMTP Client
|
||||
sed -i -e '/smtp_client:/!b' -e ':a' -e "s/open_timeout.*/open_timeout: $SMTP_CLIENT_OPEN_TIMEOUT/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_client:/!b' -e ':a' -e "s/read_timeout.*/read_timeout: $SMTP_CLIENT_READ_TIMEOUT/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
|
||||
### SMTP Relays
|
||||
sed -i -e '/smtp_relays:/!b' -e ':a' -e "s/hostname.*/hostname: $SMTP_RELAY_HOST/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_relays:/!b' -e ':a' -e "s/port.*/port: $SMTP_RELAY_PORT/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp_relays:/!b' -e ':a' -e "s/ssl_mode.*/ssl_mode: $SMTP_RELAY_SSL_MODE/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
|
||||
### SMTP
|
||||
sed -i -e '/smtp:/!b' -e ':a' -e "s/host.*/host: $SMTP_HOST/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp:/!b' -e ':a' -e "s/port.*/port: $SMTP_PORT/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp:/!b' -e ':a' -e "s/username.*/username: $SMTP_USER/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp:/!b' -e ':a' -e "s/password.*/password: $SMTP_PASS/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp:/!b' -e ':a' -e "s/from_name.*/from_name: $SMTP_FROM_NAME/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/smtp:/!b' -e ':a' -e "s/from_address.*/from_address: $SMTP_FROM_ADDRESS/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
|
||||
### Spamassassin
|
||||
sed -i -e '/spamd:/!b' -e ':a' -e "s/enabled.*/enabled: $ENABLE_SPAMASSASSIN/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/spamd:/!b' -e ':a' -e "s/host.*/host: $SPAMASSASSIN_HOST/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
sed -i -e '/spamd:/!b' -e ':a' -e "s/port.*/port: $SPAMASSASSIN_PORT/;t trail" -e 'n;ba' -e ':trail' -e 'n;btrail' /opt/postal/config/postal.yml
|
||||
##
|
||||
|
||||
## Clean Up
|
||||
rm -rf /opt/postal/tmp/pids/*
|
||||
|
||||
### Make sure that DB is accessible
|
||||
db_ready mariadb
|
||||
check_clamav
|
||||
check_rabbitmq
|
||||
check_spamassassin
|
||||
configure_tls
|
||||
configure_postal
|
||||
|
||||
### Make sure that RabbitMQ is accessible
|
||||
while true; do
|
||||
nc -z $RABBITMQ_HOST 5672
|
||||
if [ $? -eq 0 ]; then
|
||||
:
|
||||
break
|
||||
fi
|
||||
print_warn "RabbitMQ Server '${RABBITMQ_HOST}' unavailable. Sleeping a little bit ..."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
### Make sure that ClamAV is accessible
|
||||
if [ "$ENABLE_CLAMAV" = "true" ]; then
|
||||
while true; do
|
||||
nc -w1 $CLAMAV_HOST $CLAMAV_PORT< /dev/null 2>&1 > /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
:
|
||||
break
|
||||
fi
|
||||
print_warn "ClamAV Server '${CLAMAV_HOST}' unavailable. Sleeping a little bit ..."
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
|
||||
### Make sure that Spamassassin is accessible
|
||||
if [ "$ENABLE_SPAMASSASSIN" = "true" ]; then
|
||||
while true; do
|
||||
nc -w1 $SPAMASSASSIN_HOST $SPAMASSASSIN_PORT< /dev/null 2>&1 > /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
:
|
||||
break
|
||||
fi
|
||||
print_warn "Spamassassin Server '${SPAMASSASSIN_HOST}' unavailable. Sleeping a little bit ..."
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ $(mysql -h $DB_HOST -u$DB_USER -p$DB_PASS -s --skip-column-names -e "SELECT COUNT(DISTINCT table_name) FROM information_schema.columns WHERE table_schema = '"$DB_NAME"'") == 0 ]]; then
|
||||
if [[ $(mysql -h ${DB_HOST} -u${DB_USER} -p${DB_PASS} -s --skip-column-names -e "SELECT COUNT(DISTINCT table_name) FROM information_schema.columns WHERE table_schema = '"$DB_NAME"'") == 0 ]]; then
|
||||
print_warn "Detecting new Install. Initializing Database"
|
||||
if [ ! -n "$DB_HOST" ]; then
|
||||
if [ ! -n "$DB_ROOT_PASS" ]; then
|
||||
print_error "No Root Password Entered! You only need to provide the environment variable one time. Please try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
/opt/postal/bin/postal initialize
|
||||
mysql -uroot -p$DB_ROOT_PASS -h$DB_HOST -e 'GRANT ALL PRIVILEGES ON `'$DB_NAME'-%` . * to `'$DB_USER'`@`%` IDENTIFIED BY "'$DB_PASS'";'
|
||||
print_warn "Creating Administrative User"
|
||||
if [ ! -n "$ADMIN_EMAIL" ]; then
|
||||
print_warn "No Admin Email entered, setting default email login to 'postal@example.com'"
|
||||
ADMIN_EMAIL="postal@example.com"
|
||||
fi
|
||||
if [ ! -n "$ADMIN_PASS" ]; then
|
||||
print_warn "WARNING: No Admin Pass entered, setting default password to 'postal'"
|
||||
ADMIN_PASS="postal"
|
||||
fi
|
||||
if [ ! -n "$ADMIN_FNAME" ]; then
|
||||
print_warn "No Admin First Name entered, setting default to 'Postal'"
|
||||
ADMIN_FNAME="Postal"
|
||||
fi
|
||||
if [ ! -n "$ADMIN_LNAME" ]; then
|
||||
print_warn "No Admin Last Name entered, setting default to 'Admin'"
|
||||
ADMIN_LNAME="Admin"
|
||||
fi
|
||||
|
||||
cat <<EOF > /tmp/create-pass.sh
|
||||
#!/usr/bin/expect
|
||||
|
||||
spawn /usr/bin/with-contenv bash /opt/postal/bin/postal make-user
|
||||
expect "E-Mail Address :"
|
||||
send -- "$ADMIN_EMAIL\n"
|
||||
expect "First Name :"
|
||||
send -- "$ADMIN_FNAME\n"
|
||||
expect "Last Name :"
|
||||
send -- "$ADMIN_LNAME\n"
|
||||
expect "Initial Password: :"
|
||||
send -- "$ADMIN_PASS\n"
|
||||
expect "User has been created"
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x /tmp/create-pass.sh
|
||||
/tmp/create-pass.sh
|
||||
rm -rf /tmp/create-pass.sh
|
||||
initialize_database
|
||||
else
|
||||
/opt/postal/bin/postal upgrade
|
||||
silent /app/bin/postal upgrade
|
||||
fi
|
||||
|
||||
liftoff
|
||||
chown -R postal: /app/
|
||||
rm -rf /app/tmp/pids/*
|
||||
|
||||
liftoff
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
#!/usr/bin/with-contenv bash
|
||||
|
||||
for s in /assets/functions/*; do source $s; done
|
||||
PROCESS_NAME="postal"
|
||||
|
||||
check_container_initialized
|
||||
check_service_initialized init
|
||||
liftoff
|
||||
|
||||
print_info "Starting postal"
|
||||
exec /opt/postal/bin/postal run
|
15
install/etc/services.available/10-postal/run
Executable file
15
install/etc/services.available/10-postal/run
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/with-contenv bash
|
||||
|
||||
source /assets/functions/00-container
|
||||
prepare_service defaults single
|
||||
|
||||
check_container_initialized
|
||||
check_service_initialized init
|
||||
liftoff
|
||||
|
||||
if var_false $LOG_CONSOLE ; then
|
||||
console="silent"
|
||||
fi
|
||||
|
||||
print_info "Starting postal"
|
||||
${console} /app/bin/postal run
|
Loading…
Add table
Reference in a new issue