Bitwarden_rs Traefik Docker Installation With GDrive Backup and Restore

Contents
  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
  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
  1. Create required folders
1
mkdir -p ${HOME}/docker/bitwarden
 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"
  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
1
2
curl https://rclone.org/install.sh | sudo bash
sudo apt install sqlite3 -y
  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
)
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
 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"