Contents

Bitwarden_rs Traefik Docker Installation With GDrive Backup and Restore

Setting Up Docker

Docker Compose Pre-requisites

  1. Make sure Docker and Docker compose is installed, if not use this script
  2. Creating required folders to use this setup as template for future changes.
1
mkdir -p ${HOME}/docker/traefik/letsencrypt
  1. Create required files
1
2
3
4
touch ${HOME}/docker/docker-compose.yml
touch ${HOME}/docker/.env
touch ${HOME}/docker/traefik/letsencrypt/acme.json
sudo chmod 600 ${HOME}/docker/traefik/letsencrypt/acme.json
  1. create network
1
docker network create traefik_webgateway

SetUp Traefik

  1. Update ${HOME}/docker/.env file
1
2
3
4
5
6
TZ=Europe/Paris
LETSENCRYPT_PATH=/root/docker/traefik/letsencrypt

# BW_CONFIG
BW_CONFIG=/root/docker/bitwarden/data
BW_ADMIN_TOKEN=YOUR_TOKEN_GENERATED_USING_openssl

Paste the output of openssl rand -base64 48 for the value of BW_ADMIN_TOKEN

  1. Update ${HOME}/docker/docker-compose.yml
 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
version: "3.3"

networks:
  traefik:
    external:
      name: traefik_webgateway

services:
  traefik:
    image: "traefik:v2.3"
    container_name: traefik
    restart: always
    command:
      - --entrypoints.web.address=:80
      - --providers.docker.exposedByDefault=false
      # - --log.level=DEBUG
      # - --api.insecure # Don't do that in production
      - --api
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.myresolver.acme.httpchallenge=true
      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
      # - --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
      - --certificatesresolvers.myresolver.acme.email=YOUR_EMAIL
      - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
    ports:
      - "80:80"
      - "443:443"
      # - "8081:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "${LETSENCRYPT_PATH}:/letsencrypt"
    labels:
      #### set core configs
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`YOUR_DOMAIN`)" # && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
      - "traefik.http.routers.traefik.service=api@internal"
      # - "traefik.http.routers.traefik.entrypoints=web"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=myresolver"
      - "traefik.http.routers.traefik.middlewares=authtraefik"
      - "traefik.http.middlewares.authtraefik.basicauth.users=user:$$apr1$$q8eZFHjF$$Fvmkk//V6Btlaf2i/ju5n/" # user/password

      # global redirect to https
      - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"

      # middleware redirect
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
    networks:
      - traefik

BitWarden Setup

Pre-requisites

  1. Create required folders
1
mkdir -p ${HOME}/docker/bitwarden

Setup Bitwarden docker

 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
version: "3.3"

services:
  bitwarden:
    image: bitwardenrs/server:latest
    container_name: bitwarden
    restart: always
    volumes:
      - ${BW_CONFIG}:/data
    environment:
      - ADMIN_TOKEN=${BW_ADMIN_TOKEN}
      - WEBSOCKET_ENABLED=true
    networks:
      - traefik
    labels:
      - traefik.enable=true
      # Entry Point for https
      - traefik.http.routers.bwd.rule=Host(`YOUR_DOMAIN`)
      - traefik.http.routers.bwd.service=bitwarden-service
      - traefik.http.services.bitwarden-service.loadbalancer.server.port=80
      - "traefik.http.routers.bwd.entrypoints=websecure"
      - "traefik.http.routers.bwd.tls=true"
      - "traefik.http.routers.bwd.tls.certresolver=myresolver"
      # websocket
      - traefik.http.routers.bwd-ws.rule=Host(`YOUR_DOMAIN`) && Path(`/notifications/hub`)
      - traefik.http.routers.bwd-ws.service=bitwarden-ws
      - traefik.http.services.bitwarden-ws.loadbalancer.server.port=3012
      - "traefik.http.routers.bwd-ws.entrypoints=websecure"
      - "traefik.http.routers.bwd-ws.tls=true"
      - "traefik.http.routers.bwd-ws.tls.certresolver=myresolver"
      # middlewares
      - "traefik.http.middlewares.bwd-ws=bw-stripPrefix"
      - "traefik.http.middlewares.bw-stripPrefix.stripprefix.prefixes=/notifications/hub"
      - "traefik.http.middlewares.bw-stripPrefix.stripprefix.forceSlash=false"

Bitwarden Backup

Pre-requisites

  1. Create required folders and files
1
2
3
4
5
mkdir -p ${HOME}/docker/bitwarden/backup/archives
mkdir -p ${HOME}/docker/bitwarden/backup/logs
touch ${HOME}/docker/bitwarden/backup/backup.conf
touch ${HOME}/docker/bitwarden/backup/backup.sh
chmod +x ${HOME}/docker/bitwarden/backup/backup.sh
  1. Update ${HOME}/docker/bitwarden/backup/backup.sh with the following content
 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
#!/bin/bash

set -ex

