Created
January 15, 2017 02:11
-
-
Save cag/4602ede84eb56368050d711d0294d638 to your computer and use it in GitHub Desktop.
LetsEncrypt Nginx Postfix Dovecot OpenDKIM Ghost Discourse on Ubuntu 16.04
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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: '[email protected]' | |
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='[email protected]'" | |
- 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: '[email protected]' }/" /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