about summary refs log tree commit diff
path: root/in-a-box
diff options
context:
space:
mode:
Diffstat (limited to 'in-a-box')
-rw-r--r--in-a-box/.docker/mastodon/.env.production1
-rw-r--r--in-a-box/.docker/nginx/cert.pem0
-rw-r--r--in-a-box/.docker/nginx/fullchain.pem0
-rw-r--r--in-a-box/.docker/nginx/nginx.conf126
-rw-r--r--in-a-box/.docker/nginx/privkey.pem0
-rw-r--r--in-a-box/.docker/nginx/production.conf133
-rw-r--r--in-a-box/README.md21
-rw-r--r--in-a-box/docker-compose.yml73
-rwxr-xr-xin-a-box/mastodon.sh108
9 files changed, 320 insertions, 142 deletions
diff --git a/in-a-box/.docker/mastodon/.env.production b/in-a-box/.docker/mastodon/.env.production
new file mode 100644
index 0000000..eae37f3
--- /dev/null
+++ b/in-a-box/.docker/mastodon/.env.production
@@ -0,0 +1 @@
+# Run setup-mastodon.sh
diff --git a/in-a-box/.docker/nginx/cert.pem b/in-a-box/.docker/nginx/cert.pem
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/in-a-box/.docker/nginx/cert.pem
diff --git a/in-a-box/.docker/nginx/fullchain.pem b/in-a-box/.docker/nginx/fullchain.pem
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/in-a-box/.docker/nginx/fullchain.pem
diff --git a/in-a-box/.docker/nginx/nginx.conf b/in-a-box/.docker/nginx/nginx.conf
index 64404b9..3acaf5b 100644
--- a/in-a-box/.docker/nginx/nginx.conf
+++ b/in-a-box/.docker/nginx/nginx.conf
@@ -1,133 +1,7 @@
-map $http_upgrade $connection_upgrade {
-  default upgrade;
-  ''      close;
-}
-
 server {
   listen 80;
   listen [::]:80;
 
   server_name $NGINX_HOST;
   root /var/www/html;
-
-  location /.well-known/acme-challenge/ {
-      allow all;
-  }
-
-  location / {
-      return 301 https://$host$request_uri;
-  }
 }
