From 1Password on iCloud to Bitwarden on OpenBSD


I’ve been using 1Password as a password manager for quite so time. The database was on my iCloud account so I could only use it with MacOS and iOS. But it was Free. Now that AgileBits Inc switched to subscription-based, I can’t really upgrade my apps… Time to look for something else. Strongbox + KeePass seemed nice. But I found issues when accessing the same DB from mobile and laptop. Another option is Bitwarden. It’s Open Source, available on many platforms and clients… and it can be hosted on OpenBSD using Rubywarden.

I discovered Bitwarden reading about jcs@’s post years ago. And Rubywarden is way easier to run that the official Docker stuff.

Installing Rubywarden

There is a detailed README file available. I personally don’t use Ansible. So my installation went manual, on OpnBSD 6.6 and started with the prerequisites.

# doas pkg_add git ruby26-bundler sqlite3

As for any OpenBSD daemon, I use a dedicated user to run the software.

# doas useradd -g =uid -u 1010 -c Rubywarden -d /home/rubywarden \
  -m -s /bin/nologin _rubywarden
# doas rm -rf /home/rubywarden/.??*

Getting and updating the software is done using git.

# cd /home
# doas -u _rubywarden git clone
Cloning into 'rubywarden'...
# cd rubywarden
# doas -u _rubywarden bundle26 install
Fetching gem metadata from
Following files may not be writable, so sudo is needed:

As noted by bundle(1), the gems installation require high privileges. But running install using the root user raises a warning about errors that may happen latter on. The use of sudo(8) is recommended. To solve this, set a password for the Rubywarden user and allow it to run commands by editing the sudoers(5) file.

# doas passwd _rubywarden
# doas visudo
_rubywarden ALL=(ALL) SETENV: ALL

I commented out the authorization as soon as gem installation is finished. There are no reasons to keep this allowed more than required.

Time to initialize the Rubywarden database.

# doas -u _rubywarden mkdir db/production
# doas -u _rubywarden env RUBYWARDEN_ENV=production bundle26 \
  exec rake db:migrate

And create an rc.d script that will provide easy daemon management. Looking at the rubywarden.j2 template, I ended up with:

# doas vi /etc/rc.d/rubywarden


# Uncomment ALLOW_SIGNUPS to ... ;-)
env="${env} RUBYWARDEN_ENV=production"
env="${env} PATH=/bin:/usr/bin:/usr/X11R6/bin:/usr/local/bin"
env="${env} HOME=/home/rubywarden/"

daemon_flags="exec rackup -o -p 4567"

. /etc/rc.d/rc.subr

rc_start() {
  ${rcexec} "cd /home/rubywarden/ &&
  env ${env} ${daemon} ${daemon_flags} 2>&1 |
  logger -p -t rubywarden"

pexp="ruby26 .*rackup -o -p 4567"


rc_cmd $1

# doas chmod 0755 /etc/rc.d/rubywarden

By default, the daemon will not allow sign up. Uncomment the proper line and run the daemon with SIGNUPS (temporarily) allowed. When user(s) is/are created and you want to forbid signups, simply comment the ALLOW_SIGNUPS=1 line in the startup script and restart Rubywarden.

Publishing Rubywarden

You may want to run and access Rubywarden only from LAN ; with or without a VPN. In my case, I’d rather have it accessible from a Reverse Proxy that will do a bit of security filtering. As explained in the file, Rubywarden can be accessed from its own FQDN or hidden behind a sub path.

Using Apache, look for things like ProxyPass and Location. Using relayd(8), match request and forward directives do the job.

I plan to use relayd(8) soon. But as for now, I’m using Apache. The relevant part for using it’s own FQDN is:

SSLProxyEngine on
ProxyPass "/"
ProxyPassReverse "/"

<Location "/">
  Options None
  Require all denied

<LocationMatch "^/(api|attachments|identity|icons)/">
  Options None
  Require all granted

When running Rubywarden in subpath mode, daemon is started like so

# doas -u _rubywarden env RUBYWARDEN_ENV=production             \
  RUBYWARDEN_BASE_URL=/secrets/api                             \
  RUBYWARDEN_IDENTITY_BASE_URL=/secrets/identity               \
  RUBYWARDEN_ICONS_URL=/secrets/icons                          \
  RUBYWARDEN_ATTACHMENTS_URL=/secrets/attachments bundle26     \
  exec rackup -o -p 4568

and the Apache configuration goes:

Define rubywarden secrets
ProxyPass "/${rubywarden}/"${rubywarden}/
ProxyPassReverse "/${rubywarden}/"${rubywarden}/

<Location "/${rubywarden}/">
  Options None
  Require all denied

<LocationMatch "^/${rubywarden}/(api|attachments|identity|icons)/">
  Options None
  Require all granted

Creating a user vault

With ALLOW_SIGNUPS=1 enabled, start Rubywarden. Install and launch your preferred  Bitwarden client, configure the server URL and create a new user account.

When done with user vault creation, switch Rubywarden back to “no signups” mode.

Other Bitwarden clients can be configured specifying the Rubywarden instance URL and user credentials.

Migrating the data from 1Password

The migration steps are described in the file.

I launched 1Password on my Mac. Then used the File / Export function to generate a file in 1Password Interchange Format (.1pif). That file is uncrypted. Think twice before storing it in a directory that will be synced with your Google / OneDrive / Dropbox / … account.

The .1pif file was then transfered to the Rubywarden server. And imported using the tools/1password_import.rb script.

# cd /home/rubywarden/
# doas -u _rubywarden env RUBYWARDEN_ENV=production bundle26 \
  exec ruby26 tools/1password_import.rb -f ~/data.1pif -u user@domain.tld
master password for user@domain.tld:
converting login Skype (jojo)...
converting login Deezer...

Trash that 1pif file now. It is not needed any more.

Backup and restore

Backuping the data is as simple as keeping a copy of the db/production/jwt-rsa.key and db/production/production.sqlite3 files in a safe place.

Restoring the data to another server requires putting those two files back in the db directory. Not sure if jwt-rsa.key has to be restored on a new server or if a new key can be used.


I’ve been using the *warden software for a week or so and it’s great. When you create a new item, it is synced into Rubywarden and available to the other Bitwarden clients. This only annoying thing is that you have to force synchronisation on clients to get the latest data. Looking at online complains, it seems to be a client issue… It might be corrected soon.

Thanks again Joshua (jcs@) for that great piece of software!