diff --git a/Dockerfile b/Dockerfile index 27a67f4..560a9f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,19 @@ ################## RUNTIME IMAGE ################### # Create using wellbuilt vaultwarden Docker image as the base image -# Modify vaultwarden-startup to suit in your PaaS service you are using to deploy this +# Modify scripts/vaultwarden-startup to suit in your PaaS service you are building +# this image from source. FROM vaultwarden/server:alpine -COPY vaultwarden-startup /usr/bin/vaultwarden-startup +COPY scripts/ /usr/local/bin/ -ENV PORT 3000 +ENV PORT=3000 TZ=UTC EXPOSE 3000 -## because vaultwarden-startup requires bash, so we install that -RUN apk add bash coreutils \ - # Just in case we're still calling the old stuff - && ln -s /usr/bin/vaultwarden-startup /usr/bin/bwrs-startup +RUN apk add --no-cache \ + # Because the entrypoint script and the backup script itself need rclone and bash, + # we'll install them through apk + heirloom-mailx p7zip sqlite supercronic tzdata bash coreutils rclone npm \ + && npm i -g concurrently WORKDIR / -ENTRYPOINT ["usr/bin/dumb-init", "--"] -CMD ["/usr/bin/vaultwarden-startup"] +ENTRYPOINT ["/usr/local/bin/image-entrypoint"] diff --git a/scripts/.gitkeep b/scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/backup-chores.sh b/scripts/backup-chores.sh new file mode 100644 index 0000000..96e0a85 --- /dev/null +++ b/scripts/backup-chores.sh @@ -0,0 +1,195 @@ +#!/bin/bash + +. /app/includes.sh + +function clear_dir() { + rm -rf "${BACKUP_DIR}" +} + +function backup_init() { + NOW="$(date +"${BACKUP_FILE_DATE_FORMAT}")" + # backup vaultwarden database file + BACKUP_FILE_DB="${BACKUP_DIR}/db.${NOW}.sqlite3" + # backup vaultwarden config file + BACKUP_FILE_CONFIG="${BACKUP_DIR}/config.${NOW}.json" + # backup vaultwarden rsakey files + BACKUP_FILE_RSAKEY="${BACKUP_DIR}/rsakey.${NOW}.tar" + # backup vaultwarden attachments directory + BACKUP_FILE_ATTACHMENTS="${BACKUP_DIR}/attachments.${NOW}.tar" + # backup vaultwarden sends directory + BACKUP_FILE_SENDS="${BACKUP_DIR}/sends.${NOW}.tar" + # backup zip file + BACKUP_FILE_ZIP="${BACKUP_DIR}/backup.${NOW}.${ZIP_TYPE}" +} + +function backup_db() { + color blue "backup vaultwarden sqlite database" + + if [[ -f "${DATA_DB}" ]]; then + sqlite3 "${DATA_DB}" ".backup '${BACKUP_FILE_DB}'" + else + color yellow "not found vaultwarden sqlite database, skipping" + fi +} + +function backup_config() { + color blue "backup vaultwarden config" + + if [[ -f "${DATA_CONFIG}" ]]; then + cp -f "${DATA_CONFIG}" "${BACKUP_FILE_CONFIG}" + else + color yellow "not found vaultwarden config, skipping" + fi +} + +function backup_rsakey() { + color blue "backup vaultwarden rsakey" + + local FIND_RSAKEY=$(find "${DATA_RSAKEY_DIRNAME}" -name "${DATA_RSAKEY_BASENAME}*" | xargs -I {} basename {}) + local FIND_RSAKEY_COUNT=$(echo "${FIND_RSAKEY}" | wc -l) + + if [[ "${FIND_RSAKEY_COUNT}" -gt 0 ]]; then + echo "${FIND_RSAKEY}" | tar -c -C "${DATA_RSAKEY_DIRNAME}" -f "${BACKUP_FILE_RSAKEY}" -T - + + color blue "display rsakey tar file list" + + tar -tf "${BACKUP_FILE_RSAKEY}" + else + color yellow "not found vaultwarden rsakey, skipping" + fi +} + +function backup_attachments() { + color blue "backup vaultwarden attachments" + + if [[ -d "${DATA_ATTACHMENTS}" ]]; then + tar -c -C "${DATA_ATTACHMENTS_DIRNAME}" -f "${BACKUP_FILE_ATTACHMENTS}" "${DATA_ATTACHMENTS_BASENAME}" + + color blue "display attachments tar file list" + + tar -tf "${BACKUP_FILE_ATTACHMENTS}" + else + color yellow "not found vaultwarden attachments directory, skipping" + fi +} + +function backup_sends() { + color blue "backup vaultwarden sends" + + if [[ -d "${DATA_SENDS}" ]]; then + tar -c -C "${DATA_SENDS_DIRNAME}" -f "${BACKUP_FILE_SENDS}" "${DATA_SENDS_BASENAME}" + + color blue "display sends tar file list" + + tar -tf "${BACKUP_FILE_SENDS}" + else + color yellow "not found vaultwarden sends directory, skipping" + fi +} + +function backup() { + mkdir -p "${BACKUP_DIR}" + + backup_db + backup_config + backup_rsakey + backup_attachments + backup_sends + + ls -lah "${BACKUP_DIR}" +} + +function backup_package() { + if [[ "${ZIP_ENABLE}" == "TRUE" ]]; then + color blue "package backup file" + + UPLOAD_FILE="${BACKUP_FILE_ZIP}" + + if [[ "${ZIP_TYPE}" == "zip" ]]; then + 7z a -tzip -mx=9 -p"${ZIP_PASSWORD}" "${BACKUP_FILE_ZIP}" "${BACKUP_DIR}"/* + else + 7z a -t7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -mhe=on -p"${ZIP_PASSWORD}" "${BACKUP_FILE_ZIP}" "${BACKUP_DIR}"/* + fi + + ls -lah "${BACKUP_DIR}" + + color blue "display backup ${ZIP_TYPE} file list" + + 7z l -p"${ZIP_PASSWORD}" "${BACKUP_FILE_ZIP}" + else + color yellow "skip package backup files" + + UPLOAD_FILE="${BACKUP_DIR}" + fi +} + +function upload() { + # upload file not exist + if [[ ! -e "${UPLOAD_FILE}" ]]; then + color red "upload file not found" + + send_mail_content "FALSE" "File upload failed at $(date +"%Y-%m-%d %H:%M:%S %Z"). Reason: Upload file not found." + + exit 1 + fi + + # upload + local HAS_ERROR="FALSE" + + for RCLONE_REMOTE_X in "${RCLONE_REMOTE_LIST[@]}" + do + color blue "upload backup file to storage system $(color yellow "[${RCLONE_REMOTE_X}]")" + + rclone ${RCLONE_GLOBAL_FLAG} copy "${UPLOAD_FILE}" "${RCLONE_REMOTE_X}" + if [[ $? != 0 ]]; then + color red "upload failed" + + HAS_ERROR="TRUE" + fi + done + + if [[ "${HAS_ERROR}" == "TRUE" ]]; then + send_mail_content "FALSE" "File upload failed at $(date +"%Y-%m-%d %H:%M:%S %Z")." + + exit 1 + fi +} + +function clear_history() { + if [[ "${BACKUP_KEEP_DAYS}" -gt 0 ]]; then + for RCLONE_REMOTE_X in "${RCLONE_REMOTE_LIST[@]}" + do + color blue "delete ${BACKUP_KEEP_DAYS} days ago backup files $(color yellow "[${RCLONE_REMOTE_X}]")" + + mapfile -t RCLONE_DELETE_LIST < <(rclone ${RCLONE_GLOBAL_FLAG} lsf "${RCLONE_REMOTE_X}" --min-age "${BACKUP_KEEP_DAYS}d") + + for RCLONE_DELETE_FILE in "${RCLONE_DELETE_LIST[@]}" + do + color yellow "deleting \"${RCLONE_DELETE_FILE}\"" + + rclone ${RCLONE_GLOBAL_FLAG} delete "${RCLONE_REMOTE_X}/${RCLONE_DELETE_FILE}" + if [[ $? != 0 ]]; then + color red "delete \"${RCLONE_DELETE_FILE}\" failed" + fi + done + done + fi +} + +color blue "running the backup program at $(date +"%Y-%m-%d %H:%M:%S %Z")" + +init_env +check_rclone_connection + +clear_dir +backup_init +backup +backup_package +upload +clear_dir +clear_history + +send_mail_content "TRUE" "The file was successfully uploaded at $(date +"%Y-%m-%d %H:%M:%S %Z")." +send_ping + +color none "" \ No newline at end of file diff --git a/scripts/backup-integration-stdlib.sh b/scripts/backup-integration-stdlib.sh new file mode 100644 index 0000000..08ef064 --- /dev/null +++ b/scripts/backup-integration-stdlib.sh @@ -0,0 +1,403 @@ +#!/bin/bash + +ENV_FILE="/.env" +CRON_CONFIG_FILE="${HOME}/crontabs" +BACKUP_DIR="/bitwarden/backup" +RESTORE_DIR="/bitwarden/restore" +RESTORE_EXTRACT_DIR="/bitwarden/extract" + +#################### Function #################### +######################################## +# Print colorful message. +# Arguments: +# color +# message +# Outputs: +# colorful message +######################################## +function color() { + case $1 in + red) echo -e "\033[31m$2\033[0m" ;; + green) echo -e "\033[32m$2\033[0m" ;; + yellow) echo -e "\033[33m$2\033[0m" ;; + blue) echo -e "\033[34m$2\033[0m" ;; + none) echo "$2" ;; + esac +} + +######################################## +# Check storage system connection success. +# Arguments: +# None +######################################## +function check_rclone_connection() { + # check configuration exist + rclone ${RCLONE_GLOBAL_FLAG} config show "${RCLONE_REMOTE_NAME}" > /dev/null 2>&1 + if [[ $? != 0 ]]; then + color red "rclone configuration information not found" + color blue "Please configure rclone first, check https://github.com/ttionya/vaultwarden-backup/blob/master/README.md#backup" + exit 1 + fi + + # check connection + local HAS_ERROR="FALSE" + + for RCLONE_REMOTE_X in "${RCLONE_REMOTE_LIST[@]}" + do + rclone ${RCLONE_GLOBAL_FLAG} mkdir "${RCLONE_REMOTE_X}" + if [[ $? != 0 ]]; then + color red "storage system connection failure $(color yellow "[${RCLONE_REMOTE_X}]")" + + HAS_ERROR="TRUE" + fi + done + + if [[ "${HAS_ERROR}" == "TRUE" ]]; then + exit 1 + fi +} + +######################################## +# Check file is exist. +# Arguments: +# file +######################################## +function check_file_exist() { + if [[ ! -f "$1" ]]; then + color red "cannot access $1: No such file" + exit 1 + fi +} + +######################################## +# Check directory is exist. +# Arguments: +# directory +######################################## +function check_dir_exist() { + if [[ ! -d "$1" ]]; then + color red "cannot access $1: No such directory" + exit 1 + fi +} + +######################################## +# Send mail by mailx. +# Arguments: +# mail subject +# mail content +# Outputs: +# send mail result +######################################## +function send_mail() { + if [[ "${MAIL_DEBUG}" == "TRUE" ]]; then + local MAIL_VERBOSE="-v" + fi + + echo "$2" | mailx ${MAIL_VERBOSE} -s "$1" ${MAIL_SMTP_VARIABLES} "${MAIL_TO}" + if [[ $? != 0 ]]; then + color red "mail sending failed" + else + color blue "mail send was successfully" + fi +} + +######################################## +# Send mail. +# Arguments: +# backup successful +# mail content +######################################## +function send_mail_content() { + if [[ "${MAIL_SMTP_ENABLE}" == "FALSE" ]]; then + return + fi + + # successful + if [[ "$1" == "TRUE" && "${MAIL_WHEN_SUCCESS}" == "TRUE" ]]; then + send_mail "vaultwarden Backup Success" "$2" + fi + + # failed + if [[ "$1" == "FALSE" && "${MAIL_WHEN_FAILURE}" == "TRUE" ]]; then + send_mail "vaultwarden Backup Failed" "$2" + fi +} + +######################################## +# Send health check ping. +# Arguments: +# None +######################################## +function send_ping() { + if [[ -z "${PING_URL}" ]]; then + return + fi + + wget "${PING_URL}" -T 15 -t 10 -O /dev/null -q + if [[ $? != 0 ]]; then + color red "ping sending failed" + else + color blue "ping send was successfully" + fi +} + +######################################## +# Export variables from .env file. +# Arguments: +# None +# Outputs: +# variables with prefix 'DOTENV_' +# Reference: +# https://gist.github.com/judy2k/7656bfe3b322d669ef75364a46327836#gistcomment-3632918 +######################################## +function export_env_file() { + if [[ -f "${ENV_FILE}" ]]; then + color blue "find \"${ENV_FILE}\" file and export variables" + set -a + source <(cat "${ENV_FILE}" | sed -e '/^#/d;/^\s*$/d' -e 's/\(\w*\)[ \t]*=[ \t]*\(.*\)/DOTENV_\1=\2/') + set +a + fi +} + +######################################## +# Get variables from +# environment variables, +# secret file in environment variables, +# secret file in .env file, +# environment variables in .env file. +# Arguments: +# variable name +# Outputs: +# variable value +######################################## +function get_env() { + local VAR="$1" + local VAR_FILE="${VAR}_FILE" + local VAR_DOTENV="DOTENV_${VAR}" + local VAR_DOTENV_FILE="DOTENV_${VAR_FILE}" + local VALUE="" + + if [[ -n "${!VAR:-}" ]]; then + VALUE="${!VAR}" + elif [[ -n "${!VAR_FILE:-}" ]]; then + VALUE="$(cat "${!VAR_FILE}")" + elif [[ -n "${!VAR_DOTENV_FILE:-}" ]]; then + VALUE="$(cat "${!VAR_DOTENV_FILE}")" + elif [[ -n "${!VAR_DOTENV:-}" ]]; then + VALUE="${!VAR_DOTENV}" + fi + + export "${VAR}=${VALUE}" +} + +######################################## +# Get RCLONE_REMOTE_LIST variables. +# Arguments: +# None +# Outputs: +# variable value +######################################## +function get_rclone_remote_list() { + # RCLONE_REMOTE_LIST + RCLONE_REMOTE_LIST=() + + local i=0 + local RCLONE_REMOTE_NAME_X_REFER + local RCLONE_REMOTE_DIR_X_REFER + local RCLONE_REMOTE_X + + # for multiple + while true; do + RCLONE_REMOTE_NAME_X_REFER="RCLONE_REMOTE_NAME_${i}" + RCLONE_REMOTE_DIR_X_REFER="RCLONE_REMOTE_DIR_${i}" + get_env "${RCLONE_REMOTE_NAME_X_REFER}" + get_env "${RCLONE_REMOTE_DIR_X_REFER}" + + if [[ -z "${!RCLONE_REMOTE_NAME_X_REFER}" || -z "${!RCLONE_REMOTE_DIR_X_REFER}" ]]; then + break + fi + + RCLONE_REMOTE_X=$(echo "${!RCLONE_REMOTE_NAME_X_REFER}:${!RCLONE_REMOTE_DIR_X_REFER}" | sed 's@\(/*\)$@@') + RCLONE_REMOTE_LIST=(${RCLONE_REMOTE_LIST[@]} "${RCLONE_REMOTE_X}") + + ((i++)) + done +} + +######################################## +# Initialization environment variables. +# Arguments: +# None +# Outputs: +# environment variables +######################################## +function init_env() { + # export + export_env_file + + init_env_dir + init_env_mail + + # CRON + get_env CRON + CRON="${CRON:-"5 * * * *"}" + + # RCLONE_REMOTE_NAME + get_env RCLONE_REMOTE_NAME + RCLONE_REMOTE_NAME="${RCLONE_REMOTE_NAME:-"BitwardenBackup"}" + RCLONE_REMOTE_NAME_0="${RCLONE_REMOTE_NAME}" + + # RCLONE_REMOTE_DIR + get_env RCLONE_REMOTE_DIR + RCLONE_REMOTE_DIR="${RCLONE_REMOTE_DIR:-"/BitwardenBackup/"}" + RCLONE_REMOTE_DIR_0="${RCLONE_REMOTE_DIR}" + + # get RCLONE_REMOTE_LIST + get_rclone_remote_list + + # RCLONE_GLOBAL_FLAG + get_env RCLONE_GLOBAL_FLAG + RCLONE_GLOBAL_FLAG="${RCLONE_GLOBAL_FLAG:-""}" + + # ZIP_ENABLE + get_env ZIP_ENABLE + ZIP_ENABLE=$(echo "${ZIP_ENABLE}" | tr '[a-z]' '[A-Z]') + if [[ "${ZIP_ENABLE}" == "FALSE" ]]; then + ZIP_ENABLE="FALSE" + else + ZIP_ENABLE="TRUE" + fi + + # ZIP_PASSWORD + get_env ZIP_PASSWORD + ZIP_PASSWORD="${ZIP_PASSWORD:-"WHEREISMYPASSWORD?"}" + + # ZIP_TYPE + get_env ZIP_TYPE + ZIP_TYPE=$(echo "${ZIP_TYPE}" | tr '[A-Z]' '[a-z]') + if [[ "${ZIP_TYPE}" == "7z" ]]; then + ZIP_TYPE="7z" + else + ZIP_TYPE="zip" + fi + + # BACKUP_KEEP_DAYS + get_env BACKUP_KEEP_DAYS + BACKUP_KEEP_DAYS="${BACKUP_KEEP_DAYS:-"0"}" + + # BACKUP_FILE_DATE_FORMAT + get_env BACKUP_FILE_DATE_SUFFIX + BACKUP_FILE_DATE_FORMAT=$(echo "%Y%m%d${BACKUP_FILE_DATE_SUFFIX}" | sed 's/[^0-9a-zA-Z%_-]//g') + + # PING_URL + get_env PING_URL + PING_URL="${PING_URL:-""}" + + # TIMEZONE + get_env TIMEZONE + local TIMEZONE_MATCHED_COUNT=$(ls "/usr/share/zoneinfo/${TIMEZONE}" 2> /dev/null | wc -l) + if [[ "${TIMEZONE_MATCHED_COUNT}" -ne 1 ]]; then + TIMEZONE="UTC" + fi + + color yellow "========================================" + color yellow "DATA_DIR: ${DATA_DIR}" + color yellow "DATA_DB: ${DATA_DB}" + color yellow "DATA_CONFIG: ${DATA_CONFIG}" + color yellow "DATA_RSAKEY: ${DATA_RSAKEY}" + color yellow "DATA_ATTACHMENTS: ${DATA_ATTACHMENTS}" + color yellow "DATA_SENDS: ${DATA_SENDS}" + color yellow "========================================" + color yellow "CRON: ${CRON}" + + for RCLONE_REMOTE_X in "${RCLONE_REMOTE_LIST[@]}" + do + color yellow "RCLONE_REMOTE: ${RCLONE_REMOTE_X}" + done + + color yellow "RCLONE_GLOBAL_FLAG: ${RCLONE_GLOBAL_FLAG}" + color yellow "ZIP_ENABLE: ${ZIP_ENABLE}" + color yellow "ZIP_PASSWORD: ${#ZIP_PASSWORD} Chars" + color yellow "ZIP_TYPE: ${ZIP_TYPE}" + color yellow "BACKUP_FILE_DATE_FORMAT: ${BACKUP_FILE_DATE_FORMAT}" + color yellow "BACKUP_KEEP_DAYS: ${BACKUP_KEEP_DAYS}" + if [[ -n "${PING_URL}" ]]; then + color yellow "PING_URL: ${PING_URL}" + fi + color yellow "MAIL_SMTP_ENABLE: ${MAIL_SMTP_ENABLE}" + if [[ "${MAIL_SMTP_ENABLE}" == "TRUE" ]]; then + color yellow "MAIL_TO: ${MAIL_TO}" + color yellow "MAIL_WHEN_SUCCESS: ${MAIL_WHEN_SUCCESS}" + color yellow "MAIL_WHEN_FAILURE: ${MAIL_WHEN_FAILURE}" + fi + color yellow "TIMEZONE: ${TIMEZONE}" + color yellow "========================================" +} + +function init_env_dir() { + # DATA_DIR + get_env DATA_DIR + DATA_DIR="${DATA_DIR:-"/bitwarden/data"}" + check_dir_exist "${DATA_DIR}" + + # DATA_DB + get_env DATA_DB + DATA_DB="${DATA_DB:-"${DATA_DIR}/db.sqlite3"}" + + # DATA_CONFIG + DATA_CONFIG="${DATA_DIR}/config.json" + + # DATA_RSAKEY + get_env DATA_RSAKEY + DATA_RSAKEY="${DATA_RSAKEY:-"${DATA_DIR}/rsa_key"}" + DATA_RSAKEY_DIRNAME="$(dirname "${DATA_RSAKEY}")" + DATA_RSAKEY_BASENAME="$(basename "${DATA_RSAKEY}")" + + # DATA_ATTACHMENTS + get_env DATA_ATTACHMENTS + DATA_ATTACHMENTS="$(dirname "${DATA_ATTACHMENTS:-"${DATA_DIR}/attachments"}/useless")" + DATA_ATTACHMENTS_DIRNAME="$(dirname "${DATA_ATTACHMENTS}")" + DATA_ATTACHMENTS_BASENAME="$(basename "${DATA_ATTACHMENTS}")" + + # DATA_SEND + get_env DATA_SENDS + DATA_SENDS="$(dirname "${DATA_SENDS:-"${DATA_DIR}/sends"}/useless")" + DATA_SENDS_DIRNAME="$(dirname "${DATA_SENDS}")" + DATA_SENDS_BASENAME="$(basename "${DATA_SENDS}")" +} + +function init_env_mail() { + # MAIL_SMTP_ENABLE + # MAIL_TO + get_env MAIL_SMTP_ENABLE + get_env MAIL_TO + MAIL_SMTP_ENABLE=$(echo "${MAIL_SMTP_ENABLE}" | tr '[a-z]' '[A-Z]') + if [[ "${MAIL_SMTP_ENABLE}" == "TRUE" && "${MAIL_TO}" ]]; then + MAIL_SMTP_ENABLE="TRUE" + else + MAIL_SMTP_ENABLE="FALSE" + fi + + # MAIL_SMTP_VARIABLES + get_env MAIL_SMTP_VARIABLES + MAIL_SMTP_VARIABLES="${MAIL_SMTP_VARIABLES:-""}" + + # MAIL_WHEN_SUCCESS + get_env MAIL_WHEN_SUCCESS + MAIL_WHEN_SUCCESS=$(echo "${MAIL_WHEN_SUCCESS}" | tr '[a-z]' '[A-Z]') + if [[ "${MAIL_WHEN_SUCCESS}" == "FALSE" ]]; then + MAIL_WHEN_SUCCESS="FALSE" + else + MAIL_WHEN_SUCCESS="TRUE" + fi + + # MAIL_WHEN_FAILURE + get_env MAIL_WHEN_FAILURE + MAIL_WHEN_FAILURE=$(echo "${MAIL_WHEN_FAILURE}" | tr '[a-z]' '[A-Z]') + if [[ "${MAIL_WHEN_FAILURE}" == "FALSE" ]]; then + MAIL_WHEN_FAILURE="FALSE" + else + MAIL_WHEN_FAILURE="TRUE" + fi +} \ No newline at end of file diff --git a/scripts/restore-chores.sh b/scripts/restore-chores.sh new file mode 100644 index 0000000..86c3539 --- /dev/null +++ b/scripts/restore-chores.sh @@ -0,0 +1,291 @@ +#!/bin/bash + +. /app/includes.sh + +RESTORE_FILE_DB="" +RESTORE_FILE_CONFIG="" +RESTORE_FILE_RSAKEY="" +RESTORE_FILE_ATTACHMENTS="" +RESTORE_FILE_SENDS="" +RESTORE_FILE_ZIP="" +RESTORE_FILE_DIR="${RESTORE_DIR}" +ZIP_PASSWORD="" +FORCE_RESTORE="FALSE" + +function clear_extract_dir() { + rm -rf "${RESTORE_EXTRACT_DIR}" +} + +function restore_zip() { + color blue "restore vaultwarden backup zip file" + + local FIND_FILE_DB + local FIND_FILE_CONFIG + local FIND_FILE_RSAKEY + local FIND_FILE_ATTACHMENTS + local FIND_FILE_SENDS + + if [[ -n "${ZIP_PASSWORD}" ]]; then + 7z e -aoa -p"${ZIP_PASSWORD}" -o"${RESTORE_EXTRACT_DIR}" "${RESTORE_FILE_ZIP}" + else + 7z e -aoa -o"${RESTORE_EXTRACT_DIR}" "${RESTORE_FILE_ZIP}" + fi + + if [[ $? == 0 ]]; then + color green "extract vaultwarden backup zip file successful" + else + color red "extract vaultwarden backup zip file failed" + exit 1 + fi + + # get restore db file + FIND_FILE_DB="$( basename "$(ls ${RESTORE_EXTRACT_DIR}/db.*.sqlite3 2>/dev/null)" )" + RESTORE_FILE_DB="${FIND_FILE_DB:-}" + + # get restore config file + FIND_FILE_CONFIG="$( basename "$(ls ${RESTORE_EXTRACT_DIR}/config.*.json 2>/dev/null)" )" + RESTORE_FILE_CONFIG="${FIND_FILE_CONFIG:-}" + + # get restore rsakey file + FIND_FILE_RSAKEY="$( basename "$(ls ${RESTORE_EXTRACT_DIR}/rsakey.*.tar 2>/dev/null)" )" + RESTORE_FILE_RSAKEY="${FIND_FILE_RSAKEY:-}" + + # get restore attachments file + FIND_FILE_ATTACHMENTS="$( basename "$(ls ${RESTORE_EXTRACT_DIR}/attachments.*.tar 2>/dev/null)" )" + RESTORE_FILE_ATTACHMENTS="${FIND_FILE_ATTACHMENTS:-}" + + # get restore sends file + FIND_FILE_SENDS="$( basename "$(ls ${RESTORE_EXTRACT_DIR}/sends.*.tar 2>/dev/null)" )" + RESTORE_FILE_SENDS="${FIND_FILE_SENDS:-}" + + RESTORE_FILE_ZIP="" + RESTORE_FILE_DIR="${RESTORE_EXTRACT_DIR}" + restore_file +} + +function restore_db() { + color blue "restore vaultwarden sqlite database" + + cp -f "${RESTORE_FILE_DB}" "${DATA_DB}" + + if [[ $? == 0 ]]; then + color green "restore vaultwarden sqlite database successful" + else + color red "restore vaultwarden sqlite database failed" + fi +} + +function restore_config() { + color blue "restore vaultwarden config" + + cp -f "${RESTORE_FILE_CONFIG}" "${DATA_CONFIG}" + + if [[ $? == 0 ]]; then + color green "restore vaultwarden config successful" + else + color red "restore vaultwarden config failed" + fi +} + +function restore_rsakey() { + color blue "restore vaultwarden rsakey" + + mkdir -p "${DATA_RSAKEY_DIRNAME}" + tar -x -C "${DATA_RSAKEY_DIRNAME}" -f "${RESTORE_FILE_RSAKEY}" + + if [[ $? == 0 ]]; then + color green "restore vaultwarden rsakey successful" + else + color red "restore vaultwarden rsakey failed" + fi +} + +function restore_attachments() { + color blue "restore vaultwarden attachments" + + # When customizing the attachments folder, the root directory of the tar file + # is the directory name at the time of packing + local RESTORE_FILE_ATTACHMENTS_DIRNAME=$(tar -tf "${RESTORE_FILE_ATTACHMENTS}" | head -n 1 | xargs basename) + local DATA_ATTACHMENTS_EXTRACT="${DATA_ATTACHMENTS}.extract" + + rm -rf "${DATA_ATTACHMENTS}" "${DATA_ATTACHMENTS_EXTRACT}" + mkdir -p "${DATA_ATTACHMENTS_EXTRACT}" + tar -x -C "${DATA_ATTACHMENTS_EXTRACT}" -f "${RESTORE_FILE_ATTACHMENTS}" + mv "${DATA_ATTACHMENTS_EXTRACT}/${RESTORE_FILE_ATTACHMENTS_DIRNAME}" "${DATA_ATTACHMENTS}" + rm -rf "${DATA_ATTACHMENTS_EXTRACT}" + + if [[ $? == 0 ]]; then + color green "restore vaultwarden attachments successful" + else + color red "restore vaultwarden attachments failed" + fi +} + +function restore_sends() { + color blue "restore vaultwarden sends" + + # When customizing the sends folder, the root directory of the tar file + # is the directory name at the time of packing + local RESTORE_FILE_SENDS_DIRNAME=$(tar -tf "${RESTORE_FILE_SENDS}" | head -n 1 | xargs basename) + local DATA_SENDS_EXTRACT="${DATA_SENDS}.extract" + + rm -rf "${DATA_SENDS}" "${DATA_SENDS_EXTRACT}" + mkdir -p "${DATA_SENDS_EXTRACT}" + tar -x -C "${DATA_SENDS_EXTRACT}" -f "${RESTORE_FILE_SENDS}" + mv "${DATA_SENDS_EXTRACT}/${RESTORE_FILE_SENDS_DIRNAME}" "${DATA_SENDS}" + rm -rf "${DATA_SENDS_EXTRACT}" + + if [[ $? == 0 ]]; then + color green "restore vaultwarden sends successful" + else + color red "restore vaultwarden sends failed" + fi +} + +function check_restore_file_exist() { + if [[ ! -f "${RESTORE_FILE_DIR}/$1" ]]; then + color red "$2: cannot access $1: No such file" + exit 1 + fi +} + +function restore_file() { + if [[ -n "${RESTORE_FILE_ZIP}" ]]; then + check_restore_file_exist "${RESTORE_FILE_ZIP}" "--zip-file" + + RESTORE_FILE_ZIP="${RESTORE_FILE_DIR}/${RESTORE_FILE_ZIP}" + + clear_extract_dir + restore_zip + clear_extract_dir + else + if [[ -n "${RESTORE_FILE_DB}" ]]; then + check_restore_file_exist "${RESTORE_FILE_DB}" "--db-file" + + RESTORE_FILE_DB="${RESTORE_FILE_DIR}/${RESTORE_FILE_DB}" + fi + + if [[ -n "${RESTORE_FILE_CONFIG}" ]]; then + check_restore_file_exist "${RESTORE_FILE_CONFIG}" "--config-file" + + RESTORE_FILE_CONFIG="${RESTORE_FILE_DIR}/${RESTORE_FILE_CONFIG}" + fi + + if [[ -n "${RESTORE_FILE_RSAKEY}" ]]; then + check_restore_file_exist "${RESTORE_FILE_RSAKEY}" "--rsakey-file" + + RESTORE_FILE_RSAKEY="${RESTORE_FILE_DIR}/${RESTORE_FILE_RSAKEY}" + fi + + if [[ -n "${RESTORE_FILE_ATTACHMENTS}" ]]; then + check_restore_file_exist "${RESTORE_FILE_ATTACHMENTS}" "--attachments-file" + + RESTORE_FILE_ATTACHMENTS="${RESTORE_FILE_DIR}/${RESTORE_FILE_ATTACHMENTS}" + fi + + if [[ -n "${RESTORE_FILE_SENDS}" ]]; then + check_restore_file_exist "${RESTORE_FILE_SENDS}" "--sends-file" + + RESTORE_FILE_SENDS="${RESTORE_FILE_DIR}/${RESTORE_FILE_SENDS}" + fi + + if [[ -n "${RESTORE_FILE_DB}" ]]; then + restore_db + fi + if [[ -n "${RESTORE_FILE_CONFIG}" ]]; then + restore_config + fi + if [[ -n "${RESTORE_FILE_RSAKEY}" ]]; then + restore_rsakey + fi + if [[ -n "${RESTORE_FILE_ATTACHMENTS}" ]]; then + restore_attachments + fi + if [[ -n "${RESTORE_FILE_SENDS}" ]]; then + restore_sends + fi + fi +} + +function check_empty_input() { + if [[ -z "${RESTORE_FILE_ZIP}${RESTORE_FILE_DB}${RESTORE_FILE_CONFIG}${RESTORE_FILE_RSAKEY}${RESTORE_FILE_ATTACHMENTS}${RESTORE_FILE_SENDS}" ]]; then + color yellow "Empty input" + color none "" + color none "Find out more at https://github.com/ttionya/vaultwarden-backup#restore" + exit 0 + fi +} + +function check_data_dir_exist() { + if [[ ! -d "${DATA_DIR}" ]]; then + color red "vaultwarden data directory not found" + exit 1 + fi +} + +function restore() { + local READ_RESTORE_CONTINUE + + while [[ $# -gt 0 ]]; do + case "$1" in + -p|--password) + shift + ZIP_PASSWORD="$1" + shift + ;; + --zip-file) + shift + RESTORE_FILE_ZIP="$(basename "$1")" + shift + ;; + --db-file) + shift + RESTORE_FILE_DB="$(basename "$1")" + shift + ;; + --config-file) + shift + RESTORE_FILE_CONFIG="$(basename "$1")" + shift + ;; + --rsakey-file) + shift + RESTORE_FILE_RSAKEY="$(basename "$1")" + shift + ;; + --attachments-file) + shift + RESTORE_FILE_ATTACHMENTS="$(basename "$1")" + shift + ;; + --sends-file) + shift + RESTORE_FILE_SENDS="$(basename "$1")" + shift + ;; + -f|--force-restore) + shift + FORCE_RESTORE="TRUE" + shift + ;; + *) + color red "Illegal input" + exit 1 + ;; + esac + done + + init_env_dir + check_empty_input + check_data_dir_exist + + if [[ "${FORCE_RESTORE}" == "TRUE" ]]; then + restore_file + else + color yellow "Restore will overwrite the existing files, continue? (y/N)" + read -p "(Default: n): " READ_RESTORE_CONTINUE + if [[ $(echo "${READ_RESTORE_CONTINUE:-n}" | tr [a-z] [A-Z]) == "Y" ]]; then + restore_file + fi + fi +} \ No newline at end of file diff --git a/scripts/vaultwarden-startup b/scripts/vaultwarden-startup new file mode 100755 index 0000000..873d5e5 --- /dev/null +++ b/scripts/vaultwarden-startup @@ -0,0 +1,128 @@ +#!/usr/bin/dumb-init /bin/bash +#shellcheck shell=bash + +# sources the backup integration's base file where it includes necessary +# shell functions and +source /usr/local/bin/backup-integration-stdlib.sh + +if [[ $TZ != "UTC" ]]; then + TIMEZONE=$TZ +else + TIMEZONE="UTC" +fi + +if [[ "${MAKE_IT_FAIL}" != "" ]]; then + echo "error: MAKE_IT_FAIL variable is defined, aborting startup..." + echo "error: If you ready to check your environment variables for any configiration issues," + echo "error: either set it to 'false' or just remove it entirely on your SaaS provider's" + echo "error: env vars settings page. Exiting..." + exit 2 +elif [[ "${MAKE_IT_FAIL}" == "" ]]; then + echo "==> Configuration seems to be fine, proceeding..." +fi +sleep 2 + +# Let Rocket bind the PORT defined on our Dockerfile +export ROCKET_PORT="${PORT:-"8080"}" + +if [[ ${FF_RCLONE_BACKUP} == "" ]] && [[ ${RCLONE_CONFIG} == "" ]]; then + export ORG_ATTACHMENT_LIMIT=0 USER_ATTACHMENT_LIMIT=0 +elif [[ ${FF_RCLONE_BACKUP} == "true" ]] && [[ ${RCLONE_CONFIG} != "" ]]; then + echo "info: attempting to restore rclone config from base64-encoded form..." + if ! echo "${RECLONE_CONFIG}" | base64 -d > /root/.config/rclone/rclone.config; then + echo "warning: base64 decode failed, disabling feature flag..." + export FF_RCLONE_BACKUP=false + fi + export ORG_ATTACHMENT_LIMIT=${ORG_ATTACHMENT_LIMIT:-"1024000"} USER_ATTACHMENT_LIMIT=${USER_ATTACHMENT_LIMIT:-"1024000"} +fi + +# allow icons to be cached on our container in meanwhile +export DISABLE_ICON_DOWNLOAD=false +# Icon caches management +export ICON_CACHE_TTL=120 +export ICON_CACHE_NEGTTL=0 + +# Checks for different providers to provide customized error messages and some fixes, +# especially for IP addresses. +if [[ -n "$RAILWAY_STATIC_URL" ]]; then + export DEPLOY_METHOD="railway" + export IP_HEADER=X-Forwarded-For +elif [[ -n "$SSO_DSN" ]] || [[ -n "$DOMAIN_ALIASES" ]]; then + export DEPLOY_METHOD="divio" + export IP_HEADER=X-Forwarded-For +fi + +if [[ "${ENABLE_ADMIN}" == "true" ]]; then + echo "==> Enabling admin dashboard..." + if [[ "${ADMIN_TOKEN}" == "" ]]; then + echo "warn: No token found for the admin dashboard, generating one" + export ADMIN_TOKEN=$(openssl rand -hex 24) + echo "warn: Generated token is $ADMIN_TOKEN, this will be reset on restart unless set." + fi +else + echo "==> Admin panel is disabled, attempting to remove ADMIN_TOKEN..." + unset ADMIN_TOKEN +fi +echo +sleep 3 + +echo "==> Checking for RSA_CONTENT..." +sleep 3 +if [ -z "${RSA_CONTENT}" ]; then + echo "warn: RSA_CONTENT is missing, so you'll be logged out after leaving the web vault page." + echo "warn: To remove this warning, please run './tools/generate-rsakey ${DEPLOY_METHOD}' on your machine" +elif [[ ${FF_RCLONE_BACKUP} != "true" ]]; then + echo "==> RSA_CONTENT found, attempting to decode..." + if echo "${RSA_CONTENT}" | base64 -d | tar zxf -; then + export RSA_KEY_FILENAME=${PWD}/rsa_key + else + echo "error: Failed to either decode base64 or untar decoded contents." + echo "error: Run './tools/generate-rsakey ${DEPLOY_METHOD}' on your manchine" + exit 1 + fi +else + echo "info: skipped due to backups enabled" +fi +echo +sleep 3 + +# Do some checks if the database is there +if [[ -z "${DATABASE_URL}" ]] && [[ ${FF_RCLONE_BACKUP} != "true" ]]; then + echo "error: DATABASE_URL is undefined, thus the server will not start. To continue, add an DATABASE_URL secret on your PaaS service." + echo "error: If you're running locally, pease either setup an Postgres DB or presistence through rclone" + exit 1 +fi + +function configure_timezone() { + ln -sf "/usr/share/zoneinfo/${TIMEZONE}" "${LOCALTIME_FILE}" +} + +function configure_cron() { + local FIND_CRON_COUNT="$(grep -c 'backup.sh' "${CRON_CONFIG_FILE}" 2> /dev/null)" + if [[ "${FIND_CRON_COUNT}" -eq 0 ]]; then + echo "${CRON} bash /app/backup.sh" >> "${CRON_CONFIG_FILE}" + fi +} + +function setup_remote_access() { + ssh-keygen -A + /usr/sbin/sshd + if [[ $FF_TAILSCALE == "true" ]]; then + /app/tailscaled --tun=userspace-networking --socks5-server=localhost:1055 & + tailscale up --authkey "${TAILSCALE_AUTHKEY}" --hostname="$TAILSCALE_HOSTNAME" + fi +} + +if [[ "${FF_RCLONE_BACKUP}" == "true" ]]; then + init_env + check_rclone_connection + configure_timezone + configure_cron +fi + +echo "==> Checks done, starting up..." +if [[ $FF_RCLONE_BACKUP == "true" ]]; then + concurrently --prefix "[{name} {pid}]" --kill-others "/start.sh" "supercronic -passthrough-logs -quiet \"${CRON_CONFIG_FILE}\"" +else + bash /start.sh +fi \ No newline at end of file diff --git a/vaultwarden-startup b/vaultwarden-startup deleted file mode 100755 index f7487b9..0000000 --- a/vaultwarden-startup +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash - -if [[ "${MAKE_IT_FAIL}" == "true" ]]; then - echo "error: MAKE_IT_FAIL is defined, aborting..." - echo "error: If you ready to check your environment variables for any configiration issues," - echo "error: either set it to 'false' or just remove it entirely on your SaaS provider's" - echo "error: env vars settings page. Exiting..." - exit -elif [[ "${MAKE_IT_FAIL}" == "false" ]]; then - echo "==> Looks fine, starting up..." -else - echo "==> MAKE_IT_FAIL var contains unsupported options, starting up..." - echo " Only true or false are supported. Leaving it blank is also fine." -fi -sleep 2 - -# Let Rocket bind the PORT defined on our Dockerfile -export ROCKET_PORT="${PORT:-"8080"}" - -# While its possible to write stuff in the epherimal storage, we prefer to keep these disabled. -# We'll do some if-then statements if you configured these in the future. -export ORG_ATTACHMENT_LIMIT=0 -export USER_ATTACHMENT_LIMIT=0 -# allow icons to be cached on our container in meanwhile -export DISABLE_ICON_DOWNLOAD=false -# Icon caches management -export ICON_CACHE_TTL=120 -export ICON_CACHE_NEGTTL=0 - -# Checks for different providers to provide customized error messages and some fixes. -if [[ -n "$RAILWAY_STATIC_URL" ]]; then - export DEPLOY_METHOD="railway" - export IP_HEADER=X-Forwarded-For -elif [[ -n "$SSO_DSN" ]] || [[ -n "$DOMAIN_ALIASES" ]]; then - export DEPLOY_METHOD="divio" - export IP_HEADER=X-Forwarded-For -fi - -if [[ "${ENABLE_ADMIN}" == "true" ]]; then - echo "==> Enabling admin dashboard..." - if [[ "${ADMIN_TOKEN}" == "" ]]; then - echo " No token found for the admin dashboard, unsetting..." - unset ADMIN_TOKEN - fi -else - echo "==> Admin panel is disabled, removing ADMIN_TOKEN..." - unset ADMIN_TOKEN -fi -echo -sleep 3 - -echo "==> Checking for RSA_CONTENT..." -sleep 3 -if [ -z "${RSA_CONTENT}" ]; then - echo "warn: RSA_CONTENT is missing, so you'll be logged out after leaving the web vault page." - echo "warn: To remove this warning, please run './tools/generate-rsakey ${DEPLOY_METHOD}' on your machine" -else - echo "==> RSA_CONTENT found, attempting to decode..." - if echo "${RSA_CONTENT}" | base64 -d | tar zxf -; then - export RSA_KEY_FILENAME=${PWD}/rsa_key - else - echo "error: Failed to either decode base64 or untar decoded contents." - echo "error: Run './tools/generate-rsakey ${DEPLOY_METHOD}' on your manchine" - exit 1 - fi -fi -echo -sleep 3 - -# Do some checks if the database is there -if [[ -z "${DATABASE_URL}" ]]; then - echo "error: DATABASE_URL is undefined, thus the server will not start. To continue, add an DATABASE_URL secret on your PaaS service." - echo "error: If you're running locally, ease setup an Postgres instance" - exit 1 -fi - -echo "==> Checks done, starting up..." -/bin/sh /start.sh