Build your own shrine
How to build a static website as a Tor hidden service, while staying totally private.
Introduction
Privacy is one component of a healthy society. In public, we are confined to a society built on activities that no one hates – a gray slurry of what a panel of lawyers and insurance adjusters couldn’t find fault with. But in private we have a wonderful freedom, to be who we are moment to moment, tailoring our conversation and expression only to the people we’ve invited to be there with us. It is a great enabler of speech that would never be spoken without it, of things that would never get done in public.
Privacy is freedom. Privacy is a good thing for its own sake.
I think that the default society of the 21st century in the United States is less private than it ought to be, and that this impoverishes us individually and our society as a whole.
In the abstract, this document is several things:
- An homage to privacy, an appreciation of it in general.
- A way to publicly support the concept of privacy and to oppose its detractors.
But in the specific, it has one goal: to show how someone with something to say, a bit of money, and a willingness to work, can share something with the world anonymously.
It will explain how to obtain money, buy hosting, and publish words on the Internet, without tying those words to a public identity.
Ingredients
Before you begin, make sure you have access to the following:
- Tor Browser (clearnet, onionlink)
- A password manager like KeePassXC
- A Monero wallet like Feather (clearnet, onionlink) ; see other options on the Monero downloads page (clearnet, onionlink)
- Hugo static site builder
- A command-line prompt with some basic tools:
- GNU Make
- ssh
- rsync
- A budget of about $100 for six months of hosting
We keep prerequisites to a minimum, which makes this easier to explain, and keeps us more secure as we rely on fewer third parties.
This process involves using Tor (of course) and the command-line.
If you already have a password manager, you may wish to set up a separate one dedicated to your Tor identity.
Building and maintaining it will also involve a bit of money. Money is required to get hosting that isn’t associated with your identity, and a bit of extra money will speed up a required step of getting an email address.
Technical goals
- Host the website on a .onion domain we control, without tying your real-world identity to it in any way.
- Host the site on a machine not directly in our possession. This means that even if the remote machine is compromised, it is not tied to our identity. However, it also means trusting the remote host with the private key for the .onion domain.
- A site that looks trustworthy to very risk-averse readers. No JavaScript or web fonts.
- The option for a single request, including styles. This is a useful property for the very paranoid, and will make any single request faster over Tor. However, it will make requests for multiple pages that use the same CSS slower (because it must re-download the inlined CSS every time), so it is optional.
- A site that is optimized towards text.
- A good starting place to add other customizations in the future.
Tor
You can connect to Tor on your regular computer using the Tor Browser, or via a specialized operating system like Tails or Qubes. The former is easier, but the latter is more secure.
When building this shrine, I chose to do it all on my regular desktop. If I were engaging in activity that was illegal in my jurisdiction, however, I’d have used Tails or Qubes instead.
Aside: Recommendation from DNM Bible
The DNM Bible is a guide geared towards buying illegal products (mostly drugs) on darknet markets. Their strong recommendation is to use Tails directly for these transactions, and to avoid macOS and Windows, because
- “Windows, and Mac are loaded with backdoors, and both are companies that WILL cooperate with law enforcement.”
- “This guide is written to help keep you safe, and secure. Always be prepared for the worst case scenarios, people using Windows/Mac are low hanging fruit, you are a lot more likely to fall into the lap of LE.”
- “Also keep in mind one of your number one rules should always to keep your Darknet life, and your real life seperate. Would you want someone from the Darknet getting their hands on your normal files? Fuck no you don’t so don’t even let that be an option.”
In my opinion, the third reason is the most important. The easiest way for a state to link your activity on Tor to your offline identity is by making a mistake. Opsec is very difficult to maintain, especially if your adversary is a global power. For this reason alone, you should consider using Tails or Qubes.
The first reason is a combination of false and misleading. Windows and macOS are not “loaded with backdoors”, and the claim is paranoid bordering on delusional. It’s true that Microsoft and Apple will cooperate with law enforcement, but this is true of virtually everyone, and they can only give up information they actually have. The question “what information do these companies have about me” is a good one, and it’s hard to answer, which is why claims like “Windows, and Mac [sic] are loaded with backdoors” feel tempting to make, but it’s better to focus on the true uncertainty of what information a company has about you than the false certainty that they are operated by the same lizard people that control the government and put Lithium in the water supply.
The second reason, about low hanging fruit, is true, but it’s a tradeoff. For the desktop use case in particular, Windows and macOS receive more attention of researchers looking to both attack and defend their users than Linux OSes do. This means that there are more advanced attacks being developed against web browsers on popular operating systems, and more advanced defenses being developed internally at Microsoft and Apple. It’s not clear which of these is actually more likely to keep you private on Tor, although I think you could make a good argument for either case.
More secure: Using a specialized operating system
You can connect to Tor using a live OS like Tails. Under this method, you write the Tails OS to a USB stick, shut down your computer, and boot from the stick. You can keep important files on a persistent partition. In Tails, all Internet connections go through Tor, so you can be sure that you haven’t accidentally clicked a link that could de-anonymize you in the wrong browser.
You could also use Qubes, which is a Linux OS that is designed to run applications in virtual machines for security. You can create a proxy VM that runs Tor, and a second VM for building the site that only allows connections to the Tor VM. This is more complex to set up and requires deeper Linux system administration knowledge, but it is also more flexible, as you can browse the web on Tor and on the clearnet side by side, unlike with Tails.
Both of these options can provide greater certainty than using your main computer to use Tor, at the cost of some convenience. The biggest advantage is that they make it hard to accidentally click on the wrong thing in the wrong context, which might de-anonymize you.
Conveniences of the less convenient method
We noted earlier that using your regular computer to connect to Tor was more convenient, and that is true in general, but it’s worth noting one nicety of using a specialized OS for connecting to Tor: running commands like git, ssh, and rsync through Tor is easier. We don’t need to use SOCKS proxies or special config files as described in the next section; we can just run those commands the way we always do and data will be sent over Tor transparently.
Easier: Connecting to Tor on your regular desktop
We can browse the web privately through Tor with Tor Browser, but using command-line tools requires configuring them to use the built-in Tor proxy.
This only works while the Tor Browser is running.
The Tor Browser starts a SOCKS5 proxy server on port 9050.
(Note that this port number may change; see Troubleshooting for how to deal with this.)
Running SSH over Tor
We can use checkmyip to see our public IP address over SSH, and use that to prove that SSH is working over Tor before connecting to our new VPS.
First, run a command like this to check your current public IP.
ssh root@sshmyip.com
That should return something like this (real values redacted):
Result from sshmyip
{
"comment": "## Your IP Address is 1.2.3.4 (56789) ##",
"family": "ipv4",
"ip": "1234",
"port": "56789",
"protocol": "ssh",
"version": "v1.3.0",
"force_ipv4": "ipv4.telnetmyip.com",
"force_ipv6": "ipv6.telnetmyip.com",
"website": "https://github.com/packetsar/checkmyip",
"sponsor": "Sponsored by ConvergeOne, https://www.convergeone.com/"
}
Now we can run SSH over Tor and compare the result:
ssh -o ProxyCommand='nc -x 127.0.0.1:9050 %h %p' -lroot sshmyip.com
That should return the IP address of your Tor exit node, different from your actual public IP address.
Note that this works just as well with onion addresses, but we don’t have one to connect to yet.
Using curl over Tor
Curl has native support for socks5 proxies.
We use the --socks5-hostname
argument to send both the actual web requests
and also the DNS requests of the web servers over Tor.
(The similar --socks5
option sends the web requests over Tor,
but leaks DNS requests to the system DNS resolver,
and also cannot work with .onion
names.)
You can use curl over Tor with commands like:
# Retrieve the public IP address of the Tor exit node
curl --socks5-hostname localhost:9050 curl --socks5-hostname localhost:9050 https://canhazip.com
# Retrieve the Tor homepage from its .onion domain
curl --socks5-hostname localhost:9050 http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/
Using Git over Tor: Downloading this site’s code
I deployed a Git server so that I can publish the source code for the shrine and the source code for the shrine’s theme. These are currently available only on Tor.
To clone them, we tell Git to use a SOCKS proxy.
Similar to curl’s --socks5-hostname
, the socks5h://
scheme sends DNS requests over the proxy,
while the socks5://
(without the trailing h
) scheme would leak DNS requests and not work with .onion
domains.
# This is the Onion service name for my Git server.
# You can also navigate to it in Tor Browser to examine the code that way.
sacredgit=xdhbqaloeg5bid3tzjpllhnxisdguz34xtoc7qegnmh3essuesfdyqid.onion
# Clone the site
git clone "http://$sacredgit/shrineadmin/sacredground" --config "http.proxy=socks5h://localhost:9050"
# Clone the theme
git clone "http://$sacredgit/shrineadmin/hugo-theme-onionskin" --config "http.proxy=socks5h://localhost:9050"
# Note that the `--config ...` line only needs to be specified on the initial clone,
# as it saves that configuration option in the local clone.
cd ./sacredground
git config --get http.proxy
# returns 'socks5h://localhost:9951'
--config user.name="Your Pseudonym" --config user.email="pseudonym@example.com"
to clone a repo with a specific name/email.Troubleshooting
Tor Browser will configure a local proxy server that we can use to run other tools over Tor.
This is supposed to have a value of 9050 by default,
but I found that nothing could connect with that value.
I opened about:config
in Tor Browser,
and found that extensions.torbutton.custom.socks_port
was set to a different value (in my case 9170
).
Using that value instead of the default worked.
Money
To obtain an email address and hosting services, you will need some money.
What I recommend here is sold in Euros. To get an email address right away is €13/year, but if you’re willing to wait for 48 hours you can get it for free. The hosting service is €15/mo for a small VPS.
I recommend Monero (XMR) for this, as it is well known to preserve your privacy. It may be a bit difficult to obtain, however because it is so privacy-preserving you can safely buy some from a centralized exchange with an account tied to your identity, which is not the case for Bitcoin. Just like taking cash out of the bank, the exchange knows how much Monero you buy, but doesn’t know where you spend it later.
Selecting a Monero wallet
To use Monero you will need a wallet. I used Feather (clearnet, onionlink) which is a locally-synchronizing wallet that connects to remote nodes to obtain the blockchain state. When using Feather, your private key stays only on your own device, and you don’t need to download the entire blockchain yourself.
I have also used the first-party Monero client before in the past. Its biggest downside is that it takes a lot of disk space.
You can get the official Monero wallet as well as links to other wallets from Monero (clearnet, onionlink) .
Setting up Feather
The set up process is easy to follow; just download and launch the program, and it will guide you through creating a new wallet.
Make sure to save the wallet seed phrase to your password manager when it prompts you.
How to buy Monero in general
See the Merchants page on the Monero website. (It’s worth noting that Local Monero has an Onion service as well as the clearnet page mentioned there: http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion/nojs/). See also How to buy Monero on the DNM Bible.
I chose to buy Bitcoin from a centralized exchange, then trade it for Monero.
The easiest way for me to do that actually involves two exchanges – Coinbase and Kraken. Coinbase is happy to connect to my bank account and let me trade cash for cryptocurrency, but it doesn’t offer Monero. Kraken is happy to let me send cryptocurrency to it, and to trade Bitcoin for Monero, but does not connect to my bank to allow for direct purchases. (I’m not sure why this is; maybe a problem on Kraken’s end, or maybe an issue with me being in the US.) By combining them, I could spend US dollars and eventually receive Monero through a somewhat convoluted process.
One thing to note: this may take longer if you don’t already have accounts on the exchange(s) you want to use. Coinbase and Kraken both require proof of government identification and several steps to assure them of your identity, and these steps may take hours or perhaps days.
Exchanges that work this way are required to follow the so-called “know-your-customer” or “KYC” laws that banks must follow, including a strong tie from your account to a government ID that they can give to law enforcement upon request. We use Monero because of its strong privacy guarantees in light of this process – the exchanges can tell that the money you spent with them eventually goes to fund a purchase of some Monero, but they cannot see what happens to the Monero from that point forward.
There is some nuance to this. For instance, if you buy Bitcoin, send the entire amount to a Monero wallet, send the entire Monero amount to someone else, they use the same exchange to trade that Monero back to Bitcoin and/or regular currency, and you repeat this cycle of transactions multiple times, some adversaries may be able to de-anonymize you over time. To counter this, send large blocks of Bitcoin (eg for several months of hosting) to your Monero wallet, and pay smaller amounts month by month. This is called an EABE attack
How I bought Monero
Here’s the strategy that worked for me, in the US.
- I already had accounts on Coinbase and Kraken, and I had already traded on both of them, so I didn’t have to provide any extra proof of identity or wait for any waiting period.
- Buy 0.01265294 BTC (about $250 worth at the time) on Coinbase. This has a fee of a few dollars.
- My account was already created and I had done transactions before, so this was more or less instant.
- Immediately send the Bitcoin to Kraken. This incurred a small fee from both Coinbase and Kraken.
- Within a few minutes, Kraken saw this as “confirming”; by 30 minutes, it was “confirmed” and I could trade it on Kraken.
- After it was confirmed, I could trade it from Bitcoin to Monero. This incurred another fee of a few dollars, and it was instant.
- After fees, I had 1.68781 XMR (about $240-245 worth) in Kraken.
- I sent that full amount to my Feather wallet address. This incurred a fee of $0.01.
- To do this from Kraken you have to “Fund” -> “Withdraw”, which is a bit confusing.
- I created a random label from the password generator in my password manager and used it as the label in my Feather wallet and on the Kraken UI (which requires a description). I’m not sure if this was necessary.
- Feather saw it as “pending” in less than 5 minutes, and “confirmed” in less than 45 minutes.
- In the end, I had 1.68781 XMR in Feather.
Accounts: email and hosting
A new email account
We want to create an account on Njalla, but this requires an email or XMPP account.
Unfortunately, most regular providers, including Gmail, require a phone number to create a new account, and if they don’t, they may require a phone number to unlock an account at any time. Getting a phone number that isn’t tied to your main identity is difficult and costs money, so this isn’t ideal.
Proton (clearnet, onionlink) is generally privacy-friendly, but when creating an account it may require an existing email address or phone number, especially when using Tor.
One useful option is Tutanota, which does not require an existing email address or phone number. (Tutanota does not have a .onion hidden service, but it does encourage the use of Tor, and go out of its way to provide service to anonymous users.) However it has a 48 hour waiting period before you can use a free account.
You can wait for this, but you can also circumvent it if you’re willing to pay for the service. And, you can pay via Monero through the third party reseller ProxyStore (clearnet, onionlink) , which offers Tutanota gift codes for €13, and they accept Monero.
Some notes about that transaction:
- Save the URL of your order, as you need it to access your gift code if you close the tab
- About 15 minutes after ordering, the store got enough Monero confirmations that it released the gift code. It’s just a URL like
https://mail.tutanota.com/giftcard/#asdlkfjalsdkfjasdlkfjasldkfj
. - When you use the gift code, it requires that you set a country, I chose “Antarctica” which worked fine.
After you have one email account, obtaining other accounts linked to the first one gets easier. For instance, I could immediately sign up for a Proton account once my Tutanota account could receive email. Proton does not require that the email you use to verify your account remains valid; per their support article on human verification, “We don’t save CAPTCHA results. If you are presented with email or SMS verification, we only save a cryptographic hash of your email or phone number which is not permanently associated with the account that you create.”
(My Proton confirmation did go to spam, so check that if you want to do this.)
Hosting with Njalla
Njalla (clearnet, onionlink) is a VPS hosting company, DNS registrar, and VPN provider. You can pay for their services with Monero, and they do not collect any information about their users except for a point of contact email address. Signing up for an account is instant.
When I created my account, I configured a low-end VPS: €15/mo, 1 core, 1.5GB RAM, 15GB disk, 1.5TB traffic. I chose Alpine Linux 3.16 because I am already a user, but they also offer a more mainstream Debian installation.
When buying this service, you need to select a name for your VPS. I think it is useful to have a naming scheme, even though currently I only ever plan to have this single virtual machine on this project. To keep this separated from a real-life identity, you should use a naming scheme that is generic and could come from anyone. Try something like, African countries, or Canadian provinces, or Greek letter names. I’m going with the NATO Phonetic Alphabet, so my first machine will be called ‘alfa’.
Configuring the VPS will also require giving Njalla an SSH public key.
Take care that you generate a new key for this use,
so that it cannot be tied to other SSH keys you may have.
When generating the key, take care to blank the key comment with -C ""
,
otherwise it will use username@hostname
which may be tied to your real world identity.
Per the
Njalla documentation
(clearnet,
onionlink)
,
try something like this:
ssh-keygen -t ed25519 -f ./njalla_ed25519 -C ""
Finally, once you’ve selected your VPS options, set a name, and set the SSH key, it will ask you to fund the transaction. You can send more than the price of the service, and the overage will stay in your account. This is useful because Njalla cannot bill you – if you forget to make a payment, your service will simply be turned off. It may also make it harder to trace transactions if you bought Monero on a KYC exchange, although I’m not certain about this.
I sent 0.5 XMR to fund my account, which was approximately $70 at the time. It took about 30 minutes to be confirmed.
Once the transaction was confirmed, Njalla began provisioning my instance, which took a few more minutes before I could log in.
Buying a domain name with Njalla
Njalla is also a private DNS registrar who will hold domains for you.
This is different, legally, than buying a domain name yourself. From their the Njalla FAQ (clearnet, onionlink) :
We’re not actually a domain name registration service, we’re a customer to these. We sit in between the domain name registration service and you, acting as a privacy shield.
When you purchase a domain name through Njalla, we own it for you. However, the agreement between us grants you full usage rights to the domain. Whenever you want to, you can transfer the ownership to yourself or some other party.
This means that Njalla can rescind your access to the domain in accordance to the terms of their license agreement, which can happen if they are obliged by their own agreements with the registry operators for various TLDs.
The upside is significant, however: this arrangement allows you to control a domain name without giving up any identifying information.
I paid for registration of sacredground.click
through Njalla, and pointed it to the VPS.
I already had money in my Njalla wallet,
and the purchase went through immediately.
> host sacredground.click
sacredground.click has address 80.78.27.69
sacredground.click has IPv6 address 2a0a:3840:8078:27:0:504e:1b45:1337
But Njalla doesn’t know who I am (unless, I suppose, they were to read this website). The whois information is all their contact info, as they are the legal operators of “my” domain.
Whois information for sacredground.click
% IANA WHOIS server
% for more information on IANA, visit http://www.iana.org
% This query returned 1 object
refer: whois.uniregistry.net
domain: CLICK
organisation: UNR Corp.
address: Third Floor, Monaco Towers, Georgetown
address: Grand Cayman, Cayman Islands
address: 30369SMB-KY11202
address: Cayman Islands
contact: administrative
name: Shayan Rostam
organisation: UNR Corp.
address: Third Floor, Monaco Towers, Georgetown
address: Grand Cayman, Cayman Islands
address: 30369SMB-KY11202
address: Cayman Islands
phone: +1 345 746 3687
fax-no: +1 345 746 3687
e-mail: partners@unr.com
contact: technical
name: Francisco Obispo
organisation: Tucows Inc
address: 96 Mowat Avenue
address: Toronto, Ontario M6K3M1
address: Canada
phone: +1.949.903.3449
e-mail: trs-ops@tucows.com
nserver: NS1.UNIREGISTRY.NET 2620:57:4000:1:0:0:0:1 64.96.1.1
nserver: NS2.UNIREGISTRY.INFO 64.96.2.1
nserver: NS3.UNIREGISTRY.NET 185.159.197.3 2620:10a:80aa:0:0:0:0:3
nserver: NS4.UNIREGISTRY.INFO 185.159.198.3 2620:10a:80ab:0:0:0:0:3
ds-rdata: 65517 13 2 D09A2C14C4382634EDCCF9634FB32E91511E1E642F3C38468BA2407F1D1BAD24
whois: whois.uniregistry.net
status: ACTIVE
remarks: Registration information: http://uniregistry.link
created: 2014-08-15
changed: 2022-09-06
source: IANA
# whois.uniregistry.net
Domain Name: sacredground.click
Registry Domain ID: DO_b2810c4f45c91ce962d2ffb6958098fd-UR
Registrar WHOIS Server: whois.tucows.com
Registrar URL: www.tucowsdomains.com
Updated Date: 2022-10-23T01:16:24.862Z
Creation Date: 2022-10-23T01:16:23.343Z
Registry Expiry Date: 2023-10-23T01:16:23.343Z
Registrar: Tucows Domains Inc.
Registrar IANA ID: 69
Registrar Abuse Contact Email: domainabuse@tucows.com
Registrar Abuse Contact Phone: +1.4165350123
Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
Domain Status: addPeriod https://icann.org/epp#addPeriod
Registry Registrant ID: REDACTED FOR PRIVACY
Registrant Name: REDACTED FOR PRIVACY
Registrant Organization: Data Protected
Registrant Street: REDACTED FOR PRIVACY
Registrant City: REDACTED FOR PRIVACY
Registrant State/Province: Charlestown
Registrant Postal Code: REDACTED FOR PRIVACY
Registrant Country: KN
Registrant Phone: REDACTED FOR PRIVACY
Registrant Fax: REDACTED FOR PRIVACY
Registrant Email: Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant, Admin, or Tech contact of the queried domain name.
Registry Admin ID: REDACTED FOR PRIVACY
Admin Name: REDACTED FOR PRIVACY
Admin Organization: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin City: REDACTED FOR PRIVACY
Admin State/Province: REDACTED FOR PRIVACY
Admin Postal Code: REDACTED FOR PRIVACY
Admin Country: REDACTED FOR PRIVACY
Admin Phone: REDACTED FOR PRIVACY
Admin Fax: REDACTED FOR PRIVACY
Admin Email: Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant, Admin, or Tech contact of the queried domain name.
Registry Tech ID: REDACTED FOR PRIVACY
Tech Name: REDACTED FOR PRIVACY
Tech Organization: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech City: REDACTED FOR PRIVACY
Tech State/Province: REDACTED FOR PRIVACY
Tech Postal Code: REDACTED FOR PRIVACY
Tech Country: REDACTED FOR PRIVACY
Tech Phone: REDACTED FOR PRIVACY
Tech Fax: REDACTED FOR PRIVACY
Tech Email: Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant, Admin, or Tech contact of the queried domain name.
Registry Billing ID: REDACTED FOR PRIVACY
Billing Name: REDACTED FOR PRIVACY
Billing Organization: REDACTED FOR PRIVACY
Billing Street: REDACTED FOR PRIVACY
Billing City: REDACTED FOR PRIVACY
Billing State/Province: REDACTED FOR PRIVACY
Billing Postal Code: REDACTED FOR PRIVACY
Billing Country: REDACTED FOR PRIVACY
Billing Phone: REDACTED FOR PRIVACY
Billing Fax: REDACTED FOR PRIVACY
Billing Email: Please query the RDDS service of the Registrar of Record identified in this output for information on how to contact the Registrant, Admin, or Tech contact of the queried domain name.
Name Server: 3-get.njalla.fo
Name Server: 2-can.njalla.in
Name Server: 1-you.njalla.no
DNSSEC: unsigned
URL of the ICANN RDDS Inaccuracy Complaint Form: https://www.icann.org/wicf/
>>> Last update of WHOIS database: 2022-10-23T01:21:09.990Z <<<
# whois.tucows.com
Domain Name: SACREDGROUND.CLICK
Registry Domain ID: DO_b2810c4f45c91ce962d2ffb6958098fd-UR
Registrar WHOIS Server: whois.tucows.com
Registrar URL: http://tucowsdomains.com
Updated Date: 2022-10-23T01:18:09
Creation Date: 2022-10-23T01:16:23
Registrar Registration Expiration Date: 2023-10-23T01:16:23
Registrar: TUCOWS, INC.
Registrar IANA ID: 69
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
Registry Registrant ID:
Registrant Name: REDACTED FOR PRIVACY
Registrant Organization: REDACTED FOR PRIVACY
Registrant Street: REDACTED FOR PRIVACY
Registrant City: REDACTED FOR PRIVACY
Registrant State/Province: Charlestown
Registrant Postal Code: REDACTED FOR PRIVACY
Registrant Country: KN
Registrant Phone: REDACTED FOR PRIVACY
Registrant Phone Ext:
Registrant Fax: REDACTED FOR PRIVACY
Registrant Fax Ext:
Registrant Email: https://tieredaccess.com/contact/8a9b46b4-1c52-475f-abb8-8259d58ae40a
Registry Admin ID:
Admin Name: REDACTED FOR PRIVACY
Admin Organization: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin City: REDACTED FOR PRIVACY
Admin State/Province: REDACTED FOR PRIVACY
Admin Postal Code: REDACTED FOR PRIVACY
Admin Country: REDACTED FOR PRIVACY
Admin Phone: REDACTED FOR PRIVACY
Admin Phone Ext:
Admin Fax: REDACTED FOR PRIVACY
Admin Fax Ext:
Admin Email: REDACTED FOR PRIVACY
Registry Tech ID:
Tech Name: REDACTED FOR PRIVACY
Tech Organization: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech City: REDACTED FOR PRIVACY
Tech State/Province: REDACTED FOR PRIVACY
Tech Postal Code: REDACTED FOR PRIVACY
Tech Country: REDACTED FOR PRIVACY
Tech Phone: REDACTED FOR PRIVACY
Tech Phone Ext:
Tech Fax: REDACTED FOR PRIVACY
Tech Fax Ext:
Tech Email: REDACTED FOR PRIVACY
Name Server: 1-you.njalla.no
Name Server: 2-can.njalla.in
Name Server: 3-get.njalla.fo
DNSSEC: unsigned
Registrar Abuse Contact Email: domainabuse@tucows.com
Registrar Abuse Contact Phone: +1.4165350123
URL of the ICANN WHOIS Data Problem Reporting System: https://icann.org/wicf
>>> Last update of WHOIS database: 2022-10-23T01:21:10Z <<<
This is different from WHOIS privacy that is offered by other registrars. The registrar for most of my other domains is Gandi. When I buy a domain with Gandi, I usually turn on WHOIS privacy, which lists Gandi’s physical address, email address, and phone number. They act as a proxy and will forward me any communication they receive on my behalf. Unlike Njalla, Gandi has strong links to my real identity, so they could de-anonymize me if they chose to.
Configuring the VPS
Connecting over Tor
Njalla will give you an IPv4 and IPv6 address of your new VPS. You could connect over the clearnet, but we want to obfuscate our connection to this server, so we want to configure Tor to do it.
This section explicitly runs ssh/rsync through a proxy as required when using your main computer for Tor. Remember that specifying proxies is not necessary if using Tails or Qubes.
Even when running in a specialized OS like Tails,
it is sometimes useful to save the host in ~/.ssh/config
,
just make sure to remove the ProxyCommand
line.
Since we confirmed earlier that SSH over Tor is working, (and made sure that we had the correct port number if 9050 is not working) we can connect to the VPS with something like:
VPSIP4=1.2.3.4
ssh -o ProxyCommand='nc -x 127.0.0.1:9050 %h %p' root@$VPSIP4
Rather than remember the -o ProxyCommand=...
stuff,
we can save that in our SSH config file.
### Connect to the VPS's public IP over Tor
Host sacredground.ip
HostName 1.2.3.4
User root
IdentityFile ~/.ssh/njalla_ed25519
ProxyCommand nc -x 127.0.0.1:9050 %h %p
You can see in $SSH_CLIENT
environment variable in your new shell that you’re coming from a Tor exit node.
It has three space-separated values:
the IP address connected to the server (which will be your Tor exit node),
a source port number (random),
and a destination port number (22 for SSH by default).
alfa:~# env | grep SSH_CLIENT
SSH_CLIENT=1.2.3.4 56790 22
provision.sh
script
Note that the interactive SSH connection feels heinously slow. We will want to avoid interactive SSH as much as possible. To do that we can use a simple script to install packages and change config files for us.
Some notes about this script:
- Note that the variables at the top of the file – those must be customized to your environment, based on the IP addresses you got from Njalla, your hostname, etc.
- I have nginx listen on
127.69.69.1:8080
for the Tor version of this site, and then configure a hidden service in a torrc file that proxiesasdf.onion:80
to that. All addresses in127.0.0.0/8
are loopback addresses and can be used this way. - We add the
Onion-Location:
header to the shrine so that Tor Browser can show a.onion available
badge in the URL bar when users visit the clearnet site.
Here is my script:
My provision.sh script
#!/bin/sh
set -eu
#### Variables
#
# Change this to values appropriate for your environment
VPSIP4=80.78.27.69
VPSIP6=2a0a:3840:8078:27::504e:1b45:1337
VPSDNS=sacredground.click
ACMEEMAIL=surveilledmicah@tutanota.com
#### Configuring the base Alpine install
#
if ! grep -q "edge" /etc/apk/repositories; then
cat >>/etc/apk/repositories <<EOF
@edgemain https://dl-cdn.alpinelinux.org/alpine/edge/main
@edgecommunity https://dl-cdn.alpinelinux.org/alpine/edge/community
@edgetesting https://dl-cdn.alpinelinux.org/alpine/edge/testing
EOF
fi
# Ignore apk update errors, which can happen if we run this script several times in a row
apk update || true
apk add apk-autoupdate@edgetesting nginx openssl rsync tor tmux
rc-update add tor default
rc-update add nginx default
# Configure automatic package updates
cat >/etc/apk/autoupdate.conf <<EOF
services_blacklist="net.* sshd"
EOF
cat >/etc/periodic/daily/apk-autoupdate.sh <<EOF
#!/bin/sh
set -eu
apk-autoupdate
EOF
chmod 700 /etc/periodic/daily/apk-autoupdate.sh
#### Configuring SSH
#
# Change SSH to run on a different port
# This is not mandatory, but it does remove a bunch of garbage from your system logs
if ! grep -q "Port 6922" /etc/ssh/sshd_config; then
echo "" >> /etc/ssh/sshd_config
echo "Port 6922" >> /etc/ssh/sshd_config
fi
#### Configuring Tor
#
# Create directories for the hidden services
mkdir -p /var/lib/tor/services/admin /var/lib/tor/services/sacredground
chown -R tor /var/lib/tor
chmod 700 /var/lib/tor/services /var/lib/tor/services/admin /var/lib/tor/services/sacredground
# Log debug messages - Tor Project recommends against this as it may expose info about users, consider removing when finished configuring
cat >/etc/tor/torrc <<EOF
Log notice file /var/log/tor/notices.log
DataDirectory /var/lib/tor
%include /etc/tor/torrc.d/*.conf
EOF
mkdir -p /etc/tor/torrc.d/
# Create admin service that exposes SSH over port 22
cat >/etc/tor/torrc.d/admin.service.conf <<EOF
HiddenServiceDir /var/lib/tor/services/admin
HiddenServicePort 22 127.0.0.1:6922
EOF
# Create web service for the shrine
cat >/etc/tor/torrc.d/sacredground.service.conf <<EOF
HiddenServiceDir /var/lib/tor/services/sacredground
HiddenServicePort 80 127.69.69.1:8080
EOF
# rc-service tor restart
# Determine the onion service name for our shrine
SHRINEONION="$(cat /var/lib/tor/services/sacredground/hostname)"
#### Configuring nginx
#
# Disable the default nginx configuration
if test -e /etc/nginx/http.d/default.conf; then
mv /etc/nginx/http.d/default.conf /etc/nginx/http.d/default.conf.disabled
fi
# Copy the default html directory to our clearnet directory
# This will let us test nginx before starting Tor
if ! test -e /var/lib/nginx/sacredground-clearnet; then
cp -r /var/lib/nginx/html /var/lib/nginx/sacredground-clearnet
fi
# ... and will let us test nginx + Tor before uploading a real site
if ! test -e /var/lib/nginx/sacredground-onion; then
cp -r /var/lib/nginx/html /var/lib/nginx/sacredground-onion
fi
# The try_files makes sure that http://asdf.onion/uri gets redirected to /uri/ and finds the index.html.
# absolute_redirect off makes sure that the redirect will be for a relative path like /uri/
# and not http://asdf.onion:8080/uri/.
# ... it would normally generate the :8080 for sacredground-onion and homemirror-onion
# because those sites listen on that port.
# This would cause a problem bc tor is proxying asdf.onion:80 -> localhost:8080.
# <https://serverfault.com/a/905740>
cat >/etc/nginx/http.d/sacredground-clearnet-http.conf <<EOF
server {
listen $VPSIP4:80 default_server;
listen [$VPSIP6]:80;
server_name $VPSDNS;
location /.well-known/acme-challenge {
proxy_pass http://127.69.69.5:8080;
proxy_set_header Host \$host;
}
location / {
root /var/lib/nginx/sacredground-clearnet;
return 301 https://\$host\$request_uri;
try_files \$uri \$uri/ =404;
add_header Onion-Location http://$SHRINEONION\$request_uri;
error_page 404 /404.html;
}
}
EOF
cat >/etc/nginx/http.d/sacredground-onion.conf <<EOF
server {
listen 127.69.69.1:8080;
server_name $SHRINEONION;
location / {
root /var/lib/nginx/sacredground-onion;
absolute_redirect off;
try_files \$uri \$uri/ =404;
error_page 404 /404.html;
}
}
EOF
# If the cert doesn't exist yet, create the HTTPS config file as disabled
# This prevents an error when nginx looks for a cert that doesn't exist
CLEARNET_HTTPS_CONF=/etc/nginx/http.d/sacredground-clearnet-https.conf
CLEARNET_HTTPS_CONF_DISABLED="$CLEARNET_HTTPS_CONF.disabled"
CLEARNET_HTTPS_CONF_TOWRITE="$CLEARNET_HTTPS_CONF"
if ! test -e "/etc/lego/certificates/$VPSDNS.crt"; then
CLEARNET_HTTPS_CONF_TOWRITE="$CLEARNET_HTTPS_CONF_DISABLED"
fi
cat >"$CLEARNET_HTTPS_CONF_TOWRITE" <<EOF
server {
listen $VPSIP4:443 ssl;
listen [$VPSIP6]:443 ssl;
server_name $VPSDNS;
ssl_certificate /etc/lego/certificates/$VPSDNS.crt;
ssl_certificate_key /etc/lego/certificates/$VPSDNS.key;
location /.well-known/acme-challenge {
proxy_pass http://127.69.69.5:8443;
proxy_set_header Host \$host;
}
location / {
root /var/lib/nginx/sacredground-clearnet;
absolute_redirect off;
try_files \$uri \$uri/ =404;
add_header Onion-Location http://$SHRINEONION\$request_uri;
error_page 404 /404.html;
}
}
EOF
# Test the configuration
nginx -t
rc-service nginx restart
#### Configuring Let's Encrypt
#
# Create a user to run the lego command
if ! grep -q "^lego:" /etc/passwd; then
adduser -D -G nginx lego
fi
# Create a directory for lego certificates
mkdir -p /etc/lego
chown lego:nginx /etc/lego
chmod 750 /etc/lego
# If we don't have any certificates yet, retrieve them
if ! test -e /etc/lego/certificates/$VPSDNS.crt; then
su - lego -c "lego --domains='$VPSDNS' --email='$ACMEEMAIL' --accept-tos --path=/etc/lego --http --http.port=127.69.69.5:8080 run"
chmod 640 /etc/lego/certificates/*
mv "$CLEARNET_HTTPS_CONF_DISABLED" "$CLEARNET_HTTPS_CONF"
nginx -t
rc-service nginx restart
fi
# Add a cronjob to check every week if certs need to be renewed
# Lego will only actually renew them if they expire in 30 days or less
cat >/etc/periodic/weekly/lego.sh <<EOF
#!/bin/sh
set -eu
su - lego -c "/usr/bin/lego --domains='$VPSDNS' --email='$ACMEEMAIL' --accept-tos --path=/etc/lego --http --http.port=127.69.69.5:8080 --tls --tls.port=127.69.69.5:8443 renew"
chmod 640 /etc/lego/certificates/*
rc-service nginx reload
EOF
chmod 755 /etc/periodic/weekly/lego.sh
#### Finishing up...
#
echo "Tor local service names and onion names:"
for hname in /var/lib/tor/services/*/hostname; do
echo "$hname :: $(cat $hname)"
done
echo "Let's Encrypt certificate expires:"
openssl x509 -enddate -noout -in /etc/lego/certificates/$VPSDNS.crt
Copy it to the VPS and then run it:
scp provision.sh sacredground.ip:/root/
ssh sacredground.ip sh /root/provision.sh
After running this script for the first time,
you must change your ssh config for connecting to sacredground.ip
,
and add a Port
directive:
### Connect to the VPS's public IP over Tor
Host sacredground.ip
HostName 1.2.3.4
Port 6922 ############################################ ADD THIS LINE
User root
IdentityFile ~/.ssh/njalla_ed25519
ProxyCommand nc -x 127.0.0.1:9050 %h %p
You can also configure SSH to work with the .onion name. This may be faster and more privacy-preserving than using the IP address.
### Connect to the VPS's Onion address over Tor.
Host sacredground.onion
HostName asdfqwerzxcv....onion
User root
IdentityFile ~/.ssh/njalla_ed25519
ProxyCommand nc -x 127.0.0.1:9050 %h %p
Now you can do some new things:
- SSH to the admin onion name
- Browse to the default nginx HTML pages at the onion service name
- Browse to the default nginx HTML pages at the domain name you registered through Njalla
- See an HTTP redirect with
curl --socks5-hostname localhost:9050 http://YOUR-DOMAIN/
(note thehttp://
scheme) - See the
Onion-Location:
header withcurl -sD - -o /dev/null --socks5-hostname localhost:9050 https://YOUR-DOMAIN/
(note thehttps://
scheme)
Configuring HTTPS
To get a TLS certificate to enable HTTPS, we use Let’s Encrypt. This is provisioned automatically in the script.
You should probably make a backup
so that you have a copy of your private key for Let’s Encrypt.
The private key is stored in /etc/lego
,
and the example backup script will back up that location.
Backing up the VPS
Basically everything on the VPS originated on your local machine,
and can be rebuilt by the scripts we use here.
The exceptions are your Onion service keys,
which determine your .onion
domain name.
Depending on what you do with your site, you may have user-generated content like comments that you’d like to back up, or application databases or other state.
- You could consider cloud storage for this.
The most basic version of that would be another VPS,
perhaps from a provider other than Njalla in case they go down or your relationship with them deteriorates,
and just using
rsync
to transmit files back and forth. I am unfortunately not aware of any companies that offer cloud storage like S3 which don’t require a link to an offline identity, however. - You could copy that files you care about to your local machine. This means that any compromise of your local machine would link you to your Tor site.
In my case, I use target in my Makefile to create a backup tarball and copy it to my local machine.
backup
target in Makefile
.PHONY: backup
backup: ## Make a backup and save it locally
$(eval NOW=$(shell date +%Y%m%d-%H%M%S))
mkdir -p backups/
ssh "${SERVER}" "tar -f - -c /var/lib/tor/ /var/lib/gogs/ /etc/" | gzip > "backups/${SERVER}.backup.$(NOW).tar"
Vanity .onion names
Many onion services have vanity names which start with a certain string.
For instance, dark.fail
’s Onion service is
http://darkfailenbsdla5mal2mxn2uz66od5vtzd5qozslagrfzachha3f3id.onion/.
A tool called mkp224o can generate these. You have to compile it yourself. Some hardware can use special optimizations to make it faster.
You might run it like this, which attempts to generate several prefixes and stores the results in a directory called attempts/
.
./configure --enable-intfilter=native
make
./mkp224o -d attemptes/ prefix1 prefix2 prefix3 ...
Shen Hong wrote a post about the Enterprise Onion Toolkit (clearnet, onionlink) which has more details on this tool, including optimizations. He calculates the expected time it would take to generate onion names with prefixes of various lengths, which may inform your decision on whether to pursue this yourself.
It’s also worth reading Tips when mining Onion Addresses by Alec Muffett, the author of the EOTK.
The results are directories named with the .onion
service name and each containing three files:
hostname
contains the.onion
service name (again)hs_ed25519_public_key
is the public keyhs_ed25519_secret_key
is the secret key
You can copy this directory to /var/lib/tor/services/sacredground
(which matches the location set in the torrc
file by provision.sh
),
overwriting the Onion service hostname and keys that Tor generated when it started up for the first time.
Hugo
Hugo is a static site generator. It has few dependencies and executes completely on your own machine, making it a good fit for our purposes.
Building a Hugo website
If you’re completely new to Hugo, read the quickstart guide.
Take special note about the theme. Themse have significant privacy implications, since they control what code the client downloads, including requests for remote resources, tracking JavaScript, etc.
When I built this shrine, I had some unusual goals:
- Zero JavaScript. Some Tor users refuse to use JavaScript anyway, and truthfully saying there is no JavaScript on the site is a sign of trustworthiness for some users.
- No web fonts, just fonts that ship with the browser.
- No third-party resources like CSS served from a CDN.
- The ability to have a single request for every page on the site, which can be a faster experience for users depending on your site layout. This means being able to inline CSS and images.
These goals are not necessary to build a good shrine, and if you don’t share them feel free to make different choices.
With those goals, I decided to build my own simple theme, called Onionskin. At the moment, it’s only available over Tor. See its readme for more details.
A short summary of its features:
- Allow inlining everything, including CSS and the favicon, to provide a page in a single request
- No JavaScript or webfonts, which increase attack surface of your users
- No external dependencies, including build-time dependencies or code served from CDNs
- Optional link annotations to indicate whether a given link is on the same page (anchor), a different page on the same site (internal), an external link on the clearnet, or an external link to an onion site.
- A light and dark color scheme
Image Processing
We want to process all images locally, so that we cannot be tracked from e.g. a web application that we use to convert images for us. We also want to make sure that any photos we take with identifiable metadata are anonymized.
Local image processing
Screenshots can be resized with an ImageMagick command.
This keeps the aspect ratio of the original image, and resizes it to fit inside the -resize 768x768
argument.
magick content/howto/extensions/gogs-first-run-orig.png -resize 768x768 content/howto/extensions/gogs-first-run.png
You can also use ImageMagick to convert an SVG to a favicon:
magick -density 32x32 -background transparent favicon.svg -define icon:auto-resize=32 -colors 4 favicon.ico
Removing identifying metadata
Photos taken on digital cameras (including phones) contain a lot of metadata, some of which may be used to identify the source. If you have photos that you wish to publish without connecting them to your offline identity, you should process them to make sure they don’t leak.
For this purpose, we use exiftran and exiftool. Both are in Homebrew and most Linux distribution package repositories.
image=./photo.jpg
# Rotate the bitmap so that you don't need the Orientation EXIF tag
exiftran -ia "$image"
# Remove all EXIF tags, except for ICC_Profile, which affects color
exiftool -overwrite_original_in_place -all= --icc_profile:all "$image"
Creating images from text
The Google Font to SVG Path webapp can convert any string of text to an SVG in any font on Google Fonts.
It’s open source and runs only in the browser;
you can host it yourself by cloning the repository
and running it behind any webserver (e.g. with Python cd google-font-to-svg-path; python3 -m http.server
),
although it still retrieves JavaScript and fonts from CDNs.
It was used for this site to create the logo and favicon.
Favicons
Favicons are not a requirement, but they are nice to have.
Inlining our favicon
One of our goals is that large pages can be retrieved by a single request. This means our favicon must be inlined into the page itself.
- We can inline a .ico favicon, but I wasn’t able to get an SVG favicon to inline.
- Example of working inlined .ico favicon: https://dark.fail
My Onionskin theme for Hugo can do this for us. It looks something like this:
{{- $faviconIco := resources.Get "favicon.ico" }}
<link href="data:image/x-icon;base64,{{ $faviconIco.Content | base64Encode }}" rel="icon" type="image/x-icon">
Getting a favicon
- Favicon sources
- A letter from a font (see Creating images from text and/or another post I have written on the topic)
- Material Design Icons, a collection of icons designed for Android
- Twemotji, a free color Emoji font from Twitter
- FontAwesome, a business selling icons that maintains a free subset
- Icomoon, a business selling icons that maintains a free subset
- The Noun Project, a directory of icons under Creative Commons
For more privacy, you can prioritize icon packs (like Twemoji and FontAwesome) and search through them locally.
Note that many of these sources require attribution.
Deploying
We can build the site locally with hugo
,
and then copy it to the VPS with rsync
.
rsync
uses SSH to connect, and you can use name you specified in ~/.ssh/config
for your VPS.
Be careful to include the trailing slash on the source directory.
Note that rsync’s -z
argument compresses files before sending them,
which makes an enormous difference over Tor,
especially for the first deployment.
hugo mod get
hugo \
--environment onion \
--cleanDestinationDir \
--ignoreCache \
--printPathWarnings \
--minify \
--destination public/onion
hugo \
--environment clearnet \
--cleanDestinationDir \
--ignoreCache \
--printPathWarnings \
--minify \
--destination public/clearnet
rsync -zrvP --perms --chmod=ugo=rX --progress public/onion/ root@sacredground.ip:/var/lib/nginx/sacredground-onion/
rsync -zrvP --perms --chmod=ugo=rX --progress public/clearnet/ root@sacredground.ip:/var/lib/nginx/sacredground-clearnet/
I use a Makefile to do this so I can issue a single command
make deploy-onion deploy-clearnet
and it will do all of the above.
After you do this, you should see your new content when browsing to the onion address.
Tor and the modify/deploy/test cycle
If you’re restarting tor itself when deploying the site,
the local tor service might be up but the network can’t find it for a few minutes after restarting it,
with a message like it is likely that the service has changed its descriptor
.
This happens when you restart tor, and occasionally other times too when the service has to reestablish a connection to tor.
You can try getting a new Tor identity (clearnet, onionlink) in Tor Browser, but you may just have to wait it out.
Extensions
This section showcases other things I did to build this shrine. They are less documented and more specific to my use case, but they might be a useful starting place for your own modifications.
Provisioning my extensions
I have a second provision script that must be run after the first one has run at least once.
provision-ext.sh
code
#!/bin/sh
set -eu
# Note: this script expects that provision.sh has already run at least once successfully
apk update
apk add gogs gogs-openrc sqlite
#### Configure the home mirror
# This is for an Onion service with content from https://me.micahrl.com and https://com.micahrl.me
mkdir -p /var/lib/nginx/home-obverse
cat >/etc/nginx/http.d/home-obverse-onion.conf <<EOF
server {
listen 127.69.69.2:8080;
location / {
root /var/lib/nginx/home-obverse;
absolute_redirect off;
try_files \$uri \$uri/ =404;
error_page 404 /404.html;
}
}
EOF
mkdir -p /var/lib/nginx/home-reverse
cat >/etc/nginx/http.d/home-reverse-onion.conf <<EOF
server {
listen 127.69.69.6:8080;
location / {
root /var/lib/nginx/home-reverse;
absolute_redirect off;
try_files \$uri \$uri/ =404;
error_page 404 /404.html;
}
}
EOF
# Test the configuration
nginx -t
rc-service nginx restart
#### Configure Tor
# Create directories for the hidden services
mkdir -p /var/lib/tor/services/home-obverse /var/lib/tor/services/home-reverse /var/lib/tor/services/sacredgit
chown -R tor /var/lib/tor/services/home-obverse /var/lib/tor/services/home-reverse /var/lib/tor/services/sacredgit
chmod 700 /var/lib/tor/services/home-obverse /var/lib/tor/services/home-reverse /var/lib/tor/services/sacredgit
cat >/etc/tor/torrc.d/home-obverse.service.conf <<EOF
HiddenServiceDir /var/lib/tor/services/home-obverse
HiddenServicePort 80 127.69.69.2:8080
EOF
cat >/etc/tor/torrc.d/home-reverse.service.conf <<EOF
HiddenServiceDir /var/lib/tor/services/home-reverse
HiddenServicePort 80 127.69.69.6:8080
EOF
cat >/etc/tor/torrc.d/sacredgit.service.conf <<EOF
HiddenServiceDir /var/lib/tor/services/sacredgit
HiddenServicePort 80 127.69.69.3:3000
HiddenServicePort 22 127.69.69.3:3022
EOF
rc-service tor restart
#### Configure the Git server
SACREDGIT_ONION=$(cat /var/lib/tor/services/sacredgit/hostname)
if ! test -e /etc/gogs/conf/app.ini.dist; then
cp /etc/gogs/conf/app.ini /etc/gogs/conf/app.ini.dist
fi
# We only set the config file if it doesn't have our revision key, which you can randomly generate out of band.
# This is because gogs overwrites its own config file when changes are made in the admin UI.
# If making changes here, make sure to first retrieve the latest config file from the remote system.
REVISION_KEY=LicoriceFlashyUnrentedWildlandParalyzeOverdress
if ! grep -q "$REVISION_KEY" /etc/gogs/conf/app.ini; then
cat >/etc/gogs/conf/app.ini <<EOF
# REVISION_KEY=$REVISION_KEY
# Docs: https://github.com/gogs/gogs/blob/main/conf/app.ini
BRAND_NAME = SACRED REVISION CONTROL
RUN_USER = gogs
RUN_MODE = prod
[repository]
ROOT = /var/lib/gogs/git
SCRIPT_TYPE = sh
[server]
APP_DATA_PATH = /var/lib/gogs/data
STATIC_ROOT_PATH = /usr/share/webapps/gogs
EXTERNAL_URL = http://$SACREDGIT_ONION/
DOMAIN = $SACREDGIT_ONION
PROTOCOL = http
HTTP_ADDR = 127.69.69.3
HTTP_PORT = 3000
SSH_DOMAIN = $SACREDGIT_ONION
SSH_LISTEN_HOST = 127.69.69.3
SSH_LISTEN_PORT = 22
OFFLINE_MODE = true
DISABLE_SSH = false
SSH_PORT = 3022
START_SSH_SERVER = true
[database]
DB_TYPE = sqlite3
PATH = /var/lib/gogs/db/gogs.db
SSL_MODE = disable
TYPE = sqlite3
HOST = 127.0.0.1:5432
NAME = gogs
USER = gogs
PASSWORD =
[security]
SECRET_KEY = $(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32)
INSTALL_LOCK = true
[email]
ENABLED = false
[auth]
DISABLE_REGISTRATION = true
[session]
PROVIDER_CONFIG = /var/cache/gogs/sessions
PROVIDER = file
[picture]
AVATAR_UPLOAD_PATH = /var/lib/gogs/avatars
DISABLE_GRAVATAR = true
ENABLE_FEDERATED_AVATAR = false
[attachment]
PATH = /var/lib/gogs/attachements
[log]
ROOT_PATH = /var/log/gogs
MODE = file
LEVEL = Info
[mailer]
ENABLED = false
[service]
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
DISABLE_REGISTRATION = true
ENABLE_CAPTCHA = true
REQUIRE_SIGNIN_VIEW = false
EOF
fi
# This will make administration easier
# Root can run 'rungogs ...' just like the gogs user can run 'gogs ...'.
# Without this you have to su to the 'gogs' user (sudo doesn't work)
# and export environment variables stored in /etc/conf.d/gogs (dot-sourcing it doesn't work).
cat >/usr/local/bin/rungogs <<EOF
#!/bin/sh
set -eu
# Get the variables from the Alpine gogs conf file
. /etc/conf.d/gogs
# Change to a directory that \$GOGS_USER will have access to
cd /tmp
echo "$(cat <<ENDSU
# Export all the vars found in the gogs environment file
# Without doing this,
set -a
. /etc/conf.d/gogs
set +a
# Actually run gogs
gogs \$@
ENDSU
)" | su -l \$GOGS_USER
EOF
chmod 700 /usr/local/bin/rungogs
rc-update add gogs default
# Changing the config file requires a restart
rc-service gogs restart
echo "Tor local service names and onion names:"
for hname in /var/lib/tor/services/*/hostname; do
echo "$hname :: $(cat $hname)"
done
Makefile
I also use a Makefile for building and deploying the site.
Makefile
# This name must match the name you set up in your ~/.ssh/config to connect to the remote host over Tor
# You can set this to another value when running `make`, like `make SERVER=sacredground.onion`.
SERVER = sacredground.ip
.PHONY: help
help: ## Show this help
@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
.PHONY: clearnet
clearnet: ## Build the static site for the clearnet environment
hugo mod get
hugo \
--environment clearnet \
--cleanDestinationDir \
--ignoreCache \
--printPathWarnings \
--minify \
--destination public/clearnet
.PHONE: deploy-clearnet
deploy-clearnet: clearnet ## Deploy the clearnet site
time rsync \
-zrvP \
--perms \
--chmod=ugo=rX \
--progress \
--delete \
public/clearnet/ \
${SERVER}:/var/lib/nginx/sacredground-clearnet/
# NOTE: We pass --minify to normalize source code, maybe making it a bit harder to identify the author
.PHONY: shrine
shrine: ## Build the static site for the shrine environment
hugo mod get
hugo \
--environment onion \
--cleanDestinationDir \
--ignoreCache \
--printPathWarnings \
--minify \
--destination public/onion
.PHONY: deploy-shrine
deploy-shrine: shrine ## Deploy the shrine site
time rsync \
-zrvP \
--perms \
--chmod=ugo=rX \
--progress \
--delete \
public/onion/ \
${SERVER}:/var/lib/nginx/sacredground-onion/
.PHONY: deploy-shrine-both
deploy-shrine-both: deploy-shrine deploy-clearnet ## Deploy both the onion and clearnet shrine
.PHONY: provision
provision: ## Provision the remote server
scp ./content/howto/provision.sh ${SERVER}:/root/
ssh ${SERVER} sh /root/provision.sh
.PHONY: provision-ext
provision-ext: ## Run provisioning extension script on the remote server
scp ./content/howto/provision-ext.sh ${SERVER}:/root/
ssh ${SERVER} sh /root/provision-ext.sh
# hugo mod clean, via <https://discourse.gohugo.io/t/hugo-theme-and-module-stopped-working-locally/38124/2>
.PHONY: clean
clean: ## Clean up anything that the deploy might have saved locally: generated HTML, Go mod cache, etc
rm -rf public
hugo mod clean --all
.PHONY: dev
dev: ## Run the site in dev mode
hugo mod get
hugo server \
--buildDrafts \
--buildFuture \
--printPathWarnings \
--bind 0.0.0.0
.PHONY: backup
backup: ## Make a backup and save it locally
$(eval NOW=$(shell date +%Y%m%d-%H%M%S))
mkdir -p backups/
ssh "${SERVER}" "tar -c /var/lib/tor/services /var/lib/gogs/ /etc/ | bzip2" > "backups/${SERVER}.backup.$(NOW).tar.bzip2.incomplete"
mv "backups/${SERVER}.backup.$(NOW).tar.bzip2.incomplete" "backups/${SERVER}.backup.$(NOW).tar.bzip2"
.PHONY: deploy-home-obverse-onion
deploy-home-obverse-onion: ## Deploy the home obverse (regular) onion site. Must have already been built.
time rsync \
-zrvP \
--perms \
--chmod=ugo=rX \
--progress \
--delete \
../me.micahrl.com/public/onion-obverse/ \
${SERVER}:/var/lib/nginx/home-obverse/
.PHONY: deploy-home-reverse-onion
deploy-home-reverse-onion: ## Deploy the home reverse (mirror) onion site. Must have already been built.
time rsync \
-zrvP \
--perms \
--chmod=ugo=rX \
--progress \
--delete \
../me.micahrl.com/public/onion-reverse/ \
${SERVER}:/var/lib/nginx/home-reverse/
.PHONY: deploy-home-onions
deploy-home-onions: deploy-home-obverse-onion deploy-home-reverse-onion ## Deploy already-built onion sites to the server
Running make
by itself just prints a help message,
which is generated automatically by the comments in the Makefile:
> make
backup Make a backup and save it locally
clean Clean up anything that the deploy might have saved locally: generated HTML, Go mod cache, etc
clearnet Build the static site for the clearnet environment
deploy-onion Deploy the onion site
dev Run the site in dev mode
help Show this help
onion Build the static site for the onion environment
provision-ext Run provisioning extension script on the remote server
provision Provision the remote server
Configuring gogs
I deployed gogs to my onion service, so that I can publish the source code for the shrine and the source code for the theme.
After running provision-ext.sh
,
I can use Tor Browser to bring it up by connecting to the
.onion address of my Gogs hidden service.
Screenshot of the gogs first-run page
Once the first-run configuration is complete:
- Make sure the version of the config file inside of
provision-ext.sh
matches any changes that might have been made during first-run configuration. Changing gogs settings in the admin UI will modify that file directly, so it’s important to make sure we don’t overwrite it the next time we deploy. (See also theREVISION_KEY
environment variable.) - It redirected me to https:// after the first install, and didn’t seem to create the admin user for me.
Fixed with:
rungogs admin create-user --name shrineadmin --password LOLPASS --email root@localhost --admin
To set up repositories and push to them:
- Log in as the new
shrineadmin
user and upload an SSH key- Use a different SSH key than the one used for connecting to the VPS
ssh-keygen -t ed25519 -f ~/.ssh/shrineadmin_ed25519 -C ""
- Add that SSH key to
http://gitserverasdfasdfasdf.onion/user/settings/ssh
- Create a new repo for the shrine called
sacredground
and one for the theme calledhugo-theme-onion
- Note that when creating this it maybe tell you to use an SSH URL like
ssh://gogs@gitserverasdfasdfasdf.onion:3022/shrineadmin/sacredground.git
, with the port 3022. This is not correct, we will connect over port 22 as that is what our Tor hidden service is configured to use.
Add another item to ~/.ssh/config
.
Note that we use the gogs
user, NOT our username.
(Similar to git-over-SSH GitHub, where we use git@github.com
rather than our Github username.)
Host sacredgit.onion
HostName asdfqwerzxcv....onion
User gogs
IdentityFile ~/.ssh/shrineadmin_ed25519
ProxyCommand nc -x 127.0.0.1:9050 %h %p
Then you can run commands like
## Test that SSH works properly
ssh sacredgit.onion
## Add a git remote
git remote add sacredgit sacredgit.onion:shrineadmin/sacredground.git
## Push your local repo
git push -u sacredgit master
WARNING:
if you are running this on your own machine,
beware that your git commits may contain identifyable information!
Make sure you’ve set user.name
and user.email
like:
git config user.name "Shrine Admin"
git config user.email "root@localhost"
Making a mistake here could cost you your anonymity. Mistakes like this are the major reason to use Tails or Qubes instead.
And make sure that all HISTORICAL commits also don’t contain any links to your offline identity. If you need to, you can change the author of historical commits by rewriting all commits in the repository. Make sure to back up your repo first, and then run a command like this:
git-filter-branch-example.sh
#!/bin/sh
set -eu
# Change every commit in this repo to be owned by the shrine admin
git filter-branch --env-filter '
OLD_EMAIL="your-real-email-oops@example.com"
NEW_EMAIL="root@localhost"
NEW_NAME="Shrine Admin"
if test "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL"
then
GIT_AUTHOR_EMAIL=$NEW_EMAIL
GIT_AUTHOR_NAME=$NEW_NAME
fi
if test "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL"
then
GIT_COMMITTER_EMAIL=$NEW_EMAIL
GIT_COMMITTER_NAME=$NEW_NAME
fi
' -- --all
An Onion service for my site
I also generate an Onion service for my main website (clearnet, onionlink) and its mirror universe (clearnet, onionlink) making them accessible over Tor.
Appendix
Other references
- https://www.privacytools.io/ including /privacy-email/
- https://www.reddit.com/r/Monero/comments/rkubnb/tutanota_to_accept_monero_and_bitcoin_as_forms_of/
- https://github.com/alecmuffett/eotk - not doing this for my site bc it requires the site to serve my site and the onion site, and pay for a TLS certificate
- https://blog.paranoidpenguin.net/2020/02/how-to-configure-hugo-as-a-tor-hidden-service/
- DNM Bible, a guide for buying from darknet markets. Some of the recommendations given in this document are less careful than the recommendations in the DNM Bible; even for users who do not plan to buy illegal items from darknet markets on Tor, it’s worth reading the guide to understand how to improve security further.
Miscellaneous onion site resources
- DuckDuckGo (clearnet, onionlink)
- 0xacab (clearnet, onionlink) , a GitLab instance run by RiseUp.
- Cryptwerk is a directory of business that accept cryptocurrencies. This includes hosting companies, providing a list of alternatives to Njalla.
Onion site directories
This page is not intended to be a comprehensive list of Onion services. However, it can be useful to have a starting point for exploring what’s available on Tor.
- Wikipedia’s list of Tor onion services
- dark.fail (clearnet, onionlink)
- tor.taxi (clearnet, onionlink)