# Use the value of the corresponding environment variable, or the
# default if none exists.
: ${BITWARDEN_ROOT:="${HOME}/docker/bitwarden"}
: ${SQLITE3:="/usr/bin/sqlite3"}
: ${RCLONE:="/usr/bin/rclone"}

DATA_DIR="data"
BACKUP_ROOT="${BITWARDEN_ROOT}/backup"
BACKUP_DIR_NAME="bitwarden-$(date '+%Y%m%d-%H%M')"
BACKUP_DIR_PATH="${BACKUP_ROOT}/${BACKUP_DIR_NAME}"
BACKUP_FILE_DIR="archives"
BACKUP_FILE_NAME="${BACKUP_DIR_NAME}.tar.xz"
BACKUP_FILE_PATH="${BACKUP_ROOT}/${BACKUP_FILE_DIR}/${BACKUP_FILE_NAME}"
DB_FILE="db.sqlite3"

source "${BACKUP_ROOT}"/backup.conf

cd "${BITWARDEN_ROOT}"
mkdir -p "${BACKUP_DIR_PATH}"

# Back up the database using the Online Backup API (https://www.sqlite.org/backup.html)
# as implemented in the SQLite CLI. However, if a call to sqlite3_backup_step() returns
# one of the transient errors SQLITE_BUSY or SQLITE_LOCKED, the CLI doesn't retry the
# backup step; instead, it simply stops the backup and returns an error. This is unlikely,
# but to minimize the possibility of a failed backup, implement a retry mechanism here.
max_tries=10
tries=0
until ${SQLITE3} "file:${DATA_DIR}/${DB_FILE}?mode=ro" ".backup '${BACKUP_DIR_PATH}/${DB_FILE}'"; do
    if (( ++tries >= max_tries )); then
        echo "Aborting after ${max_tries} failed backup attempts..."
        exit 1
    fi
    echo "Backup failed. Retry #${tries}..."
    rm -f "${BACKUP_DIR_PATH}/${DB_FILE}"
    sleep 1
done

sudo cp -a "${DATA_DIR}"/{attachments,config.json,rsa_key.*} "${BACKUP_DIR_PATH}"
tar -cJf "${BACKUP_FILE_PATH}" -C "${BACKUP_ROOT}" "${BACKUP_DIR_NAME}"
rm -rf "${BACKUP_DIR_PATH}"
md5sum "${BACKUP_FILE_PATH}"
sha1sum "${BACKUP_FILE_PATH}"

for dest in "${RCLONE_DESTS[@]}"; do
    ${RCLONE} -vv sync --exclude .gitignore "${BACKUP_ROOT}/${BACKUP_FILE_DIR}" "${dest}"
done

Backup to Google Drive

Install rclone and sq

1
2
curl https://rclone.org/install.sh | sudo bash
sudo apt install sqlite3 -y

rclone setup

  1. setup rclone with Google Drive
  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
# rclone config
2021/03/17 15:57:26 NOTICE: Config file "/root/.config/rclone/rclone.conf" not found - using defaults
No remotes found - make a new one
n) New remote
s) Set configuration password
q) Quit config
n/s/q> n
15 / Google Drive
   \ "drive"
Storage> 15
** See help for drive backend at: https://rclone.org/drive/ **

Google Application Client Id
Setting your own is recommended.
See https://rclone.org/drive/#making-your-own-client-id for how to create your own.
If you leave this blank, it will use an internal key which is low performance.
Enter a string value. Press Enter for the default ("").
client_id> <YOUR_ID>.apps.googleusercontent.com
OAuth Client Secret
Leave blank normally.
Enter a string value. Press Enter for the default ("").
client_secret> <YOUR_SECRET>
Scope that rclone should use when requesting access from drive.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
1 / Full access all files, excluding Application Data Folder.
  \ "drive"
2 / Read-only access to file metadata and file contents.
  \ "drive.readonly"
  / Access to files created by rclone only.
3 | These are visible in the drive website.
  | File authorization is revoked when the user deauthorizes the app.
  \ "drive.file"
  / Allows read and write access to the Application Data folder.
4 | This is not visible in the drive website.
  \ "drive.appfolder"
  / Allows read-only access to file metadata but
5 | does not allow any access to read or download file content.
  \ "drive.metadata.readonly"
scope> 3
ID of the root folder
Leave blank normally.

Fill in to access "Computers" folders (see docs), or for rclone to use
a non root folder as its starting point.

Enter a string value. Press Enter for the default ("").
root_folder_id>
Service Account Credentials JSON file path
Leave blank normally.
Needed only if you want use SA instead of interactive login.

Leading `~` will be expanded in the file name as will environment variables such as `${RCLONE_CONFIG_DIR}`.

