09 September 2018

BitWarden password manager self-hosted on free Google Cloud instance

BitWarden on free Google Compute Engine

enter image description here
A friend mentioned the BitWarden password manager to me yesterday and I had to confess that I'd never heard of it. I started researching it and was impressed by what I found: it's free, open-source, feature-packed, fully cross-platform (with Windows/Linux/MacOS desktop clients, Android/iOS mobile apps, and browser extensions for Chrome/Firefox/Opera/Safari/Edge/etc), and even offers a self-hosted option.

I wanted to try out the self-hosted setup, and I discovered that the official distribution works beautifully on an n1-standard-1 1-vCPU Google Compute Engine instance - but that would cost me an estimated $25/mo to run after my free Google Cloud Platform trial runs out. And I can't really scale that instance down further because the embedded database won't start with less than 2GB of RAM.

I then came across this comment on Reddit which discussed in somewhat-vague terms the steps required to get BitWarden to run on the free f1-micro instance, and also introduced me to the community-built bitwarden_rs project which is specifically designed to run a BW-compatible server on resource-constrained hardware. So here are the steps I wound up taking to get this up and running.

Spin up a VM

Easier said than done, but head over to https://console.cloud.google.com/ and fumble through:

  1. Creating a new project (or just add an instance to an existing one).
  2. Creating a new Compute Engine instance, selecting f1-micro for the Machine Type and ticking the Allow HTTPS traffic box.
  3. (Optional) Editing the instance to add an ssh-key for easier remote access.

Configure Dynamic DNS

Because we're cheap and don't want to pay for a static IP.

  1. Log in to the Google Domain admin portal and create a new Dynamic DNS record. This will provide a username and password specific for that record.

  2. Log in to GCE instance and run sudo apt-get update followed by sudo apt-get install ddclient. Part of the install process prompts you to configure things... just accept the defaults and move on.

  3. Edit the ddclient config file to look like this, substituting the username, password, and FDQN from Google Domains:

     $ sudo vi /etc/ddclient.conf
         # Configuration file for ddclient generated by debconf
         # /etc/ddclient.conf
  4. sudo vi /etc/default/ddclient and make sure that run_daemon="true":

    # Configuration for ddclient scripts 
    # generated from debconf on Sat Sep  8 21:58:02 UTC 2018
    # /etc/default/ddclient
    # Set to "true" if ddclient should be run every time DHCP client ('dhclient'
    # from package isc-dhcp-client) updates the systems IP address.
    # Set to "true" if ddclient should be run every time a new ppp connection is 
    # established. This might be useful, if you are using dial-on-demand.
    # Set to "true" if ddclient should run in daemon mode
    # If this is changed to true, run_ipup and run_dhclient must be set to false.
    # Set the time interval between the updates of the dynamic DNS name in seconds.
    # This option only takes effect if the ddclient runs in daemon mode.
  5. Restart the ddclient service - twice for good measure (daemon mode only gets activated on the second go because reasons):

    $ sudo systemctl restart ddclient
    $ sudo systemctl restart ddclient
  6. After a few moments, refresh the Google Domains page to verify that your instance's external IP address is showing up on the new DDNS record.

Install Docker

Steps taken from here.

  1. Update apt package index:

    $ sudo apt-get update
  2. Install package management prereqs:

    $ sudo apt-get install \
        apt-transport-https \
        ca-certificates \
        curl \
        gnupg2 \
  3. Add Docker GPG key:

    $ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
  4. Add the Docker repo:

    $ sudo add-apt-repository \
        "deb [arch=amd64] https://download.docker.com/linux/debian \
        $(lsb_release -cs) \
  5. Update apt index again:

    $ sudo apt-get update
  6. Install Docker:

    $ sudo apt-get install docker-ce

Install Certbot and generate SSL cert

Steps taken from here

  1. Add stretch-backports repo:

    $ sudo add-apt-repository \
        "deb https://ftp.debian.org/debian \
        stretch-backports main"
  2. Install Certbot:

    $ sudo apt-get install certbot -t stretch-backports
  3. Generate certificate:

    $ sudo certbot certonly --standalone -d [FQDN]
  4. Create a directory to store the new certificates and copy them there:

    $ sudo mkdir -p /ssl/keys/
    $ sudo cp -p /etc/letsencrypt/live/[FQDN]/fullchain.pem /ssl/keys/
    $ sudo cp -p /etc/letsencrypt/live/[FQDN]/privkey.pem /ssl/keys/

