Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Example of building Tor instance
  • Install to Debian Buster
  • All of services needed to run Mastodon instance (tor, postgresql, redis, nginx) will be installed to the same server.

Install packages

# apt install postgresql redis nodejs npm ruby tor ffmpeg imagemagick git \
  nginx wget bundler protobuf-compiler iptables-persistent ncurses-term vim \
  gcc g++ make patch ruby-dev libprotobuf-dev libidn11-dev libpq-dev libicu-dev libxml2-dev libxslt1-dev 

Create user

# adduser mastodon
# adduser mastodon www-data

Create DB user

Create mastodon as superuser.

# su -c 'createuser -s mastodon' postgres

Config redis

Edit following directives in /etc/redis/redis.conf.

port 0
unixsocket /run/redis/redis-server.sock
unixsocketperm 666
# systemctl restart redis-server

Install yarn

# npm install -g --prefix /opt/yarn yarn
# echo 'export PATH=$PATH:/opt/yarn/bin' >/etc/profile.d/yarn.sh

Setup Tor & transparent proxy

Append following to /etc/tor/torrc.

TransPort 127.0.0.1:9040
DNSPort 127.0.0.1:5353
VirtualAddrNetwork 127.192.0.0/10
TransPort [::1]:9040
DNSPort [::1]:5353
VirtualAddrNetworkIPv6 [fc00:70a::]/32
AutomapHostsOnResolve 1

HiddenServiceDir /var/lib/tor/mastodon
HiddenServiceVersion 3
HiddenServicePort 80 [::1]:80
# systemctl restart tor

The onion address will be generated at /var/lib/tor/mastodon/hostname.

Make transparent proxy by iptables.

Create a chain for "torify."

# iptables -N TORIFY
# iptables -A TORIFY -m state --state RELATED,ESTABLISHED -j ACCEPT
# iptables -A TORIFY -j REJECT --reject-with icmp-net-prohibited
# iptables -t nat -N TORIFY
# iptables -t nat -A TORIFY -p udp --dport 53 -j REDIRECT --to-ports 5353
# iptables -t nat -A TORIFY -p tcp -j REDIRECT --to-ports 9040

Create a chain for accessing to mastodon backends. If you choose to access backend services on remote, You need to prepend the rules (-I) to this chain.

# iptables -N MASTODON-BE
# iptables -A MASTODON-BE -j TORIFY
# iptables -t nat -N MASTODON-BE
# iptables -t nat -A MASTODON-BE -j TORIFY

Apply these rules for mastodon user.

# iptables -A OUTPUT -m owner --uid-owner mastodon -d 127.0.0.0/8 -j ACCEPT
# iptables -A OUTPUT -m owner --uid-owner mastodon -j MASTODON-BE
# iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j ACCEPT
# iptables -t nat -A OUTPUT -m owner --uid-owner mastodon -j MASTODON-BE

Repeat to IPv6.

# ip6tables -N TORIFY
# ip6tables -A TORIFY -m state --state RELATED,ESTABLISHED -j ACCEPT
# ip6tables -A TORIFY -j REJECT --reject-with icmp6-adm-prohibited
# ip6tables -t nat -N TORIFY
# ip6tables -t nat -A TORIFY -p udp --dport 53 -j REDIRECT --to-ports 5353
# ip6tables -t nat -A TORIFY -p tcp -j REDIRECT --to-ports 9040

# ip6tables -N MASTODON-BE
# ip6tables -A MASTODON-BE -j TORIFY
# ip6tables -t nat -N MASTODON-BE
# ip6tables -t nat -A MASTODON-BE -j TORIFY

# ip6tables -A OUTPUT -m owner --uid-owner mastodon -d ::1 -j ACCEPT
# ip6tables -A OUTPUT -m owner --uid-owner mastodon -j MASTODON-BE
# ip6tables -t nat -A OUTPUT -d ::1 -j ACCEPT
# ip6tables -t nat -A OUTPUT -m owner --uid-owner mastodon -j MASTODON-BE

Persistent the rules.

# iptables-save >/etc/iptables/rules.v4
# ip6tables-save >/etc/iptables/rules.v6

Check connectivity

