##### ##### This file is part of Mail-in-a-Box-LDAP which is released under the ##### terms of the GNU Affero General Public License as published by the ##### Free Software Foundation, either version 3 of the License, or (at ##### your option) any later version. See file LICENSE or go to ##### https://github.com/downtownallday/mailinabox-ldap for full license ##### details. ##### # # requires: # # test scripts: [ lib/misc.sh, lib/system.sh, lib/color-output.sh, lib/installed-state.sh ] # die() { local msg="$1" echo "$msg" 1>&2 exit 1 } wait_for_docker_nextcloud() { local container="$1" local config_key="$2" echo -n "Waiting ..." local count=0 while true; do if [ $count -ge 10 ]; then echo "FAILED" return 1 fi sleep 6 let count+=1 if [ $(docker exec "$container" php -n -r "include 'config/config.php'; print \$CONFIG['$config_key']?'true':'false';") == "true" ]; then echo "ok" break fi echo -n "${count}..." done return 0 } dump_conf_files() { local skip if [ $# -eq 0 ]; then skip="false" else skip="true" for item; do if is_true "$item"; then skip="false" break fi done fi if [ "$skip" == "false" ]; then dump_file "/etc/mailinabox.conf" dump_file_if_exists "/etc/mailinabox_mods.conf" dump_file "/etc/hosts" dump_file "/etc/nsswitch.conf" dump_file "/etc/resolv.conf" dump_file "/etc/nsd/nsd.conf" #dump_file "/etc/postfix/main.cf" fi } # # Initialize the test system # hostname, time, apt update/upgrade, etc # # Errors are fatal # init_test_system() { H2 "Update /etc/hosts" if ! set_system_hostname; then dump_file "/etc/hosts" die "Could not set hostname" fi # update system time H2 "Set system time" update_system_time || echo "Ignoring error..." # update package lists before installing anything H2 "apt-get update" wait_for_apt exec_no_output apt-get update -qq || die "apt-get update failed!" H2 "snap refresh" exec_no_output snap refresh || echo "snap refresh failed! ignoring..." # install .emacs file, if available if [ -e tests/assets/.emacs -a -d /root ]; then cp tests/assets/.emacs /root 1>/dev/null 2>&1 fi # upgrade packages - if we don't do this and something like bind # is upgraded through automatic upgrades (because maybe MiaB was # previously installed), it may cause problems with the rest of # the setup, such as with name resolution failures if is_false "$TRAVIS" && [ "$SKIP_SYSTEM_UPDATE" != "1" ]; then H2 "apt-get upgrade" wait_for_apt cp /var/log/apt/history.log /tmp/history.log \ || die "Unable to copy /var/log/apt/history.log to /tmp" exec_no_output apt-get upgrade -y --with-new-pkgs \ || die "apt-get upgrade failed!" diff /tmp/history.log /var/log/apt/history.log \ | sed 's/^> //' \ | awk '/^(Upgrade|Install): / { print $0 }' rm -f /tmp/history.log fi # install avahi if the system dns domain is .local - note that # /bin/dnsdomainname returns empty string at this point case "$PRIMARY_HOSTNAME" in *.local ) H2 "Install avahi" wait_for_apt exec_no_output apt-get install -y avahi-daemon \ || die "could not install avahi" ;; esac } # # Initialize the test system with QA prerequisites # Anything needed to use the test runner, speed up the installation, # etc # init_miab_testing() { [ -z "$STORAGE_ROOT" ] \ && echo "Error: STORAGE_ROOT not set" 1>&2 \ && return 1 # If EHDD_KEYFILE is set, use encryption-at-rest support. The # drive must be created and mounted so that our QA files can be # copied there. H2 "Encryption-at-rest" if [ ! -z "$EHDD_KEYFILE" ]; then ehdd/create_hdd.sh ${EHDD_GB} || die "create luks drive failed" ehdd/mount.sh || die "unable to mount luks drive" else echo "Not configured for encryption-at-rest" fi H2 "QA prerequisites" local rc=0 # python3-pip: installed by setup, but we need it now # python3-dnspython: is used by the python scripts in 'tests' and is # not installed by setup # also install 'jq' for json processing echo "Install python3-pip, python3-dnspython, jq, git" wait_for_apt exec_no_output apt-get install -y python3-pip python3-dnspython jq git \ || die "Unable to install setup prerequisites !!" # browser-based tests echo "Install chromium, selenium" exec_no_output snap install chromium \ || die "Unable to install chromium!" # TODO: selenium 4 (has breaking changes). See: https://www.selenium.dev/documentation/webdriver/getting_started/upgrade_to_selenium_4/ exec_no_output python3 -m pip install "selenium>=3,<4" --quiet \ || die "Selenium install failed!" # tell git our directory is safe (new requirement for git 2.35.2) if [ -d .git ]; then git config --global --add safe.directory "$(pwd)" fi # copy in pre-built MiaB-LDAP ssl files # 1. avoid the lengthy generation of DH params if ! mkdir -p $STORAGE_ROOT/ssl; then echo "Unable to create $STORAGE_ROOT/ssl ($?)" rc=1 fi echo "Copy dhparams" if ! cp tests/assets/ssl/dh2048.pem $STORAGE_ROOT/ssl; then echo "Copy failed ($?)" rc=1 fi # create miab_ldap.conf to specify what the Nextcloud LDAP service # account password will be to avoid a random one created by start.sh if [ ! -z "$LDAP_NEXTCLOUD_PASSWORD" ]; then if ! mkdir -p $STORAGE_ROOT/ldap; then echo "Could not create $STORAGE_ROOT/ldap" rc=1 fi [ -e $STORAGE_ROOT/ldap/miab_ldap.conf ] && \ echo "Warning: exists: $STORAGE_ROOT/ldap/miab_ldap.conf" 1>&2 touch $STORAGE_ROOT/ldap/miab_ldap.conf || rc=1 if ! grep "^LDAP_NEXTCLOUD_PASSWORD=" $STORAGE_ROOT/ldap/miab_ldap.conf >/dev/null; then echo "LDAP_NEXTCLOUD_PASSWORD=\"$LDAP_NEXTCLOUD_PASSWORD\"" >> $STORAGE_ROOT/ldap/miab_ldap.conf fi fi # process command line args while [ $# -gt 0 ]; do case "$1" in --qa-ca ) echo "Copy certificate authority" shift if ! cp tests/assets/ssl/ca_*.pem $STORAGE_ROOT/ssl; then echo "Copy failed ($?)" rc=1 fi ;; --enable-mod=* ) local mod="$(awk -F= '{print $2}' <<<"$1")" shift echo "Enabling local mod '$mod'" if ! enable_miab_mod "$mod"; then echo "Enabling mod '$mod' failed" rc=1 fi ;; * ) # ignore unknown option - may be interpreted elsewhere shift ;; esac done # now that we've copied our files, unmount STORAGE_ROOT if # encryption-at-rest was enabled ehdd/umount.sh return $rc } enable_miab_mod() { local name="${1}.sh" if [ ! -e "$LOCAL_MODS_DIR/$name" ]; then mkdir -p "$LOCAL_MODS_DIR" if ! ln -s "$(pwd)/setup/mods.available/$name" "$LOCAL_MODS_DIR/$name" then echo "Warning: copying instead of symlinking $LOCAL_MODS_DIR/$name" cp "setup/mods.available/$name" "$LOCAL_MODS_DIR/$name" fi fi } disable_miab_mod() { local name="${1}.sh" rm -f "$LOCAL_MODS_DIR/$name" } tag_from_readme() { # extract the recommended TAG from README.md # sets a global "TAG" local readme="${1:-README.md}" TAG="$(grep -F 'git checkout' "$readme" | sed 's/.*\(v[0123456789]*\.[0123456789]*\).*/\1/')" [ $? -ne 0 -o -z "$TAG" ] && return 1 return 0 } workaround_dovecot_sieve_bug() { # Workaround a bug in dovecot/sieve that causes attempted sieve # compilation when a compiled sieve has the same date as the # source file. The fialure occurs with miab-installed "spam" # sieve, which can't be recompiled due to the read-only /etc # filesystem restriction in systemd (ProtectSystem=efull is set, # see `systemctl cat dovecot.service`). sleep 1 touch /etc/dovecot/sieve-spam.svbin } say_release_info() { H2 "Release info" echo "Code version: $(git describe)" echo "Migration version (miab): $(cat "$STORAGE_ROOT/mailinabox.version")" echo "Migration version (miabldap): $(cat "$STORAGE_ROOT/mailinabox-ldap.version")" } clone_repo_and_pushd() { local repo="" local treeish="" local targetdir="" for arg; do case "$arg" in --checkout-repo=* ) repo=$(awk -F= '{print $2}' <<<"$arg") ;; --checkout-treeish=* | --checkout-tag=* ) treeish=$(awk -F= '{print $2}' <<<"$arg") ;; --checkout-targetdir=* ) targetdir=$(awk -F= '{print $2}' <<<"$arg") ;; esac done if [ -z "$repo" -o -z "$treeish" -o -z "$targetdir" ]; then return 1 fi H1 "Clone release $treeish from $repo" git_clone \ "$repo" \ "$treeish" \ "$targetdir" \ "keep-existing" \ || die "could not clone $repo ($treeish) to $targetdir" pushd "$targetdir" >/dev/null return 0 } # # install mail-in-a-box (upstream) # upstream_install() { local need_pop="no" if clone_repo_and_pushd "$@"; then need_pop="yes" fi H1 "MIAB UPSTEAM INSTALL [$(git describe 2>/dev/null)]" # ensure we're in a MiaB working directory if [ -e setup/ldap.sh ]; then die "Cannot install: the working directory is MiaB-LDAP!" fi if [ ! -e setup/start.sh ]; then die "Cannot install: the working directory must contain the source" fi # Upstream expects a virgin system and we may have preinstalled # nsd that was unable to start and is in the failed state # (eg. could not bind to ::53 because named is also # installed). Using `systemctl reset-failed` won't work becuase # upstream's setup/dns.sh script doesn't start nsd - it only # installs nsd using apt after creating nsd's configuration. if systemctl is-failed --quiet nsd; then echo "notice: removing nsd because systemd has it in the failed state" exec_no_output apt-get remove -y nsd #systemctl reset-failed nsd fi if ! setup/start.sh; then echo "$F_WARN" dump_file /var/log/syslog 100 echo "$F_RESET" die "Upstream setup failed!" fi H2 "Post-setup actions" workaround_dovecot_sieve_bug # set actual STORAGE_ROOT, STORAGE_USER, PRIVATE_IP, etc . /etc/mailinabox.conf || die "Could not source /etc/mailinabox.conf" H2 "miab install success" if [ "$need_pop" = "yes" ]; then if [ ! -e tests/vagrant ]; then # if this is an upstream install, then populate using # miabldap's populate scripts (upstream doesn't have any) local d d=$(pwd) popd >/dev/null populate_by_cli_argument "$@" pushd "$d" >/dev/null else # otherwise populate using branch-supplied populate scripts populate_by_cli_argument "$@" fi # state capture must be run from the source tree corresponding to the # the installed state capture_state_by_cli_argument "$@" popd >/dev/null else # populate if specified on command line populate_by_cli_argument "$@" capture_state_by_cli_argument "$@" fi } miab_ldap_install() { local need_pop="no" if clone_repo_and_pushd "$@"; then need_pop="yes" fi H1 "MIAB-LDAP INSTALL [$(pwd)] [$(git describe 2>/dev/null)]" # ensure we're in a MiaB-LDAP working directory if [ ! -e setup/ldap.sh ]; then die "Cannot install: the working directory is not MiaB-LDAP!" fi # setup/questions.sh installs the email_validator python3 module # but only when in interactive mode. make sure it's also installed # in non-interactive mode if [ ! -z "${NONINTERACTIVE:-}" ]; then echo "Install email_validator python3 module" wait_for_apt exec_no_output apt-get install -y -qq python3-pip \ || die "Unable to install pip !" exec_no_output pip3 install -q "email_validator>=1.0.0" \ || die "Unable to install email_validator !" fi H2 "Run mailinabox-ldap setup" # if EHDD_KEYFILE is set, use encryption-at-rest support if [ ! -z "$EHDD_KEYFILE" ]; then ehdd/start-encrypted.sh else setup/start.sh fi if [ $? -ne 0 ]; then H1 "OUTPUT OF SELECT FILES" dump_file "/var/log/syslog" 100 dump_conf_files "$TRAVIS" H2; H2 "End"; H2 die "MiaB-LDAP setup failed!" fi H2 "Post-setup actions" workaround_dovecot_sieve_bug # set actual STORAGE_ROOT, STORAGE_USER, PRIVATE_IP, etc . /etc/mailinabox.conf || die "Could not source /etc/mailinabox.conf" # setup changes the hostname so avahi must be restarted if systemctl is-active --quiet avahi-daemon; then systemctl restart avahi-daemon fi H2 "miab-ldap install success" # populate if specified on command line populate_by_cli_argument "$@" capture_state_by_cli_argument "$@" if [ "$need_pop" = "yes" ]; then popd >/dev/null fi } capture_state_by_cli_argument() { # this must be run with the working directory set to the source # tree corresponding to the the installed state # ...ignore unknown options they may be interpreted elsewhere local state_dir="" for arg; do case "$arg" in --capture-state=* ) state_dir=$(awk -F= '{print $2}' <<<"$arg") ;; esac done if [ ! -z "$state_dir" ]; then installed_state_capture "$state_dir" fi } populate_by_cli_argument() { # ...ignore unknown options they may be interpreted elsewhere local populate_names=() for arg; do case "$arg" in --populate=* ) populate_names+=( $(awk -F= '{print $2}' <<<"$arg") ) ;; esac done if [ ${#populate_names} -gt 0 ]; then populate_by_name "${populate_names[@]}" fi } populate_by_name() { local populate_name for populate_name; do H1 "Populate Mail-in-a-Box ($populate_name)" local populate_script="tests/system-setup/populate/${populate_name}-populate.sh" if [ ! -e "$populate_script" ]; then die "Does not exist: $populate_script" fi "$populate_script" || die "Failed: $populate_script" done }