Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ KEEPASS_BACKUP_PATH="./backup"
KEEPASS_BACKUP_FILE_NAME="BitwardenBackup" # same name every time: overwrite
KEEPASS_BACKUP_DATABASE_NAME="BitwardenBackup_%date%"
ORGANIZATIONS_GROUP_NAME="Organizations"
ORGANIZATION_MODE="flat"
ORGANIZATION_FOLDERS_NAME="Folders"
ORGANIZATION_COLLECTIONS_NAME="Collections"
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 1.2.0

### Changed

- Organization items are saved differently in the keepass file.
- Organizations no longer have each collection as direct keepass group children (and no folders; see fix below).
- Organizations now have a keepass group for folders and collections; in these groups the folders and collections are created as keepass groups.
- This resembles the structure how it is presented in vaultwarden.

### Added

- Option to save organization items nested with subgroups for folders/collections or flat without duplicate entries.

### Fixed

- Organization items appearing in the folders outside of the organization.

## 1.1.0

### Added

- Support for keepass key files (thanks to [rahul-kurup](https://github.com/rahul-kurup)).

## 1.0.0

### Added

- Initial implementation.

### Fixed

- Issue where an already valid otpauth://totp/ URI would be incorrectly re-encoded, leading to a corrupted TOTP value (thanks to [DmitriiPetukhov](https://github.com/DmitriiPetukhov)).
32 changes: 18 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,25 @@ This is similar to projects like [lazywarden](https://github.com/querylab/lazywa

Use the following environment variables to configure the script:

| variable | default | mandatory | notes |
| --------------------------------------- | ------------------------ | --------- | -------------------------------------------------------------------------------------------------------- |
| `URL` | - | x | use the url to your bitwarden/vaultwarden instance |
| `BW_CLIENTID` | - | x | see [personal api key](https://bitwarden.com/help/personal-api-key/) |
| `BW_CLIENTSECRET` | - | x | see [personal api key](https://bitwarden.com/help/personal-api-key/) |
| `BW_PASSWORD` <sup>\*</sup> | - | x | password to your bitwarden/vaultwarden account (base64-encoded) |
| `KEEPASS_BACKUP_PASSWORD` <sup>\*</sup> | _[same as BW_PASSWORD]_ | | password for the KeePass database (base64-encoded) |
| `KEEPASS_BACKUP_KEYFILE_PATH` | _undefined_ | | provide the key file’s absolute path to use it instead of a password or as an additional security layer. |
| `ATTACHMENT_TEMP_FOLDER` | ./attachmentBackup | | directory where attachments are temporarily stored (recommendation: use `/tmp` for linux machines) |
| `MAX_ATTACHMENT_BYTES` | 100000 | | maximum size of an attachment that should be backed up in the KeePass database |
| `KEEPASS_BACKUP_PATH` | ./backup | | location where KeePass backup should be saved |
| `KEEPASS_BACKUP_FILE_NAME` | `BitwardenBackup_%date%` | | name of the KeePass database file; use `%date%` anywhere to insert path-friendly date+time string |
| `KEEPASS_BACKUP_DATABASE_NAME` | _[same as filename]_ | | name of the KeePass database (when opened); can use `%date%` as well |
| `ORGANIZATIONS_GROUP_NAME` | Organizations | | name of the KeePass group where organizations and its items should be stored |
| variable | default | mandatory | notes |
| --------------------------------------- | ------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `URL` | - | x | use the url to your bitwarden/vaultwarden instance |
| `BW_CLIENTID` | - | x | see [personal api key](https://bitwarden.com/help/personal-api-key/) |
| `BW_CLIENTSECRET` | - | x | see [personal api key](https://bitwarden.com/help/personal-api-key/) |
| `BW_PASSWORD` <sup>\*</sup> | - | x | password to your bitwarden/vaultwarden account (base64-encoded) |
| `KEEPASS_BACKUP_PASSWORD` <sup>\*</sup> | _[same as BW_PASSWORD]_ | | password for the KeePass database (base64-encoded); if this not set and keyfile is provided it is not set instead of taking the default value |
| `KEEPASS_BACKUP_KEYFILE_PATH` | _undefined_ | | absolute path of a key file to use instead of or as an additional security layer to a password |
| `ATTACHMENT_TEMP_FOLDER` | ./attachmentBackup | | directory where attachments are temporarily stored (recommendation: use `/tmp` for linux machines) |
| `MAX_ATTACHMENT_BYTES` | 100000 | | maximum size of an attachment that should be backed up in the KeePass database |
| `KEEPASS_BACKUP_PATH` | ./backup | | location where KeePass backup should be saved |
| `KEEPASS_BACKUP_FILE_NAME` | `BitwardenBackup_%date%` | | name of the KeePass database file; use `%date%` anywhere to insert path-friendly date+time string |
| `KEEPASS_BACKUP_DATABASE_NAME` | _[same as filename]_ | | name of the KeePass database (when opened); can use `%date%` as well |
| `ORGANIZATIONS_GROUP_NAME` | Organizations | | name of the KeePass group where organizations and its items should be stored |
| `ORGANIZATION_MODE` | flat | | how entries are saved in an organization (flat/nested)<sup>\*\*</sup> |
| `ORGANIZATION_FOLDERS_NAME` | Folders | | name of the KeePass group where folders for an organization should be stored; only relevant when mode is nested |
| `ORGANIZATION_COLLECTIONS_NAME` | Collections | | name of the KeePass group where collections for an organizations should be stored; only relevant when mode is nested |

\*: In most cases these environment variables are stored in plain text. That means they can easily be read. To make this _somewhat_ more secure and conceal them on first sight, your passwords have to be base64-encoded. To encode your password in base64 use some (online) tool of your choice or just open the developer tools console in any browser (usually via F12) and use the output of `btoa("your_password")`.
\*\*: In flat-mode, all organization entries are saved in the organization itself without creating keepass groups for folders/collections (the folder/collections information is instead saved to a field). Nested-mode creates keepass groups for folders/collections and creates duplicate entries to sort into these groups. The advantage of flat-mode is, that there are no duplicate entries, so entries are better found by searching. The advantage of nested-mode is, that it more closely resembles the structure of the items in vaultwarden, so entries are better found by browsing. Decide yourself, what's more important for your backup strategy.

Depending how you use this script (preferably in your local network), you may access your self-hosted vaultwarden/bitwarden server with a self signed certificate. In this case just set the node environment variable which disables certificate checking: `NODE_TLS_REJECT_UNAUTHORIZED=0`.
31 changes: 17 additions & 14 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@ services:
build: https://github.com/genericFJS/vaultwarden2keepass.git
command: npm run start
volumes:
- './backup:/app/backup'
- "./backup:/app/backup"
# Cache bitwarden cli config (else every start of the script would trigger a "new device" mail)
- './backup/cli-config:/root/.config/Bitwarden CLI/'
- "./backup/cli-config:/root/.config/Bitwarden CLI/"
environment:
# Mandatory
- 'URL=https://vault.bitwarden.com/'
- 'BW_CLIENTID=user.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
- 'BW_CLIENTSECRET=abcdefghijklmnopqrstuvwxz01234'
- 'BW_PASSWORD=Y29ycmVjdGhvcnNlYmF0dGVyeXN0YXBsZQ=='
- "URL=https://vault.bitwarden.com/"
- "BW_CLIENTID=user.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
- "BW_CLIENTSECRET=abcdefghijklmnopqrstuvwxz01234"
- "BW_PASSWORD=Y29ycmVjdGhvcnNlYmF0dGVyeXN0YXBsZQ=="

# Optional
- 'KEEPASS_BACKUP_PASSWORD=VHIwdWI0ZG9yJjM='
- 'KEEPASS_BACKUP_KEYFILE_PATH=/path/to/keyfile.keyx'
- 'ATTACHMENT_TEMP_FOLDER=/tmp'
- 'MAX_ATTACHMENT_BYTES=100000'
- 'KEEPASS_BACKUP_PATH=./backup'
- 'KEEPASS_BACKUP_FILE_NAME=BitwardenBackup'
- 'KEEPASS_BACKUP_DATABASE_NAME=BitwardenBackup_%date%'
- 'ORGANIZATIONS_GROUP_NAME=Organizations'
- "KEEPASS_BACKUP_PASSWORD=VHIwdWI0ZG9yJjM="
- "KEEPASS_BACKUP_KEYFILE_PATH=/path/to/keyfile.keyx"
- "ATTACHMENT_TEMP_FOLDER=/tmp"
- "MAX_ATTACHMENT_BYTES=100000"
- "KEEPASS_BACKUP_PATH=./backup"
- "KEEPASS_BACKUP_FILE_NAME=BitwardenBackup"
- "KEEPASS_BACKUP_DATABASE_NAME=BitwardenBackup_%date%"
- "ORGANIZATIONS_GROUP_NAME=Organizations"
- "ORGANIZATION_MODE=flat"
- "ORGANIZATION_FOLDERS_NAME=Folders"
- "ORGANIZATION_COLLECTIONS_NAME=Collections"
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vaultwarden2keepass",
"version": "1.1.0",
"version": "1.2.0",
"type": "module",
"scripts": {
"start": "tsx ./src/index.ts",
Expand Down
26 changes: 21 additions & 5 deletions src/executeBackup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ const DEFAULTS = {
KEEPASS_BACKUP_PATH: './backup',
KEEPASS_BACKUP_FILE_NAME: 'BitwardenBackup_%date%',
ORGANIZATIONS_GROUP_NAME: 'Organizations',
};
ORGANIZATION_MODE: 'flat',
ORGANIZATION_FOLDERS_NAME: 'Folders',
ORGANIZATION_COLLECTIONS_NAME: 'Collections',
} as const;

function ensureParameter(parameter: string, explanation: string) {
const value = process.env[parameter];
Expand Down Expand Up @@ -58,25 +61,38 @@ export async function executeBackup() {
process.env['KEEPASS_BACKUP_FILE_NAME'] || DEFAULTS.KEEPASS_BACKUP_FILE_NAME;
const backupDatabaseName = process.env['KEEPASS_BACKUP_DATABASE_NAME'] || backupFileName;

const organizationsFolderName =
const organizationsRootName =
process.env['ORGANIZATIONS_GROUP_NAME'] || DEFAULTS.ORGANIZATIONS_GROUP_NAME;

const organizationMode =
(process.env['ORGANIZATION_MODE'] || DEFAULTS.ORGANIZATION_MODE).toLowerCase() === 'nested'
? 'nested'
: 'flat';
const organizationsFoldersName =
process.env['ORGANIZATION_FOLDERS_NAME'] || DEFAULTS.ORGANIZATION_FOLDERS_NAME;
const organizationsCollectionsName =
process.env['ORGANIZATION_COLLECTIONS_NAME'] || DEFAULTS.ORGANIZATION_COLLECTIONS_NAME;

// ================ DO BACKUP ================
const backupDate = new Date().toISOString().slice(0, -5).replaceAll(':', '-');
const keepassFileName = backupFileName.replaceAll('%date%', backupDate);
const keepassDatabaseName = backupDatabaseName.replaceAll('%date%', backupDate);

const bitwardenExtractor = new BitwardenExtractor(url, attachmentTempFolder, maxAttachmentBytes);
const bitwardenData = await bitwardenExtractor.getBitwardenData();
bitwardenExtractor.logout();

const keepassWriter = new KeePassWriter({
name: keepassDatabaseName,
keyFile: backupKeyFile,
password: backupPassword,
organizationsFolderName,
organizationMode,
organizationFolderNames: {
organizations: organizationsRootName,
collections: organizationsCollectionsName,
folders: organizationsFoldersName,
},
});
await keepassWriter.fillDatabaseWithBitwardenData(bitwardenData);
await keepassWriter.writeDatabase(backupPath, keepassFileName);

bitwardenExtractor.logout();
}
5 changes: 0 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { executeBackup } from './executeBackup';

function success() {
console.log('🎉 Backup completed successfully');
process.exit(0);
}

executeBackup().then(
() => {
console.log('🎉 Backup completed successfully');
Expand Down
Loading