#!/bin/bash # Collecting authentification data InstallDependencies() { packagesNeeded='curl jq expect pwgen' if [ -x "$(command -v apk)" ]; then sudo apk add --no-cache $packagesNeeded # Alpine Linux elif [ -x "$(command -v apt-get)" ]; then sudo apt-get install $packagesNeeded # Debian/Ubuntu Linux elif [ -x "$(command -v dnf)" ]; then sudo dnf install $packagesNeeded # Fedora Linux elif [ -x "$(command -v rpm-ostree)" ]; then sudo rpm-ostree install $packagesNeeded # Fedora Linux Silverblue elif [ -x "$(command -v zypper)" ]; then sudo zypper install $packagesNeeded # openSUSE Linux elif [ -x "$(command -v pacman)" ]; then sudo pacman -S $packagesNeeded # Arch/Manjaro Linux elif [ -x "$(command -v emerge)" ]; then sudo emerge --ask $packagesNeeded # Gentoo Linux elif [ -x "$(command -v nix-env)" ]; then nix-env -iA $packagesNeeded # NixOS else echo "FAILED TO INSTALL PACKAGE: Package manager not found. You must manually install: $packagesNeeded">&2; fi wget https://selfprivacy.org/configuration.nix wget https://selfprivacy.org/mailserver.nix wget https://selfprivacy.org/goss.nix wget https://selfprivacy.org/restic.nix wget https://selfprivacy.org/restic.yaml wget https://selfprivacy.org/s3cli } CollectData() { read -p "Please, paste your Hetzner API token here: " HETZNER_TOKEN read -p "Please, paste your CloudFlare Token: " CLOUDFLARE_TOKEN read -p "Please, paste your AWS Secret Access Key: " AWS_TOKEN read -p "Please, paste your AWS Access Key ID: " AWS_TOKEN_ID read -p "Please, define your domain there: " DOMAIN read -p "Please, define your mail username: " USERNAME read -p "Please, define your password: " PASSWORD && PASSWORD=$( mkpasswd -m sha-512 "$PASSWORD" ) } # Generate SSH key GenerateSSHKey() { echo "Generating SSH keys..." mkdir ~/.nix-ms ssh-keygen -f ~/.nix-ms/id_rsa > /dev/null export sshKey=$( cat ~/.nix-ms/id_rsa.pub ) } # Add SSH key to Hetzner AddSSHKey() { echo "Adding SSH keys to Hetzner..." curl -s \ -X POST \ -H "Authorization: Bearer $HETZNER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"nixosms","public_key":"'"${sshKey}"'","labels":{}}' \ 'https://api.hetzner.cloud/v1/ssh_keys' > /dev/null } # Create NixOS config file MakeConfig() { # Mailserver sed -i '15s/.*/ fqdn = "'$DOMAIN'";/' mailserver.nix sed -i '16s/.*/ domains = [ "'"$DOMAIN"'" ];/' mailserver.nix sed -i '21s/.*/\t"'$USERNAME'@'$DOMAIN'" = {/' mailserver.nix sed -i '22s/.*/\t hashedPassword = "'"$PASSWORD"'";/' mailserver.nix sed -i '31s/.*/\t\t"'"$DOMAIN"'"/' mailserver.nix sed -i '41s/.*/\t "admin@'"$DOMAIN"'" = "'"$USERNAME"'@'"$DOMAIN"'";/' mailserver.nix sed -i '63s/.*/ email = "'"$USERNAME"'@'"$DOMAIN"'";/' mailserver.nix # System Configuration sed -i "15s,.*,\t\"${sshKey}\"," configuration.nix sed -i "16s,.*,\t restic -r s3:s3.amazonaws.com/${AWS_BUCKET_NAME} backup /var/vmail /var/vmail ," restic.nix } MakeServer() { echo "Creating Server..." curl -s \ -X POST \ -H "Authorization: Bearer $HETZNER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"nixos-mailserver","server_type":"cx11","start_after_create":true,"image":"ubuntu-20.04","ssh_keys":["nixosms"],"volumes":[],"networks":[],"user_data":"#cloud-config\nruncmd:\n- curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-20.03 bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":false}' \ 'https://api.hetzner.cloud/v1/servers' > /dev/null } CreateS3BucketRaw() { local date="$(date -u '+%a, %e %b %Y %H:%M:%S +0000')" local string_to_sign local MD5="" printf -v string_to_sign "%s\n%s\n\n%s\n%s" "PUT" "$MD5" "$date" "$path" signature=$(echo -n "$string_to_sign" | openssl sha1 -binary -hmac "${AWS_SECRET_ACCESS_KEY}" | openssl base64) curl -s \ -X PUT \ -H "Host: $AWS_TOKEN_ID.s3-control.us-east-2.amazonaws.com" \ -H "Content-Length: 0" \ -H "Date: $(date +'%a, %d %b %Y %H:%M:%S %Z')" \ -H "Authorization: AWS $AWS_TOKEN_ID:$signature" } CreateS3Bucket() { mkdir ~/.aws touch ~/.aws/credentials echo "[default]" >> ~/.aws/credentials echo "aws_access_key_id=$AWS_TOKEN_ID" >> ~/.aws/credentials echo "aws_secret_access_key=$AWS_TOKEN" >> ~/.aws/credentials if [[ -z "$(./s3cli -e http://s3.us-east-2.amazonaws.com --ak "$AWS_TOKEN_ID" --sk "$AWS_TOKEN" --region us-east-2 bucket ls | grep backup)" ]]; then read "AWS S3 bucket found in your account. Do you want to restore backup from there? (y/n) " RESTORE_MAILBACKUP else export AWS_BUCKET_NAME=$(pwgen -1 --no-capitalize 6)-backup ./s3cli -e http://s3.us-east-2.amazonaws.com --ak "$AWS_TOKEN_ID" --sk "$AWS_TOKEN" --region us-east-2 bucket create $AWS_BUCKET_NAME fi } # Get machine IP GetMachineIP() { curl -s \ -H "Authorization: Bearer $HETZNER_TOKEN" \ 'https://api.hetzner.cloud/v1/servers' > .machine.json export machineip=$( for i in {0..24}; do jq 'if .servers['$i'].name == "nixos-mailserver" then .servers['$i'].public_net.ipv4.ip else null end' .machine.json; done | grep -v null | sed 's/"//' | sed 's/"//' ) } # Copy and apply mailserver config ApplyConfig() { ssh -i ~/.nix-ms/id_rsa "root@$machineip" echo "Authentificated" scp -i ~/.nix-ms/id_rsa mailserver.nix "root@$machineip:/root" scp -i ~/.nix-ms/id_rsa configuration.nix "root@$machineip:/root" scp -i ~/.nix-ms/id_rsa goss.nix "root@$machineip:/root" scp -i ~/.nix-ms/id_rsa goss.yaml "root@$machineip:/root" ssh -i ~/.nix-ms/id_rsa "root@$machineip" cp /root/mailserver.nix /etc/nixos/mailserver.nix ssh -i ~/.nix-ms/id_rsa "root@$machineip" cp /root/configuration.nix /etc/nixos/configuration.nix ssh -i ~/.nix-ms/id_rsa "root@$machineip" cp /root/goss.nix /etc/nixos/goss.nix sleep 3 ssh -i ~/.nix-ms/id_rsa "root@$machineip" nixos-rebuild switch ssh -i ~/.nix-ms/id_rsa "root@$machineip" export AWS_ACCESS_KEY_ID=$AWS_TOKEN_ID ssh -i ~/.nix-ms/id_rsa "root@$machineip" export AWS_SECRET_ACCESS_KEY=$AWS_TOKEN } RestoreBackup() { ssh -i ~/.nix-ms/id_rsa "root@$machineip" restic -r s3:s3.amazonaws/$AWS_BUCKET_NAME restore latest --target /var/vmail /var/dkim } # Get DKIM keys from remote machine GetDKIM() { scp -i ~/.nix-ms/id_rsa "root@$machineip:/var/dkim/$DOMAIN.selector.txt" . sed -i '1d' $DOMAIN.selector.txt sed -i 's/ ) ; ----- DKIM key selector for '$DOMAIN'//g' $DOMAIN.selector.txt export dkim=$( cat $DOMAIN.selector.txt ) } ClearTempFiles() { rm .machine.json rm .cloudflare.json rm $DOMAIN.selector.txt rm -rf ~/.nix-ms/ rm ~/.ssh/known_hosts rm .healthz.json rm .hetzner_machines.json } # Cloudflare configuration # Get zone ID GetZoneID() { curl -s -X GET "https://api.cloudflare.com/client/v4/zones" \ -H "Authorization: Bearer $CLOUDFLARE_TOKEN" \ -H "Content-Type: application/json" > .cloudflare.json export zoneid=$( for i in {0..24}; do jq 'if .result['$i'].name == "'$DOMAIN'" then .result['$i'].id else null end' .cloudflare.json; done | grep -v null | sed -e 's/^"//' -e 's/"$//' ) } # Create records CreateARecord() { curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_TOKEN" \ -H "Content-Type: application/json" \ --data '{"type":"A","name":"'$DOMAIN'","content":"'$machineip'","ttl":3600,"priority":10,"proxied":false}' > /dev/null } CreateMXRecord() { curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_TOKEN" \ -H "Content-Type: application/json" \ --data '{"type":"MX","name":"@","content":"'$DOMAIN'","ttl":3600,"priority":10,"proxied":false}' > /dev/null } CreateDMARCRecord() { curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_TOKEN" \ -H "Content-Type: application/json" \ --data '{"type":"TXT","name":"_dmarc","content":"v=DMARC1; p=none","ttl":18000,"priority":10,"proxied":false}' > /dev/null } CreateSPFRecord() { curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_TOKEN" \ -H "Content-Type: application/json" \ --data '{"type":"TXT","name":"'$DOMAIN'","content":"v=spf1 a mx ip4:'$machineip' -all","ttl":18000,"priority":10,"proxied":false}' > /dev/null } CreateDKIMRecord() { export dkim=$( echo $dkim | sed -e 's/^"//' -e 's/"$//' ) curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records" -H "Authorization: Bearer $CLOUDFLARE_TOKEN" -H "Content-Type: application/json" --data '{"type":"TXT","name":"selector._domainkey","content":"v=DKIM1; '$dkim'","ttl":18000,"priority":10,"proxied":false}' > /dev/null } PostInstallation() { ssh -i ~/.nix-ms/id_rsa "root@$machineip" restic -r s3:s3.amazonaws.com/$AWS_BUCKET_NAME init ssh -i ~/.nix-ms/id_rsa "root@$machineip" restic -r s3:s3.amazonaws.com/$AWS_BUCKET_NAME forget --prune --keep-hourly 2 --keep-daily 7 --keep-weekly 4 ssh -i ~/.nix-ms/id_rsa "root@$machineip" cp /root/result/bin/goss /root/ ssh -i ~/.nix-ms/id_rsa "root@$machineip" /root/goss serve --format json & } PerformTests() { curl $machineip:8080/healthz > .healthz.json for i in {0..24}; do jq 'if .results['$i'].err != null then "FAIL" else "OK" end' .healthz.json; done } RunPreFlightChecks() { curl -H "Authorization: Bearer $HETZNER_TOKEN" 'https://api.hetzner.cloud/v1/servers' > .hetzner_test.json jq 'if .error != null then "Preflight checks failed" else "Success" end' .hetzner_test.json | if [ "Preflight checks failed" ] then echo "Hetzner Authorization failed" exit -1 fi } if test -z "$HETZNER_TOKEN" || test -z "$CLOUDFLARE_TOKEN" || test -z "$PASSWORD" then CollectData if [ ${#HETZNER_TOKEN} != 64 ] then echo "Hetzner Token is incorrect. Please double check your input" exit -1 elif [ ${#CLOUDFLARE_TOKEN} != 40 ] then echo "Cloudflare Token is incorrect. Please double check your input" exit -1 elif [[ ${DOMAIN} != *.* ]] then echo "Got unexpected domain. Possibly wrong input" exit -1 fi fi #RunPreFlightChecks InstallDependencies GenerateSSHKey printf "Importing SSH key into your Hetzner account..." AddSSHKey printf "done\n" printf "Generating config file..." MakeConfig printf "done\n" printf "Waiting for the server to create...\n" MakeServer sleep 30 printf "Waiting for nixos-infect to replace system files(this may take some time)...\n" sleep 240 CreateS3Bucket GetMachineIP ApplyConfig if [$RESTORE_MAILBACKUP == "y"]; then RestoreBackup fi GetDKIM echo "Beginning CloudFlare configuration" GetZoneID printf "Creating records..." CreateARecord CreateMXRecord CreateDMARCRecord CreateSPFRecord CreateDKIMRecord printf "done\n" PostInstallation #PerformTests #while ! ping -c1 192.168.0.107 &>/dev/null # do echo "Ping Fail - `date`" #done #echo "Host Found - `date`" printf "Clearing temporary files..." ClearTempFiles printf "done\n"