-
-server {
-  listen 443 ssl http2;
-  listen [::]:443 ssl http2;
-
-  server_name $NGINX_HOST;
-  server_tokens off;
-
-  ssl_protocols TLSv1.2;
-  ssl_ciphers ECDHE+CHACHA20:AES256+EECDH:AES256+EDH:!aNULL;
-  ssl_ecdh_curve secp384r1;
-  ssl_prefer_server_ciphers on;
-  ssl_session_cache shared:TLS:2m;
-  ssl_session_timeout 10m;
-  ssl_session_tickets off;
-  ssl_stapling on;
-  ssl_stapling_verify on;
-
-  keepalive_timeout 70;
-  sendfile on;
-  client_max_body_size 0;
-
-  add_header X-Frame-Options DENY;
-  add_header X-Content-Type-Options nosniff;
-  add_header X-XSS-Protection "1; mode=block";
-  add_header Referrer-Policy "same-origin";
-  add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
-
-  ssl_certificate /etc/ssl/fullchain.pem;
-  ssl_certificate_key /etc/ssl/privkey.pem;
-  ssl_trusted_certificate /etc/ssl/cert.pem;
-
-  resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001] valid=300s;
-  resolver_timeout 5s;
-
-  root /var/www/html;
-
-  add_header Access-Control-Allow-Origin "https://$host";
-  add_header X-Cache-Status $upstream_cache_status;
-
-  location / {
-    try_files $uri @proxy;
-  }
-
-  location /sw.js {
-    add_header Cache-Control "public, max-age=0";
-    try_files $uri @proxy;
-  }
-
-  location ~ ^/(emoji|packs|sounds|system/media_attachments/files) {
-    add_header Cache-Control "public, max-age=31536000, immutable";
-
-    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;
-
-    try_files $uri @proxy;
-  }
-
-  location @proxy {
-    proxy_set_header Host $host;
-    proxy_set_header X-Real-IP $remote_addr;
-    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-    proxy_set_header X-Forwarded-Proto https;
-    proxy_set_header Proxy "";
-    proxy_pass_header Server;
-
-    proxy_pass http://mstweb:3000;
-    proxy_buffering on;
-    proxy_redirect off;
-    proxy_http_version 1.1;
-    proxy_set_header Upgrade $http_upgrade;
-    proxy_set_header Connection $connection_upgrade;
-
-    proxy_cache CACHE;
-    proxy_cache_valid 200 7d;
-    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
-    proxy_cache_lock on;
-    proxy_cache_revalidate on;
-
-    tcp_nodelay on;
-  }
-
-  location /api/v1/streaming {
-    proxy_set_header Host $host;
-    proxy_set_header X-Real-IP $remote_addr;
-    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-    proxy_set_header X-Forwarded-Proto https;
-    proxy_set_header Proxy "";
-
-    proxy_pass http://mstweb:4000;
-    proxy_buffering off;
-    proxy_redirect off;
-    proxy_http_version 1.1;
-    proxy_set_header Upgrade $http_upgrade;
-    proxy_set_header Connection $connection_upgrade;
-
-    tcp_nodelay on;
-  }
-
-  error_page 403 /assets/403.html;
-  error_page 404 /assets/404.html;
-  error_page 410 /assets/410.html;
-  error_page 422 /assets/422.html;
-  error_page 500 501 502 503 504 /assets/500.html;
-}
-
-proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=2g use_temp_path=off;
diff --git a/in-a-box/.docker/nginx/privkey.pem b/in-a-box/.docker/nginx/privkey.pem
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/in-a-box/.docker/nginx/privkey.pem
diff --git a/in-a-box/.docker/nginx/production.conf b/in-a-box/.docker/nginx/production.conf
new file mode 100644
index 0000000..02bbad5
--- /dev/null
+++ b/in-a-box/.docker/nginx/production.conf
@@ -0,0 +1,133 @@
+map $http_upgrade $connection_upgrade {
+  default upgrade;
+  ''      close;
+}
+
+server {
+  listen 80;
+  listen [::]:80;
+
+  server_name $NGINX_HOST;
+  root /var/www/html;
+
+  location /.well-known/acme-challenge/ {
+      allow all;
+  }
+
+  location / {
+      return 301 https://$host$request_uri;
+  }
+}
+
+server {
+  listen 443 ssl http2;
+  listen [::]:443 ssl http2;
+
+  server_name $NGINX_HOST;
+  server_tokens off;
+
+  ssl_protocols TLSv1.2;
+  ssl_ciphers ECDHE+CHACHA20:AES256+EECDH:AES256+EDH:!aNULL;
+  ssl_ecdh_curve secp384r1;
+  ssl_prefer_server_ciphers on;
+  ssl_session_cache shared:TLS:2m;
+  ssl_session_timeout 10m;
+  ssl_session_tickets off;
+  ssl_stapling on;
+  ssl_stapling_verify on;
+
+  keepalive_timeout 70;
+  sendfile on;
+  client_max_body_size 0;
+
+  add_header X-Frame-Options DENY;
+  add_header X-Content-Type-Options nosniff;
+  add_header X-XSS-Protection "1; mode=block";
+  add_header Referrer-Policy "same-origin";
+  add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
+
+  ssl_certificate /etc/ssl/fullchain.pem;
+  ssl_certificate_key /etc/ssl/privkey.pem;
+  ssl_trusted_certificate /etc/ssl/cert.pem;
+
+  resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001] valid=300s;
+  resolver_timeout 5s;
+
+  root /var/www/html;
+
+  add_header Access-Control-Allow-Origin "https://$host";
+  add_header X-Cache-Status $upstream_cache_status;
+
+  location / {
+    try_files $uri @proxy;
+  }
+
+  location /sw.js {
+    add_header Cache-Control "public, max-age=0";
+    try_files $uri @proxy;
+  }
+
+  location ~ ^/(emoji|packs|sounds|system/media_attachments/files) {
+    add_header Cache-Control "public, max-age=31536000, immutable";
+
+    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;
+
+    try_files $uri @proxy;
+  }
+
+  location @proxy {
+    proxy_set_header Host $host;
+    proxy_set_header X-Real-IP $remote_addr;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header X-Forwarded-Proto https;
+    proxy_set_header Proxy "";
+    proxy_pass_header Server;
+
+    proxy_pass http://web:3000;
+    proxy_buffering on;
+    proxy_redirect off;
+    proxy_http_version 1.1;
+    proxy_set_header Upgrade $http_upgrade;
+    proxy_set_header Connection $connection_upgrade;
+
+    proxy_cache CACHE;
+    proxy_cache_valid 200 7d;
+    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
+    proxy_cache_lock on;
+    proxy_cache_revalidate on;
+
+    tcp_nodelay on;
+  }
+
+  location /api/v1/streaming {
+    proxy_set_header Host $host;
+    proxy_set_header X-Real-IP $remote_addr;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header X-Forwarded-Proto https;
+    proxy_set_header Proxy "";
+
+    proxy_pass http://streaming:4000;
+    proxy_buffering off;
+    proxy_redirect off;
+    proxy_http_version 1.1;
+    proxy_set_header Upgrade $http_upgrade;
+    proxy_set_header Connection $connection_upgrade;
+
+    tcp_nodelay on;
+  }
+
+  error_page 403 /assets/403.html;
+  error_page 404 /assets/404.html;
+  error_page 410 /assets/410.html;
+  error_page 422 /assets/422.html;
+  error_page 500 501 502 503 504 /assets/500.html;
+}
+
+proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=2g use_temp_path=off;
diff --git a/in-a-box/README.md b/in-a-box/README.md
new file mode 100644
index 0000000..42c3b12
--- /dev/null
+++ b/in-a-box/README.md
@@ -0,0 +1,21 @@
+# Mastodon Secure-ish Setup in a Box
+
+Step 1: Git clone this repository.
+
+Step 2: Replace `example.com` in `docker-compose.yml` with your server name.
+
+Step 3: Run `./mastodon.sh setup` and afterwards, move `public/system/.env.production` to the `.docker/mastodon/` folder.
+
+Step 4: Run `./mastodon.sh acme` to set up Let's Encrypt. Make sure that port 80 is allowed by the firewall.
+
+Step 5: Run `docker-compose up -d` to start all services. Verify with `docker-compose ps` that they're up.
+
+Step 6: Register on the instance.
+
+Step 7: Run `./mastodon.sh make_admin USERNAME=yourusername` (replace "yourusername" with your username).
+
+Step 8: Set a cron job to run `./mastodon.sh cron` daily via inserting `@daily /path/to/mastodon.sh cron` into `crontab -e`.
+
+Step 9: Optionally, set a daily cron to run `./mastodon.sh backup daily` and an hourly cron for `./mastodon.sh backup hourly`.
+
+Step 10: Enjoy! Don't forget to set up your OS's firewall.
diff --git a/in-a-box/docker-compose.yml b/in-a-box/docker-compose.yml
index ff71cea..fee98b1 100644
--- a/in-a-box/docker-compose.yml
+++ b/in-a-box/docker-compose.yml
@@ -5,24 +5,22 @@ services:
     restart: always
     image: nginx:mainline
     volumes:
