#!/bin/bash # Collecting authentification data InstallDependencies() { packagesNeeded='curl jq pwgen nano dnsutils' 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 curl jq whois pwgen # 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 sudo nix-env -iA $packagesNeeded # NixOS elif [ -x "$(command -v pkg)" ]; then sudo pkg install $packagesNeeded # FreeBSD 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/goss.yaml wget https://selfprivacy.org/restic.nix wget https://selfprivacy.org/mkpasswd wget https://selfprivacy.org/s3cli chmod +x s3cli chmod +x mkpasswd export PASSWORD=$( ./mkpasswd -m sha-512 "$PASSWORD" ) } 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: " USER read -p "Please, define your password: " 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() { 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 '17s/.*/ fqdn = "'$DOMAIN'";/' mailserver.nix sed -i '18s/.*/ domains = [ "'"$DOMAIN"'" ];/' mailserver.nix sed -i '23s/.*/\t"'$USER'@'$DOMAIN'" = {/' mailserver.nix sed -i "24s,.*,\t\ hashedPassword = \"${PASSWORD}\";," mailserver.nix sed -i '33s/.*/\t\t"'"$DOMAIN"'"/' mailserver.nix sed -i '50s/.*/\t "admin@'"$DOMAIN"'" = "'"$USER"'@'"$DOMAIN"'";/' mailserver.nix sed -i '72s/.*/ email = "'"$USER"'@'"$DOMAIN"'";/' mailserver.nix # System Configuration sed -i "16s,.*,\t\"${sshKey}\"," configuration.nix # Restic sed -i '14s/.*/\t\tEnvironment = [ "AWS_ACCESS_KEY_ID='$AWS_TOKEN_ID'" "AWS_SECRET_ACCESS_KEY='$AWS_TOKEN'" ];/' restic.nix sed -i "17s,.*,\t restic -r s3:s3.amazonaws.com/${AWS_BUCKET_NAME} backup /var/vmail /var/vmail ," restic.nix nano mailserver.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() { if test -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 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 else read -p "Restore from backup? y/n " RESTORE_MAILBACKUP 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 restic.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 ssh -i ~/.nix-ms/id_rsa "root@$machineip" cp /root/restic.nix /etc/nixos/restic.nix sleep 3 ssh -i ~/.nix-ms/id_rsa "root@$machineip" nixos-rebuild switch } RestoreBackup() { export AWS_BUCKET_NAME=$(./s3cli -e http://s3.us-east-2.amazonaws.com --ak "$AWS_TOKEN_ID" --sk "$AWS_TOKEN" --region us-east-2 bucket ls | grep backup) 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 ~/.ssh/known_hosts rm configuration.nix rm goss.nix rm goss.yaml rm mailserver.nix rm restic.nix rm s3cli rm .dns_records.json rm .records rm mkpasswd exit 0 } # 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/"$//' ) } #Purge DNS records PurgeDNSRecords() { curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records" \ -H "Authorization: Bearer $CLOUDFLARE_TOKEN" \ -H "Content-Type: application/json" > .dns_records.json for i in {0..24} do jq '.result['$i'].id' .dns_records.json done | grep -v null | sed 's/"//g' > .records export recordIDs=() while IFS= read -r line do recordIDs+=("$line") done < .records for recordid in "${recordIDs[@]}" do curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records/$recordid" \ -H "Authorization: Bearer $CLOUDFLARE_TOKEN" \ -H "Content-Type: application/json" > /dev/null done } WaitDNSPropagation() { printf "Waiting for DNS Record to propagate..." while [[ $( dig $DOMAIN a +short ) != $machineip ]] do sleep 1 done printf "done" } # 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 } 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 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 280 CreateS3Bucket GetMachineIP read -p "ALL YOUR EXISTING DNS RECORDS ON CLOUDFLARE WILL BE REMOVED!!! ARE YOU SURE(y/N) " CREATEDNS if [ $CREATEDNS != "y" || $CREATEDNS == "Y" ] then exit -1 fi printf "Purging records..." GetZoneID PurgeDNSRecords printf "done" CreateARecord WaitDNSPropagation ApplyConfig if [ $RESTORE_MAILBACKUP == "y" ] then RestoreBackup fi GetDKIM echo "Beginning CloudFlare configuration" GetZoneID printf "Creating records..." CreateMXRecord CreateDMARCRecord CreateSPFRecord CreateDKIMRecord printf "done\n" PostInstallation printf "Clearing temporary files..." ClearTempFiles printf "done\n"