diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..eaf6989 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,78 @@ +--- +name: Release + +on: + workflow_dispatch: + inputs: + release_version: + description: "Release Version" + required: true + +jobs: + setup: + name: Setup + runs-on: ubuntu-20.04 + outputs: + release_version: ${{ steps.get-release.outputs.version }} + branch-name: ${{ steps.branch.outputs.branch-name }} + steps: + - name: Branch check + run: | + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then + echo "===================================" + echo "[!] Can only release from the 'rc' or 'hotfix' branches" + echo "===================================" + exit 1 + fi + + - name: Checkout repo + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + + - name: Get Latest Self-Host Version + id: get-self-host + uses: bitwarden/gh-actions/get-release-version@3e4c4366c104a744e458b8f9eb92944237dd1099 + with: + repository: bitwarden/self-host + + - name: Check Release Version + id: get-release + env: + RELEASE_VERSION: ${{ github.event.inputs.release_version }} + PREVIOUS_RELEASE_VERSION: ${{ steps.get-self-host.outputs.version }} + run: | + if [ "$RELEASE_VERSION" == "$PREVIOUS_RELEASE_VERSION" ]; then + echo "[!] Already released v$RELEASE_VERSION. Please bump version to continue" + exit 1 + fi + + - name: Get branch name + id: branch + run: | + BRANCH_NAME=$(basename ${{ github.ref }}) + echo "::set-output name=branch-name::$BRANCH_NAME" + + + release: + name: Create GitHub Release + runs-on: ubuntu-20.04 + needs: + - setup + steps: + - name: Checkout repo + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + with: + ref: ${{ needs.setup.outputs.branch-name }} + + - name: Create release + uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 + with: + artifacts: 'bitwarden.sh, + run.sh, + bitwarden.ps1, + run.ps1' + commit: ${{ github.sha }} + tag: "v${{ needs.setup.outputs.release_version }}" + name: "Version ${{ needs.setup.outputs.release_version }}" + body: "" + token: ${{ secrets.GITHUB_TOKEN }} + draft: true diff --git a/.github/workflows/update-versions.yml b/.github/workflows/update-versions.yml new file mode 100644 index 0000000..6b3a32d --- /dev/null +++ b/.github/workflows/update-versions.yml @@ -0,0 +1,104 @@ +--- +name: Update Versions + +on: + workflow_dispatch: + inputs: {} + +jobs: + setup: + name: Setup + runs-on: ubuntu-20.04 + outputs: + core_version: ${{ steps.get-core.outputs.version }} + web_version: ${{ steps.get-web.outputs.version }} + key_connector_version: ${{ steps.get-key-connector.outputs.version }} + steps: + - name: Get Latest Core Version + id: get-core + uses: bitwarden/gh-actions/get-release-version@3e4c4366c104a744e458b8f9eb92944237dd1099 + with: + repository: bitwarden/server + + - name: Get Latest Web Version + id: get-web + uses: bitwarden/gh-actions/get-release-version@3e4c4366c104a744e458b8f9eb92944237dd1099 + with: + repository: bitwarden/web + + - name: Get Latest Key Connector Version + id: get-key-connector + uses: bitwarden/gh-actions/get-release-version@3e4c4366c104a744e458b8f9eb92944237dd1099 + with: + repository: bitwarden/key-connector + + + update-versions: + name: "Create update_versions branch" + runs-on: ubuntu-20.04 + needs: setup + steps: + - name: Checkout Branch + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + + - name: Create Update Versions Branch + run: | + git switch -c update-versions + git push -u origin update-versions + + - name: Checkout Update Versions Branch + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + with: + ref: update-versions + + - name: Update Core Version + env: + VERSION: ${{ needs.setup.outputs.core_version }} + run: | + sed -i -e "/^\s*COREVERSION\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$VERSION/" bitwarden.sh + sed -i -e "/^\s*\$coreVersion\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$VERSION/" bitwarden.ps1 + + - name: Update Web Version + env: + VERSION: ${{ needs.setup.outputs.web_version }} + run: | + sed -i -e "/^\s*WEBVERSION\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$VERSION/" bitwarden.sh + sed -i -e "/^\s*\$webVersion\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$VERSION/" bitwarden.ps1 + + - name: Update Key Connector Version + env: + VERSION: ${{ needs.setup.outputs.key_connector_version }} + run: | + sed -i -e "/^\s*KEYCONNECTORVERSION\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$VERSION/" bitwarden.sh + sed -i -e "/^\s*\$keyConnectorVersion\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$VERSION/" bitwarden.ps1 + + - name: Commit updated files + run: | + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git commit -m "Updated core, web, and key-connector versions" -a + + - name: Push changes + run: git push -u origin update-versions + + - name: Create Update Versions PR + env: + PR_BRANCH: "update-versions" + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + BASE_BRANCH: master + TITLE: "Update core, web, and key-connector versions" + run: | + gh pr create --title "$TITLE" \ + --base "$BASE" \ + --head "$PR_BRANCH" \ + --label "automated pr" \ + --body " + ## Type of change + - [ ] Bug fix + - [ ] New feature development + - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) + - [ ] Build/deploy pipeline (DevOps) + - [X] Other + + ## Objective + Automated version updates to core, web, and key-connector versions in `bitwarden.sh` and `bitwarden.ps1`." diff --git a/bitwarden.ps1 b/bitwarden.ps1 new file mode 100644 index 0000000..1e6dc22 --- /dev/null +++ b/bitwarden.ps1 @@ -0,0 +1,165 @@ +param ( + [switch] $install, + [switch] $start, + [switch] $restart, + [switch] $stop, + [switch] $update, + [switch] $rebuild, + [switch] $updateconf, + [switch] $renewcert, + [switch] $updatedb, + [switch] $updaterun, + [switch] $updateself, + [switch] $help, + [string] $output = "" +) + +# Setup + +$scriptPath = $MyInvocation.MyCommand.Path +$dir = Split-Path -Parent $MyInvocation.MyCommand.Path +if ($output -eq "") { + $output = "${dir}\bwdata" +} + +$scriptsDir = "${output}\scripts" +$githubBaseUrl = "https://raw.githubusercontent.com/bitwarden/server/master" + +# Please do not create pull requests modifying the version numbers. +$coreVersion = "1.45.2" +$webVersion = "2.25.0" +$keyConnectorVersion = "1.0.0" + +# Functions + +function Get-Self { + Invoke-RestMethod -OutFile $scriptPath -Uri "${githubBaseUrl}/scripts/bitwarden.ps1" +} + +function Get-Run-File { + if (!(Test-Path -Path $scriptsDir)) { + New-Item -ItemType directory -Path $scriptsDir | Out-Null + } + Invoke-RestMethod -OutFile $scriptsDir\run.ps1 -Uri "${githubBaseUrl}/scripts/run.ps1" +} + +function Test-Output-Dir-Exists { + if (!(Test-Path -Path $output)) { + throw "Cannot find a Bitwarden installation at $output." + } +} + +function Test-Output-Dir-Not-Exists { + if (Test-Path -Path "$output\docker") { + throw "Looks like Bitwarden is already installed at $output." + } +} + +function Show-Commands { + Write-Line " +Available commands: + +-install +-start +-restart +-stop +-update +-updatedb +-updaterun +-updateself +-updateconf +-renewcert +-rebuild +-help + +See more at https://bitwarden.com/help/article/install-on-premise/#script-commands-reference +" +} + +function Write-Line($str) { + if($env:BITWARDEN_QUIET -ne "true") { + Write-Host $str + } +} + +# Intro + +$year = (Get-Date).year + +Write-Line @' + _ _ _ _ +| |__ (_) |___ ____ _ _ __ __| | ___ _ __ +| '_ \| | __\ \ /\ / / _` | '__/ _` |/ _ \ '_ \ +| |_) | | |_ \ V V / (_| | | | (_| | __/ | | | +|_.__/|_|\__| \_/\_/ \__,_|_| \__,_|\___|_| |_| +'@ + +Write-Line " +Open source password management solutions +Copyright 2015-${year}, 8bit Solutions LLC +https://bitwarden.com, https://github.com/bitwarden + +=================================================== +" + +if($env:BITWARDEN_QUIET -ne "true") { + Write-Line "bitwarden.ps1 version ${coreVersion}" + docker --version + docker-compose --version +} + +Write-Line "" + +# Commands + +if ($install) { + Test-Output-Dir-Not-Exists + New-Item -ItemType directory -Path $output -ErrorAction Ignore | Out-Null + Get-Run-File + Invoke-Expression "& `"$scriptsDir\run.ps1`" -install -outputDir `"$output`" -coreVersion $coreVersion -webVersion $webVersion -keyConnectorVersion $keyConnectorVersion" +} +elseif ($start -Or $restart) { + Test-Output-Dir-Exists + Invoke-Expression "& `"$scriptsDir\run.ps1`" -restart -outputDir `"$output`" -coreVersion $coreVersion -webVersion $webVersion -keyConnectorVersion $keyConnectorVersion" +} +elseif ($update) { + Test-Output-Dir-Exists + Get-Run-File + Invoke-Expression "& `"$scriptsDir\run.ps1`" -update -outputDir `"$output`" -coreVersion $coreVersion -webVersion $webVersion -keyConnectorVersion $keyConnectorVersion" +} +elseif ($rebuild) { + Test-Output-Dir-Exists + Invoke-Expression "& `"$scriptsDir\run.ps1`" -rebuild -outputDir `"$output`" -coreVersion $coreVersion -webVersion $webVersion -keyConnectorVersion $keyConnectorVersion" +} +elseif ($updateconf) { + Test-Output-Dir-Exists + Invoke-Expression "& `"$scriptsDir\run.ps1`" -updateconf -outputDir `"$output`" -coreVersion $coreVersion -webVersion $webVersion -keyConnectorVersion $keyConnectorVersion" +} +elseif ($updatedb) { + Test-Output-Dir-Exists + Invoke-Expression "& `"$scriptsDir\run.ps1`" -updatedb -outputDir `"$output`" -coreVersion $coreVersion -webVersion $webVersion -keyConnectorVersion $keyConnectorVersion" +} +elseif ($stop) { + Test-Output-Dir-Exists + Invoke-Expression "& `"$scriptsDir\run.ps1`" -stop -outputDir `"$output`" -coreVersion $coreVersion -webVersion $webVersion -keyConnectorVersion $keyConnectorVersion" +} +elseif ($renewcert) { + Test-Output-Dir-Exists + Invoke-Expression "& `"$scriptsDir\run.ps1`" -renewcert -outputDir `"$output`" -coreVersion $coreVersion -webVersion $webVersion -keyConnectorVersion $keyConnectorVersion" +} +elseif ($updaterun) { + Test-Output-Dir-Exists + Get-Run-File +} +elseif ($updateself) { + Get-Self + Write-Line "Updated self." +} +elseif ($help) { + Show-Commands +} +else { + Write-Line "No command found." + Write-Line "" + Show-Commands +} diff --git a/bitwarden.sh b/bitwarden.sh new file mode 100755 index 0000000..9982643 --- /dev/null +++ b/bitwarden.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +set -e + +cat << "EOF" + _ _ _ _ +| |__ (_) |___ ____ _ _ __ __| | ___ _ __ +| '_ \| | __\ \ /\ / / _` | '__/ _` |/ _ \ '_ \ +| |_) | | |_ \ V V / (_| | | | (_| | __/ | | | +|_.__/|_|\__| \_/\_/ \__,_|_| \__,_|\___|_| |_| + +EOF + +cat << EOF +Open source password management solutions +Copyright 2015-$(date +'%Y'), 8bit Solutions LLC +https://bitwarden.com, https://github.com/bitwarden + +=================================================== + +EOF + +# Setup + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +SCRIPT_NAME=$(basename "$0") +SCRIPT_PATH="$DIR/$SCRIPT_NAME" +OUTPUT="$DIR/bwdata" +if [ $# -eq 2 ] +then + OUTPUT=$2 +fi + +SCRIPTS_DIR="$OUTPUT/scripts" +GITHUB_BASE_URL="https://raw.githubusercontent.com/bitwarden/server/master" + +# Please do not create pull requests modifying the version numbers. +COREVERSION="1.45.2" +WEBVERSION="2.25.0" +KEYCONNECTORVERSION="1.0.0" + +echo "bitwarden.sh version $COREVERSION" +docker --version +docker-compose --version + +echo "" + +# Functions + +function downloadSelf() { + if curl -s -w "http_code %{http_code}" -o $SCRIPT_PATH.1 $GITHUB_BASE_URL/scripts/bitwarden.sh | grep -q "^http_code 20[0-9]" + then + mv $SCRIPT_PATH.1 $SCRIPT_PATH + chmod u+x $SCRIPT_PATH + else + rm -f $SCRIPT_PATH.1 + fi +} + +function downloadRunFile() { + if [ ! -d "$SCRIPTS_DIR" ] + then + mkdir $SCRIPTS_DIR + fi + curl -s -o $SCRIPTS_DIR/run.sh $GITHUB_BASE_URL/scripts/run.sh + chmod u+x $SCRIPTS_DIR/run.sh + rm -f $SCRIPTS_DIR/install.sh +} + +function checkOutputDirExists() { + if [ ! -d "$OUTPUT" ] + then + echo "Cannot find a Bitwarden installation at $OUTPUT." + exit 1 + fi +} + +function checkOutputDirNotExists() { + if [ -d "$OUTPUT/docker" ] + then + echo "Looks like Bitwarden is already installed at $OUTPUT." + exit 1 + fi +} + +function listCommands() { +cat << EOT +Available commands: + +install +start +restart +stop +update +updatedb +updaterun +updateself +updateconf +renewcert +rebuild +help + +See more at https://bitwarden.com/help/article/install-on-premise/#script-commands-reference + +EOT +} + +# Commands + +case $1 in + "install") + checkOutputDirNotExists + mkdir -p $OUTPUT + downloadRunFile + $SCRIPTS_DIR/run.sh install $OUTPUT $COREVERSION $WEBVERSION $KEYCONNECTORVERSION + ;; + "start" | "restart") + checkOutputDirExists + $SCRIPTS_DIR/run.sh restart $OUTPUT $COREVERSION $WEBVERSION $KEYCONNECTORVERSION + ;; + "update") + checkOutputDirExists + downloadRunFile + $SCRIPTS_DIR/run.sh update $OUTPUT $COREVERSION $WEBVERSION $KEYCONNECTORVERSION + ;; + "rebuild") + checkOutputDirExists + $SCRIPTS_DIR/run.sh rebuild $OUTPUT $COREVERSION $WEBVERSION $KEYCONNECTORVERSION + ;; + "updateconf") + checkOutputDirExists + $SCRIPTS_DIR/run.sh updateconf $OUTPUT $COREVERSION $WEBVERSION $KEYCONNECTORVERSION + ;; + "updatedb") + checkOutputDirExists + $SCRIPTS_DIR/run.sh updatedb $OUTPUT $COREVERSION $WEBVERSION $KEYCONNECTORVERSION + ;; + "stop") + checkOutputDirExists + $SCRIPTS_DIR/run.sh stop $OUTPUT $COREVERSION $WEBVERSION $KEYCONNECTORVERSION + ;; + "renewcert") + checkOutputDirExists + $SCRIPTS_DIR/run.sh renewcert $OUTPUT $COREVERSION $WEBVERSION $KEYCONNECTORVERSION + ;; + "updaterun") + checkOutputDirExists + downloadRunFile + ;; + "updateself") + downloadSelf && echo "Updated self." && exit + ;; + "help") + listCommands + ;; + *) + echo "No command found." + echo + listCommands +esac diff --git a/run.ps1 b/run.ps1 new file mode 100644 index 0000000..78e560c --- /dev/null +++ b/run.ps1 @@ -0,0 +1,256 @@ +param ( + [string]$outputDir = "../.", + [string]$coreVersion = "latest", + [string]$webVersion = "latest", + [string]$keyConnectorVersion = "latest", + [switch] $install, + [switch] $start, + [switch] $restart, + [switch] $stop, + [switch] $pull, + [switch] $updateconf, + [switch] $renewcert, + [switch] $updatedb, + [switch] $update +) + +# Setup + +$dockerDir = "${outputDir}\docker" +$setupQuiet = 0 +$qFlag = "" +$quietPullFlag = "" +$certbotHttpPort = "80" +$certbotHttpsPort = "443" +if ($env:BITWARDEN_QUIET -eq "true") { + $setupQuiet = 1 + $qFlag = " -q" + $quietPullFlag = " --quiet-pull" +} +if ("${env:BITWARDEN_CERTBOT_HTTP_PORT}" -ne "") { + $certbotHttpPort = $env:BITWARDEN_CERTBOT_HTTP_PORT +} +if ("${env:BITWARDEN_CERTBOT_HTTPS_PORT}" -ne "") { + $certbotHttpsPort = $env:BITWARDEN_CERTBOT_HTTPS_PORT +} + +# Functions + +function Install() { + [string]$letsEncrypt = "n" + Write-Host "(!) " -f cyan -nonewline + [string]$domain = $( Read-Host "Enter the domain name for your Bitwarden instance (ex. bitwarden.example.com)" ) + echo "" + + if ($domain -eq "") { + $domain = "localhost" + } + + if ($domain -ne "localhost") { + Write-Host "(!) " -f cyan -nonewline + $letsEncrypt = $( Read-Host "Do you want to use Let's Encrypt to generate a free SSL certificate? (y/n)" ) + echo "" + + if ($letsEncrypt -eq "y") { + Write-Host "(!) " -f cyan -nonewline + [string]$email = $( Read-Host ("Enter your email address (Let's Encrypt will send you certificate " + + "expiration reminders)") ) + echo "" + + $letsEncryptPath = "${outputDir}/letsencrypt" + if (!(Test-Path -Path $letsEncryptPath )) { + New-Item -ItemType directory -Path $letsEncryptPath | Out-Null + } + Invoke-Expression ("docker pull{0} certbot/certbot" -f "") #TODO: qFlag + $certbotExp = "docker run -it --rm --name certbot -p ${certbotHttpsPort}:443 -p ${certbotHttpPort}:80 " + ` + "-v ${outputDir}/letsencrypt:/etc/letsencrypt/ certbot/certbot " + ` + "certonly{0} --standalone --noninteractive --agree-tos --preferred-challenges http " + ` + "--email ${email} -d ${domain} --logs-dir /etc/letsencrypt/logs" + Invoke-Expression ($certbotExp -f $qFlag) + } + } + + Write-Host "(!) " -f cyan -nonewline + [string]$database = $( Read-Host "Enter the database name for your Bitwarden instance (ex. vault): ") + echo "" + + if ($database -eq "") { + $database = "vault" + } + + Pull-Setup + docker run -it --rm --name setup -v ${outputDir}:/bitwarden bitwarden/setup:$coreVersion ` + dotnet Setup.dll -install 1 -domain ${domain} -letsencrypt ${letsEncrypt} ` + -os win -corev $coreVersion -webv $webVersion -keyconnectorv $keyConnectorVersion -q $setupQuiet -dbname "$database" +} + +function Docker-Compose-Up { + Docker-Compose-Files + Docker-Compose-Volumes + Invoke-Expression ("docker-compose up -d{0}" -f $quietPullFlag) +} + +function Docker-Compose-Down { + Docker-Compose-Files + if ((Invoke-Expression ("docker-compose ps{0}" -f "") | Measure-Object -Line).lines -gt 2 ) { + Invoke-Expression ("docker-compose down{0}" -f "") #TODO: qFlag + } +} + +function Docker-Compose-Pull { + Docker-Compose-Files + Invoke-Expression ("docker-compose pull{0}" -f $qFlag) +} + +function Docker-Compose-Files { + if (Test-Path -Path "${dockerDir}\docker-compose.override.yml" -PathType leaf) { + $env:COMPOSE_FILE = "${dockerDir}\docker-compose.yml;${dockerDir}\docker-compose.override.yml" + } + else { + $env:COMPOSE_FILE = "${dockerDir}\docker-compose.yml" + } + $env:COMPOSE_HTTP_TIMEOUT = "300" +} + +function Docker-Compose-Volumes { + Create-Dir "core" + Create-Dir "core/attachments" + Create-Dir "logs" + Create-Dir "logs/admin" + Create-Dir "logs/api" + Create-Dir "logs/events" + Create-Dir "logs/icons" + Create-Dir "logs/identity" + Create-Dir "logs/mssql" + Create-Dir "logs/nginx" + Create-Dir "logs/notifications" + Create-Dir "logs/sso" + Create-Dir "logs/portal" + Create-Dir "mssql/backups" + Create-Dir "mssql/data" +} + +function Create-Dir($str) { + $outPath = "${outputDir}/$str" + if (!(Test-Path -Path $outPath )) { + Write-Line "Creating directory $outPath" + New-Item -ItemType directory -Path $outPath | Out-Null + } +} + +function Docker-Prune { + docker image prune --all --force --filter="label=com.bitwarden.product=bitwarden" ` + --filter="label!=com.bitwarden.project=setup" +} + +function Update-Lets-Encrypt { + if (Test-Path -Path "${outputDir}\letsencrypt\live") { + Invoke-Expression ("docker pull{0} certbot/certbot" -f "") #TODO: qFlag + $certbotExp = "docker run -it --rm --name certbot -p ${certbotHttpsPort}:443 -p ${certbotHttpPort}:80 " + ` + "-v ${outputDir}/letsencrypt:/etc/letsencrypt/ certbot/certbot " + ` + "renew{0} --logs-dir /etc/letsencrypt/logs" -f $qFlag + Invoke-Expression $certbotExp + } +} + +function Force-Update-Lets-Encrypt { + if (Test-Path -Path "${outputDir}\letsencrypt\live") { + Invoke-Expression ("docker pull{0} certbot/certbot" -f "") #TODO: qFlag + $certbotExp = "docker run -it --rm --name certbot -p ${certbotHttpsPort}:443 -p ${certbotHttpPort}:80 " + ` + "-v ${outputDir}/letsencrypt:/etc/letsencrypt/ certbot/certbot " + ` + "renew{0} --logs-dir /etc/letsencrypt/logs --force-renew" -f $qFlag + Invoke-Expression $certbotExp + } +} + +function Update-Database { + Pull-Setup + Docker-Compose-Files + $mssqlId = docker-compose ps -q mssql + docker run -it --rm --name setup --network container:$mssqlId ` + -v ${outputDir}:/bitwarden bitwarden/setup:$coreVersion ` + dotnet Setup.dll -update 1 -db 1 -os win -corev $coreVersion -webv $webVersion ` + -keyconnectorv $keyConnectorVersion -q $setupQuiet + Write-Line "Database update complete" +} + +function Update([switch] $withpull) { + if ($withpull) { + Pull-Setup + } + docker run -it --rm --name setup -v ${outputDir}:/bitwarden bitwarden/setup:$coreVersion ` + dotnet Setup.dll -update 1 -os win -corev $coreVersion -webv $webVersion ` + -keyconnectorv $keyConnectorVersion -q $setupQuiet +} + +function Print-Environment { + Pull-Setup + docker run -it --rm --name setup -v ${outputDir}:/bitwarden bitwarden/setup:$coreVersion ` + dotnet Setup.dll -printenv 1 -os win -corev $coreVersion -webv $webVersion ` + -keyconnectorv $keyConnectorVersion -q $setupQuiet +} + +function Restart { + Docker-Compose-Down + Docker-Compose-Pull + Update-Lets-Encrypt + Docker-Compose-Up + Print-Environment +} + +function Cert-Restart { + Docker-Compose-Down + Docker-Compose-Pull + Force-Update-Lets-Encrypt + Docker-Compose-Up + Print-Environment +} + + +function Pull-Setup { + Invoke-Expression ("docker pull{0} bitwarden/setup:${coreVersion}" -f "") #TODO: qFlag +} + +function Write-Line($str) { + if ($env:BITWARDEN_QUIET -ne "true") { + Write-Host $str + } +} + +# Commands + +if ($install) { + Install +} +elseif ($start -Or $restart) { + Restart +} +elseif ($pull) { + Docker-Compose-Pull +} +elseif ($stop) { + Docker-Compose-Down +} +elseif ($renewcert) { + Cert-Restart +} +elseif ($updateconf) { + Docker-Compose-Down + Update -withpull +} +elseif ($updatedb) { + Update-Database +} +elseif ($update) { + Docker-Compose-Down + Update -withpull + Restart + Docker-Prune + Write-Line "Pausing 60 seconds for database to come online. Please wait..." + Start-Sleep -s 60 + Update-Database +} +elseif ($rebuild) { + Docker-Compose-Down + Update +} diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..a08a465 --- /dev/null +++ b/run.sh @@ -0,0 +1,292 @@ +#!/usr/bin/env bash +set -e + +# Setup + +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +OUTPUT_DIR=".." +if [ $# -gt 1 ] +then + OUTPUT_DIR=$2 +fi + +COREVERSION="latest" +if [ $# -gt 2 ] +then + COREVERSION=$3 +fi + +WEBVERSION="latest" +if [ $# -gt 3 ] +then + WEBVERSION=$4 +fi + +KEYCONNECTORVERSION="latest" +if [ $# -gt 4 ] +then + KEYCONNECTORVERSION=$5 +fi + +OS="lin" +[ "$(uname)" == "Darwin" ] && OS="mac" +ENV_DIR="$OUTPUT_DIR/env" +DOCKER_DIR="$OUTPUT_DIR/docker" + +# Initialize UID/GID which will be used to run services from within containers +if ! grep -q "^LOCAL_UID=" $ENV_DIR/uid.env 2>/dev/null || ! grep -q "^LOCAL_GID=" $ENV_DIR/uid.env 2>/dev/null +then + LUID="LOCAL_UID=`id -u $USER`" + [ "$LUID" == "LOCAL_UID=0" ] && LUID="LOCAL_UID=65534" + LGID="LOCAL_GID=`id -g $USER`" + [ "$LGID" == "LOCAL_GID=0" ] && LGID="LOCAL_GID=65534" + mkdir -p $ENV_DIR + echo $LUID >$ENV_DIR/uid.env + echo $LGID >>$ENV_DIR/uid.env +fi + +# Functions + +function install() { + LETS_ENCRYPT="n" + echo -e -n "${CYAN}(!)${NC} Enter the domain name for your Bitwarden instance (ex. bitwarden.example.com): " + read DOMAIN + echo "" + + if [ "$DOMAIN" == "" ] + then + DOMAIN="localhost" + fi + + if [ "$DOMAIN" != "localhost" ] + then + echo -e -n "${CYAN}(!)${NC} Do you want to use Let's Encrypt to generate a free SSL certificate? (y/n): " + read LETS_ENCRYPT + echo "" + + if [ "$LETS_ENCRYPT" == "y" ] + then + echo -e -n "${CYAN}(!)${NC} Enter your email address (Let's Encrypt will send you certificate expiration reminders): " + read EMAIL + echo "" + + mkdir -p $OUTPUT_DIR/letsencrypt + docker pull certbot/certbot + docker run -it --rm --name certbot -p 80:80 -v $OUTPUT_DIR/letsencrypt:/etc/letsencrypt/ certbot/certbot \ + certonly --standalone --noninteractive --agree-tos --preferred-challenges http \ + --email $EMAIL -d $DOMAIN --logs-dir /etc/letsencrypt/logs + fi + fi + + echo -e -n "${CYAN}(!)${NC} Enter the database name for your Bitwarden instance (ex. vault): " + read DATABASE + echo "" + + if [ "$DATABASE" == "" ] + then + DATABASE="vault" + fi + + pullSetup + docker run -it --rm --name setup -v $OUTPUT_DIR:/bitwarden \ + --env-file $ENV_DIR/uid.env bitwarden/setup:$COREVERSION \ + dotnet Setup.dll -install 1 -domain $DOMAIN -letsencrypt $LETS_ENCRYPT -os $OS \ + -corev $COREVERSION -webv $WEBVERSION -dbname "$DATABASE" -keyconnectorv $KEYCONNECTORVERSION +} + +function dockerComposeUp() { + dockerComposeFiles + dockerComposeVolumes + docker-compose up -d +} + +function dockerComposeDown() { + dockerComposeFiles + if [ $(docker-compose ps | wc -l) -gt 2 ]; then + docker-compose down + fi +} + +function dockerComposePull() { + dockerComposeFiles + docker-compose pull +} + +function dockerComposeFiles() { + if [ -f "${DOCKER_DIR}/docker-compose.override.yml" ] + then + export COMPOSE_FILE="$DOCKER_DIR/docker-compose.yml:$DOCKER_DIR/docker-compose.override.yml" + else + export COMPOSE_FILE="$DOCKER_DIR/docker-compose.yml" + fi + export COMPOSE_HTTP_TIMEOUT="300" +} + +function dockerComposeVolumes() { + createDir "core" + createDir "core/attachments" + createDir "logs" + createDir "logs/admin" + createDir "logs/api" + createDir "logs/events" + createDir "logs/icons" + createDir "logs/identity" + createDir "logs/mssql" + createDir "logs/nginx" + createDir "logs/notifications" + createDir "logs/sso" + createDir "logs/portal" + createDir "mssql/backups" + createDir "mssql/data" +} + +function createDir() { + if [ ! -d "${OUTPUT_DIR}/$1" ] + then + echo "Creating directory $OUTPUT_DIR/$1" + mkdir -p $OUTPUT_DIR/$1 + fi +} + +function dockerPrune() { + docker image prune --all --force --filter="label=com.bitwarden.product=bitwarden" \ + --filter="label!=com.bitwarden.project=setup" +} + +function updateLetsEncrypt() { + if [ -d "${OUTPUT_DIR}/letsencrypt/live" ] + then + docker pull certbot/certbot + docker run -i --rm --name certbot -p 443:443 -p 80:80 \ + -v $OUTPUT_DIR/letsencrypt:/etc/letsencrypt/ certbot/certbot \ + renew --logs-dir /etc/letsencrypt/logs + fi +} + +function forceUpdateLetsEncrypt() { + if [ -d "${OUTPUT_DIR}/letsencrypt/live" ] + then + docker pull certbot/certbot + docker run -i --rm --name certbot -p 443:443 -p 80:80 \ + -v $OUTPUT_DIR/letsencrypt:/etc/letsencrypt/ certbot/certbot \ + renew --logs-dir /etc/letsencrypt/logs --force-renew + fi +} + +function updateDatabase() { + pullSetup + dockerComposeFiles + MSSQL_ID=$(docker-compose ps -q mssql) + docker run -i --rm --name setup --network container:$MSSQL_ID \ + -v $OUTPUT_DIR:/bitwarden --env-file $ENV_DIR/uid.env bitwarden/setup:$COREVERSION \ + dotnet Setup.dll -update 1 -db 1 -os $OS -corev $COREVERSION -webv $WEBVERSION -keyconnectorv $KEYCONNECTORVERSION + echo "Database update complete" +} + +function updatebw() { + KEY_CONNECTOR_ENABLED=$(grep -A3 'enable_key_connector:' $OUTPUT_DIR/config.yml | tail -n1 | awk '{ print $2}') + CORE_ID=$(docker-compose ps -q admin) + WEB_ID=$(docker-compose ps -q web) + if [ $KEY_CONNECTOR_ENABLED = true ]; + then + KEYCONNECTOR_ID=$(docker-compose ps -q key-connector) + fi + + if [ $KEYCONNECTOR_ID ] && + docker inspect --format='{{.Config.Image}}:' $CORE_ID | grep -F ":$COREVERSION:" | grep -q ":[0-9.]*:$" && + docker inspect --format='{{.Config.Image}}:' $WEB_ID | grep -F ":$WEBVERSION:" | grep -q ":[0-9.]*:$" && + docker inspect --format='{{.Config.Image}}:' $KEYCONNECTOR_ID | grep -F ":$KEYCONNECTORVERSION:" | grep -q ":[0-9.]*:$" + then + echo "Update not needed" + exit + elif + docker inspect --format='{{.Config.Image}}:' $CORE_ID | grep -F ":$COREVERSION:" | grep -q ":[0-9.]*:$" && + docker inspect --format='{{.Config.Image}}:' $WEB_ID | grep -F ":$WEBVERSION:" | grep -q ":[0-9.]*:$" + then + echo "Update not needed" + exit + fi + dockerComposeDown + update withpull + restart + dockerPrune + echo "Pausing 60 seconds for database to come online. Please wait..." + sleep 60 +} + +function update() { + if [ "$1" == "withpull" ] + then + pullSetup + fi + docker run -i --rm --name setup -v $OUTPUT_DIR:/bitwarden \ + --env-file $ENV_DIR/uid.env bitwarden/setup:$COREVERSION \ + dotnet Setup.dll -update 1 -os $OS -corev $COREVERSION -webv $WEBVERSION -keyconnectorv $KEYCONNECTORVERSION +} + +function printEnvironment() { + pullSetup + docker run -i --rm --name setup -v $OUTPUT_DIR:/bitwarden \ + --env-file $ENV_DIR/uid.env bitwarden/setup:$COREVERSION \ + dotnet Setup.dll -printenv 1 -os $OS -corev $COREVERSION -webv $WEBVERSION -keyconnectorv $KEYCONNECTORVERSION +} + +function restart() { + dockerComposeDown + dockerComposePull + updateLetsEncrypt + dockerComposeUp + printEnvironment +} + +function certRestart() { + dockerComposeDown + dockerComposePull + forceUpdateLetsEncrypt + dockerComposeUp + printEnvironment +} + +function pullSetup() { + docker pull bitwarden/setup:$COREVERSION +} + +# Commands + +case $1 in + "install") + install + ;; + "start" | "restart") + restart + ;; + "pull") + dockerComposePull + ;; + "stop") + dockerComposeDown + ;; + "renewcert") + certRestart + ;; + "updateconf") + dockerComposeDown + update withpull + ;; + "updatedb") + updateDatabase + ;; + "update") + dockerComposeFiles + updatebw + updateDatabase + ;; + "rebuild") + dockerComposeDown + update nopull + ;; +esac