-      - ./.docker/nginx/nginx.conf:/etc/nginx/conf.d/template.ro:ro
-#     Replace the /path/to/letsencrypt/stuff.pem with wherever the corresponding files are:
-      - /path/to/letsencrypt/fullchain.pem:/etc/ssl/fullchain.pem:ro
-      - /path/to/letsencrypt/privkey.pem:/etc/ssl/privkey.pem:ro
-      - /path/to/letsencrypt/cert.pem:/etc/ssl/cert.pem:ro
+      - ./.docker/nginx/nginx.conf:/tmp/template.conf:ro
+      - ./.docker/nginx/fullchain.pem:/etc/ssl/fullchain.pem:ro
+      - ./.docker/nginx/privkey.pem:/etc/ssl/privkey.pem:ro
+      - ./.docker/nginx/cert.pem:/etc/ssl/cert.pem:ro
       - ./public:/var/www/html:ro
       - /etc/localtime:/etc/localtime:ro
     ports:
       - "80:80"
       - "443:443"
     environment:
-#       Replace example.com with your webserver:
       - NGINX_HOST=example.com
     networks:
       - external_network
-    command: sh -c "envsubst \"`env | awk -F = '{printf \" $$%s, $$1}'`\" < /etc/nginx/conf.d/template.ro > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
+    command: sh -c "envsubst \"`env | awk -F = '{printf \" $$%s, $$1}'`\" < /tmp/template.conf > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
 
