From e8f81dc9057639547d11284cbec1c63eebeb53d6 Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Sun, 17 Aug 2014 08:48:27 -0400 Subject: [PATCH 1/7] dockerize (work in progress) Docker support was initially worked on in 2bbb7a5e7ea9ec96650685c288d31449a0cc75a4, but it never really worked. This extends f7d7434012800c3572049af82a501743d4aed583 which was an old branch for docker work. --- Dockerfile | 47 +++++++++++++++ containers/docker/apt_package_list.txt | 82 ++++++++++++++++++++++++++ containers/docker/container_start.sh | 30 ++++++++++ containers/docker/run.sh | 50 ++++++++++++++++ setup/functions.sh | 34 +++++++++-- setup/preflight.sh | 6 +- setup/start.sh | 6 +- tools/list_all_packages.py | 23 ++++++++ 8 files changed, 270 insertions(+), 8 deletions(-) create mode 100644 Dockerfile create mode 100644 containers/docker/apt_package_list.txt create mode 100755 containers/docker/container_start.sh create mode 100755 containers/docker/run.sh create mode 100644 tools/list_all_packages.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..18d8a808 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +# Mail-in-a-Box Dockerfile +########################### +# +# This file lets Mail-in-a-Box run inside of Docker (https://docker.io), +# a virtualization/containerization manager. +# +# Run: +# $ containers/docker/run.sh +# to build the image, launch a storage container, and launch a Mail-in-a-Box +# container. +# +########################################### + +# We need a better starting image than docker's ubuntu image because that +# base image doesn't provide enough to run most Ubuntu services. See +# http://phusion.github.io/baseimage-docker/ for an explanation. + +FROM phusion/baseimage:0.9.15 + +# Dockerfile metadata. +MAINTAINER Joshua Tauberer (http://razor.occams.info) +EXPOSE 22 25 53 80 443 587 993 + +# Docker has a beautiful way to cache images after each step. The next few +# steps of installing system packages are very intensive, so we take care +# of them early and let docker cache the image after that, before doing +# any Mail-in-a-Box specific system configuration. That makes rebuilds +# of the image extremely fast. + +# Update system packages. +RUN apt-get update +RUN DEBIAN_FRONTEND=noninteractive apt-get upgrade -y + +# Install packages needed by Mail-in-a-Box. +ADD containers/docker/apt_package_list.txt /tmp/mailinabox_apt_package_list.txt +RUN DEBIAN_FRONTEND=noninteractive apt-get install -y $(cat /tmp/mailinabox_apt_package_list.txt) +RUN rm -f /tmp/mailinabox_apt_package_list.txt + +# Now add Mail-in-a-Box to the system. +ADD . /usr/local/mailinabox + +# We can't know things like the IP address where the container will eventually +# be deployed until the container is started. We also don't want to create any +# private keys during the creation of the image --- that should wait until the +# container is started too. So our whole setup process is deferred until the +# container is started. +ENTRYPOINT ["/usr/local/mailinabox/containers/docker/container_start.sh"] diff --git a/containers/docker/apt_package_list.txt b/containers/docker/apt_package_list.txt new file mode 100644 index 00000000..96bfdb56 --- /dev/null +++ b/containers/docker/apt_package_list.txt @@ -0,0 +1,82 @@ +bc +bind9 +ca-certificates +coreutils +cron +curl +dbconfig-common +dovecot-antispam +dovecot-core +dovecot-imapd +dovecot-lmtpd +dovecot-managesieved +dovecot-sieve +dovecot-sqlite +duplicity +fail2ban +git +haveged +ldnsutils +libapr1 +libawl-php +libcurl4-openssl-dev +libjs-jquery +libjs-jquery-mousewheel +libmagic1 +libtool +libyaml-dev +links +memcached +nginx +nsd +ntp +opendkim +opendkim-tools +opendmarc +openssh-client +openssl +php-apc +php-auth +php-crypt-gpg +php-mail-mime +php-net-sieve +php-net-smtp +php-net-socket +php-pear +php-soap +php-xml-parser +php5 +php5-cli +php5-common +php5-curl +php5-dev +php5-fpm +php5-gd +php5-imap +php5-intl +php5-json +php5-mcrypt +php5-memcache +php5-pspell +php5-sqlite +php5-xsl +postfix +postfix-pcre +postgrey +python3 +python3-dateutil +python3-dev +python3-dnspython +python3-flask +python3-pip +pyzor +razor +resolvconf +spampd +sqlite3 +sudo +tinymce +ufw +unattended-upgrades +unzip +wget diff --git a/containers/docker/container_start.sh b/containers/docker/container_start.sh new file mode 100755 index 00000000..9e0e9650 --- /dev/null +++ b/containers/docker/container_start.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# This script is used within containers to turn it into a Mail-in-a-Box. +# It is referenced by the Dockerfile. You should not run it directly. +######################################################################## + +# Local configuration details were not known at the time the Docker +# image was created, so all setup is defered until the container +# is started. That's when this script runs. + +# If we're not in an interactive shell, set defaults. +if [ ! -t 0 ]; then + export PUBLIC_IP=auto + export PUBLIC_IPV6=auto + export PRIMARY_HOSTNAME=auto + export CSR_COUNTRY=US + export NONINTERACTIVE=1 +fi + +# Start configuration. +cd /usr/local/mailinabox +export IS_DOCKER=1 +export DISABLE_FIREWALL=1 +source setup/start.sh # using 'source' means an exit from inside also exits this script and terminates container + +# Once the configuration is complete, start the Unix init process +# provided by the base image. We're running as process 0, and +# /sbin/my_init needs to run as process 0, so use 'exec' to replace +# this shell process and not fork a new one. Nifty right? +exec /sbin/my_init -- bash diff --git a/containers/docker/run.sh b/containers/docker/run.sh new file mode 100755 index 00000000..2816d138 --- /dev/null +++ b/containers/docker/run.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Use this script to launch Mail-in-a-Box within a docker container. +# ================================================================== +# +# A base image is created first. The base image installs Ubuntu +# packages and pulls in the Mail-in-a-Box source code. This is +# defined in Dockerfile at the root of this repository. +# +# A mailinabox-userdata container is started next. This container +# contains nothing but a shared volume for storing user data. +# It is segregated from the rest of the live system to make backups +# easier. +# +# The mailinabox-services container is started last. It is the +# real thing: it runs the mailinabox image. This container will +# initialize itself and will initialize the mailinabox-userdata +# volume if the volume is new. + + +DOCKER=docker.io + +# Build or rebuild the image. +# Rebuilds are very fast. +$DOCKER build -q -t mailinabox . + +# Start the user-data containerw which is merely to create +# a container that maintains a reference to a volume so that +# we can destroy the main container without losing user data. +if ! $DOCKER ps -a | grep mailinabox-userdata > /dev/null; then + echo Starting user-data volume container... + $DOCKER run -d \ + --name mailinabox-userdata \ + -v /home/user-data \ + scratch bash +fi + +# End a running container. +if $DOCKER ps -a | grep mailinabox-services > /dev/null; then + echo Deleting container... + $DOCKER rm mailinabox-services +fi + +# Start container. +echo Starting new container... +$DOCKER run \ + -p 25 -p 53 -p 80 -p 443 -p 587 -p 993 \ + --volumes-from mailinabox-userdata \ + --name mailinabox-services \ + -t -i \ + mailinabox diff --git a/setup/functions.sh b/setup/functions.sh index 3b3b513b..92164037 100644 --- a/setup/functions.sh +++ b/setup/functions.sh @@ -70,12 +70,20 @@ function get_default_hostname { # Guess the machine's hostname. It should be a fully qualified # domain name suitable for DNS. None of these calls may provide # the right value, but it's the best guess we can make. - set -- $(hostname --fqdn 2>/dev/null || - hostname --all-fqdns 2>/dev/null || - hostname 2>/dev/null) + set -- $( + get_hostname_from_reversedns || + hostname --fqdn 2>/dev/null || + hostname --all-fqdns 2>/dev/null || + hostname 2>/dev/null) printf '%s\n' "$1" # return this value } +function get_hostname_from_reversedns { + # Do a reverse DNS lookup on our public IPv4 address. The output of + # `host` is complex -- use sed to get the FDQN. + host $(get_publicip_from_web_service 4) | sed "s/.*pointer \(.*\)\./\1/" +} + function get_publicip_from_web_service { # This seems to be the most reliable way to determine the # machine's public IP address: asking a very nice web API @@ -151,7 +159,25 @@ function ufw_allow { } function restart_service { - hide_output service $1 restart + # Restart a service quietly. + if [ ! "$IS_DOCKER" ]; then + # The normal way to restart a service. + hide_output service $1 restart + else + # On docker, sysvinit is not present. Our base image provides + # a weird way to manage running services. But we're not going + # to use it. Just execute the init.d script directly. + + if [ "$1" == "dovecot" ]; then + # Dovecot does not provide an init.d script. It just provides + # an upstart init configuration. But Docker doesn't provide + # upstart. Start Dovecot specially. + killall dovecot + dovecot -c /etc/dovecot/dovecot.conf + else + hide_output /etc/init.d/$1 restart + fi + fi } ## Dialog Functions ## diff --git a/setup/preflight.sh b/setup/preflight.sh index 90d36cc1..637eef2e 100644 --- a/setup/preflight.sh +++ b/setup/preflight.sh @@ -4,7 +4,7 @@ if [[ $EUID -ne 0 ]]; then echo echo "sudo $0" echo - exit + exit 1 fi # Check that we are running on Ubuntu 14.04 LTS (or 14.04.xx). @@ -14,7 +14,7 @@ if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" != "U lsb_release -d | sed 's/.*:\s*//' echo echo "We can't write scripts that run on every possible setup, sorry." - exit + exit 1 fi # Check that we have enough memory. @@ -30,6 +30,6 @@ if [ ! -d /vagrant ]; then echo "Your Mail-in-a-Box needs more memory (RAM) to function properly." echo "Please provision a machine with at least 768 MB, 1 GB recommended." echo "This machine has $TOTAL_PHYSICAL_MEM MB memory." - exit + exit 1 fi fi diff --git a/setup/start.sh b/setup/start.sh index 72c364c8..48b012f5 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -59,7 +59,11 @@ if [ "$PUBLIC_IPV6" = "auto" ]; then # Use a public API to get our public IPv6 address, or fall back to local network configuration. PUBLIC_IPV6=$(get_publicip_from_web_service 6 || get_default_privateip 6) fi -if [ "$PRIMARY_HOSTNAME" = "auto-easy" ]; then +if [ "$PRIMARY_HOSTNAME" = "auto" ]; then + # Use reverse DNS to get this machine's hostname. Install bind9-host early. + hide_output apt-get -y install bind9-host + PRIMARY_HOSTNAME=$(get_default_hostname) +elif [ "$PRIMARY_HOSTNAME" = "auto-easy" ]; then # Generate a probably-unique subdomain under our justtesting.email domain. PRIMARY_HOSTNAME=`echo $PUBLIC_IP | sha1sum | cut -c1-5`.justtesting.email fi diff --git a/tools/list_all_packages.py b/tools/list_all_packages.py new file mode 100644 index 00000000..9b2eab70 --- /dev/null +++ b/tools/list_all_packages.py @@ -0,0 +1,23 @@ +#!/usr/bin/python3 + +import os.path, glob, re + +packages = set() + +def add(line): + global packages + if line.endswith("\\"): line = line[:-1] + packages |= set(p for p in line.split(" ") if p not in("", "apt_install")) + +for fn in glob.glob(os.path.join(os.path.dirname(__file__), "../setup/*.sh")): + with open(fn) as f: + in_apt_install = False + for line in f: + line = line.strip() + if line.startswith("apt_install "): + in_apt_install = True + if in_apt_install: + add(line) + in_apt_install = in_apt_install and line.endswith("\\") + +print("\n".join(sorted(packages))) From b9eb01a462c8c7f62f486834c74e5da44d3223a1 Mon Sep 17 00:00:00 2001 From: Morteza Milani Date: Sat, 24 Jan 2015 13:59:16 +0330 Subject: [PATCH 2/7] Fully working docker! --- Dockerfile | 11 +++-- containers/docker/apt_package_list.txt | 1 + containers/docker/container_start.sh | 30 ------------ containers/docker/init.sh | 67 ++++++++++++++++++++++++++ containers/docker/run.sh | 16 +++--- services/bind9.sh | 11 +++++ services/dovecot.sh | 3 ++ services/fail2ban.sh | 9 ++++ services/mailinabox.sh | 12 +++++ services/memcached.sh | 10 ++++ services/nginx.sh | 10 ++++ services/nsd.sh | 10 ++++ services/opendkim.sh | 10 ++++ services/php5-fpm.sh | 10 ++++ services/postfix.sh | 10 ++++ services/postgrey.sh | 10 ++++ services/rsyslogd.sh | 3 ++ services/spampd.sh | 10 ++++ setup/functions.sh | 15 +----- setup/start.sh | 3 +- setup/webmail.sh | 6 +-- 21 files changed, 207 insertions(+), 60 deletions(-) delete mode 100755 containers/docker/container_start.sh create mode 100755 containers/docker/init.sh create mode 100755 services/bind9.sh create mode 100755 services/dovecot.sh create mode 100755 services/fail2ban.sh create mode 100755 services/mailinabox.sh create mode 100755 services/memcached.sh create mode 100755 services/nginx.sh create mode 100755 services/nsd.sh create mode 100755 services/opendkim.sh create mode 100755 services/php5-fpm.sh create mode 100755 services/postfix.sh create mode 100755 services/postgrey.sh create mode 100755 services/rsyslogd.sh create mode 100755 services/spampd.sh diff --git a/Dockerfile b/Dockerfile index 18d8a808..74ec64b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,11 +15,12 @@ # base image doesn't provide enough to run most Ubuntu services. See # http://phusion.github.io/baseimage-docker/ for an explanation. -FROM phusion/baseimage:0.9.15 +FROM phusion/baseimage:0.9.16 # Dockerfile metadata. MAINTAINER Joshua Tauberer (http://razor.occams.info) -EXPOSE 22 25 53 80 443 587 993 +EXPOSE 25 53/udp 53/tcp 80 443 587 993 +VOLUME /data # Docker has a beautiful way to cache images after each step. The next few # steps of installing system packages are very intensive, so we take care @@ -35,13 +36,17 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get upgrade -y ADD containers/docker/apt_package_list.txt /tmp/mailinabox_apt_package_list.txt RUN DEBIAN_FRONTEND=noninteractive apt-get install -y $(cat /tmp/mailinabox_apt_package_list.txt) RUN rm -f /tmp/mailinabox_apt_package_list.txt +RUN useradd -m user-data +RUN rm -rf /etc/service/syslog-ng # Now add Mail-in-a-Box to the system. ADD . /usr/local/mailinabox +#RUN /usr/local/mailinabox/containers/docker/setup.sh + # We can't know things like the IP address where the container will eventually # be deployed until the container is started. We also don't want to create any # private keys during the creation of the image --- that should wait until the # container is started too. So our whole setup process is deferred until the # container is started. -ENTRYPOINT ["/usr/local/mailinabox/containers/docker/container_start.sh"] +ENTRYPOINT /usr/local/mailinabox/containers/docker/init.sh diff --git a/containers/docker/apt_package_list.txt b/containers/docker/apt_package_list.txt index 96bfdb56..eac9073f 100644 --- a/containers/docker/apt_package_list.txt +++ b/containers/docker/apt_package_list.txt @@ -72,6 +72,7 @@ python3-pip pyzor razor resolvconf +rsyslog spampd sqlite3 sudo diff --git a/containers/docker/container_start.sh b/containers/docker/container_start.sh deleted file mode 100755 index 9e0e9650..00000000 --- a/containers/docker/container_start.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -# This script is used within containers to turn it into a Mail-in-a-Box. -# It is referenced by the Dockerfile. You should not run it directly. -######################################################################## - -# Local configuration details were not known at the time the Docker -# image was created, so all setup is defered until the container -# is started. That's when this script runs. - -# If we're not in an interactive shell, set defaults. -if [ ! -t 0 ]; then - export PUBLIC_IP=auto - export PUBLIC_IPV6=auto - export PRIMARY_HOSTNAME=auto - export CSR_COUNTRY=US - export NONINTERACTIVE=1 -fi - -# Start configuration. -cd /usr/local/mailinabox -export IS_DOCKER=1 -export DISABLE_FIREWALL=1 -source setup/start.sh # using 'source' means an exit from inside also exits this script and terminates container - -# Once the configuration is complete, start the Unix init process -# provided by the base image. We're running as process 0, and -# /sbin/my_init needs to run as process 0, so use 'exec' to replace -# this shell process and not fork a new one. Nifty right? -exec /sbin/my_init -- bash diff --git a/containers/docker/init.sh b/containers/docker/init.sh new file mode 100755 index 00000000..aea2ef3d --- /dev/null +++ b/containers/docker/init.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# This script is used within containers to turn it into a Mail-in-a-Box. +# It is referenced by the Dockerfile. You should not run it directly. +######################################################################## + +# Local configuration details were not known at the time the Docker +# image was created, so all setup is defered until the container +# is started. That's when this script runs. + +# If we're not in an interactive shell, set defaults. +if [ ! -t 0 ]; then + export PUBLIC_IP=auto + export PUBLIC_IPV6=auto + export PRIMARY_HOSTNAME=auto + export CSR_COUNTRY=US + export NONINTERACTIVE=1 +fi + +# Start configuration. +cd /usr/local/mailinabox +export IS_DOCKER=1 +export STORAGE_ROOT=/data +export STORAGE_USER=user-data +export DISABLE_FIREWALL=1 + +mkdir /etc/service/rsyslogd +mkdir /etc/service/bind9 +mkdir /etc/service/dovecot +mkdir /etc/service/fail2ban +mkdir /etc/service/mailinabox +mkdir /etc/service/memcached +mkdir /etc/service/nginx +mkdir /etc/service/nsd +mkdir /etc/service/opendkim +mkdir /etc/service/php5-fpm +mkdir /etc/service/postfix +mkdir /etc/service/postgrey +mkdir /etc/service/spampd +cp services/rsyslogd.sh /etc/service/rsyslogd/run +cp services/bind9.sh /etc/service/bind9/run +cp services/dovecot.sh /etc/service/dovecot/run +cp services/fail2ban.sh /etc/service/fail2ban/run +cp services/mailinabox.sh /etc/service/mailinabox/run +cp services/memcached.sh /etc/service/memcached/run +cp services/nginx.sh /etc/service/nginx/run +cp services/nsd.sh /etc/service/nsd/run +cp services/opendkim.sh /etc/service/opendkim/run +cp services/php5-fpm.sh /etc/service/php5-fpm/run +cp services/postfix.sh /etc/service/postfix/run +cp services/postgrey.sh /etc/service/postgrey/run +cp services/spampd.sh /etc/service/spampd/run + +rsyslogd +source setup/start.sh +/etc/init.d/mailinabox start +/usr/sbin/dovecot -c /etc/dovecot/dovecot.conf +sleep 5 +curl -s -d POSTDATA --user $( /dev/null; then echo Starting user-data volume container... $DOCKER run -d \ --name mailinabox-userdata \ -v /home/user-data \ - scratch bash + scratch /bin/bash fi # End a running container. @@ -43,8 +40,9 @@ fi # Start container. echo Starting new container... $DOCKER run \ - -p 25 -p 53 -p 80 -p 443 -p 587 -p 993 \ - --volumes-from mailinabox-userdata \ + --privileged \ + -v /dev/urandom:/dev/random \ + -p 25 -p 53/udp -p 53/tcp -p 80 -p 443 -p 587 -p 993 \ --name mailinabox-services \ - -t -i \ - mailinabox + --volumes-from mailinabox-userdata \ + mailinabox \ No newline at end of file diff --git a/services/bind9.sh b/services/bind9.sh new file mode 100755 index 00000000..a962510b --- /dev/null +++ b/services/bind9.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +EXEC=bind9 +PROCESS=named + +/etc/init.d/$EXEC start + +while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do + sleep 30 +done + diff --git a/services/dovecot.sh b/services/dovecot.sh new file mode 100755 index 00000000..5e5c1474 --- /dev/null +++ b/services/dovecot.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +/usr/sbin/dovecot -F -c /etc/dovecot/dovecot.conf &> /var/log/dovecot.log diff --git a/services/fail2ban.sh b/services/fail2ban.sh new file mode 100755 index 00000000..3a5720e4 --- /dev/null +++ b/services/fail2ban.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +PROCESS=fail2ban + +/etc/init.d/$PROCESS start + +while [ `ps aux | grep fail2ban | grep -v grep | wc -l` -gt 0 ]; do + sleep 30 +done diff --git a/services/mailinabox.sh b/services/mailinabox.sh new file mode 100755 index 00000000..2ed281c9 --- /dev/null +++ b/services/mailinabox.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +EXEC=mailinabox +PROCESS=mailinabox-daemon + +if [ `ps aux | grep $PROCESS | grep -v grep | wc -l` -eq 0 ]; then + /etc/init.d/$EXEC start +fi + +while [ `ps aux | grep $PROCESS | grep -v grep | wc -l` -gt 0 ]; do + sleep 30 +done diff --git a/services/memcached.sh b/services/memcached.sh new file mode 100755 index 00000000..823997f1 --- /dev/null +++ b/services/memcached.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +PROCESS=memcached + +/etc/init.d/$PROCESS start + +while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do + sleep 60 +done + diff --git a/services/nginx.sh b/services/nginx.sh new file mode 100755 index 00000000..ea73ae92 --- /dev/null +++ b/services/nginx.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +PROCESS=nginx + +/etc/init.d/$PROCESS start + +while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do + sleep 30 +done + diff --git a/services/nsd.sh b/services/nsd.sh new file mode 100755 index 00000000..392215d4 --- /dev/null +++ b/services/nsd.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +PROCESS=nsd + +/etc/init.d/$PROCESS start + +while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do + sleep 30 +done + diff --git a/services/opendkim.sh b/services/opendkim.sh new file mode 100755 index 00000000..a3a76eb2 --- /dev/null +++ b/services/opendkim.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +PROCESS=opendkim + +/etc/init.d/$PROCESS start + +while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do + sleep 30 +done + diff --git a/services/php5-fpm.sh b/services/php5-fpm.sh new file mode 100755 index 00000000..e00987a8 --- /dev/null +++ b/services/php5-fpm.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +PROCESS=php5-fpm + +/etc/init.d/$PROCESS start + +while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do + sleep 30 +done + diff --git a/services/postfix.sh b/services/postfix.sh new file mode 100755 index 00000000..18755980 --- /dev/null +++ b/services/postfix.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +PROCESS=postfix + +/etc/init.d/$PROCESS start + +while [ `ps aux | grep $PROCESS | grep -v grep | wc -l` -gt 0 ]; do + sleep 30 +done + diff --git a/services/postgrey.sh b/services/postgrey.sh new file mode 100755 index 00000000..7a052f0d --- /dev/null +++ b/services/postgrey.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +PROCESS=postgrey + +/etc/init.d/$PROCESS start + +while [ `ps aux | grep $PROCESS | grep -v grep | wc -l` -gt 0 ]; do + sleep 30 +done + diff --git a/services/rsyslogd.sh b/services/rsyslogd.sh new file mode 100755 index 00000000..497d38ad --- /dev/null +++ b/services/rsyslogd.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +rsyslogd -n diff --git a/services/spampd.sh b/services/spampd.sh new file mode 100755 index 00000000..a9fd4393 --- /dev/null +++ b/services/spampd.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +PROCESS=spampd + +/etc/init.d/$PROCESS start + +while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do + sleep 30 +done + diff --git a/setup/functions.sh b/setup/functions.sh index 92164037..abd7a357 100644 --- a/setup/functions.sh +++ b/setup/functions.sh @@ -37,6 +37,7 @@ function apt_get_quiet { } function apt_install { + if [ ! "$IS_DOCKER" ];then # Report any packages already installed. PACKAGES=$@ TO_INSTALL="" @@ -163,20 +164,6 @@ function restart_service { if [ ! "$IS_DOCKER" ]; then # The normal way to restart a service. hide_output service $1 restart - else - # On docker, sysvinit is not present. Our base image provides - # a weird way to manage running services. But we're not going - # to use it. Just execute the init.d script directly. - - if [ "$1" == "dovecot" ]; then - # Dovecot does not provide an init.d script. It just provides - # an upstart init configuration. But Docker doesn't provide - # upstart. Start Dovecot specially. - killall dovecot - dovecot -c /etc/dovecot/dovecot.conf - else - hide_output /etc/init.d/$1 restart - fi fi } diff --git a/setup/start.sh b/setup/start.sh index 48b012f5..0605974c 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -118,7 +118,7 @@ fi # Create mailinabox.version file if not exists if [ ! -f $STORAGE_ROOT/mailinabox.version ]; then echo $(setup/migrate.py --current) > $STORAGE_ROOT/mailinabox.version - chown $STORAGE_USER.$STORAGE_USER $STORAGE_ROOT/mailinabox.version + chown $STORAGE_USER:$STORAGE_USER $STORAGE_ROOT/mailinabox.version fi @@ -187,3 +187,4 @@ openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint \ echo echo Then you can confirm the security exception and continue. echo + diff --git a/setup/webmail.sh b/setup/webmail.sh index 0c04c8ae..0f1d4a4c 100755 --- a/setup/webmail.sh +++ b/setup/webmail.sh @@ -118,7 +118,7 @@ EOF # Create writable directories. mkdir -p /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube -chown -R www-data.www-data /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube +chown -R www-data:www-data /var/log/roundcubemail /tmp/roundcubemail $STORAGE_ROOT/mail/roundcube # Password changing plugin settings # The config comes empty by default, so we need the settings @@ -139,9 +139,9 @@ usermod -a -G dovecot www-data # set permissions so that PHP can use users.sqlite # could use dovecot instead of www-data, but not sure it matters -chown root.www-data $STORAGE_ROOT/mail +chown root:www-data $STORAGE_ROOT/mail chmod 775 $STORAGE_ROOT/mail -chown root.www-data $STORAGE_ROOT/mail/users.sqlite +chown root:www-data $STORAGE_ROOT/mail/users.sqlite chmod 664 $STORAGE_ROOT/mail/users.sqlite # Enable PHP modules. From a9ed6d88cf611227cb36753e55329e49928b59a4 Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Fri, 30 Jan 2015 17:40:58 -0500 Subject: [PATCH 3/7] simplify dockerization --- Dockerfile | 10 +- containers/docker/apt_package_list.txt | 1 - containers/docker/init.sh | 98 +++++++++++-------- containers/docker/{run.sh => run} | 57 ++++++++--- .../docker/runit}/dovecot.sh | 0 .../docker/runit}/rsyslogd.sh | 0 services/bind9.sh | 11 --- services/fail2ban.sh | 9 -- services/mailinabox.sh | 12 --- services/memcached.sh | 10 -- services/nginx.sh | 10 -- services/nsd.sh | 10 -- services/opendkim.sh | 10 -- services/php5-fpm.sh | 10 -- services/postfix.sh | 10 -- services/postgrey.sh | 10 -- services/spampd.sh | 10 -- setup/functions.sh | 23 ++++- setup/start.sh | 11 ++- 19 files changed, 131 insertions(+), 181 deletions(-) rename containers/docker/{run.sh => run} (52%) rename {services => containers/docker/runit}/dovecot.sh (100%) rename {services => containers/docker/runit}/rsyslogd.sh (100%) delete mode 100755 services/bind9.sh delete mode 100755 services/fail2ban.sh delete mode 100755 services/mailinabox.sh delete mode 100755 services/memcached.sh delete mode 100755 services/nginx.sh delete mode 100755 services/nsd.sh delete mode 100755 services/opendkim.sh delete mode 100755 services/php5-fpm.sh delete mode 100755 services/postfix.sh delete mode 100755 services/postgrey.sh delete mode 100755 services/spampd.sh diff --git a/Dockerfile b/Dockerfile index 74ec64b4..3663649c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,18 +35,20 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get upgrade -y # Install packages needed by Mail-in-a-Box. ADD containers/docker/apt_package_list.txt /tmp/mailinabox_apt_package_list.txt RUN DEBIAN_FRONTEND=noninteractive apt-get install -y $(cat /tmp/mailinabox_apt_package_list.txt) +RUN DEBIAN_FRONTEND=noninteractive apt-get install -y rsyslog RUN rm -f /tmp/mailinabox_apt_package_list.txt +RUN apt-get clean + +# Create the user-data user, so the start script doesn't have to. RUN useradd -m user-data -RUN rm -rf /etc/service/syslog-ng # Now add Mail-in-a-Box to the system. ADD . /usr/local/mailinabox -#RUN /usr/local/mailinabox/containers/docker/setup.sh - # We can't know things like the IP address where the container will eventually # be deployed until the container is started. We also don't want to create any # private keys during the creation of the image --- that should wait until the # container is started too. So our whole setup process is deferred until the # container is started. -ENTRYPOINT /usr/local/mailinabox/containers/docker/init.sh +RUN mkdir -p /etc/my_init.d +RUN ln -s /usr/local/mailinabox/containers/docker/init.sh /etc/my_init.d/20-mailinabox.sh diff --git a/containers/docker/apt_package_list.txt b/containers/docker/apt_package_list.txt index eac9073f..96bfdb56 100644 --- a/containers/docker/apt_package_list.txt +++ b/containers/docker/apt_package_list.txt @@ -72,7 +72,6 @@ python3-pip pyzor razor resolvconf -rsyslog spampd sqlite3 sudo diff --git a/containers/docker/init.sh b/containers/docker/init.sh index aea2ef3d..8f7e377e 100755 --- a/containers/docker/init.sh +++ b/containers/docker/init.sh @@ -17,51 +17,63 @@ if [ ! -t 0 ]; then export NONINTERACTIVE=1 fi -# Start configuration. +# The phusion/baseimage base image we use for a working Ubuntu +# replaces the normal Upstart system service management with +# a ligher-weight service management system called runit that +# requires a different configuration. We need to create service +# run files that do not daemonize. + +# For most of the services, there is a common pattern we can use: +# execute the init.d script that the Ubuntu package installs, and +# then poll for the termination of the daemon. +function make_runit_service { + INITD_NAME=$1 + WAIT_ON_PROCESS_NAME=$2 + mkdir -p /etc/service/$INITD_NAME + cat > /etc/service/$INITD_NAME/run < /dev/null; then - echo Starting user-data volume container... - $DOCKER run -d \ +tput setaf 2 +echo "Building/updating base image (mailinabox)..." +tput setaf 7 + +docker build -q -t mailinabox . + +if ! docker ps -a | grep mailinabox-userdata > /dev/null; then + tput setaf 2 + echo + echo "Creating a new container for your data (mailinabox-userdata)..." + tput setaf 7 + + docker run -d \ --name mailinabox-userdata \ -v /home/user-data \ - scratch /bin/bash + scratch /bin/does-not-exist-but-thats-ok +else + tput setaf 2 + echo + echo "Using existing container mailinabox-userdata for your data." + tput setaf 7 fi # End a running container. -if $DOCKER ps -a | grep mailinabox-services > /dev/null; then - echo Deleting container... - $DOCKER rm mailinabox-services + +if docker ps -a | grep mailinabox-services > /dev/null; then + tput setaf 2 + echo + echo "Destroying mailinabox-services container..." + tput setaf 7 + + docker rm -f mailinabox-services fi # Start container. -echo Starting new container... -$DOCKER run \ + +tput setaf 2 +echo +echo "Starting new container (mailinabox-services)..." +tput setaf 7 + +# Notes: +# * Passing through SKIP_NETWORK_CHECKS makes it easier to do testing +# on a residential network. + +docker run \ --privileged \ -v /dev/urandom:/dev/random \ -p 25 -p 53/udp -p 53/tcp -p 80 -p 443 -p 587 -p 993 \ --name mailinabox-services \ --volumes-from mailinabox-userdata \ - mailinabox \ No newline at end of file + -e "SKIP_NETWORK_CHECKS=$SKIP_NETWORK_CHECKS" \ + mailinabox diff --git a/services/dovecot.sh b/containers/docker/runit/dovecot.sh similarity index 100% rename from services/dovecot.sh rename to containers/docker/runit/dovecot.sh diff --git a/services/rsyslogd.sh b/containers/docker/runit/rsyslogd.sh similarity index 100% rename from services/rsyslogd.sh rename to containers/docker/runit/rsyslogd.sh diff --git a/services/bind9.sh b/services/bind9.sh deleted file mode 100755 index a962510b..00000000 --- a/services/bind9.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -EXEC=bind9 -PROCESS=named - -/etc/init.d/$EXEC start - -while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do - sleep 30 -done - diff --git a/services/fail2ban.sh b/services/fail2ban.sh deleted file mode 100755 index 3a5720e4..00000000 --- a/services/fail2ban.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -PROCESS=fail2ban - -/etc/init.d/$PROCESS start - -while [ `ps aux | grep fail2ban | grep -v grep | wc -l` -gt 0 ]; do - sleep 30 -done diff --git a/services/mailinabox.sh b/services/mailinabox.sh deleted file mode 100755 index 2ed281c9..00000000 --- a/services/mailinabox.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -EXEC=mailinabox -PROCESS=mailinabox-daemon - -if [ `ps aux | grep $PROCESS | grep -v grep | wc -l` -eq 0 ]; then - /etc/init.d/$EXEC start -fi - -while [ `ps aux | grep $PROCESS | grep -v grep | wc -l` -gt 0 ]; do - sleep 30 -done diff --git a/services/memcached.sh b/services/memcached.sh deleted file mode 100755 index 823997f1..00000000 --- a/services/memcached.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -PROCESS=memcached - -/etc/init.d/$PROCESS start - -while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do - sleep 60 -done - diff --git a/services/nginx.sh b/services/nginx.sh deleted file mode 100755 index ea73ae92..00000000 --- a/services/nginx.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -PROCESS=nginx - -/etc/init.d/$PROCESS start - -while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do - sleep 30 -done - diff --git a/services/nsd.sh b/services/nsd.sh deleted file mode 100755 index 392215d4..00000000 --- a/services/nsd.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -PROCESS=nsd - -/etc/init.d/$PROCESS start - -while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do - sleep 30 -done - diff --git a/services/opendkim.sh b/services/opendkim.sh deleted file mode 100755 index a3a76eb2..00000000 --- a/services/opendkim.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -PROCESS=opendkim - -/etc/init.d/$PROCESS start - -while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do - sleep 30 -done - diff --git a/services/php5-fpm.sh b/services/php5-fpm.sh deleted file mode 100755 index e00987a8..00000000 --- a/services/php5-fpm.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -PROCESS=php5-fpm - -/etc/init.d/$PROCESS start - -while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do - sleep 30 -done - diff --git a/services/postfix.sh b/services/postfix.sh deleted file mode 100755 index 18755980..00000000 --- a/services/postfix.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -PROCESS=postfix - -/etc/init.d/$PROCESS start - -while [ `ps aux | grep $PROCESS | grep -v grep | wc -l` -gt 0 ]; do - sleep 30 -done - diff --git a/services/postgrey.sh b/services/postgrey.sh deleted file mode 100755 index 7a052f0d..00000000 --- a/services/postgrey.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -PROCESS=postgrey - -/etc/init.d/$PROCESS start - -while [ `ps aux | grep $PROCESS | grep -v grep | wc -l` -gt 0 ]; do - sleep 30 -done - diff --git a/services/spampd.sh b/services/spampd.sh deleted file mode 100755 index a9fd4393..00000000 --- a/services/spampd.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -PROCESS=spampd - -/etc/init.d/$PROCESS start - -while [ `ps -C $PROCESS -o pid= | wc -l` -gt 0 ]; do - sleep 30 -done - diff --git a/setup/functions.sh b/setup/functions.sh index abd7a357..a7a34196 100644 --- a/setup/functions.sh +++ b/setup/functions.sh @@ -37,9 +37,14 @@ function apt_get_quiet { } function apt_install { - if [ ! "$IS_DOCKER" ];then - # Report any packages already installed. PACKAGES=$@ + + if [ ! -z "$IS_DOCKER" ]; then + # Speed things up because packages are already installed by the image. + PACKAGES="" + fi + + # Report any packages already installed. TO_INSTALL="" ALREADY_INSTALLED="" for pkg in $PACKAGES; do @@ -161,10 +166,18 @@ function ufw_allow { function restart_service { # Restart a service quietly. - if [ ! "$IS_DOCKER" ]; then - # The normal way to restart a service. - hide_output service $1 restart + + if [[ ! -z "$IS_DOCKER" && "$1" == "dovecot" ]]; then + # In Docker, sysvinit takes care of any services with an init.d + # script. The dovecot package provides an Upstart config only, + # and so it won't work this way. We make a new script for it + # elsewhere. We also cant do `sv restart dovecot` because runit + # is not running until after the setup scripts are run. So we + # will have to skip starting dovecot for now. + return 0 fi + + hide_output service $1 restart } ## Dialog Functions ## diff --git a/setup/start.sh b/setup/start.sh index 0605974c..e7dfb6f2 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -150,6 +150,14 @@ source setup/owncloud.sh source setup/zpush.sh source setup/management.sh +# In Docker, sysvinit services are started automatically. Runit services +# aren't started until after this setup script finishes. But we need +# Dovecot (which is Upstart-only) running in order to create the first +# mail user. So start dovecot now. +if [ ! -z "$IS_DOCKER" ]; then + /usr/sbin/dovecot -c /etc/dovecot/dovecot.conf +fi + # Ping the management daemon to write the DNS and nginx configuration files. while [ ! -f /var/lib/mailinabox/api.key ]; do echo Waiting for the Mail-in-a-Box management daemon to start... @@ -186,5 +194,4 @@ openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint \ | sed "s/SHA1 Fingerprint=//" echo echo Then you can confirm the security exception and continue. -echo - +echo \ No newline at end of file From ebf6c807c5db2e327dde03c465dd4b90857006d0 Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Thu, 19 Feb 2015 18:09:07 -0500 Subject: [PATCH 4/7] zpush: missing install of 'git' which is how we fetch Z-Push sources, and git may not otherwise be available in a containerized install --- setup/zpush.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup/zpush.sh b/setup/zpush.sh index 3b5d0bdf..37436071 100755 --- a/setup/zpush.sh +++ b/setup/zpush.sh @@ -16,7 +16,7 @@ source /etc/mailinabox.conf # load global vars # Prereqs. apt_install \ - php-soap php5-imap libawl-php php5-xsl + php-soap php5-imap libawl-php php5-xsl git php5enmod imap @@ -26,7 +26,7 @@ needs_update=0 #NODOC if [ ! -f /usr/local/lib/z-push/version ]; then needs_update=1 #NODOC elif [[ $TARGETHASH != `cat /usr/local/lib/z-push/version` ]]; then - # checks if the version + # checks if the version needs_update=1 #NODOC fi if [ $needs_update == 1 ]; then From 5b23a06a7410e4530a56fd6200a6c46c3c6ea9b6 Mon Sep 17 00:00:00 2001 From: Toilal Date: Sat, 4 Apr 2015 17:39:40 +0200 Subject: [PATCH 5/7] Extract configuration generation to configure.sh script --- setup/configure.sh | 93 +++++++++++++++++++++++++++++++++++++++++++ setup/start.sh | 99 +++------------------------------------------- 2 files changed, 99 insertions(+), 93 deletions(-) create mode 100755 setup/configure.sh diff --git a/setup/configure.sh b/setup/configure.sh new file mode 100755 index 00000000..e06f7180 --- /dev/null +++ b/setup/configure.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +source setup/functions.sh # load our functions + +# Ask the user for the PRIMARY_HOSTNAME, PUBLIC_IP, PUBLIC_IPV6, and CSR_COUNTRY +# if values have not already been set in environment variables. When running +# non-interactively, be sure to set values for all! +source setup/questions.sh + +# Automatic configuration, e.g. as used in our Vagrant configuration. +if [ "$PUBLIC_IP" = "auto" ]; then + # Use a public API to get our public IP address, or fall back to local network configuration. + PUBLIC_IP=$(get_publicip_from_web_service 4 || get_default_privateip 4) +fi +if [ "$PUBLIC_IPV6" = "auto" ]; then + # Use a public API to get our public IPv6 address, or fall back to local network configuration. + PUBLIC_IPV6=$(get_publicip_from_web_service 6 || get_default_privateip 6) +fi +if [ "$PRIMARY_HOSTNAME" = "auto" ]; then + # Use reverse DNS to get this machine's hostname. Install bind9-host early. + hide_output apt-get -y install bind9-host + PRIMARY_HOSTNAME=$(get_default_hostname) +elif [ "$PRIMARY_HOSTNAME" = "auto-easy" ]; then + # Generate a probably-unique subdomain under our justtesting.email domain. + PRIMARY_HOSTNAME=`echo $PUBLIC_IP | sha1sum | cut -c1-5`.justtesting.email +fi + +# Show the configuration, since the user may have not entered it manually. +echo +echo "Primary Hostname: $PRIMARY_HOSTNAME" +echo "Public IP Address: $PUBLIC_IP" +if [ ! -z "$PUBLIC_IPV6" ]; then + echo "Public IPv6 Address: $PUBLIC_IPV6" +fi +if [ "$PRIVATE_IP" != "$PUBLIC_IP" ]; then + echo "Private IP Address: $PRIVATE_IP" +fi +if [ "$PRIVATE_IPV6" != "$PUBLIC_IPV6" ]; then + echo "Private IPv6 Address: $PRIVATE_IPV6" +fi +if [ -f /usr/bin/git ]; then + echo "Mail-in-a-Box Version: " $(git describe) +fi +echo + +# Run some network checks to make sure setup on this machine makes sense. +if [ -z "$SKIP_NETWORK_CHECKS" ]; then + . setup/network-checks.sh +fi + +# For the first time (if the config file (/etc/mailinabox.conf) not exists): +# Create the user named "user-data" and store all persistent user +# data (mailboxes, etc.) in that user's home directory. +# +# If the config file exists: +# Apply the existing configuration options for STORAGE_USER/ROOT +if [ -z "$STORAGE_USER" ]; then + STORAGE_USER=$([[ -z "$DEFAULT_STORAGE_USER" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER") +fi + +if [ -z "$STORAGE_ROOT" ]; then + STORAGE_ROOT=$([[ -z "$DEFAULT_STORAGE_ROOT" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT") +fi + +# Create the STORAGE_USER if it not exists +if ! id -u $STORAGE_USER >/dev/null 2>&1; then + useradd -m $STORAGE_USER +fi + +# Create the STORAGE_ROOT if it not exists +if [ ! -d $STORAGE_ROOT ]; then + mkdir -p $STORAGE_ROOT +fi + +# Create mailinabox.version file if not exists +if [ ! -f $STORAGE_ROOT/mailinabox.version ]; then + echo $(setup/migrate.py --current) > $STORAGE_ROOT/mailinabox.version + chown $STORAGE_USER:$STORAGE_USER $STORAGE_ROOT/mailinabox.version +fi + + +# Save the global options in /etc/mailinabox.conf so that standalone +# tools know where to look for data. +cat > /etc/mailinabox.conf << EOF; +STORAGE_USER=$STORAGE_USER +STORAGE_ROOT=$STORAGE_ROOT +PRIMARY_HOSTNAME=$PRIMARY_HOSTNAME +PUBLIC_IP=$PUBLIC_IP +PUBLIC_IPV6=$PUBLIC_IPV6 +PRIVATE_IP=$PRIVATE_IP +PRIVATE_IPV6=$PRIVATE_IPV6 +CSR_COUNTRY=$CSR_COUNTRY +EOF diff --git a/setup/start.sh b/setup/start.sh index e7dfb6f2..fc0855b3 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -14,8 +14,8 @@ source setup/preflight.sh # the management daemon startup script. if [ -z `locale -a | grep en_US.utf8` ]; then - # Generate locale if not exists - hide_output locale-gen en_US.UTF-8 + # Generate locale if not exists + hide_output locale-gen en_US.UTF-8 fi export LANGUAGE=en_US.UTF-8 @@ -45,97 +45,10 @@ source setup/start.sh EOF chmod +x /usr/local/bin/mailinabox -# Ask the user for the PRIMARY_HOSTNAME, PUBLIC_IP, PUBLIC_IPV6, and CSR_COUNTRY -# if values have not already been set in environment variables. When running -# non-interactively, be sure to set values for all! -source setup/questions.sh +# Start configuration +source setup/configure.sh -# Automatic configuration, e.g. as used in our Vagrant configuration. -if [ "$PUBLIC_IP" = "auto" ]; then - # Use a public API to get our public IP address, or fall back to local network configuration. - PUBLIC_IP=$(get_publicip_from_web_service 4 || get_default_privateip 4) -fi -if [ "$PUBLIC_IPV6" = "auto" ]; then - # Use a public API to get our public IPv6 address, or fall back to local network configuration. - PUBLIC_IPV6=$(get_publicip_from_web_service 6 || get_default_privateip 6) -fi -if [ "$PRIMARY_HOSTNAME" = "auto" ]; then - # Use reverse DNS to get this machine's hostname. Install bind9-host early. - hide_output apt-get -y install bind9-host - PRIMARY_HOSTNAME=$(get_default_hostname) -elif [ "$PRIMARY_HOSTNAME" = "auto-easy" ]; then - # Generate a probably-unique subdomain under our justtesting.email domain. - PRIMARY_HOSTNAME=`echo $PUBLIC_IP | sha1sum | cut -c1-5`.justtesting.email -fi - -# Show the configuration, since the user may have not entered it manually. -echo -echo "Primary Hostname: $PRIMARY_HOSTNAME" -echo "Public IP Address: $PUBLIC_IP" -if [ ! -z "$PUBLIC_IPV6" ]; then - echo "Public IPv6 Address: $PUBLIC_IPV6" -fi -if [ "$PRIVATE_IP" != "$PUBLIC_IP" ]; then - echo "Private IP Address: $PRIVATE_IP" -fi -if [ "$PRIVATE_IPV6" != "$PUBLIC_IPV6" ]; then - echo "Private IPv6 Address: $PRIVATE_IPV6" -fi -if [ -f .git ]; then - echo "Mail-in-a-Box Version: " $(git describe) -fi -echo - -# Run some network checks to make sure setup on this machine makes sense. -if [ -z "$SKIP_NETWORK_CHECKS" ]; then - . setup/network-checks.sh -fi - -# For the first time (if the config file (/etc/mailinabox.conf) not exists): -# Create the user named "user-data" and store all persistent user -# data (mailboxes, etc.) in that user's home directory. -# -# If the config file exists: -# Apply the existing configuration options for STORAGE_USER/ROOT -if [ -z "$STORAGE_USER" ]; then - STORAGE_USER=$([[ -z "$DEFAULT_STORAGE_USER" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER") -fi - -if [ -z "$STORAGE_ROOT" ]; then - STORAGE_ROOT=$([[ -z "$DEFAULT_STORAGE_ROOT" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT") -fi - -# Create the STORAGE_USER if it not exists -if ! id -u $STORAGE_USER >/dev/null 2>&1; then - useradd -m $STORAGE_USER -fi - -# Create the STORAGE_ROOT if it not exists -if [ ! -d $STORAGE_ROOT ]; then - mkdir -p $STORAGE_ROOT -fi - -# Create mailinabox.version file if not exists -if [ ! -f $STORAGE_ROOT/mailinabox.version ]; then - echo $(setup/migrate.py --current) > $STORAGE_ROOT/mailinabox.version - chown $STORAGE_USER:$STORAGE_USER $STORAGE_ROOT/mailinabox.version -fi - - -# Save the global options in /etc/mailinabox.conf so that standalone -# tools know where to look for data. -cat > /etc/mailinabox.conf << EOF; -STORAGE_USER=$STORAGE_USER -STORAGE_ROOT=$STORAGE_ROOT -PRIMARY_HOSTNAME=$PRIMARY_HOSTNAME -PUBLIC_IP=$PUBLIC_IP -PUBLIC_IPV6=$PUBLIC_IPV6 -PRIVATE_IP=$PRIVATE_IP -PRIVATE_IPV6=$PRIVATE_IPV6 -CSR_COUNTRY=$CSR_COUNTRY -EOF - -# Start service configuration. +# Start service installation. source setup/system.sh source setup/ssl.sh source setup/dns.sh @@ -191,7 +104,7 @@ else echo fi openssl x509 -in $STORAGE_ROOT/ssl/ssl_certificate.pem -noout -fingerprint \ - | sed "s/SHA1 Fingerprint=//" + | sed "s/SHA1 Fingerprint=//" echo echo Then you can confirm the security exception and continue. echo \ No newline at end of file From 2b9ce85ba880684a3d6f44a80748fd0dc5463c24 Mon Sep 17 00:00:00 2001 From: Toilal Date: Sat, 4 Apr 2015 17:44:14 +0200 Subject: [PATCH 6/7] Use netcat to check if mailinabox webservice is available --- setup/management.sh | 5 +---- setup/start.sh | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/setup/management.sh b/setup/management.sh index fe71385f..62034f4a 100755 --- a/setup/management.sh +++ b/setup/management.sh @@ -41,8 +41,5 @@ EOF chmod +x /etc/cron.daily/mailinabox-statuschecks -# Start it. Remove the api key file first so that start.sh -# can wait for it to be created to know that the management -# server is ready. -rm -f /var/lib/mailinabox/api.key +# Start it. restart_service mailinabox diff --git a/setup/start.sh b/setup/start.sh index fc0855b3..10c9afa2 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -72,7 +72,8 @@ if [ ! -z "$IS_DOCKER" ]; then fi # Ping the management daemon to write the DNS and nginx configuration files. -while [ ! -f /var/lib/mailinabox/api.key ]; do +until nc -z -w 4 localhost 10222 +do echo Waiting for the Mail-in-a-Box management daemon to start... sleep 2 done From 1eb77c332bca0a74a37f792460903a1fb660befc Mon Sep 17 00:00:00 2001 From: Toilal Date: Sat, 4 Apr 2015 17:50:16 +0200 Subject: [PATCH 7/7] Dockerize using phusion/baseimage and runit services --- Dockerfile | 44 ++++-- containers/docker/init.sh | 79 ---------- containers/docker/my_init.d/10-mailinabox.sh | 51 +++++++ .../docker/patch/setup/functions_docker.sh | 22 +++ containers/docker/run | 81 ++++++---- containers/docker/runit/bind9/run | 52 +++++++ containers/docker/runit/dovecot.sh | 3 - containers/docker/runit/dovecot/run | 3 + containers/docker/runit/fail2ban/run | 93 +++++++++++ containers/docker/runit/mailinabox/run | 14 ++ containers/docker/runit/memcached/run | 18 +++ containers/docker/runit/nginx/run | 24 +++ containers/docker/runit/nsd/run | 30 ++++ containers/docker/runit/opendkim/run | 71 +++++++++ containers/docker/runit/opendmarc/run | 70 +++++++++ containers/docker/runit/php5-fpm/run | 42 +++++ containers/docker/runit/postfix/run | 144 ++++++++++++++++++ containers/docker/runit/postgrey/run | 29 ++++ containers/docker/runit/rsyslogd.sh | 3 - containers/docker/runit/spampd/run | 98 ++++++++++++ containers/docker/tools/disable_services.sh | 14 ++ containers/docker/tools/lsb_compat.sh | 20 +++ containers/docker/tools/runit_logs.sh | 26 ++++ setup/dns.sh | 1 + setup/functions.sh | 20 +-- setup/mail-postfix.sh | 1 + setup/management.sh | 10 +- setup/owncloud.sh | 3 +- setup/questions.sh | 1 + setup/start.sh | 8 - 30 files changed, 915 insertions(+), 160 deletions(-) delete mode 100755 containers/docker/init.sh create mode 100755 containers/docker/my_init.d/10-mailinabox.sh create mode 100755 containers/docker/patch/setup/functions_docker.sh create mode 100755 containers/docker/runit/bind9/run delete mode 100755 containers/docker/runit/dovecot.sh create mode 100755 containers/docker/runit/dovecot/run create mode 100755 containers/docker/runit/fail2ban/run create mode 100755 containers/docker/runit/mailinabox/run create mode 100755 containers/docker/runit/memcached/run create mode 100755 containers/docker/runit/nginx/run create mode 100755 containers/docker/runit/nsd/run create mode 100755 containers/docker/runit/opendkim/run create mode 100755 containers/docker/runit/opendmarc/run create mode 100755 containers/docker/runit/php5-fpm/run create mode 100755 containers/docker/runit/postfix/run create mode 100755 containers/docker/runit/postgrey/run delete mode 100755 containers/docker/runit/rsyslogd.sh create mode 100755 containers/docker/runit/spampd/run create mode 100755 containers/docker/tools/disable_services.sh create mode 100755 containers/docker/tools/lsb_compat.sh create mode 100755 containers/docker/tools/runit_logs.sh diff --git a/Dockerfile b/Dockerfile index 3663649c..93f02977 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,8 +19,14 @@ FROM phusion/baseimage:0.9.16 # Dockerfile metadata. MAINTAINER Joshua Tauberer (http://razor.occams.info) -EXPOSE 25 53/udp 53/tcp 80 443 587 993 -VOLUME /data +EXPOSE 25 53/udp 53/tcp 80 443 587 993 4190 +VOLUME /home/user-data + +# Use baseimage init system +CMD ["/sbin/my_init"] + +# Create the user-data user, so the start script doesn't have to. +RUN useradd -m user-data # Docker has a beautiful way to cache images after each step. The next few # steps of installing system packages are very intensive, so we take care @@ -35,20 +41,28 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get upgrade -y # Install packages needed by Mail-in-a-Box. ADD containers/docker/apt_package_list.txt /tmp/mailinabox_apt_package_list.txt RUN DEBIAN_FRONTEND=noninteractive apt-get install -y $(cat /tmp/mailinabox_apt_package_list.txt) -RUN DEBIAN_FRONTEND=noninteractive apt-get install -y rsyslog -RUN rm -f /tmp/mailinabox_apt_package_list.txt -RUN apt-get clean - -# Create the user-data user, so the start script doesn't have to. -RUN useradd -m user-data # Now add Mail-in-a-Box to the system. ADD . /usr/local/mailinabox -# We can't know things like the IP address where the container will eventually -# be deployed until the container is started. We also don't want to create any -# private keys during the creation of the image --- that should wait until the -# container is started too. So our whole setup process is deferred until the -# container is started. -RUN mkdir -p /etc/my_init.d -RUN ln -s /usr/local/mailinabox/containers/docker/init.sh /etc/my_init.d/20-mailinabox.sh +# Patch setup/functions.sh +RUN cp /usr/local/mailinabox/setup/functions.sh /usr/local/mailinabox/setup/functions.orig.sh +RUN echo "# Docker patches" >> /usr/local/mailinabox/setup/functions.sh && \ + echo "source containers/docker/patch/setup/functions_docker.sh" >> /usr/local/mailinabox/setup/functions.sh +# Skip apt-get install +RUN sed 's/PACKAGES=$@/PACKAGES=""/g' -i /usr/local/mailinabox/setup/functions.sh + +# Install runit services +ADD containers/docker/runit/ /etc/service/ + +# LSB Compatibility +RUN /usr/local/mailinabox/containers/docker/tools/lsb_compat.sh + +# Configure service logs +RUN /usr/local/mailinabox/containers/docker/tools/runit_logs.sh + +# Disable services +RUN /usr/local/mailinabox/containers/docker/tools/disable_services.sh + +# Add my_init scripts +ADD containers/docker/my_init.d/* /etc/my_init.d/ diff --git a/containers/docker/init.sh b/containers/docker/init.sh deleted file mode 100755 index 8f7e377e..00000000 --- a/containers/docker/init.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash - -# This script is used within containers to turn it into a Mail-in-a-Box. -# It is referenced by the Dockerfile. You should not run it directly. -######################################################################## - -# Local configuration details were not known at the time the Docker -# image was created, so all setup is defered until the container -# is started. That's when this script runs. - -# If we're not in an interactive shell, set defaults. -if [ ! -t 0 ]; then - export PUBLIC_IP=auto - export PUBLIC_IPV6=auto - export PRIMARY_HOSTNAME=auto - export CSR_COUNTRY=US - export NONINTERACTIVE=1 -fi - -# The phusion/baseimage base image we use for a working Ubuntu -# replaces the normal Upstart system service management with -# a ligher-weight service management system called runit that -# requires a different configuration. We need to create service -# run files that do not daemonize. - -# For most of the services, there is a common pattern we can use: -# execute the init.d script that the Ubuntu package installs, and -# then poll for the termination of the daemon. -function make_runit_service { - INITD_NAME=$1 - WAIT_ON_PROCESS_NAME=$2 - mkdir -p /etc/service/$INITD_NAME - cat > /etc/service/$INITD_NAME/run < /dev/null; then +if [ -z "$SKIP_BUILD" ]; then tput setaf 2 - echo - echo "Creating a new container for your data (mailinabox-userdata)..." + echo "Building/updating base image (mailinabox)..." tput setaf 7 - docker run -d \ - --name mailinabox-userdata \ - -v /home/user-data \ - scratch /bin/does-not-exist-but-thats-ok + docker build -q -t mailinabox . +fi; + +if ! docker inspect ${CONTAINER_DATA_NAME} > /dev/null; then + tput setaf 2 + echo + echo "Creating a new container for your data (${CONTAINER_DATA_NAME})..." + tput setaf 7 + + docker create \ + --name ${CONTAINER_DATA_NAME} \ + $([ -z "$HOST_USERDATA_VOLUME" ] && echo "-v $HOST_USERDATA_VOLUME:/home/user-data" || echo "-v /home/user-data") \ + phusion/baseimage:0.9.16 else tput setaf 2 echo - echo "Using existing container mailinabox-userdata for your data." + echo "Using existing container ${CONTAINER_DATA_NAME} for your data." tput setaf 7 fi # End a running container. - -if docker ps -a | grep mailinabox-services > /dev/null; then +if docker inspect ${CONTAINER_NAME} > /dev/null; then tput setaf 2 echo - echo "Destroying mailinabox-services container..." + echo "Destroying ${CONTAINER_NAME} container..." tput setaf 7 - docker rm -f mailinabox-services + docker rm -f ${CONTAINER_NAME} fi # Start container. - tput setaf 2 echo -echo "Starting new container (mailinabox-services)..." +echo "Starting new container (${CONTAINER_NAME})..." tput setaf 7 +# Run the services container +# detached if NONINTERACTIVE is set, +# interactively if NONINTERACTIVE is not set, # Notes: # * Passing through SKIP_NETWORK_CHECKS makes it easier to do testing # on a residential network. - +# * --privileged flag cause an issue with bind9/named failing to start in this case +# see docker/docker#7318 docker run \ - --privileged \ -v /dev/urandom:/dev/random \ - -p 25 -p 53/udp -p 53/tcp -p 80 -p 443 -p 587 -p 993 \ - --name mailinabox-services \ - --volumes-from mailinabox-userdata \ - -e "SKIP_NETWORK_CHECKS=$SKIP_NETWORK_CHECKS" \ + -p 25:25 \ + $([ -z "$NODNS" ] && echo "-p 53:53/udp -p 53:53/tcp") \ + -p $HOST_HTTP_PORT:80 \ + -p $HOST_HTTPS_PORT:443 \ + -p 587:587 \ + -p 993:993 \ + -p 4190:4190 \ + --name ${CONTAINER_NAME} \ + --volumes-from ${CONTAINER_DATA_NAME} \ + --restart always \ + $([ ! -z "$NONINTERACTIVE" ] && echo "-d") \ + -it \ mailinabox + +if [ -z "$NONINTERACTIVE" ]; then + tput setaf 2 + echo + echo "Restarting container ${CONTAINER_NAME}..." + tput setaf 7 + + docker restart ${CONTAINER_NAME} +fi \ No newline at end of file diff --git a/containers/docker/runit/bind9/run b/containers/docker/runit/bind9/run new file mode 100755 index 00000000..0a03e3b8 --- /dev/null +++ b/containers/docker/runit/bind9/run @@ -0,0 +1,52 @@ +#!/bin/bash + +PATH=/sbin:/bin:/usr/sbin:/usr/bin + +# for a chrooted server: "-u bind -t /var/lib/named" +# Don't modify this line, change or create /etc/default/bind9. +OPTIONS="" +RESOLVCONF=no + +test -f /etc/default/bind9 && . /etc/default/bind9 + +test -x /usr/sbin/rndc || exit 0 + +. /lib/lsb/init-functions + +check_network() { + if [ -x /usr/bin/uname ] && [ "X$(/usr/bin/uname -o)" = XSolaris ]; then + IFCONFIG_OPTS="-au" + else + IFCONFIG_OPTS="" + fi + if [ -z "$(/sbin/ifconfig $IFCONFIG_OPTS)" ]; then + #log_action_msg "No networks configured." + return 1 + fi + return 0 +} + +log_daemon_msg "Starting domain name service..." "bind9" + +modprobe capability >/dev/null 2>&1 || true + +# dirs under /var/run can go away on reboots. +mkdir -p /var/run/named +chmod 775 /var/run/named +chown root:bind /var/run/named >/dev/null 2>&1 || true + +if [ ! -x /usr/sbin/named ]; then + log_action_msg "named binary missing - not starting" + log_end_msg 1 +fi + +if ! check_network; then + log_action_msg "no networks configured" + log_end_msg 1 +fi + +if [ "X$RESOLVCONF" != "Xno" ] && [ -x /sbin/resolvconf ] ; then + echo "nameserver 127.0.0.1" | /sbin/resolvconf -a lo.named +fi + +exec /usr/sbin/named -f $OPTIONS diff --git a/containers/docker/runit/dovecot.sh b/containers/docker/runit/dovecot.sh deleted file mode 100755 index 5e5c1474..00000000 --- a/containers/docker/runit/dovecot.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -/usr/sbin/dovecot -F -c /etc/dovecot/dovecot.conf &> /var/log/dovecot.log diff --git a/containers/docker/runit/dovecot/run b/containers/docker/runit/dovecot/run new file mode 100755 index 00000000..bd3939fc --- /dev/null +++ b/containers/docker/runit/dovecot/run @@ -0,0 +1,3 @@ +#!/bin/bash + +exec /usr/sbin/dovecot -F -c /etc/dovecot/dovecot.conf diff --git a/containers/docker/runit/fail2ban/run b/containers/docker/runit/fail2ban/run new file mode 100755 index 00000000..ce22c625 --- /dev/null +++ b/containers/docker/runit/fail2ban/run @@ -0,0 +1,93 @@ +#!/bin/bash + +PATH=/usr/sbin:/usr/bin:/sbin:/bin +DESC="authentication failure monitor" +NAME=fail2ban + +# fail2ban-client is not a daemon itself but starts a daemon and +# loads its with configuration +#DAEMON=/usr/bin/$NAME-client +DAEMON=/usr/bin/$NAME-server +SCRIPTNAME=/etc/init.d/$NAME + +# Ad-hoc way to parse out socket file name +SOCKFILE=`grep -h '^[^#]*socket *=' /etc/$NAME/$NAME.conf /etc/$NAME/$NAME.local 2>/dev/null \ + | tail -n 1 | sed -e 's/.*socket *= *//g' -e 's/ *$//g'` +[ -z "$SOCKFILE" ] && SOCKFILE='/tmp/fail2ban.sock' + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Run as root by default. +FAIL2BAN_USER=root + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME +DAEMON_ARGS="-f $FAIL2BAN_OPTS" + +# Load the VERBOSE setting and other rcS variables +[ -f /etc/default/rcS ] && . /etc/default/rcS + +# Predefine what can be missing from lsb source later on -- necessary to run +# on sarge. Just present it in a bit more compact way from what was shipped +log_daemon_msg () { + [ -z "$1" ] && return 1 + echo -n "$1:" + [ -z "$2" ] || echo -n " $2" +} + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. +# Actually has to (>=2.0-7) present in sarge. log_daemon_msg is predefined +# so we must be ok +. /lib/lsb/init-functions + +# +# Shortcut function for abnormal init script interruption +# +report_bug() +{ + echo $* + echo "Please submit a bug report to Debian BTS (reportbug fail2ban)" + exit 1 +} + +# +# Helper function to check if socket is present, which is often left after +# abnormal exit of fail2ban and needs to be removed +# +check_socket() +{ + # Return + # 0 if socket is present and readable + # 1 if socket file is not present + # 2 if socket file is present but not readable + # 3 if socket file is present but is not a socket + [ -e "$SOCKFILE" ] || return 1 + [ -r "$SOCKFILE" ] || return 2 + [ -S "$SOCKFILE" ] || return 3 + return 0 +} + +if [ -e "$SOCKFILE" ]; then + log_failure_msg "Socket file $SOCKFILE is present" + [ "$1" = "force-start" ] \ + && log_success_msg "Starting anyway as requested" \ + || return 2 + DAEMON_ARGS="$DAEMON_ARGS -x" +fi + +# Assure that /var/run/fail2ban exists +[ -d /var/run/fail2ban ] || mkdir -p /var/run/fail2ban + +if [ "$FAIL2BAN_USER" != "root" ]; then + # Make the socket directory, IP lists and fail2ban log + # files writable by fail2ban + chown "$FAIL2BAN_USER" /var/run/fail2ban + # Create the logfile if it doesn't exist + touch /var/log/fail2ban.log + chown "$FAIL2BAN_USER" /var/log/fail2ban.log + find /proc/net/xt_recent -name 'fail2ban-*' -exec chown "$FAIL2BAN_USER" {} \; +fi + +exec /sbin/setuser $FAIL2BAN_USER $DAEMON $DAEMON_ARGS diff --git a/containers/docker/runit/mailinabox/run b/containers/docker/runit/mailinabox/run new file mode 100755 index 00000000..abeb4331 --- /dev/null +++ b/containers/docker/runit/mailinabox/run @@ -0,0 +1,14 @@ +#!/bin/bash + +NAME=mailinabox +DAEMON=/usr/local/bin/mailinabox-daemon + +export LANGUAGE=en_US.UTF-8 +export LC_ALL=en_US.UTF-8 +export LANG=en_US.UTF-8 +export LC_TYPE=en_US.UTF-8 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +exec $DAEMON 2>&1 \ No newline at end of file diff --git a/containers/docker/runit/memcached/run b/containers/docker/runit/memcached/run new file mode 100755 index 00000000..51728127 --- /dev/null +++ b/containers/docker/runit/memcached/run @@ -0,0 +1,18 @@ +#!/bin/sh + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/bin/memcached + +test -x $DAEMON || exit 0 +set -e + +# Edit /etc/default/memcached to change this. +ENABLE_MEMCACHED=no +test -r /etc/default/memcached && . /etc/default/memcached + +echo -n "Starting $DESC: " +if [ $ENABLE_MEMCACHED = yes ]; then + exec /sbin/setuser memcache $DAEMON +else + exit 1 +fi \ No newline at end of file diff --git a/containers/docker/runit/nginx/run b/containers/docker/runit/nginx/run new file mode 100755 index 00000000..5c4f483c --- /dev/null +++ b/containers/docker/runit/nginx/run @@ -0,0 +1,24 @@ +#!/bin/bash + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/nginx +NAME=nginx +DESC=nginx + +# Include nginx defaults if available +if [ -r /etc/default/nginx ]; then + . /etc/default/nginx +fi + +test -x $DAEMON || exit 0 + +. /lib/init/vars.sh +. /lib/lsb/init-functions + +# Check if the ULIMIT is set in /etc/default/nginx +if [ -n "$ULIMIT" ]; then + # Set the ulimits + ulimit $ULIMIT +fi + +exec $DAEMON $DAEMON_OPTS -g "daemon off;" diff --git a/containers/docker/runit/nsd/run b/containers/docker/runit/nsd/run new file mode 100755 index 00000000..720a812e --- /dev/null +++ b/containers/docker/runit/nsd/run @@ -0,0 +1,30 @@ +#!/bin/bash +set -e + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +NAME=nsd # Introduce the short server's name here +DAEMON=/usr/sbin/$NAME # Introduce the server's location here +CONFFILE=/etc/nsd/nsd.conf +DAEMON_ARGS="-d -c $CONFFILE" + +NSDC=/usr/sbin/nsd-control + +# Exit if the package is not installed +[ -x $DAEMON ] || exit 0 + +PIDFILE=$(nsd-checkconf -o pidfile $CONFFILE) + +prepare_environment() { + mkdir -p "$(dirname "$(/usr/sbin/nsd-checkconf -o pidfile $CONFFILE)")" + chown "$(/usr/sbin/nsd-checkconf -o username $CONFFILE)" "$(dirname "$(/usr/sbin/nsd-checkconf -o pidfile $CONFFILE)")" + mkdir -p "$(dirname "$(/usr/sbin/nsd-checkconf -o database $CONFFILE)")" + chown "$(/usr/sbin/nsd-checkconf -o username $CONFFILE)" "$(dirname "$(/usr/sbin/nsd-checkconf -o database $CONFFILE)")" +} + +prepare_environment + +# Check if daemon is running +nc -z -w 4 localhost 10222 +/usr/local/mailinabox/tools/dns_update + +exec $DAEMON $DAEMON_ARGS diff --git a/containers/docker/runit/opendkim/run b/containers/docker/runit/opendkim/run new file mode 100755 index 00000000..9a43d941 --- /dev/null +++ b/containers/docker/runit/opendkim/run @@ -0,0 +1,71 @@ +#!/bin/bash + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/opendkim +NAME=opendkim +DESC="OpenDKIM" +RUNDIR=/var/run/$NAME +USER=opendkim +GROUP=opendkim +SOCKET=local:$RUNDIR/$NAME.sock +PIDFILE=$RUNDIR/$NAME.pid +CONFFILE=/etc/$NAME.conf + +test -x $DAEMON || exit 0 +test -f $CONFFILE || exit 0 + +# Check if mailinabox configuration files are there +test -f /etc/opendkim/SigningTable || exit 0 + +# Include LSB provided init functions +. /lib/lsb/init-functions + +# Include opendkim defaults if available +if [ -f /etc/default/opendkim ] ; then + . /etc/default/opendkim +fi + +if [ -f /etc/opendkim.conf ]; then + CONFIG_SOCKET=`awk '$1 == "Socket" { print $2 }' /etc/opendkim.conf` +fi + +# This can be set via Socket option in config file, so it's not required +if [ -n "$SOCKET" -a -z "$CONFIG_SOCKET" ]; then + DAEMON_OPTS="-p $SOCKET $DAEMON_OPTS" +fi + +DAEMON_OPTS="-f -x $CONFFILE -u $USER -P $PIDFILE $DAEMON_OPTS" + + +# Create the run directory if it doesn't exist +if [ ! -d "$RUNDIR" ]; then + install -o "$USER" -g "$GROUP" -m 755 -d "$RUNDIR" || return 2 + [ -x /sbin/restorecon ] && /sbin/restorecon "$RUNDIR" +fi + +# Clean up stale sockets +if [ -f "$PIDFILE" ]; then + pid=`cat $PIDFILE` + if ! ps -C "$DAEMON" -s "$pid" >/dev/null; then + rm "$PIDFILE" + TMPSOCKET="" + if [ -n "$SOCKET" ]; then + TMPSOCKET="$SOCKET" + elif [ -n "$CONFIG_SOCKET" ]; then + TMPSOCKET="$CONFIG_SOCKET" + fi + if [ -n "$TMPSOCKET" ]; then + # UNIX sockets may be specified with or without the + # local: prefix; handle both + t=`echo $SOCKET | cut -d: -f1` + s=`echo $SOCKET | cut -d: -f2` + if [ -e "$s" -a -S "$s" ]; then + if [ "$t" = "$s" -o "$t" = "local" ]; then + rm "$s" + fi + fi + fi + fi +fi + +exec $DAEMON $DAEMON_OPTS \ No newline at end of file diff --git a/containers/docker/runit/opendmarc/run b/containers/docker/runit/opendmarc/run new file mode 100755 index 00000000..03a2c09b --- /dev/null +++ b/containers/docker/runit/opendmarc/run @@ -0,0 +1,70 @@ +#!/bin/sh + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/opendmarc +NAME=opendmarc +DESC="OpenDMARC" +RUNDIR=/var/run/$NAME +USER=opendmarc +GROUP=opendmarc +SOCKET=local:$RUNDIR/$NAME.sock +PIDFILE=$RUNDIR/$NAME.pid +CONFFILE=/etc/$NAME.conf + +test -x $DAEMON || exit 0 +test -f $CONFFILE || exit 0 + +# Check if mailinabox configuration files are there +test -f /etc/opendkim/SigningTable || exit 0 + +# Include LSB provided init functions +. /lib/lsb/init-functions + +# Include opendkim defaults if available +if [ -f /etc/default/opendmarc ] ; then + . /etc/default/opendmarc +fi + +if [ -f /etc/opendmarc.conf ]; then + CONFIG_SOCKET=`awk '$1 == "Socket" { print $2 }' /etc/opendmarc.conf` +fi + +# This can be set via Socket option in config file, so it's not required +if [ -n "$SOCKET" -a -z "$CONFIG_SOCKET" ]; then + DAEMON_OPTS="-p $SOCKET $DAEMON_OPTS" +fi + +DAEMON_OPTS="-f -c $CONFFILE -u $USER -P $PIDFILE $DAEMON_OPTS" + + +# Create the run directory if it doesn't exist +if [ ! -d "$RUNDIR" ]; then + install -o "$USER" -g "$GROUP" -m 755 -d "$RUNDIR" || return 2 + [ -x /sbin/restorecon ] && /sbin/restorecon "$RUNDIR" +fi +# Clean up stale sockets +if [ -f "$PIDFILE" ]; then + pid=`cat $PIDFILE` + if ! ps -C "$DAEMON" -s "$pid" >/dev/null; then + rm "$PIDFILE" + TMPSOCKET="" + if [ -n "$SOCKET" ]; then + TMPSOCKET="$SOCKET" + elif [ -n "$CONFIG_SOCKET" ]; then + TMPSOCKET="$CONFIG_SOCKET" + fi + if [ -n "$TMPSOCKET" ]; then + # UNIX sockets may be specified with or without the + # local: prefix; handle both + t=`echo $SOCKET | cut -d: -f1` + s=`echo $SOCKET | cut -d: -f2` + if [ -e "$s" -a -S "$s" ]; then + if [ "$t" = "$s" -o "$t" = "local" ]; then + rm "$s" + fi + fi + fi + fi +fi + +exec $DAEMON $DAEMON_OPTS diff --git a/containers/docker/runit/php5-fpm/run b/containers/docker/runit/php5-fpm/run new file mode 100755 index 00000000..480740dd --- /dev/null +++ b/containers/docker/runit/php5-fpm/run @@ -0,0 +1,42 @@ +#!/bin/bash + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="PHP5 FastCGI Process Manager" +NAME=php5-fpm +DAEMON=/usr/sbin/$NAME +DAEMON_ARGS="-F --fpm-config /etc/php5/fpm/php-fpm.conf" +PIDFILE=/var/run/php5-fpm.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. +. /lib/lsb/init-functions + +# Don't run if we are running upstart +if init_is_upstart; then + exit 1 +fi + +# +# Function to check the correctness of the config file +# +do_check() +{ + /usr/lib/php5/php5-fpm-checkconf || return 1 + return 0 +} + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +do_check +exec $DAEMON $DAEMON_ARGS \ No newline at end of file diff --git a/containers/docker/runit/postfix/run b/containers/docker/runit/postfix/run new file mode 100755 index 00000000..c06d0f47 --- /dev/null +++ b/containers/docker/runit/postfix/run @@ -0,0 +1,144 @@ +#!/bin/bash + +exec 1>&2 + +PATH=/bin:/usr/bin:/sbin:/usr/sbin +DAEMON=/usr/sbin/postfix +NAME=Postfix +TZ= +unset TZ + +# Defaults - don't touch, edit /etc/default/postfix +SYNC_CHROOT="y" + +test -f /etc/default/postfix && . /etc/default/postfix + +test -x $DAEMON && test -f /etc/postfix/main.cf || exit 0 + +. /lib/lsb/init-functions + +configure_instance() { + POSTCONF="postconf" + + # if you set myorigin to 'ubuntu.com' or 'debian.org', it's wrong, and annoys the admins of + # those domains. See also sender_canonical_maps. + + MYORIGIN=$($POSTCONF -h myorigin | tr 'A-Z' 'a-z') + if [ "X${MYORIGIN#/}" != "X${MYORIGIN}" ]; then + MYORIGIN=$(tr 'A-Z' 'a-z' < $MYORIGIN) + fi + if [ "X$MYORIGIN" = Xubuntu.com ] || [ "X$MYORIGIN" = Xdebian.org ]; then + log_failure_msg "Invalid \$myorigin ($MYORIGIN), refusing to start" + log_end_msg 1 + exit 1 + fi + + config_dir=$($POSTCONF -h config_directory) + # see if anything is running chrooted. + NEED_CHROOT=$(awk '/^[0-9a-z]/ && ($5 ~ "[-yY]") { print "y"; exit}' ${config_dir}/master.cf) + + if [ -n "$NEED_CHROOT" ] && [ -n "$SYNC_CHROOT" ]; then + # Make sure that the chroot environment is set up correctly. + oldumask=$(umask) + umask 022 + queue_dir=$($POSTCONF -h queue_directory) + cd "$queue_dir" + + # copy the CA path if specified + ca_path=$($POSTCONF -h smtp_tls_CApath) + case "$ca_path" in + '') :;; # no ca_path + $queue_dir/*) :;; # skip stuff already in chroot, (and to make vim syntax happy: */) + *) + if test -d "$ca_path"; then + dest_dir="$queue_dir/${ca_path#/}" + # strip any/all trailing / + while [ "${dest_dir%/}" != "${dest_dir}" ]; do + dest_dir="${dest_dir%/}" + done + new=0 + if test -d "$dest_dir"; then + # write to a new directory ... + dest_dir="${dest_dir}.NEW" + new=1 + fi + mkdir --parent ${dest_dir} + # handle files in subdirectories + (cd "$ca_path" && find . -name '*.pem' -print0 | cpio -0pdL --quiet "$dest_dir") 2>/dev/null || + (log_failure_msg failure copying certificates; exit 1) + c_rehash "$dest_dir" >/dev/null 2>&1 + if [ "$new" = 1 ]; then + # and replace the old directory + rm -rf "${dest_dir%.NEW}" + mv "$dest_dir" "${dest_dir%.NEW}" + fi + fi + ;; + esac + + # if there is a CA file, copy it + ca_file=$($POSTCONF -h smtp_tls_CAfile) + case "$ca_file" in + $queue_dir/*) :;; # skip stuff already in chroot + '') # no ca_file + # or copy the bundle to preserve functionality + ca_bundle=/etc/ssl/certs/ca-certificates.crt + if [ -f $ca_bundle ]; then + mkdir --parent "$queue_dir/${ca_bundle%/*}" + cp -L "$ca_bundle" "$queue_dir/${ca_bundle%/*}" + fi + ;; + *) + if test -f "$ca_file"; then + dest_dir="$queue_dir/${ca_path#/}" + mkdir --parent "$dest_dir" + cp -L "$ca_file" "$dest_dir" + fi + ;; + esac + + # if we're using unix:passwd.byname, then we need to add etc/passwd. + local_maps=$($POSTCONF -h local_recipient_maps) + if [ "X$local_maps" != "X${local_maps#*unix:passwd.byname}" ]; then + if [ "X$local_maps" = "X${local_maps#*proxy:unix:passwd.byname}" ]; then + sed 's/^\([^:]*\):[^:]*/\1:x/' /etc/passwd > etc/passwd + chmod a+r etc/passwd + fi + fi + + FILES="etc/localtime etc/services etc/resolv.conf etc/hosts \ + etc/nsswitch.conf etc/nss_mdns.config" + for file in $FILES; do + [ -d ${file%/*} ] || mkdir -p ${file%/*} + if [ -f /${file} ]; then rm -f ${file} && cp /${file} ${file}; fi + if [ -f ${file} ]; then chmod a+rX ${file}; fi + done + # ldaps needs this. debian bug 572841 + (echo /dev/random; echo /dev/urandom) | cpio -pdL --quiet . 2>/dev/null || true + rm -f usr/lib/zoneinfo/localtime + mkdir -p usr/lib/zoneinfo + ln -sf /etc/localtime usr/lib/zoneinfo/localtime + + LIBLIST=$(for name in gcc_s nss resolv; do + for f in /lib/*/lib${name}*.so* /lib/lib${name}*.so*; do + if [ -f "$f" ]; then echo ${f#/}; fi; + done; + done) + + if [ -n "$LIBLIST" ]; then + for f in "$LIBLIST"; do + rm -f "$f" + done + tar cf - -C / $LIBLIST 2>/dev/null |tar xf - + fi + umask $oldumask + fi +} +configure_instance + +command_directory=`postconf -h command_directory` +daemon_directory=`$command_directory/postconf -h daemon_directory` +# make consistency check +$command_directory/postfix check +# run Postfix +exec $daemon_directory/master \ No newline at end of file diff --git a/containers/docker/runit/postgrey/run b/containers/docker/runit/postgrey/run new file mode 100755 index 00000000..b1d352dd --- /dev/null +++ b/containers/docker/runit/postgrey/run @@ -0,0 +1,29 @@ +#!/bin/bash + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/postgrey +NAME=postgrey +DESC="postfix greylisting daemon" + +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Gracefully exit if the package has been removed. +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +# Read config file if it is present. +if [ -r /etc/default/$NAME ] +then + . /etc/default/$NAME +fi + +POSTGREY_OPTS="--pidfile=$PIDFILE $POSTGREY_OPTS" +if [ -z "$POSTGREY_TEXT" ]; then + POSTGREY_TEXT_OPT="" +else + POSTGREY_TEXT_OPT="--greylist-text=$POSTGREY_TEXT" +fi + +exec $DAEMON $POSTGREY_OPTS "$POSTGREY_TEXT_OPT" \ No newline at end of file diff --git a/containers/docker/runit/rsyslogd.sh b/containers/docker/runit/rsyslogd.sh deleted file mode 100755 index 497d38ad..00000000 --- a/containers/docker/runit/rsyslogd.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -rsyslogd -n diff --git a/containers/docker/runit/spampd/run b/containers/docker/runit/spampd/run new file mode 100755 index 00000000..545eb2d8 --- /dev/null +++ b/containers/docker/runit/spampd/run @@ -0,0 +1,98 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin +DESC='spam checking proxy daemon' +NAME='spampd' +PROGRAM=/usr/sbin/spampd +#EXECUTABLE=`head -n 1 $PROGRAM | sed -e 's,^#![ ]*/,/,;s,[ ].*$,,'` +EXECUTABLE=/usr/bin/perl +PIDFILE=/var/run/spampd.pid + +if [ -f $PIDFILE ]; then + # If can't delete pidfile, this means process is running ... + rm $PIDFILE || exit 0 +fi + +. /lib/lsb/init-functions + +# set some important defaults (overridable via /etc/default/spampd) +USERID=spampd +GRPID=spampd + +if [ -f /etc/default/$NAME ]; then + . /etc/default/$NAME +fi + +istrue () { + ANS=$(echo $1 | tr A-Z a-z) + [ "$ANS" = 'yes' -o "$ANS" = 'true' -o "$ANS" = 'enable' -o "$ANS" = '1' ] +} + +# +# find out wether to start spampd or not +# +istrue ${STARTSPAMPD} && STARTSPAMPD='true' + +# +# Check wether the program is actually there +# +# return 5 as demanded by LSB 2.1 when program isn't installed. +[ -x $PROGRAM ] || exit 5 + +# +# Calculate final commandline +# +S_TAGALL='' +S_AWL='' +S_LOCALONLY='' + +istrue "$TAGALL" \ +&& S_TAGALL='--tagall' + +istrue "$AUTOWHITELIST" \ +&& S_AWL='--auto-whitelist' + +istrue "$LOCALONLY" \ +&& S_LOCALONLY='--L' + +istrue "$LOGINET" \ +&& LOGTARGET="inet" \ +|| LOGTARGET="unix" + +ARGS="${S_LOCALONLY} ${S_AWL} ${S_TAGALL} " + +[ -n "${LISTENPORT}" ] && ARGS="${ARGS} --port=${LISTENPORT}" + +[ -n "${LISTENHOST}" ] && ARGS="${ARGS} --host=${LISTENHOST}" + +[ -n "${DESTPORT}" ] && ARGS="${ARGS} --relayport=${DESTPORT}" + +[ -n "${DESTHOST}" ] && ARGS="${ARGS} --relayhost=${DESTHOST}" + +[ -n "${PIDFILE}" ] && ARGS="${ARGS} --pid=${PIDFILE}" + +[ -n "${CHILDREN}" ] && ARGS="${ARGS} --children=${CHILDREN}" + +[ -n "${USERID}" ] && ARGS="${ARGS} --user=${USERID}" + +[ -n "${GRPID}" ] && ARGS="${ARGS} --group=${GRPID}" + +[ -n "${LOGTARGET}" ] && ARGS="${ARGS} --logsock=${LOGTARGET}" + +[ -n "${ADDOPTS}" ] && ARGS="${ARGS} ${ADDOPTS}" + +# Don't daemonize +ARGS="${ARGS} --nodetach" + +if ! istrue "${STARTSPAMPD}"; then + log_warning_msg "Starting $DESC: $NAME (disabled in /etc/default/$NAME)." + # LSB 2.1: 6 mean unconfigured. This seems appropriate here. + exit 6 +fi +log_daemon_msg "Starting $DESC" "$NAME" +# if spampd is not installed, return 5 as demanded by LSB 2.1 +if [ ! -x $EXECUTABLE ]; then + log_error_msg "failed! - executable not found" + exit 5 +fi +# start daemon +exec $PROGRAM $ARGS \ No newline at end of file diff --git a/containers/docker/tools/disable_services.sh b/containers/docker/tools/disable_services.sh new file mode 100755 index 00000000..757302d8 --- /dev/null +++ b/containers/docker/tools/disable_services.sh @@ -0,0 +1,14 @@ +SERVICES=/etc/service/* + +for f in $SERVICES +do + service=$(basename "$f") + if [ "$service" = "syslog-ng" ]; then continue; fi; + if [ "$service" = "syslog-forwarder" ]; then continue; fi; + if [ "$service" = "ssh" ]; then continue; fi; + if [ "$service" = "cron" ]; then continue; fi; + if ([ -d /etc/service/$service ] && [ ! -f /etc/service/$service/down ]); then + echo "Creating down file for '$service'" + touch /etc/service/$service/down + fi +done \ No newline at end of file diff --git a/containers/docker/tools/lsb_compat.sh b/containers/docker/tools/lsb_compat.sh new file mode 100755 index 00000000..92042c17 --- /dev/null +++ b/containers/docker/tools/lsb_compat.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# This removes /etc/init.d service if service exists in runit. +# It also creates a symlink from /usr/bin/sv to /etc/init.d/$service +# to support SysV syntax: service $service or /etc/init.d/$service + +SERVICES=/etc/service/* + +for f in $SERVICES +do + service=$(basename "$f") + if [ -d /etc/service/$service ]; then + echo "LSB Compatibility for '$service'" + if [ -f /etc/init.d/$service ]; then + mv /etc/init.d/$service /etc/init.d/$service.lsb + chmod -x /etc/init.d/$service.lsb + fi + ln -s /usr/bin/sv /etc/init.d/$service + fi +done \ No newline at end of file diff --git a/containers/docker/tools/runit_logs.sh b/containers/docker/tools/runit_logs.sh new file mode 100755 index 00000000..3981c94d --- /dev/null +++ b/containers/docker/tools/runit_logs.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# This adds a log/run file on each runit service directory. +# This file make services stdout/stderr output to svlogd log +# directory located in /var/log/runit/$service. + +SERVICES=/etc/service/* + +for f in $SERVICES +do + service=$(basename "$f") + if [ -d /etc/service/$service ]; then + echo "Creating log/run for '$service'" + mkdir -p /etc/service/$service/log + cat > /etc/service/$service/log/run < /etc/cron.daily/mailinabox-backup << EOF; diff --git a/setup/owncloud.sh b/setup/owncloud.sh index 57f66767..00923b0c 100755 --- a/setup/owncloud.sh +++ b/setup/owncloud.sh @@ -75,7 +75,7 @@ if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then 'instanceid' => '$instanceid', - 'trusted_domains' => + 'trusted_domains' => array ( 0 => '$PRIMARY_HOSTNAME', ), @@ -172,4 +172,5 @@ chmod +x /etc/cron.hourly/mailinabox-owncloud # Enable PHP modules and restart PHP. php5enmod imap +restart_service memcached restart_service php5-fpm diff --git a/setup/questions.sh b/setup/questions.sh index 08531fe8..0b10a895 100644 --- a/setup/questions.sh +++ b/setup/questions.sh @@ -4,6 +4,7 @@ if [ -z "$NONINTERACTIVE" ]; then # e.g. if we piped a bootstrapping install script to bash to get started. In that # case, the nifty '[ -t 0 ]' test won't work. But with Vagrant we must suppress so we # use a shell flag instead. Really supress any output from installing dialog. + apt_get_quiet update apt_get_quiet install dialog message_box "Mail-in-a-Box Installation" \ "Hello and thanks for deploying a Mail-in-a-Box! diff --git a/setup/start.sh b/setup/start.sh index 10c9afa2..e1b0d613 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -63,14 +63,6 @@ source setup/owncloud.sh source setup/zpush.sh source setup/management.sh -# In Docker, sysvinit services are started automatically. Runit services -# aren't started until after this setup script finishes. But we need -# Dovecot (which is Upstart-only) running in order to create the first -# mail user. So start dovecot now. -if [ ! -z "$IS_DOCKER" ]; then - /usr/sbin/dovecot -c /etc/dovecot/dovecot.conf -fi - # Ping the management daemon to write the DNS and nginx configuration files. until nc -z -w 4 localhost 10222 do