Enter a string value. Press Enter for the default ("").
service_account_file>
Edit advanced config? (y/n)
y) Yes
n) No (default)
y/n>
Remote config
Use auto config?
* Say Y if not sure
* Say N if you are working on a remote or headless machine
y) Yes (default)
n) No
y/n> n
Please go to the following link: https://accounts.google.com/o/oauth2/auth?access_type=offline&client_id=.....
Log in and authorize rclone for access
Enter verification code> YOUR_VERIFICATION_CODE
Configure this as a Shared Drive (Team Drive)?
y) Yes
n) No (default)
y/n>
--------------------
[gdrive]
type = drive
client_id = YOUR_CLIENT_ID
client_secret = YOUR_CLIENT_SECRET
scope = drive.file
token = YOUR_TOKEN
--------------------
y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote
y/e/d>
Current remotes:

Name                 Type
====                 ====
gdrive               drive

e) Edit existing remote
n) New remote
d) Delete remote
r) Rename remote
c) Copy remote
s) Set configuration password
q) Quit config
e/n/d/r/c/s/q>q
  1. check the remote
1
2
# rclone listremotes
gdrive:
  1. update ${HOME}/docker/bitwarden/backup/backup.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!sh

# GPG_CIPHER_ALGO=AES128
# GPG_PASSPHRASE='hunter2'

RCLONE_DESTS=(
    # https://rclone.org/drive/
    # https://rclone.org/drive/#making-your-own-client-id
    #     / Access to files created by rclone only.
    # 3   | These are visible in the drive website.
    #     | File authorization is revoked when the user deauthorizes the app.
    #     \ "drive.file"
    gdrive:bitwarden_rs_bkp
)

Backup Cron scheduler

1
2
3
4
5 3 * * * ${HOME}/docker/bitwarden/backup/backup.sh >${HOME}/docker/bitwarden/backup/logs/backup-$(date '+\%Y\%m\%d-\%H\%M').log 2>&1

5 4 * * * find "${HOME}/docker/bitwarden/backup/archives" -name 'bitwarden-*.tar.*' -mtime +14 -delete
5 4 * * * find "${HOME}/docker/bitwarden/backup/logs" -name '*.log' -mtime +14 -delete

Full Compose File

 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
version: "3.3"

networks:
  traefik:
    external:
      name: traefik_webgateway

services:
  traefik:
    image: "traefik:v2.3"
    container_name: traefik
    restart: always
    command:
      - --entrypoints.web.address=:80
      - --providers.docker.exposedByDefault=false
      # - --log.level=DEBUG
      # - --api.insecure # Don't do that in production
      - --api
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.myresolver.acme.httpchallenge=true
      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
      # - --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
      - --certificatesresolvers.myresolver.acme.email=YOUR_EMAIL
      - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
    ports:
      - "80:80"
      - "443:443"
      # - "8081:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "${LETSENCRYPT_PATH}:/letsencrypt"
    labels:
      #### set core configs
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`YOUR_DOMAIN`)" # && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
      - "traefik.http.routers.traefik.service=api@internal"
      # - "traefik.http.routers.traefik.entrypoints=web"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=myresolver"
      - "traefik.http.routers.traefik.middlewares=authtraefik"
      - "traefik.http.middlewares.authtraefik.basicauth.users=user:$$apr1$$q8eZFHjF$$Fvmkk//V6Btlaf2i/ju5n/" # user/password

      # global redirect to https
      - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"

      # middleware redirect
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
    networks:
      - traefik

  bitwarden:
    image: bitwardenrs/server:latest
    container_name: bitwarden
    restart: always
    volumes:
      - ${BW_CONFIG}:/data
    environment:
      - ADMIN_TOKEN=${BW_ADMIN_TOKEN}
      - WEBSOCKET_ENABLED=true
    networks:
      - traefik
    labels:
      - traefik.enable=true
      # Entry Point for https
      - traefik.http.routers.bwd.rule=Host(`YOUR_DOMAIN`)
      - traefik.http.routers.bwd.service=bitwarden-service
      - traefik.http.services.bitwarden-service.loadbalancer.server.port=80
      - "traefik.http.routers.bwd.entrypoints=websecure"
      - "traefik.http.routers.bwd.tls=true"
      - "traefik.http.routers.bwd.tls.certresolver=myresolver"
      # websocket
      - traefik.http.routers.bwd-ws.rule=Host(`YOUR_DOMAIN`) && Path(`/notifications/hub`)
      - traefik.http.routers.bwd-ws.service=bitwarden-ws
      - traefik.http.services.bitwarden-ws.loadbalancer.server.port=3012
      - "traefik.http.routers.bwd-ws.entrypoints=websecure"
      - "traefik.http.routers.bwd-ws.tls=true"
      - "traefik.http.routers.bwd-ws.tls.certresolver=myresolver"
      # middlewares
      - "traefik.http.middlewares.bwd-ws=bw-stripPrefix"
      - "traefik.http.middlewares.bw-stripPrefix.stripprefix.prefixes=/notifications/hub"
      - "traefik.http.middlewares.bw-stripPrefix.stripprefix.forceSlash=false"