# su -c 'wget -O- --quiet https://check.torproject.org |sed -n "/<h1/,/<\\/h1/p"' mastodon
  <h1 class="not">
    
      Congratulations. This browser is configured to use Tor.
    
  </h1>
# su -c 'wget -O/dev/null https://www.facebookcorewwwi.onion' mastodon

Config nginx

Save following as /etc/nginx/sites-available/mastodon

map $http_upgrade $connection_upgrade {
	default upgrade;
	''      close;
}

server {
	listen [::1]:80;
	server_name %%%.onion;

	keepalive_timeout    70;
	sendfile             on;
	client_max_body_size 8m;

	root /home/mastodon/live/public;
	add_header Referrer-Policy "same-origin";

	gzip on;
	gzip_disable "msie6";
	gzip_vary on;
	gzip_proxied any;
	gzip_comp_level 8;
	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;

	location / {
		try_files $uri @proxy;
	}

	location ~ ^/(emoji|packs) {
		add_header Cache-Control "public, max-age=31536000, immutable";
		gzip_static on;
		try_files $uri @proxy;
	}
	
	location /system {
		add_header Cache-Control "public, max-age=31536000, immutable";
		gzip off;
		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 Proxy "";
		proxy_pass_header Server;

		proxy_pass http://unix:/home/mastodon/live/tmp/web.sock:;
		proxy_buffering off;
		proxy_redirect off;
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection $connection_upgrade;
	}

	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 Proxy "";

		proxy_pass http://unix:/home/mastodon/live/tmp/streaming.sock:;
		proxy_buffering off;
		proxy_redirect off;
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection $connection_upgrade;
	}

	error_page 500 501 502 503 504 /500.html;
}
# ln -s ../sites-available/mastodon /etc/nginx/sites-enabled/
# systemctl restart nginx

Setup Mastodon

Login as mastodon

Prepare source

$ git clone https://github.com/tootsuite/mastodon live
$ cd live
$ git checkout -b run v2.4.5

Save following as ~/disable-https.patch.

diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 29ba6cad6..df22cf32e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -33,7 +33,7 @@ class ApplicationController < ActionController::Base
   private
 
   def https_enabled?
-    Rails.env.production?
+    false
   end
 
   def store_current_location
diff --git a/config/initializers/ostatus.rb b/config/initializers/ostatus.rb
index 5773b7290..93961fd19 100644
--- a/config/initializers/ostatus.rb
+++ b/config/initializers/ostatus.rb
@@ -7,7 +7,7 @@ web_host = ENV.fetch('WEB_DOMAIN') { host }
 alternate_domains = ENV.fetch('ALTERNATE_DOMAINS') { '' }
 
 Rails.application.configure do
-  https    = Rails.env.production? || ENV['LOCAL_HTTPS'] == 'true'
+  https    = false
 
   config.x.local_domain = host
   config.x.web_domain   = web_host
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 3dc0edd6f..85983d259 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -1,3 +1,3 @@
 # Be sure to restart your server when you modify this file.
 
-Rails.application.config.session_store :cookie_store, key: '_mastodon_session', secure: (Rails.env.production? || ENV['LOCAL_HTTPS'] == 'true')
+Rails.application.config.session_store :cookie_store, key: '_mastodon_session'
$ cd live
$ patch -p1 <~/disable-https.patch
patching file app/controllers/application_controller.rb
patching file config/initializers/ostatus.rb
patching file config/initializers/session_store.rb

Install libraries

$ bundle config --local build.nokogiri --use-system-libraries
You are replacing the current local value of build.nokogiri, which is currently nil
$ bundle install --deployment --jobs 3 --without development:test
$ yarn install --pure-lockfile --prod

Fill envfile

~/live/.env.production

LOCAL_DOMAIN=%%%.onion
SINGLE_USER_MODE=true|false
DB_HOST=/run/postgresql
DB_NAME=mastodon_production
DB_USER=mastodon
REDIS_URL=unix:///run/redis/redis-server.sock

SMTP_FROM_ADDRESS=notifications@%%%.onion
SMTP_DELIVERY_METHOD=smtp|sendmail
SMTP_SERVER=***.onion
SMTP_PORT=25
#SMTP_LOGIN=
#SMTP_PASSWORD=
SMTP_AUTH_METHOD=none|plain|cram_md5
SMTP_OPENSSL_VERIFY_MODE=none|...

