Skip to content

Instantly share code, notes, and snippets.

@cag
Created January 15, 2017 02:11
Show Gist options
  • Save cag/4602ede84eb56368050d711d0294d638 to your computer and use it in GitHub Desktop.
Save cag/4602ede84eb56368050d711d0294d638 to your computer and use it in GitHub Desktop.
LetsEncrypt Nginx Postfix Dovecot OpenDKIM Ghost Discourse on Ubuntu 16.04
#cloud-config
users:
- name: alan
ssh-authorized-keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDZ/639L+2Npw2AgQIE4cro933W4BGH0UPOaPVFUZikJ/BwnCDcUXscR/ehoZblbNwt1YbPSOm4yR93Yl6kFQwYRWhL8WI20mt9/ukeNKG2D3Cc/w63AjUa61kDapNRQCuqlaO1bf2//h18RuZnnrJnoMcsWVs5cWOYmpOHKdIdcX7MBy2+AYSs5OCYRqJri9WaZbFKQ13MfpJ1kA771GNNRwSypqRVNiBtqzG8fMaZEofmefnZdhNOApHZ94Tpfj7X+wUbLEGV7B9VmlHOF5fPzBGBTmg7NjXR9o1jud5bHRHtlLmK9Oj9M5VovL8qTNrJzmRQKzC/6FMbNFS0JSJuxs7KQTYKfNIXFk/53ZoK2oSnALh/v7p3s5pm8yVzUlUIxQtiUXQAsbnEHNhsWQXKBa8UHNCQYSxDojbw0Edkc1oLGZ9aX5HWD6kHxxlBheSOxXxtgn1bVGACiTgfwB9B5aKAiT1Eu7vBzQUoZyK5n31nKh0Q/4doNrQuZAy+XUjEVlLCU8e0FmThSd+ydfbIGBnKpPyz2MGnoreemN2Eli2Fl+eb+98uTya10V8VZO/bmOdnt9ZrjYqT8ZmOHyCKgHLItomhbJglEnpHBYZlEaQXO0aFEam1Dvgz1i9V62hwsLEayyKfPWckknUi6LFOWdP8RoKUrzHLpllbfZAFxQ== alanarch
sudo: ['ALL=(ALL) NOPASSWD:ALL']
groups: sudo
shell: /bin/bash
packages:
- letsencrypt
- nginx
- postfix
- dovecot-core
- dovecot-imapd
- opendkim
- opendkim-tools
- cgroup-lite
- unzip
package_update: true
package_upgrade: true
package_reboot_if_required: true
write_files:
- path: /etc/nginx/snippets/le-challenge.conf
content: |
location /.well-known/acme-challenge {
root /var/www/letsencrypt;
}
- path: /etc/nginx/snippets/ssl-params.conf
content: |
# https://cipherli.st/
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
- path: /etc/nginx/nginx.conf
content: |
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include snippets/ssl-params.conf;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
- path: /etc/nginx/sites-available/default
content: |
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name _;
include snippets/le-challenge.conf;
location / {
try_files $uri $uri/ =404;
}
}
- path: /etc/nginx/sites-available/default-ssl
content: |
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
include snippets/ssl-example.com.conf;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
include snippets/le-challenge.conf;
location / {
try_files $uri $uri/ =404;
}
}
- path: /etc/nginx/sites-available/blog.example.com
content: |
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
include snippets/ssl-example.com.conf;
server_name blog.example.com;
include snippets/le-challenge.conf;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://localhost:2368;
}
}
- path: /etc/nginx/sites-available/talk.example.com
content: |
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
include snippets/ssl-example.com.conf;
server_name talk.example.com;
include snippets/le-challenge.conf;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
}
}
- path: /etc/nginx/sites-available/example.com
content: |
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
include snippets/ssl-example.com.conf;
include snippets/le-challenge.conf;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
include snippets/ssl-example.com.conf;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name example.com;
include snippets/le-challenge.conf;
location / {
try_files $uri $uri/ =404;
}
}
- path: /etc/postfix/master.cf
content: |
smtp inet n - y - - smtpd
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_reject_unlisted_recipient=no
-o smtpd_recipient_restrictions=
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
pickup unix n - y 60 1 pickup
cleanup unix n - y - 0 cleanup
qmgr unix n - n 300 1 qmgr
tlsmgr unix - - y 1000? 1 tlsmgr
rewrite unix - - y - - trivial-rewrite
bounce unix - - y - 0 bounce
defer unix - - y - 0 bounce
trace unix - - y - 0 bounce
verify unix - - y - 1 verify
flush unix n - y 1000? 0 flush
proxymap unix - - n - - proxymap
proxywrite unix - - n - 1 proxymap
smtp unix - - y - - smtp
relay unix - - y - - smtp
showq unix n - y - - showq
error unix - - y - - error
retry unix - - y - - error
discard unix - - y - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - y - - lmtp
anvil unix - - y - 1 anvil
scache unix - - y - 1 scache
maildrop unix - n n - - pipe
flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
uucp unix - n n - - pipe
flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
ifmail unix - n n - - pipe
flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp unix - n n - - pipe
flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix - n n - 2 pipe
flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman unix - n n - - pipe
flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
${nexthop} ${user}
- path: /etc/aliases
content: |
mailer-daemon: postmaster
postmaster: root
root: alan
- path: /etc/dovecot/dovecot.conf
content: |
disable_plaintext_auth = no
mail_privileged_group = mail
mail_location = mbox:~/mail:INBOX=/var/mail/%u
userdb {
driver = passwd
}
passdb {
args = %s
driver = pam
}
protocols = " imap"
protocol imap {
mail_plugins = " autocreate"
}
plugin {
autocreate = Trash
autocreate2 = Sent
autosubscribe = Trash
autosubscribe2 = Sent
}
service auth {
unix_listener /var/spool/postfix/private/auth {
group = postfix
mode = 0660
user = postfix
}
}
- path: /etc/default/opendkim
content: SOCKET="inet:12301@localhost"
- path: /etc/opendkim.conf
content: |
AutoRestart Yes
AutoRestartRate 10/1h
Syslog yes
UMask 002
Domain example.com
KeyFile /etc/dkimkeys/default.private
Selector default
Canonicalization relaxed
OversignHeaders From
TrustAnchorFile /usr/share/dns/root.key
Socket inet:12301@localhost
- path: /home/alan/discourse_app.yml.template
content: |
templates:
- "templates/postgres.template.yml"
- "templates/redis.template.yml"
- "templates/web.template.yml"
- "templates/web.ratelimited.template.yml"
- "templates/web.socketed.template.yml"
params:
db_default_text_search_config: "pg_catalog.english"
db_shared_buffers: "128MB"
env:
LANG: en_US.UTF-8
UNICORN_WORKERS: 2
DISCOURSE_HOSTNAME: talk.example.com
DISCOURSE_DEVELOPER_EMAILS: 'alan@example.com'
DISCOURSE_SMTP_ADDRESS: example.com
DISCOURSE_SMTP_PORT: 587
DISCOURSE_SMTP_USER_NAME: alan
DISCOURSE_SMTP_PASSWORD: PASSWORD_PLACEHOLDER
volumes:
- volume:
host: /var/discourse/shared/standalone
guest: /shared
- volume:
host: /var/discourse/shared/standalone/log/var-log
guest: /var/log
hooks:
after_code:
- exec:
cd: $home/plugins
cmd:
- git clone https://github.com/discourse/docker_manager.git
run:
- exec: echo "Beginning of custom commands"
## If you want to set the 'From' email address for your first registration, uncomment and change:
## After getting the first signup email, re-comment the line. It only needs to run once.
- exec: rails r "SiteSetting.notification_email='talk@example.com'"
- exec: echo "End of custom commands"
- path: /home/alan/init-server.sh
content: |
# get some SSL certs
echo "ssl_certificate /etc/letsencrypt/live/$(hostname -f)/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$(hostname -f)/privkey.pem;" > /etc/nginx/snippets/ssl-$(hostname -f).conf
nginx -s reload
letsencrypt certonly -a webroot --webroot-path=/var/www/letsencrypt -d "$(hostname -f)" -d "www.$(hostname -f)" -d "talk.$(hostname -f)" -d "blog.$(hostname -f)"
(crontab -l ; echo '30 3 * * 2 letsencrypt renew && nginx -s reload') | crontab -
echo "ssl=required
ssl_cert = </etc/letsencrypt/live/$(hostname -f)/fullchain.pem
ssl_key = </etc/letsencrypt/live/$(hostname -f)/privkey.pem" >> /etc/dovecot/dovecot.conf
# do some nginx configuration
ln -sf /etc/nginx/sites-available/default-ssl /etc/nginx/sites-enabled/default
ln -s /etc/nginx/sites-available/$(hostname -f) /etc/nginx/sites-enabled/$(hostname -f)
ln -s /etc/nginx/sites-available/talk.$(hostname -f) /etc/nginx/sites-enabled/talk.$(hostname -f)
ln -s /etc/nginx/sites-available/blog.$(hostname -f) /etc/nginx/sites-enabled/blog.$(hostname -f)
dovecot reload
nginx -s reload
runcmd:
# user stuff
- 'head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 > /home/alan/passwd.shredme'
- chown alan:alan -R /home/alan
- echo alan:$(cat /home/alan/passwd.shredme) | chpasswd
# no root logins
- sed -i -e '/^PermitRootLogin/s/^.*$/PermitRootLogin no/' -e '$aAllowUsers alan' /etc/ssh/sshd_config
- restart ssh
# swap memory
- fallocate -l 4G /swapfile
- chmod 600 /swapfile
- mkswap /swapfile
- swapon /swapfile
- echo '/swapfile none swap sw 0 0' | tee -a /etc/fstab
- sysctl vm.swappiness=10
- sysctl vm.vfs_cache_pressure=50
# setup dat firewall
- ufw allow OpenSSH
- ufw allow 'Nginx Full'
- ufw allow 'Postfix Submission'
- ufw enable
# get you a docker
- curl https://get.docker.com/ | sh
# get ready for Let's Encrypt
- mkdir -p /var/www/letsencrypt
- chown www-data:www-data /var/www/letsencrypt
# make a strong DH group
- openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
# make some substitutions
- rename "s/example\.com/$(hostname -f)/" /etc/nginx/sites-available/*
- sed -i "s/example\.com/$(hostname -f)/g" /etc/nginx/sites-available/* /etc/dovecot/dovecot.conf /etc/opendkim.conf /home/alan/discourse_app.yml.template
- sed -i "s/PASSWORD_PLACEHOLDER/$(cat /home/alan/passwd.shredme)/g" /home/alan/discourse_app.yml.template
# do some opendkim stuff
- opendkim-genkey --selector=default --domain=$(hostname -f) --directory=/etc/dkimkeys/
- chown opendkim:opendkim -R /etc/dkimkeys
# do some postfix stuff
- postconf -e "smtpd_tls_cert_file=/etc/letsencrypt/live/$(hostname -f)/fullchain.pem"
- postconf -e "smtpd_tls_key_file=/etc/letsencrypt/live/$(hostname -f)/privkey.pem"
- postconf -e "smtpd_tls_CAfile=/etc/letsencrypt/live/$(hostname -f)/cert.pem"
- postconf -e 'smtpd_tls_security_level=may'
- postconf -e 'smtpd_tls_protocols=!SSLv2, !SSLv3'
- postconf -e "smtp_tls_CAfile=/etc/letsencrypt/live/$(hostname -f)/cert.pem"
- postconf -e 'smtp_tls_security_level=may'
- postconf -e 'milter_protocol=2'
- postconf -e 'milter_default_action=accept'
- postconf -e 'smtpd_milters=inet:localhost:12301'
- postconf -e 'non_smtpd_milters=inet:localhost:12301'
# reload mailing stuff
- newaliases
- postfix reload
- service opendkim restart
# make you a Discourse
- mkdir /var/discourse
- git clone git://github.com/discourse/discourse_docker.git /var/discourse
- mv /home/alan/discourse_app.yml.template /var/discourse/containers/app.yml
# Docker complains about something here...
- /var/discourse/launcher bootstrap app || true
# I don't know why but I have to run this twice sometimes
- /var/discourse/launcher start app || true
# install Ghost... BOO!
- curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
- apt-get install -yq nodejs
- curl -sL https://ghost.org/zip/ghost-latest.zip -o /tmp/ghost.zip
- unzip -uo /tmp/ghost.zip -d /var/www/ghost
- rm /tmp/ghost.zip
- cp /var/www/ghost/config.example.js /var/www/ghost/config.js
- sed -i -e "s/url: 'http:\/\/my-ghost-blog.com',/url: 'https:\/\/blog.$(hostname -f)',/" -e "s/mail: {}/mail: { transport: 'SMTP', from: 'blog@alanlu.rocks' }/" /var/www/ghost/config.js
- npm install -g pm2
- pm2 startup
- (cd /var/www/ghost && NODE_ENV=production pm2 start index.js --name "Ghost")
- pm2 save
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment