├── .gitignore ├── LICENSE ├── README.md ├── check.sh ├── crontab ├── data ├── .gitignore └── config.json.template ├── docker-image-extract ├── env.sh ├── passenger_wsgi.py └── start.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | vaultwarden 3 | output/ 4 | web-vault/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2021, Jeremy Lin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Archived 2 | 3 | DreamHost no longer offers Passenger, so this configuration won't work anymore. 4 | 5 | From https://help.dreamhost.com/hc/en-us/articles/23628302213652-How-to-upgrade-to-a-VPS-and-use-a-Proxy-Server: 6 | 7 | > Passenger will be removed from all Shared servers on March 31st, 2024. 8 | > 9 | > If you previously enabled Passenger on a Shared server and configured your website to use it, your application will no longer function as of this date. 10 | 11 | --- 12 | 13 | ## Running Vaultwarden on a shared hosting service 14 | 15 | **Note: Vaultwarden was formerly known as bitwarden_rs.** 16 | 17 | This is not a common configuration, and not recommended compared to running 18 | on a VPS or similar, but sometimes an organization only has access to a 19 | shared hosting service and only wants to use that. This repo contains an 20 | example of how that can be accomplished on 21 | [DreamHost](https://www.dreamhost.com/) specifically, though with minor 22 | changes, it can probably be applied to many other shared hosting services. 23 | 24 | Shared hosting is generally geared towards running PHP apps. Many hosts also 25 | support Ruby, Python, and/or Node.js apps, but there is generally no direct 26 | support for reverse proxying to an arbitrary backend service. The example 27 | provided here uses a small Python 28 | [WSGI](https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface) app to 29 | proxy to the Vaultwarden backend. 30 | 31 | This document assumes basic familiarity with Linux (knowing how to SSH into a 32 | host, copy files to the host, make and change into directories, etc.). 33 | 34 | ## Prerequisites 35 | 36 | In the DreamHost admin panel, create a new subdomain (e.g., `bw.example.org`) 37 | and enable HTTPS and [Passenger](https://www.phusionpassenger.com/). 38 | 39 | For more details, see [How do I enable Passenger on my domain?](https://help.dreamhost.com/hc/en-us/articles/216385637-How-do-I-enable-Passenger-on-my-domain-) 40 | 41 | ## Clone this repo 42 | 43 | SSH into your subdomain account. From your home directory, run 44 | 45 | $ git clone https://github.com/jjlin/vaultwarden-shared-hosting.git vaultwarden 46 | 47 | This makes a copy of this repo in a directory called `vaultwarden`. You can use 48 | a different directory name if you prefer, but you'll have to modify the value 49 | of `VAULTWARDEN_HOME` in `passenger_wsgi.py` accordingly. 50 | 51 | If the `git` command above isn't available for some reason, you can also just 52 | download a zip file of the repo and extract it: 53 | 54 | $ wget https://github.com/jjlin/vaultwarden-shared-hosting/archive/main.zip 55 | $ unzip main.zip 56 | $ mv vaultwarden-shared-hosting-main vaultwarden 57 | 58 | Stay logged in via SSH, as the rest of the steps below will need SSH as well. 59 | 60 | ## Install Python dependencies 61 | 62 | Run this command: 63 | 64 | $ pip3 install WebOb WSGIProxy2 65 | 66 | The output should look like: 67 | 68 | Collecting WebOb 69 | Using cached https://files.pythonhosted.org/packages/18/3c/de37900faff3c95c7d55dd557aa71bd77477950048983dcd4b53f96fde40/WebOb-1.8.6-py2.py3-none-any.whl 70 | Collecting WSGIProxy2 71 | Using cached https://files.pythonhosted.org/packages/2d/a5/3afac2542081b890de83e0089a0057cfb7dc9ad877ccc5594e6c6e1976b8/WSGIProxy2-0.4.6-py3-none-any.whl 72 | Collecting six (from WSGIProxy2) 73 | Using cached https://files.pythonhosted.org/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl 74 | Installing collected packages: WebOb, six, WSGIProxy2 75 | Successfully installed WSGIProxy2-0.4.6 WebOb-1.8.6 six-1.14.0 76 | 77 | ## Enable the Python WSGI proxy app 78 | 79 | Copy the `passenger_wsgi.py` file in this repo into your 80 | `/home//` directory. 81 | 82 | Note that Passenger starts a persistent Python process that loads the 83 | `passenger_wsgi.py` script. If you need to modify the script, you'll need to 84 | kill the Python process (e.g., `pkill python3`) to force it to reload the 85 | modified script. 86 | 87 | For more details, see [Passenger and Python WSGI](https://help.dreamhost.com/hc/en-us/articles/215769548-Passenger-and-Python-WSGI). 88 | 89 | If you visit your subdomain now (e.g., `https://bw.example.org`), you should 90 | see a `502 Bad Gateway` message. This is because the Vaultwarden backend is 91 | not yet running. 92 | 93 | ## Start the Vaultwarden backend 94 | 95 | Make sure you're in the `vaultwarden` directory for the steps below. 96 | 97 | ### Download the Vaultwarden server and web vault 98 | 99 | Run this command: 100 | 101 | $ ./docker-image-extract vaultwarden/server:alpine 102 | 103 | The output should look like the following (the layer IDs will likely all be different) : 104 | 105 | Getting multi-arch manifest list... 106 | Platform linux/amd64 resolved to 'sha256:deec30b3985444c8efc42338717a9b64f3185e1f3e149a7137afc98ebeb815e1'... 107 | Getting API token... 108 | Getting image manifest for vaultwarden/server:alpine... 109 | Fetching and extracting layer c158987b05517b6f2c5913f3acef1f2182a32345a304fe357e3ace5fadcad715... 110 | Fetching and extracting layer d58d1c0e0c7df3aac19cadde4e0910a04ab277976c90ab6973bd6018edd02588... 111 | Fetching and extracting layer 630c69c2a5502c32eace11f461d2db3308aaa800b2f1207f5e194195d44b765b... 112 | Fetching and extracting layer c655444a35c3827719e17f549f5421b211193d3bab3f1ff430ec3154651cecd4... 113 | Fetching and extracting layer a940b1feefa501c534ededd3e6dee86cd6827fc5c100c798e07d33c4a0012097... 114 | Fetching and extracting layer 857810bada4c372d6aa453ba9f7c0f1a029ab1a32bb20eb35790d8df76a61df2... 115 | Image contents extracted into ./output. 116 | 117 | This pulls the latest Vaultwarden server Docker image and extracts its files 118 | into a directory called `output`. Move the server binary and web vault files 119 | into your `vaultwarden` directory. The other files in `output` aren't needed, 120 | so you can delete the directory afterwards. 121 | 122 | $ mv output/vaultwarden output/web-vault . 123 | $ rm -rf output 124 | 125 | ### Configure Vaultwarden 126 | 127 | For purposes of this tutorial, start by copying `config.json.template` to 128 | `config.json` and editing it as described below. 129 | 130 | $ cp data/config.json.template data/config.json 131 | $ # Now edit data/config.json with your preferred editor. 132 | 133 | The initial `config.json` looks like 134 | ```json 135 | { 136 | "domain": "https://bw.example.org", 137 | "admin_token": "" 138 | } 139 | ``` 140 | 141 | Do the following: 142 | 143 | * Change the value of `domain` to the subdomain URL you selected. 144 | * Run `openssl rand -hex 32` to generate a random 32-byte (256-bit) hex 145 | string, and change the value of `admin_token` to this hex string. You'll 146 | use this admin token to log into the admin page to perform further 147 | configuration. 148 | 149 | ### Run the Vaultwarden backend server 150 | 151 | Run this command: 152 | 153 | $ ./start.sh 154 | 155 | This script runs the `vaultwarden` executable in the background, with logs 156 | saved in `vaultwarden.log`. 157 | 158 | After this, visiting https://bw.example.org should show the Bitwarden web 159 | vault interface, and https://bw.example.org/admin should lead to the admin 160 | page (after you input the admin token). 161 | 162 | You can re-run this command to restart the backend server if needed. 163 | 164 | ### Install the healthcheck script 165 | 166 | This step is technically optional, but especially if your shared host only 167 | allows a process to run for a certain amount of time or use a certain amount 168 | of CPU, you'll want to set up a cron job that periodically checks that the 169 | server process is still alive, and restarts it automatically if not. 170 | 171 | Run this command: 172 | 173 | $ crontab -e 174 | 175 | Paste the contents of the `crontab` file in this repo into the editor and 176 | save it. If you cloned this repo into a directory not named `vaultwarden`, 177 | make sure to adjust the path in the crontab directive accordingly. 178 | 179 | ### Next steps 180 | 181 | You'll probably want to lock down your settings a bit, and set up email 182 | service. From the admin page, you should review at least the following 183 | settings: 184 | 185 | * General settings 186 | * `Allow new signups` -- you might want to disable this so random users 187 | can't create accounts on your server. 188 | * `Require email verification on signups` 189 | * SMTP Email Settings 190 | * You can use your hosting service's SMTP server, in which case you should 191 | consult their documentation (e.g., 192 | [Email client protocols and port numbers](https://help.dreamhost.com/hc/en-us/articles/215612887-Email-client-protocols-and-port-numbers) 193 | for DreamHost). 194 | * Your hosting service's SMTP service may not deliver email promptly. In 195 | that case, you might consider using an external SMTP service like 196 | [SendGrid](https://sendgrid.com/) or [MailJet](https://www.mailjet.com/). 197 | These both provide 100-200 outgoing emails per day on their free tier, 198 | which is probably enough for small organizations. 199 | 200 | There are a lot more things that can be configured in Vaultwarden, and a 201 | detailed treatment is beyond the scope of this tutorial. For more details, 202 | the best place to start is 203 | https://github.com/dani-garcia/vaultwarden/wiki/Configuration-overview. 204 | 205 | ### Upgrade Vaultwarden 206 | 207 | From time to time, you may want to upgrade Vaultwarden to access bug fixes 208 | or new features. To do this, change into the `vaultwarden` directory, stop 209 | the Vaultwarden server, and delete the existing vaultwarden and web vault 210 | files: 211 | 212 | $ pkill vaultwarden 213 | $ rm -rf vaultwarden web-vault 214 | 215 | Then repeat the steps from 216 | [Download the Vaultwarden server and web vault](#download-the-vaultwarden-server-and-web-vault) 217 | and [Run the Vaultwarden backend server](#run-the-vaultwarden-backend-server). 218 | 219 | ## Limitations 220 | 221 | This configuration currently doesn't support [WebSocket notifications](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-WebSocket-notifications), though this isn't essential functionality. 222 | But if you know how to get this to work in the shared host environment, feel free to send a PR. 223 | -------------------------------------------------------------------------------- /check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | VAULTWARDEN_HOME="$(dirname $0)" 4 | cd "${VAULTWARDEN_HOME}" 5 | . ./env.sh 6 | 7 | if ! curl -fsS "http://${ROCKET_ADDRESS}:${ROCKET_PORT}/alive" >/dev/null; then 8 | echo "vaultwarden server not alive, restarting it..." 9 | ./start.sh 10 | fi 11 | -------------------------------------------------------------------------------- /crontab: -------------------------------------------------------------------------------- 1 | # Check that the vaultwarden backend is up, once a minute. You can replace 2 | # */1 with */2 to check every 2 minutes, */5 to check every 5 minutes, etc. 3 | */1 * * * * $HOME/vaultwarden/check.sh 4 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | attachments/ 2 | config.json 3 | db.sqlite3* 4 | icon_cache/ 5 | rsa_key.* 6 | sends/ 7 | -------------------------------------------------------------------------------- /data/config.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "https://bw.example.org", 3 | "admin_token": "" 4 | } 5 | -------------------------------------------------------------------------------- /docker-image-extract: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script pulls and extracts all files from an image in Docker Hub. 4 | # 5 | # Copyright (c) 2020-2023, Jeremy Lin 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a 8 | # copy of this software and associated documentation files (the "Software"), 9 | # to deal in the Software without restriction, including without limitation 10 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | # and/or sell copies of the Software, and to permit persons to whom the 12 | # Software is furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | # DEALINGS IN THE SOFTWARE. 24 | 25 | PLATFORM_DEFAULT="linux/amd64" 26 | PLATFORM="${PLATFORM_DEFAULT}" 27 | OUT_DIR="./output" 28 | 29 | usage() { 30 | echo "This script pulls and extracts all files from an image in Docker Hub." 31 | echo 32 | echo "$0 [OPTIONS...] IMAGE[:REF]" 33 | echo 34 | echo "IMAGE can be a community user image (like 'some-user/some-image') or a" 35 | echo "Docker official image (like 'hello-world', which contains no '/')." 36 | echo 37 | echo "REF is either a tag name or a full SHA-256 image digest (with a 'sha256:' prefix)." 38 | echo "The default ref is the 'latest' tag." 39 | echo 40 | echo "Options:" 41 | echo 42 | echo " -p PLATFORM Pull image for the specified platform (default: ${PLATFORM})" 43 | echo " For a given image on Docker Hub, the 'Tags' tab lists the" 44 | echo " platforms supported for that image." 45 | echo " -o OUT_DIR Extract image to the specified output dir (default: ${OUT_DIR})" 46 | echo " -h Show help with usage examples" 47 | } 48 | 49 | usage_detailed() { 50 | usage 51 | echo 52 | echo "Examples:" 53 | echo 54 | echo "# Pull and extract all files in the 'hello-world' image tagged 'latest'." 55 | echo "\$ $0 hello-world:latest" 56 | echo 57 | echo "# Same as above; ref defaults to the 'latest' tag." 58 | echo "\$ $0 hello-world" 59 | echo 60 | echo "# Pull the 'hello-world' image for the 'linux/arm64/v8' platform." 61 | echo "\$ $0 -p linux/arm64/v8 hello-world" 62 | echo 63 | echo "# Pull an image by digest." 64 | echo "\$ $0 hello-world:sha256:90659bf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cbc042" 65 | } 66 | 67 | if [ $# -eq 0 ]; then 68 | usage_detailed 69 | exit 0 70 | fi 71 | 72 | while getopts ':ho:p:' opt; do 73 | case $opt in 74 | o) 75 | OUT_DIR="${OPTARG}" 76 | ;; 77 | p) 78 | PLATFORM="${OPTARG}" 79 | ;; 80 | h) 81 | usage_detailed 82 | exit 0 83 | ;; 84 | \?) 85 | echo "ERROR: Invalid option '-$OPTARG'." 86 | echo 87 | usage 88 | exit 1 89 | ;; 90 | \:) echo "ERROR: Argument required for option '-$OPTARG'." 91 | echo 92 | usage 93 | exit 1 94 | ;; 95 | esac 96 | done 97 | shift $(($OPTIND - 1)) 98 | 99 | if [ $# -eq 0 ]; then 100 | echo "ERROR: Image to pull must be specified." 101 | echo 102 | usage 103 | exit 1 104 | fi 105 | 106 | if [ -e "${OUT_DIR}" ]; then 107 | if [ -d "${OUT_DIR}" ]; then 108 | echo "WARNING: Output dir already exists. If it contains a previous extracted image," 109 | echo "there may be errors when trying to overwrite files with read-only permissions." 110 | echo 111 | else 112 | echo "ERROR: Output dir already exists, but is not a directory." 113 | exit 1 114 | fi 115 | fi 116 | 117 | have_curl() { 118 | command -v curl >/dev/null 119 | } 120 | 121 | have_wget() { 122 | command -v wget >/dev/null 123 | } 124 | 125 | if ! have_curl && ! have_wget; then 126 | echo "This script requires either curl or wget." 127 | exit 1 128 | fi 129 | 130 | image_spec="$1" 131 | image="${image_spec%%:*}" 132 | if [ "${image#*/}" = "${image}" ]; then 133 | # Docker official images are in the 'library' namespace. 134 | image="library/${image}" 135 | fi 136 | ref="${image_spec#*:}" 137 | if [ "${ref}" = "${image_spec}" ]; then 138 | echo "Defaulting ref to tag 'latest'..." 139 | ref=latest 140 | fi 141 | 142 | # Split platform (OS/arch/variant) into separate variables. 143 | # A platform specifier doesn't always include the `variant` component. 144 | OLD_IFS="${IFS}" 145 | IFS=/ read -r OS ARCH VARIANT <":"" (assumes key/val won't contain double quotes). 155 | # The colon may have whitespace on either side. 156 | grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]\+\"" | 157 | # Extract just by deleting the last '"', and then greedily deleting 158 | # everything up to '"'. 159 | sed -e 's/"$//' -e 's/.*"//' 160 | } 161 | 162 | # Fetch a URL to stdout. Up to two header arguments may be specified: 163 | # 164 | # fetch [name1: value1] [name2: value2] 165 | # 166 | fetch() { 167 | if have_curl; then 168 | if [ $# -eq 2 ]; then 169 | set -- -H "$2" "$1" 170 | elif [ $# -eq 3 ]; then 171 | set -- -H "$2" -H "$3" "$1" 172 | fi 173 | curl -sSL "$@" 174 | else 175 | if [ $# -eq 2 ]; then 176 | set -- --header "$2" "$1" 177 | elif [ $# -eq 3 ]; then 178 | set -- --header "$2" --header "$3" "$1" 179 | fi 180 | wget -qO- "$@" 181 | fi 182 | } 183 | 184 | # https://docs.docker.com/docker-hub/api/latest/#tag/repositories 185 | manifest_list_url="https://hub.docker.com/v2/repositories/${image}/tags/${ref}" 186 | 187 | # If the ref is already a SHA-256 image digest, then we don't need to look up anything. 188 | if [ -z "${ref##sha256:*}" ]; then 189 | digest="${ref}" 190 | else 191 | echo "Getting multi-arch manifest list..." 192 | digest=$(fetch "${manifest_list_url}" | 193 | # Break up the single-line JSON output into separate lines by adding 194 | # newlines before and after the chars '[', ']', '{', and '}'. 195 | sed -e 's/\([][{}]\)/\n\1\n/g' | 196 | # Extract the "images":[...] list. 197 | sed -n '/"images":/,/]/ p' | 198 | # Each image's details are now on a separate line, e.g. 199 | # "architecture":"arm64","features":"","variant":"v8","digest":"sha256:054c85801c4cb41511b176eb0bf13a2c4bbd41611ddd70594ec3315e88813524","os":"linux","os_features":"","os_version":null,"size":828724,"status":"active","last_pulled":"2022-09-02T22:46:48.240632Z","last_pushed":"2022-09-02T00:42:45.69226Z" 200 | # The image details are interspersed with lines of stray punctuation, 201 | # so grep for an arbitrary string that must be in these lines. 202 | grep architecture | 203 | # Search for an image that matches the platform. 204 | while read -r image; do 205 | # Arch is probably most likely to be unique, so check that first. 206 | arch="$(echo ${image} | extract 'architecture')" 207 | if [ "${arch}" != "${ARCH}" ]; then continue; fi 208 | 209 | os="$(echo ${image} | extract 'os')" 210 | if [ "${os}" != "${OS}" ]; then continue; fi 211 | 212 | variant="$(echo ${image} | extract 'variant')" 213 | if [ "${variant}" = "${VARIANT}" ]; then 214 | echo ${image} | extract 'digest' 215 | break 216 | fi 217 | done) 218 | fi 219 | 220 | if [ -n "${digest}" ]; then 221 | echo "Platform ${PLATFORM} resolved to '${digest}'..." 222 | else 223 | echo "No image digest found. Verify that the image, ref, and platform are valid." 224 | exit 1 225 | fi 226 | 227 | # https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate 228 | api_token_url="https://auth.docker.io/token?service=registry.docker.io&scope=repository:$image:pull" 229 | 230 | # https://github.com/docker/distribution/blob/master/docs/spec/api.md#pulling-an-image-manifest 231 | manifest_url="https://registry-1.docker.io/v2/${image}/manifests/${digest}" 232 | 233 | # https://github.com/docker/distribution/blob/master/docs/spec/api.md#pulling-a-layer 234 | blobs_base_url="https://registry-1.docker.io/v2/${image}/blobs" 235 | 236 | echo "Getting API token..." 237 | token=$(fetch "${api_token_url}" | extract 'token') 238 | auth_header="Authorization: Bearer $token" 239 | 240 | # https://github.com/distribution/distribution/blob/main/docs/spec/manifest-v2-2.md 241 | docker_manifest_v2="application/vnd.docker.distribution.manifest.v2+json" 242 | 243 | # https://github.com/opencontainers/image-spec/blob/main/manifest.md 244 | oci_manifest_v1="application/vnd.oci.image.manifest.v1+json" 245 | 246 | # Docker Hub can return either type of manifest format. Most images seem to 247 | # use the Docker format for now, but the OCI format will likely become more 248 | # common as features that require that format become enabled by default 249 | # (e.g., https://github.com/docker/build-push-action/releases/tag/v3.3.0). 250 | accept_header="Accept: ${docker_manifest_v2},${oci_manifest_v1}" 251 | 252 | echo "Getting image manifest for $image:$ref..." 253 | layers=$(fetch "${manifest_url}" "${auth_header}" "${accept_header}" | 254 | # Extract `digest` values only after the `layers` section appears. 255 | sed -n '/"layers":/,$ p' | 256 | extract 'digest') 257 | 258 | if [ -z "${layers}" ]; then 259 | echo "No layers returned. Verify that the image and ref are valid." 260 | exit 1 261 | fi 262 | 263 | mkdir -p "${OUT_DIR}" 264 | 265 | for layer in $layers; do 266 | hash="${layer#sha256:}" 267 | echo "Fetching and extracting layer ${hash}..." 268 | fetch "${blobs_base_url}/${layer}" "${auth_header}" | gzip -d | tar -C "${OUT_DIR}" -xf - 269 | # Ref: https://github.com/moby/moby/blob/master/image/spec/v1.2.md#creating-an-image-filesystem-changeset 270 | # https://github.com/moby/moby/blob/master/pkg/archive/whiteouts.go 271 | # Search for "whiteout" files to indicate files deleted in this layer. 272 | OLD_IFS="${IFS}" 273 | find "${OUT_DIR}" -name '.wh.*' | while IFS= read -r f; do 274 | dir="${f%/*}" 275 | wh_file="${f##*/}" 276 | file="${wh_file#.wh.}" 277 | # Delete both the whiteout file and the whited-out file. 278 | rm -rf "${dir}/${wh_file}" "${dir}/${file}" 279 | done 280 | IFS="${OLD_IFS}" 281 | done 282 | 283 | echo "Image contents extracted into ${OUT_DIR}." 284 | -------------------------------------------------------------------------------- /env.sh: -------------------------------------------------------------------------------- 1 | # This file is also read by a Python script, and doesn't support 2 | # full shell syntax. Don't use quotes in values. 3 | 4 | # The address of the vaultwarden backend. You shouldn't need to change this. 5 | ROCKET_ADDRESS=127.0.0.1 6 | 7 | # The port that the vaultwarden backend is configured to listen on. 8 | # You only need to change this in the unlikely event that some other program 9 | # on the shared host is already using this port. 10 | ROCKET_PORT=28973 11 | 12 | export ROCKET_ADDRESS ROCKET_PORT 13 | -------------------------------------------------------------------------------- /passenger_wsgi.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020-2021, Jeremy Lin 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the "Software"), 5 | # to deal in the Software without restriction, including without limitation 6 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | # and/or sell copies of the Software, and to permit persons to whom the 8 | # Software is furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | # DEALINGS IN THE SOFTWARE. 20 | 21 | import sys, os 22 | 23 | PYTHON_BIN = "/usr/bin/python3" 24 | if sys.executable != PYTHON_BIN: 25 | os.execl(PYTHON_BIN, PYTHON_BIN, *sys.argv) 26 | 27 | import webob # https://pypi.org/project/WebOb/ 28 | import wsgiproxy # https://pypi.org/project/WSGIProxy2/ 29 | 30 | # Change this if you cloned the repo into a non-default directory. 31 | VAULTWARDEN_HOME = "{}/{}".format(os.getenv("HOME"), "vaultwarden") 32 | 33 | def getenv(key, default): 34 | env_file = "{}/{}".format(VAULTWARDEN_HOME, "env.sh") 35 | with open(env_file, "r") as f: 36 | for ln in f: 37 | ln = ln.strip() 38 | if len(ln) == 0 or ln.startswith("#"): 39 | # Skip blank/commented lines. 40 | continue 41 | toks = ln.split("=") 42 | if len(toks) == 2: 43 | # We're only looking for lines of the form KEY=VAL. 44 | if key == toks[0].strip(): 45 | return toks[1].strip() 46 | return default 47 | 48 | BACKEND_HOST = getenv("ROCKET_ADDRESS", "127.0.0.1") 49 | BACKEND_PORT = getenv("ROCKET_PORT", 28973) 50 | 51 | HTTP_PREFIX = "http://" 52 | HTTPS_PREFIX = "https://" 53 | BACKEND_URL = "http://{}:{}".format(BACKEND_HOST, BACKEND_PORT) 54 | PROXY = wsgiproxy.HostProxy(BACKEND_URL) 55 | 56 | def application(environ, start_response): 57 | req = webob.Request(environ) 58 | 59 | if req.url.startswith(HTTP_PREFIX): 60 | # Redirect HTTP to HTTPS. 61 | https_url = HTTPS_PREFIX + req.url[len(HTTP_PREFIX):] 62 | res = webob.exc.HTTPMovedPermanently(location=https_url) 63 | else: 64 | # Proxy the request to the backend. 65 | res = req.get_response(PROXY) 66 | 67 | start_response(res.status, res.headerlist) 68 | return [res.body] 69 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | VAULTWARDEN_HOME="$(dirname $0)" 4 | cd "${VAULTWARDEN_HOME}" 5 | . ./env.sh 6 | 7 | if pgrep vaultwarden >/dev/null 2>&1; then 8 | echo "Killing existing vaultwarden process..." 9 | pkill vaultwarden 10 | fi 11 | nohup ./vaultwarden >>vaultwarden.log 2>&1 & 12 | echo "Started vaultwarden." 13 | --------------------------------------------------------------------------------