From 295981828f3fcfa57b3186c1cb638060582981ed Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Wed, 4 Jun 2014 19:39:58 -0400 Subject: [PATCH] Vagrantize * adding a Vagrantfile * in a non-interactive setup like this, create the user's first email account for them * let the machine auto-detect its IP address using http://icanhazip.com/ * use our own justtesting.email domain to provision a subdomain for users so they can quickly get started --- Vagrantfile | 33 +++++++++++++++++++++++++++++ management/dns_update.py | 41 ++++++++++++++++++++++++++++++++++++ setup/start.sh | 45 +++++++++++++++++++++++++++++++++------- 3 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 Vagrantfile diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 00000000..08d6daac --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,33 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure("2") do |config| + config.vm.box = "ubuntu14.04-gitmachine" + config.vm.box_url = "ubuntu14.04-gitmachine.box" + + # Network config: Since it's a mail server, it only makes sense + # to put it on the public network. This will let the machine + # take an IP address from your DHCP server. It's up to you to + # make sure its ports are exposed on the public web. + config.vm.hostname = "mailinabox" + config.vm.network "public_network" + + config.vm.provision :shell, :inline => <<-SH + # Our install will fail if SSH is installed and allows password-based authentication. + # `vagrant ssh` will still work if we disable password authentication. + echo "PasswordAuthentication no" >> /etc/ssh/sshd_config + + # Set environment variables so that the setup script does + # not ask any questions during provisioning. We'll let the + # machine figure out its own public IP and it'll take a + # subdomain on our justtesting.email domain so we can get + # started quickly. + export PUBLIC_IP=auto-web + export PUBLIC_HOSTNAME=auto-easy + export CSR_COUNTRY=US + + # Start the setup script. + cd /vagrant + setup/start.sh +SH +end diff --git a/management/dns_update.py b/management/dns_update.py index 63a06f34..67a40970 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -27,6 +27,7 @@ def do_dns_update(env): for domain, zonefile in zonefiles: records = build_zone(domain, env) if write_nsd_zone(domain, "/etc/nsd/zones/" + zonefile, records, env): + justtestingdotemail(domain, records) updated_domains.append(domain) # Write the main nsd.conf file. @@ -186,3 +187,43 @@ def write_opendkim_tables(zonefiles, env): for domain, zonefile in zonefiles )) +######################################################################## + +def justtestingdotemail(domain, records): + # If the domain is a subdomain of justtesting.email, which we own, + # automatically populate the zone where it is set up on dns4e.com. + # Ideally if dns4e.com supported NS records we would just have it + # delegate DNS to us, but instead we will populate the whole zone. + + import subprocess, json, urllib.parse + + if not domain.endswith(".justtesting.email"): + return + + for subdomain, querytype, value in records: + if querytype in ("NS",): continue + if subdomain in ("www", "ns1", "ns2"): continue # don't do unnecessary things + + if subdomain == None: + subdomain = domain + else: + subdomain = subdomain + "." + domain + + if querytype == "TXT": + # nsd requires parentheses around txt records with multiple parts, + # but DNS4E requires there be no parentheses; also it goes into + # nsd with a newline and a tab, which we replace with a space here + value = re.sub("^\s*\(\s*([\w\W]*)\)", r"\1", value) + value = re.sub("\s+", " ", value) + else: + continue + + print("Updating DNS for %s/%s..." % (subdomain, querytype)) + resp = json.loads(subprocess.check_output([ + "curl", + "-s", + "https://api.dns4e.com/v7/%s/%s" % (urllib.parse.quote(subdomain), querytype.lower()), + "--user", "2ddbd8e88ed1495fa0ec:A97TDJV26CVUJS6hqAs0CKnhj4HvjTM7MwAAg8xb", + "--data", "record=%s" % urllib.parse.quote(value), + ]).decode("utf8")) + print("\t...", resp.get("message", "?")) diff --git a/setup/start.sh b/setup/start.sh index aedc2c6f..fbde276c 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -67,6 +67,24 @@ if [ -z "$CSR_COUNTRY" ]; then read -e -i "$DEFAULT_CSR_COUNTRY" -p "Country Code: " CSR_COUNTRY fi +# Automatic configuration, e.g. as used in our Vagrant configuration. +if [ "$PUBLIC_IP" == "auto" ]; then + # Assume `hostname -i` gives the correct public IP address for the machine. + PUBLIC_IP=`hostname -i` + echo "IP Address: $PUBLIC_IP" +fi +if [ "$PUBLIC_IP" == "auto-web" ]; then + # Use a public API to get our public IP address. + PUBLIC_IP=`curl -s icanhazip.com` + echo "IP Address: $PUBLIC_IP" +fi +if [ "$PUBLIC_HOSTNAME" == "auto-easy" ]; then + # Generate a probably-unique subdomain under our justtesting.email domain. + PUBLIC_HOSTNAME=m`hostname -i | sha1sum | cut -c1-5`.justtesting.email + echo "Public Hostname: $PUBLIC_HOSTNAME" +fi + + # Create the user named "user-data" and store all persistent user # data (mailboxes, etc.) in that user's home directory. if [ -z "$STORAGE_ROOT" ]; then @@ -107,19 +125,30 @@ fi . setup/management.sh # Write the DNS configuration files. -sleep 2 # wait for the daemon to start +sleep 5 # wait for the daemon to start curl -d POSTDATA http://127.0.0.1:10222/dns/update -if [ -t 0 ]; then # are we in an interactive shell? +# If there aren't any mail users yet, create one. if [ -z "`tools/mail.py user`" ]; then # The outut of "tools/mail.py user" is a list of mail users. If there - # are none configured, ask the user to configure one. - echo - echo "Let's create your first mail user." - read -e -i "user@$PUBLIC_HOSTNAME" -p "Email Address: " EMAIL_ADDR - tools/mail.py user add $EMAIL_ADDR # will ask for password + # aren't any yet, it'll be empty. + + # In an interactive shell, ask the user for an email address. + if [ -t 0 ]; then + echo + echo "Let's create your first mail user." + read -e -i "user@$PUBLIC_HOSTNAME" -p "Email Address: " EMAIL_ADDR + else + # Use me@PUBLIC_HOSTNAME + EMAIL_ADDR=me@$PUBLIC_HOSTNAME + EMAIL_PW=1234 + echo + echo "Creating a new mail account for $EMAIL_ADDR with password $EMAIL_PW." + echo + fi + + tools/mail.py user add $EMAIL_ADDR $EMAIL_PW # will ask for password if none given tools/mail.py alias add hostmaster@$PUBLIC_HOSTNAME $EMAIL_ADDR tools/mail.py alias add postmaster@$PUBLIC_HOSTNAME $EMAIL_ADDR fi -fi