-  mstdb:
+  db:
     restart: always
     image: postgres:10.3-alpine
     networks:
@@ -31,7 +29,7 @@ services:
       - /etc/localtime:/etc/localtime:ro
       - ./.docker/mastodon/db:/var/lib/postgresql/data
 
-  mstredis:
+  redis:
     restart: always
     image: redis:alpine
     networks:
@@ -40,7 +38,7 @@ services:
       - /etc/localtime:/etc/localtime:ro
       - ./.docker/mastodon/redis:/data
 
-#  mstes:
+#  es:
 #    restart: always
 #    image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.3
 #    environment:
@@ -57,27 +55,64 @@ services:
 #      - /etc/timezone:/etc/timezone:ro
 #      - ./.docker/mastodon/es:/usr/share/elasticsearch/data
 
-  mstweb:
-    image: pluralcafe/mastodon:stable
+  web:
+    image: tootsuite/mastodon:latest
     restart: always
-# Get .env.production from https://raw.githubusercontent.com/tootsuite/mastodon/master/.env.production.sample
     env_file: ./.docker/mastodon/.env.production
+    command: bundle exec rails s -p 3000 -b '0.0.0.0'
     networks:
-      - external_network
       - mstdb_network
       - mstredis_network
+#      - mstes_network
+      - mstweb_network
     depends_on:
       - mstdb
       - mstredis
 #      - mstes
     volumes:
+      - ./public/assets:/mastodon/public/assets
+      - ./public/packs:/mastodon/public/packs
+      - ./public/system:/mastodon/public/system
+      - /etc/localtime:/etc/localtime:ro
+    tmpfs:
+      - /mastodon/tmp/pids:rw,noexec,uid=991,gid=991,mode=0666
+
+  streaming:
+    image: tootsuite/mastodon:latest
+    restart: always
+    env_file: ./.docker/mastodon/.env.production
+    command: yarn start
+    networks:
+      - mstdb_network
+      - mstredis_network
+      - mststreaming_network
+    volumes:
+      - /etc/localtime:/etc/localtime:ro
+    depends_on:
+      - db
+      - redis
+
+  sidekiq:
+    image: tootsuite/mastodon:latest
+    restart: always
+    env_file: ./.docker/mastodon/.env.production
+    command: bundle exec sidekiq -q default -q mailers -q pull -q push
+    depends_on:
+      - db
+      - redis
+    networks:
+      - external_network
+      - mstdb_network
+#      - mstes_network
+      - mstredis_network
+    volumes:
+      - ./public/assets:/mastodon/public/assets
+      - ./public/packs:/mastodon/public/packs
       - ./public/system:/mastodon/public/system
       - /etc/localtime:/etc/localtime:ro
 
 networks:
   external_network:
-# Uncomment the below for IPv6 support and fill in the last subnet
-# with the IPv6 subnet your hosting provider gave you
 #    driver: bridge
 #    enable_ipv6: true
 #    ipam:
@@ -89,3 +124,9 @@ networks:
     internal: true
   mstredis_network:
     internal: true