Set up bitwarden_rs

Using the container image available here

  1. Let's just get it up and running first:

    $ sudo docker run -d --name bitwarden \
        -e ROCKET_TLS={certs='"/ssl/fullchain.pem", key="/ssl/privkey.pem"}' \
        -e ROCKET_PORT='8000' \
        -v /ssl/keys/:/ssl/ \
        -v /bw-data/:/data/ \
        -v /icon_cache/ \
        -p \
  2. At this point you should be able to point your web browser at https://[FQDN] and see the BitWarden login screen. Click on the Create button and set up a new account. Log in, look around, add some passwords, etc. Everything should work just fine (with a few exceptions).

  3. Unless you want to host passwords for all of the Internet you'll probably want to disable signups at some point by adding the env option SIGNUPS_ALLOWED=false. And you'll need to set DOMAIN=https://[FQDN] if you want to use U2F authentication:

    $ sudo docker stop bitwarden
    $ sudo docker rm bitwarden
    $ sudo docker run -d --name bitwarden \
        -e ROCKET_TLS={certs='"/ssl/fullchain.pem",key="/ssl/privkey.pem"'} \
        -e ROCKET_PORT='8000' \
        -e SIGNUPS_ALLOWED=false \
        -e DOMAIN=https://[FQDN] \
        -v /ssl/keys/:/ssl/ \
        -v /bw-data/:/data/ \
        -v /icon_cache/ \
        -p \

Install bitwarden_rs as a service

So we don't have to keep manually firing this thing off.

  1. Create a script to stop, remove, update, and (re)start the bitwarden_rs container:

    $ sudo vi /usr/local/bin/start-bitwarden.sh
        docker stop bitwarden
        docker rm bitwarden
        docker pull mprasil/bitwarden
        docker run -d --name bitwarden \
                -e ROCKET_TLS={certs='"/ssl/fullchain.pem",key="/ssl/privkey.pem"'} \
                -e ROCKET_PORT='8000' \
                -e SIGNUPS_ALLOWED=false \
                -e DOMAIN=https://[FQDN] \
                -v /ssl/keys/:/ssl/ \
                -v /bw-data/:/data/ \
                -v /icon_cache/ \
                -p \
    $ sudo chmod 744 /usr/local/bin/start-bitwarden.sh
  2. And add it as a systemd service:

    $ sudo vi /etc/systemd/system/bitwarden.service
        Description=BitWarden container
        ExecStop=/usr/bin/docker stop bitwarden
    $ sudo chmod 644 /etc/systemd/system/bitwarden.service
  3. Try it out:

    $ sudo systemctl start bitwarden
    $ sudo systemctl status bitwarden
        ● bitwarden.service - BitWarden container
           Loaded: loaded (/etc/systemd/system/bitwarden.service; enabled; vendor preset: enabled)
           Active: deactivating (stop) since Sun 2018-09-09 03:43:20 UTC;   1s ago
          Process: 13104 ExecStart=/usr/local/bin/bitwarden-start.sh (code=exited, status=0/SUCCESS)
         Main PID: 13104 (code=exited, status=0/SUCCESS); Control PID:  13229 (docker)
            Tasks: 5 (limit: 4915)
           Memory: 9.7M
              CPU: 375ms
           CGroup: /system.slice/bitwarden.service
                     └─13229 /usr/bin/docker stop bitwarden
        Sep 09 03:43:20 bitwarden bitwarden-start.sh[13104]: Status: Image is up to date for mprasil/bitwarden:latest
        Sep 09 03:43:20 bitwarden bitwarden-start.sh[13104]:        ace64ca5294eee7e21be764ea1af9e328e944658b4335ce8721b99a33061d645


If all went according to plan, you've now got a highly-secure open-source full-featured cross-platform password manager running on an Always Free Google Compute Engine instance resolved by Google Domains dynamic DNS. Very slick!

18 July 2018

Zolo Liberty True Wireless In-Ear Headphones

I listen to a lot of music at work - easily 4-6 hours every day. For the past nine months, I’ve been using (and loving) the much-maligned Google Pixel Buds to get through the day. For the last week, though, I’ve been wearing the Zolo Liberty True Wireless In-Ear Headphones from Anker’s audio brand. These truly-wireless Bluetooth headphones sound great and have incredible stamina, but how do they compare to my beloved Pixel Buds?


Many have complained about the Pixel Buds’ one-size-fits-all approach, and those users will be pleased to learn that the Zolo Liberty headphones come with liquid silicone tips in four different sizes. Also included are GripFit “Jackets” in four sizes which can be slipped over the body of the earbuds to further adjust the fit. After selecting the best combination of tips and jackets for my ears, I gently pushed the Liberty headphones into my ears and rotated them to the rear as instructed to lock them securely in place. It’s a very solid fit, and these earbuds aren’t going to fall out even with heavy jostling from working out.

That said, fitment is a very personal metric to quantify, and every pair of ears is different. While the GripFit EarTips and Jackets provided a very secure fit, none of the various combinations I tried were particularly comfortable for me. The truly wireless design of the Zolo Liberty headphones means that each earbud needs to contain its own battery, Bluetooth radio, and other electronics, while the wire tethering the two Pixel Buds allows the left side to house the battery while the right holds the radio, touch sensor, and other electrical bits. The result is that the Liberty headphones are considerably bulkier and heavier than the Pixel Buds. I truly could forget that I was wearing the Pixel Buds, but I always felt the Libertys in my ears and often experienced a bit of soreness after extended listening sessions. Your mileage will likely vary, but the Pixel Buds are far more comfortable for me.

17 March 2018

Portable 45W Power for the Google Pixelbook!

I've been using a Google Pixelbook as my primary computing device for five months now. The Pixelbook has a ton of great qualities: it's unbelievably quick, has an incredible build quality, runs Android apps, packs a fantastic touchscreen that folds flat for tablet usage, and (with developer mode + crouton) can handle just about everything I need to do on a computer. I have also been pleased with the battery life; depending on what I'm doing, I tend to get 6-7 hours of use out of a charge.

Of course, even with great battery life it doesn't hurt to have backup power available - and the convenience of dual USB-C ports should make that pretty easy to accomplish, right? I should just be able to grab any power bank which advertises 30W or 45W USB Power Delivery and get started charging on the go, right? 

Unfortunately, it hasn't worked out to be that easy. I've been quietly and casually testing USB-C PD power banks for a month or two now, trying to find one which worked reliably with my Pixelbook. I don't have the skills, knowledge, or resources to do compliance testing like Nathan or Benson, but I used a Plugable USB-C power meter to monitor the current flow and make sure that it didn't drift wildly out of spec. I probably tested a half-dozen power banks before finding one that actually works to my satisfaction. 

I didn't encounter any that I'd consider dangerous; they just didn't work. Some would only draw power from the Pixelbook. One wouldn't pass any power when the Pixelbook was connected. One apparently provided enough power to activate the meter but the measured flow was 0V @ 0A. A few would constantly reboot; one only did so while the meter was connected and seemed to charge the Pixelbook okay without the pass-through meter in-line but that was still clearly incorrect behavior. Without a PD sniffer I don't know why these power banks didn't work but my hunch is negotiation problems. 

All of which brings me to the reason for this post: I found one that works! 

This is the ZMI PowerPack 20000, a 20000mAh battery pack with 45W USB PD output. I was a bit apprehensive since I hadn't heard of the brand before but this has succeeded where power banks from brands like Anker, RAVPower, and dodocool let me down.

While testing the ZMI power bank, I measured a pretty consistent 14.4V @ 2.7A - which exactly matches what my meter reports from the 45W wall charger that came with the Pixelbook. 

It charged the Pixelbook from 67%-87% in about 25 minutes. (The charging rate dropped from 45W (14.4V @ 2.7A) to 30W (14.4V @ 1.8A) somewhere around 85% so that did slow things a little bit. Again, I've observed the same behavior with the original charger). 

As a bonus, the ZMI power bank also has a USB 2.0 hub mode which works great for USB mice and USB storage drives - while still charging the Pixelbook at the appropriate rate.

In subsequent charging tests the ZMI power bank has continued to perform predictably. Again, I'm not able to comment on any of the PD negotiation traffic or USB spec compliance but it seems to me that it is doing exactly what it's supposed to. I haven't had any problems with the power bank trying to charge from the Pixelbook, failing to charge the Pixelbook, or trying to push an unexpected voltage/amperage combination. 

Consistent 45W charging in a small form factor with the added benefit of USB hub functionality at a reasonable $70 price makes the ZMI PowerPack 20000 an easy recommendation.