new post: silverbullet-self-hosted-knowledge-management

This commit is contained in:
John Bowdre 2024-08-21 21:56:51 -05:00
parent bae5c63a70
commit e6180a1815

View file

@ -1,9 +1,8 @@
--- ---
title: "SilverBullet: A Brilliant Self-Hosted Knowledge Management Web App" title: "SilverBullet: Self-Hosted Knowledge Management Web App"
date: 2024-08-12 date: "2024-08-22T02:56:12Z"
# lastmod: 2024-08-12 # lastmod: 2024-08-12
draft: true description: "Deploying SilverBullet with Docker Compose, and accessing it from anywhere with Tailscale and Cloudflare Tunnel."
description: "This is a new post about..."
featured: false featured: false
toc: true toc: true
reply: true reply: true
@ -12,13 +11,12 @@ tags:
- cloudflare - cloudflare
- containers - containers
- docker - docker
- javascript
- linux - linux
- selfhosting - selfhosting
- tailscale - tailscale
--- ---
A few days ago I [posted on my other blog](https://srsbsns.lol/is-silverbullet-the-note-keeping-silver-bullet/) about trying out [SilverBullet](https://silverbullet.md), an open-source self-hosted web-based note-keeping app. SilverBullet has continued to impress me as I use it and learn more about its [features](https://silverbullet.md/SilverBullet@1992). It really fits my multi-device use case much better than Obsidian ever did (even with its paid sync plugin). I [recently posted on my other blog](https://srsbsns.lol/is-silverbullet-the-note-keeping-silver-bullet/) about trying out [SilverBullet](https://silverbullet.md), an open-source self-hosted web-based note-keeping app. SilverBullet has continued to impress me as I use it and learn more about its [features](https://silverbullet.md/SilverBullet@1992). It really fits my multi-device use case much better than Obsidian ever did (even with its paid sync plugin).
In that post, I shared a brief overview of how I set up SilverBullet: In that post, I shared a brief overview of how I set up SilverBullet:
@ -40,11 +38,12 @@ sudo chown john:docker /opt/silverbullet # [tl! .cmd:1]
cd /opt/silverbullet cd /opt/silverbullet
``` ```
### SilverBullet ### SilverBullet Setup
The documentation offers easy-to-follow guidance on [installing SilverBullet with Docker Compose](https://silverbullet.md/Install/Docker), and that makes for a pretty good starting point. The only change I make here is setting the `SB_USER` variable from an environment variable instead of directly in the YAML: The documentation offers easy-to-follow guidance on [installing SilverBullet with Docker Compose](https://silverbullet.md/Install/Docker), and that makes for a pretty good starting point. The only change I make here is setting the `SB_USER` variable from an environment variable instead of directly in the YAML:
```yaml ```yaml
# torchlight! {"lineNumbers":true} # torchlight! {"lineNumbers":true}
# docker-compose.yml
services: services:
silverbullet: silverbullet:
image: zefhemel/silverbullet image: zefhemel/silverbullet
@ -64,17 +63,22 @@ services:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
``` ```
I used a password manager to generate a random password *and username*, and I store those in a `.env` file alongside the Docker Compose configuration. For example: I used a password manager to generate a random password *and username*, and I stored those in a `.env` file alongside the Docker Compose configuration; I'll need those credentials to log in to each SilverBullet session. For example:
```text ```shell
# .env
SB_CREDS='alldiaryriver:XCTpmddGc3Ga4DkUr7DnPBYzt1b' SB_CREDS='alldiaryriver:XCTpmddGc3Ga4DkUr7DnPBYzt1b'
``` ```
### Tailscale That's all that's needed for running SilverBullet locally, and I *could* go ahead and `docker compose up -d` to get it running. But I really want to be able to access my notes from other systems too, so let's move on to enabling remote access right away.
That's all that's really needed for running SilverBullet locally, but I also want to be able to access the application from any device connected to my Tailscale tailnet. So I add in a [Tailscale sidecar](/tailscale-serve-docker-compose-sidecar/#compose-configuration), and update the `silverbullet` service to share Tailscale's network:
### Remote Access
#### Tailscale
It's no secret that I'm a [big fan of Tailscale](/secure-networking-made-simple-with-tailscale/) so I use Tailscale Serve to enable secure remote access through my tailnet. I just need to add in a [Tailscale sidecar](/tailscale-serve-docker-compose-sidecar/#compose-configuration) and update the `silverbullet` service to share Tailscale's network:
```yaml ```yaml
# torchlight! {"lineNumbers":true} # torchlight! {"lineNumbers":true}
# docker-compose.yml
services: services:
tailscale: # [tl! ++:12 **:12] tailscale: # [tl! ++:12 **:12]
image: tailscale/tailscale:latest image: tailscale/tailscale:latest
@ -102,18 +106,22 @@ services:
- 3000:3000 - 3000:3000
network_mode: service:tailscale # [tl! ++ **] network_mode: service:tailscale # [tl! ++ **]
watchtower: watchtower: # [tl! collapse:4]
image: containrrr/watchtower image: containrrr/watchtower
container_name: silverbullet-watchtower container_name: silverbullet-watchtower
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
``` ```
That of course means adding a few more items to the `.env` file: a [pre-authentication key](https://tailscale.com/kb/1085/auth-keys), the hostname to use for the application's presence on my tailnet, and the `--ssh` extra argument to enable SSH access to the container (not strictly necessary, but can be handy for troubleshooting): That of course means adding a few more items to the `.env` file:
- a [pre-authentication key](https://tailscale.com/kb/1085/auth-keys),
- the hostname to use for the application's presence on my tailnet,
- and the `--ssh` extra argument to enable SSH access to the container (not strictly necessary, but can be handy for troubleshooting).
```text ```shell
# .env
SB_CREDS='alldiaryriver:XCTpmddGc3Ga4DkUr7DnPBYzt1b' SB_CREDS='alldiaryriver:XCTpmddGc3Ga4DkUr7DnPBYzt1b'
TS_AUTHKEY=tskey-auth-[...] [tl! ++:2 **:2] TS_AUTHKEY=tskey-auth-[...] # [tl! ++:2 **:2]
TS_HOSTNAME=silverbullet TS_HOSTNAME=silverbullet
TS_EXTRA_ARGS=--ssh TS_EXTRA_ARGS=--ssh
``` ```
@ -122,6 +130,7 @@ And I need to create a `serve-config.json` file to configure [Tailscale Serve](/
```json ```json
// torchlight! {"lineNumbers":true} // torchlight! {"lineNumbers":true}
// serve-config.json
{ {
"TCP": { "TCP": {
"443": { "443": {
@ -140,17 +149,18 @@ And I need to create a `serve-config.json` file to configure [Tailscale Serve](/
} }
``` ```
### Cloudflare Tunnel #### Cloudflare Tunnel
But what if I want to consult my notes from outside of my tailnet? Sure, I *could* use [Tailscale Funnel](/tailscale-ssh-serve-funnel/#tailscale-funnel) to publish the SilverBullet service on the internet, but (1) funnel would require me to use a URL like `https://silverbullet.tailnet-name.ts.net` instead of simply `https://silverbullet.example.com` and (2) I'm still a little wary of putting a login page on the public web. But what if I want to consult my notes from *outside* of my tailnet? Sure, I *could* use [Tailscale Funnel](/tailscale-ssh-serve-funnel/#tailscale-funnel) to publish the SilverBullet service on the internet, but (1) funnel would require me to use a URL like `https://silverbullet.tailnet-name.ts.net` instead of simply `https://silverbullet.example.com` and (2) I've seen enough traffic logs to not want to expose a login page directly to the public internet if I can avoid it.
[Cloudflare Tunnel](/publish-services-cloudflare-tunnel/) is able to address those concerns without a lot of extra work. I can set up a tunnel at `silverbullet.example.com` and use [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/applications/configure-apps/) to put an additional challenge in front of the login page. [Cloudflare Tunnel](/publish-services-cloudflare-tunnel/) is able to address those concerns without a lot of extra work. I can set up a tunnel at `silverbullet.example.com` and use [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/applications/configure-apps/) to put an additional challenge in front of the login page.
I just need to add a `cloudflared` container to my stack: I just have to add a `cloudflared` container to my stack:
```yaml ```yaml
# torchlight! {"lineNumbers":true} # torchlight! {"lineNumbers":true}
# docker-compose.yml
services: services:
tailscale: tailscale: # [tl! collapse:12]
image: tailscale/tailscale:latest image: tailscale/tailscale:latest
container_name: silverbullet-tailscale container_name: silverbullet-tailscale
restart: unless-stopped restart: unless-stopped
@ -185,26 +195,27 @@ services:
- ./space:/space - ./space:/space
network_mode: service:tailscale network_mode: service:tailscale
watchtower: watchtower: # [tl! collapse:4]
image: containrrr/watchtower image: containrrr/watchtower
container_name: silverbullet-watchtower container_name: silverbullet-watchtower
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
``` ```
To get the required `$CLOUDFLARED_TOKEN`, I have to [create a new `cloudflared` tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/create-remote-tunnel/) in the Cloudflare dashboard, and then I just add the generated value to my `.env` file: To get the required `$CLOUDFLARED_TOKEN`, I [create a new `cloudflared` tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/create-remote-tunnel/) in the Cloudflare dashboard and add the generated token value to my `.env` file:
```text ```shell
# .env
SB_CREDS='alldiaryriver:XCTpmddGc3Ga4DkUr7DnPBYzt1b' SB_CREDS='alldiaryriver:XCTpmddGc3Ga4DkUr7DnPBYzt1b'
TS_AUTHKEY=tskey-auth-[...] TS_AUTHKEY=tskey-auth-[...]
TS_HOSTNAME=silverbullet TS_HOSTNAME=silverbullet
TS_EXTRA_ARGS=--ssh TS_EXTRA_ARGS=--ssh
CLOUDFLARED_TOKEN=eyJhIjo[...]BNSJ9 [tl! ++ **] CLOUDFLARED_TOKEN=eyJhIjo[...]BNSJ9 # [tl! ++ **]
``` ```
Back in the Cloudflare Tunnel setup flow, I select my desired public hostname (`silverbullet.example.com`) and then specify that the backend service is `http://localhost:3000`. Back in the Cloudflare Tunnel setup flow, I select my desired public hostname (`silverbullet.example.com`) and then specify that the backend service is `http://localhost:3000`.
Now I'm ready to start up my containers: Now I'm finally ready to start up my containers:
```shell ```shell
docker compose up -d # [tl! .cmd .nocopy:1,5] docker compose up -d # [tl! .cmd .nocopy:1,5]
@ -216,8 +227,8 @@ docker compose up -d # [tl! .cmd .nocopy:1,5]
✔ Container silverbullet-cloudflared Started ✔ Container silverbullet-cloudflared Started
``` ```
### Cloudflare Access #### Cloudflare Access
The finishing touch will be configuring a bit of extra protection in front of the public-facing login page, and Cloudflare Access makes that very easy. I'll just used the wizard to [add a new web application](https://developers.cloudflare.com/cloudflare-one/applications/configure-apps/) through the Cloudflare Zero Trust dashboard. The finishing touch will be configuring a bit of extra protection in front of the public-facing login page, and Cloudflare Access makes that very easy. I'll just use the wizard to [add a new web application](https://developers.cloudflare.com/cloudflare-one/applications/configure-apps/) through the Cloudflare Zero Trust dashboard.
The first part of that workflow asks "What type of application do you want to add?". I select **Self-hosted**. The first part of that workflow asks "What type of application do you want to add?". I select **Self-hosted**.
@ -228,4 +239,6 @@ I'm then asked to Add Policies, and I have to start by giving a name for my poli
And then I just click through the rest of the defaults. And then I just click through the rest of the defaults.
### Recap ### Recap
So now I have deployed SilverBullet in Docker Compose on a server in my homelab. I can access it from any device on my tailnet at `https://silverbullet.tailnet-name.ts.net` (thanks to the magic of Tailscale Serve). And I can visit it on external devices at `https://silverbullet.example.com` (thanks to Cloudflare Tunnel), So now I have SilverBullet running in Docker Compose on a server in my homelab. I can access it from any device on my tailnet at `https://silverbullet.tailnet-name.ts.net` (thanks to the magic of Tailscale Serve). I can also get to it from outside my tailnet at `https://silverbullet.example.com` (thanks to Cloudflare Tunnel), and but I'll use a one-time passcode sent to my approved email address before also authenticating through the SilverBullet login page (thanks to Cloudflare Access).
I think it's a pretty sweet setup that gives me full control and ownership of my notes and lets me read/write my notes from multiple devices without having to worry about synchronization.