+  mststreaming_network:
+    internal: true
+  mstweb_network:
+    internal: true
+#  mstes_network:
+#    internal: true
diff --git a/in-a-box/mastodon.sh b/in-a-box/mastodon.sh
new file mode 100755
index 0000000..339913d
--- /dev/null
+++ b/in-a-box/mastodon.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+
+do_help() {
+  local myself
+  myself="$(basename $0)"
+  echo "Usage: $myself [command] [arguments]"
+  echo
+  echo "Possible commands:"
+  echo
+  echo " - $myself setup: Setup this instance. Generates .env.production in public/system."
+  echo " - $myself update: Download and apply updates if there are newer available."
+  echo " - $myself acme: Install Lets Encrypt certificates."
+  echo " - $myself cron: Run cron job."
+  echo " - $myself backup hourly: Run hourly backup."
+  echo " - $myself backup daily: Run daily backup."
+  echo " - $myself backup restore [file]: Restore .pgsql file from backup."
+  echo
+  echo "Also runs rake commands, e.g.:"
+  echo
+  echo " - $myself make_admin USERNAME=yourname"
+  echo " - $myself revoke_staff USERNAME=yourname"
+  echo " - $myself confirm_email USER_EMAIL=your@email"
+  echo
+  echo "See https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/List-of-Rake-tasks.md"
+  echo "for a full list of Rake tasks."
+}
+
+do_setup() {
+  docker-compose run --rm web bash -c "rake mastodon:setup; mv /mastodon/.env.production /mastodon/system/"
+  echo
+  echo "Please move (not copy!) public/system/.env.production into .docker/mastodon/"
+}
+
+do_update() {
+  (docker-compose pull 2>&1 | grep --silent "Downloaded newer") && {
+    docker-compose up -d
+
+    docker-compose run --rm web rake db:migrate
+    docker-compose run --rm web rake assets:precompile
+
+    docker image prune -f
+  }
+}
+
+do_cron() {
+  docker-compose run -T --rm web rake mastodon:media:remove_remote
+}
+
+do_acme() {
+  local DOMAIN
+  read -s "Domain Name: " DOMAIN
+
+  curl -sS https://raw.githubusercontent.com/Neilpang/acme.sh/master/acme.sh | INSTALLONLINE=1  sh
+
+  docker-compose up -d nginx
+  acme.sh --issue -d $DOMAIN -w $(pwd)/public --keylength ec-384
+  acme.sh --install-cert -d $DOMAIN --cert-file $(pwd)/.docker/nginx/cert.pem --key-file $(pwd)/.docker/nginx/privkey.pem --fullchain-file $(pwd)/.docker/nginx/fullchain.pem --ecc --reloadcmd "$(command -v docker-compose) -f $(pwd)/docker-compose.yml stop nginx; $(command -v docker-compose) -f $(pwd)/docker-compose.yml up -d nginx"
+
+  [ -e "$(pwd)/.docker/nginx/production.conf" ] && \
+    rm "$(pwd)/.docker/nginx/nginx.conf" && \
+    mv "$(pwd)/.docker/nginx/production.conf" "$(pwd)/.docker/nginx/nginx.conf"
+
+  docker-compose stop nginx
+}
+
+do_backup() {
+  if ["$2" == "daily" ]; then
+    find "$(pwd)/.docker/mastodon/backups" -type f -name postgres-daily.* -mtime +7 -delete
+    docker-compose exec -T -u postgres db sh -c "umask 0377 && /usr/local/bin/pg_dump -Fc -h db -d postgres -U postgres" > "$(pwd)/.docker/mastodon/backups/postgres-daily.$(date -Iseconds).pgsql"
+  fi
+
+  if ["$2" == "hourly" ]; then
+    find "$(pwd)/.docker/mastodon/backups" -type f -name postgres-hourly.* --min +360 -delete
+    docker-compose exec -T -u postgres db sh -c "umask 0377 && /usr/local/bin/pg_dump -Fc -h db -d postgres -U postgres" > "$(pwd)/.docker/mastodon/backups/postgres-hourly.$(date -Iseconds).pgsql"
+  fi
+
+  if ["$2" == "restore" ]; then
+    docker-compose run --rm db sh -c "/usr/local/bin/psql --set ON_ERROR_STOP=on -Fc -h db -d postgres -U postgres" < "$3"
+  fi
+}
+
+do_rake() {
+  docker-compose run --rm web rake mastodon:$1 ${@:2}
+}
+
+case "$1" in
+  help|h|--help)
+    do_help
+    ;;
+  setup)
+    do_setup
+    ;;
+  acme)
+    do_acme
+    ;;
+  update)
+    do_update
+    ;;
+  cron)
+    do_cron
+    ;;
+  backup)
+    do_backup
+    ;;
+  *)
+    do_rake
+    ;;
+esac