Bitcoin is easy, lightning is hard.
I run my own bitcoin node but use a hosted bitcoin service provider for my lightning infrastructure.
You will learn how to semi-self-sovereignly accept bitcoin payments using BTCPay Server. And have your own lightning addresses (like an email address, but for your Bitcoin) with your own domain.
Public-facing stuff I usually host on a VPS at hetzner, personal stuff I host in my homelab. I usually do not want publicly accessible services to be dependent on my homelab because I actually use my homelab as a lab, stuff is changing all the time. But for a full-node you currently need round about 750 GB of disk space which is expensive in the cloud. And for my use-case it is not important for the node to be always up and running. I do not yet have a use-case where I need to verify incoming payments. You can always receive BTC on chain by just providing a static address. And for small amounts lightning is recommended anyway.
hosting
On my homeserver I use NixOS. Following is an abbrevated version of my /etc/nixos/configuration.nix
. If you are familiar with containers (and I suppose you are when you are reading this) it should be no problem to translate this to a corresponding run-command or compose-file. Keep in mind it can take weeks for your node to fully sync.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
{ config, pkgs, ... }:
{
systemd.tmpfiles.rules = [
"d /mnt/data/bitcoincore 0777 simon users -"
"d /mnt/data/nbxplorer 0777 simon users -"
"d /mnt/data/nbxplorer-db 0777 simon users -"
"d /mnt/data/nbxplorer-tailscale 0777 simon users -"
];
systemd.services.create-bitcoin-network = with config.virtualisation.oci-containers; {
serviceConfig.Type = "oneshot";
script = ''
${pkgs.podman}/bin/podman network exists bitcoin || \
${pkgs.podman}/bin/podman network create bitcoin
'';
};
virtualisation.oci-containers.containers = {
bitcoincore = {
image = "bitcoin/bitcoin";
autoStart = true;
extraOptions = [ "--network=bitcoin" ];
volumes = [
"/mnt/data/bitcoincore:/bitcoin/.bitcoin/"
];
cmd = [
"-listen=1"
"-server=1"
"-txindex=1"
"-rpcbind=0.0.0.0"
"-rpcport=8332"
"-rpcallowip=0.0.0.0/0"
"-rpcuser=bitcoin"
"-rpcpassword=bitcoin"
"-disablewallet=1"
"-whitelist=nbxplorer"
"-zmqpubrawtx=tcp://0.0.0.0:28332"
"-zmqpubrawblock=tcp://0.0.0.0:28332"
"-zmqpubhashblock=tcp://0.0.0.0:28332"
"-zmqpubhashtx=tcp://0.0.0.0:28332"
];
environment = {
BITCOIN_DATA = "/bitcoin/.bitcoin";
};
};
nbxplorer = {
image = "docker.io/nicolasdorier/nbxplorer:2.5.28";
autoStart = true;
autoRemoveOnStop = false;
extraOptions = [
"--network=container:nbxplorer-tailscale"
];
environment = {
NBXPLORER_NOAUTH = "1";
NBXPLORER_CHAINS = "btc";
NBXPLORER_NETWORK = "mainnet";
NBXPLORER_SIGNALFILEDIR = "/datadir";
NBXPLORER_BIND = "0.0.0.0:32838";
NBXPLORER_BTCRPCURL = "http://bitcoincore:8332";
NBXPLORER_BTCNODEENDPOINT = "bitcoincore:8333";
NBXPLORER_BTCRPCUSER = "bitcoin";
NBXPLORER_BTCRPCPASSWORD = "bitcoin";
NBXPLORER_POSTGRES = "Username=nbxplorer;password=nbxplorer;Host=nbxplorer-db;Port=5432;Database=nbxplorer";
};
volumes = [
"/mnt/data/nbxplorer:/datadir"
"/mnt/data/bitcoincore:/root/.bitcoin:ro"
];
};
nbxplorer-db = {
image = "postgres";
autoStart = true;
extraOptions = [ "--network=bitcoin" ];
environment = {
POSTGRES_PASSWORD = "nbxplorer";
POSTGRES_USER = "nbxplorer";
POSTGRES_DB = "nbxplorer";
};
volumes = [
"/mnt/data/nbxplorer-db:/var/lib/postgresql/data"
];
};
nbxplorer-tailscale = {
image = "docker.io/tailscale/tailscale:latest";
autoStart = true;
extraOptions = [ "--network=bitcoin" ];
autoRemoveOnStop = false;
hostname = "nbxplorer";
capabilities = {
net_admin = true;
sys_module = true;
};
volumes = [
"/mnt/data/nbxplorer-tailscale:/var/lib/tailscale"
"/dev/net/tun:/dev/net/tun"
"/etc/nixos/dozzle:/config"
];
environment = {
TS_AUTHKEY = "tskey-auth-asdf";
TS_STATE_DIR = "/var/lib/tailscale";
};
};
};
}
BTCPay connects to a bitcoin node not directly but through nbxplorer which I add with the tailscale-sidecar container to my tailnet.
My VPS is a Ubuntu instance running the publicly accessible BTCPay.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
services:
btcpay:
image: docker.io/btcpayserver/btcpayserver:2.1.6
restart: unless-stopped
environment:
BTCPAY_BIND: "0.0.0.0:23000"
BTCPAY_POSTGRES: "Username=btcpay;password=btcpay;Host=btcpay-db;Port=5432;Database=btcpay"
BTCPAY_NETWORK: "mainnet"
BTCPAY_CHAINS: "btc"
BTCPAY_ROOTPATH: "/"
BTCPAY_BTCEXPLORERURL: "http://nbxplorer:32838"
volumes:
- ./data/btcpay:/datadir
- ./data/plugins:/root/.btcpayserver/Plugins
network_mode: service:tailscale
btcpay-db:
image: postgres
restart: unless-stopped
environment:
POSTGRES_PASSWORD: "btcpay"
POSTGRES_USER: "btcpay"
POSTGRES_DB: "btcpay"
volumes:
- ./data/db:/var/lib/postgresql/data
tailscale:
image: tailscale/tailscale:latest
hostname: btcpay
environment:
TS_AUTHKEY:
TS_STATE_DIR: /var/lib/tailscale
TS_SERVE_CONFIG: /config/serve.json
volumes:
- ./data/tailscale:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
- ./config:/config
cap_add:
- net_admin
- sys_module
restart: unless-stopped
ports:
- 100.107.87.40:23000:23000
networks:
default:
name: npm
I actually expose BTCPay in two different ways.
One is via Tailscale funnel with the following config in ./config/serve.json
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"btcpay.my-tailnet.ts.net:443": {
"Handlers": {
"/": {
"Proxy": "http://localhost:23000"
}
}
}
},
"AllowFunnel": {
"btcpay.my-tailnet.ts.net:443": true
}
}
The other is via nginx-proxy-manager:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
services:
app:
image: jc21/nginx-proxy-manager:latest
restart: unless-stopped
ports:
- 80:80
- 443:443
- 100.107.87.40:8080:81
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
default:
name: npm
You will need to setup a proxy host forwarding to your btcpay’s tailscale sidecar on port 23000 like http://btcpayserver-tailscale-1:23000
. A DNS A-record for the Domain Name (btcpay.simonhaas.eu) is pointing to the vps’s public IP.
config
Inside BTCPay the config to accept on-chain Bitcoin payments is straight forward. Just connect a existing wallet or create a new one.
For Lightning payments I use strike.me because I do not want to manage my own lightning channels. For that you need to install the corresponding strike plugin within BTCPay. The the reset is again self explanatory. Create a API key for strike and add it to BTCPay.
Now you can create a Pay Button like mine and all the other features of BTCPay.
lightning addresses
Lighting addresses are like email addresses but for bitcoin - but simpler. In BTCPay under Lightning Address
you can create a lightning address like simonhaas@btcpay.simonhaas.eu
But just like my email address is not simonhaas@mail.simonhaas.eu
but `simonhaas@simonhaas.eu I also would like to get rid of the subdomain of my btcpay server. It just looks cleaner and seperates the address from the underlying implementation. As with all things a basic understanding of how things works helps a lot.
BTCPay serves a specific JSON object under https://btcpay.simonhaas.eu/.well-known/lnurlp/simonhaas. Now we just have to serve the same content from https://simonhaas.eu/.well-known/lnurlp/simonhaas.
1
2
3
4
5
6
7
8
{
"callback": "https://btcpay.simonhaas.eu/BTC/UILNURL/pay/lnaddress/simonhaas",
"metadata": "[[\"text/identifier\",\"simonhaas@btcpay.simonhaas.eu\"],[\"text/plain\",\"Paid to Simon Haas\"]]",
"tag": "payRequest",
"minSendable": 1000,
"maxSendable": 612000000000,
"commentAllowed": 2000
}
I just have added a file to the static site acting as my homepage https://github.com/SimonHaas/simonhaas.github.io/blob/main/.well-known/lnurlp/simonhaas.
Now if someone wants to send you Bitcoin via Lightning you can just give them your lightning address and their wallet will know what to do: simonhaas@simonhaas.eu
If you only want a lightning address with your own domain but do not care about BTCPay you can achieve it by simply serving the JSON content of your strike lightning address. Strike provides me with the lightning address simonhaas@strike.me
, https://strike.me/.well-known/lnurlp/simonhaas.