ALLOW_ACCESS_TO_HIDDEN_SERVICE=true

Generate some keys and append them

$ echo SECRET_KEY_BASE=$(openssl rand -hex 64) >>.env.production
$ echo OTP_SECRET=$(openssl rand -hex 64) >>.env.production
$ RAILS_ENV=production bundle exec rails mastodon:webpush:generate_vapid_key >>.env.production

Prepare DB & assets

$ RAILS_ENV=production bundle exec rails db:setup SAFETY_ASSURED=1
$ RAILS_ENV=production bundle exec rails assets:precompile

Remove superuser role

# su -c 'psql -c "alter role mastodon nosuperuser nocreaterole nocreatedb;"' postgres
ALTER ROLE

Create systemd unit

/etc/systemd/system/mastodon-web.service

[Unit]
Description=mastodon-web
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
ExecStartPre=/bin/sh -c '/bin/rm tmp/web.sock 2>/dev/null || :'
ExecStart=/usr/bin/bundle exec puma -C config/puma.rb -e production -b unix:///home/mastodon/live/tmp/web.sock
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target

/etc/systemd/system/mastodon-sidekiq.service

[Unit]
Description=mastodon-sidekiq
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment=RAILS_ENV=production
Environment=DB_POOL=5
ExecStart=/usr/bin/bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target

/etc/systemd/system/mastodon-streaming.service

[Unit]
Description=mastodon-streaming
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment=NODE_ENV=production
Environment=PORT=/home/mastodon/live/tmp/streaming.sock
ExecStartPre=/bin/sh -c '/bin/rm $PORT 2>/dev/null || :'
ExecStart=/opt/npm/bin/npm run start
ExecStartPost=/bin/sh -c 'retry=30; while [ $retry -gt 0 ]; do if [ -e $PORT ]; then chmod 660 $PORT; chown :www-data $PORT; exit; else sleep 1; retry=$((retry-1)); fi; done; kill $MAINPID; exit 1'
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target

Start Mastodon

# systemctl daemon-reload
# systemctl start mastodon-{web,sidekiq,streaming}
# systemctl enable mastodon-{web,sidekiq,streaming}
Created symlink /etc/systemd/system/multi-user.target.wants/mastodon-web.service → /etc/systemd/system/mastodon-web.service.
Created symlink /etc/systemd/system/multi-user.target.wants/mastodon-sidekiq.service → /etc/systemd/system/mastodon-sidekiq.service.
Created symlink /etc/systemd/system/multi-user.target.wants/mastodon-streaming.service → /etc/systemd/system/mastodon-streaming.service.
@grubles

This comment has been minimized.

Copy link

commented Aug 11, 2018

Hello, I have used this gist to setup a mastodon hidden service. Everything other than email confirmation (which I do not want anyway) and logging in works. When attempting to log in, the login prompt in the web UI redisplays and states "You need to sign in or sign up before continuing." even though the user is signed up and (manually) confirmed.

@hcmiya

This comment has been minimized.

Copy link
Owner Author

commented Aug 15, 2018

@grubles
This is affected by the following commit:
tootsuite/mastodon@69bf116
Reverting this commit will allow you to log in as before. Also, I need to update the patch in this document...

@hcmiya

This comment has been minimized.

Copy link
Owner Author

commented Aug 15, 2018

更新。

  • 依存パッケージが明らかに足りなかったので色々追加。
  • iptablesのルールにチェーンを使うようにした。
  • nginxの設定で、ヘッダreferrer-policy: same-originを出力するやつ。
  • 7月末のmasterからクッキーの取扱いの問題でログインできなくなるので、2.4.3を使わせるようにした。cf. tootsuite/mastodon@69bf116
  • HIDDEN_SERVICE_VIA_TRANSPARENT_PROXYは2.4.3から使わなくなったので削除。
  • systemdユニット更新。nginxから各サービスのソケットへ接続させるためのパーミッション設定について、以前はumaskをいじっていたのを、今回から起動設定見直しや起動後の監視スクリプトで代えることにした。
  • その他諸々
@hcmiya

This comment has been minimized.

Copy link
Owner Author

commented Sep 21, 2018

更新。

  • npmがDebian busterに含まれるようになったので、npmインストール周りを簡略化。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.