#!/bin/bash
#
# Password Management Appliance Installer
# An Open Source Appliance from Robbie Ferguson
#
# Licensed under the Apache License, Version 2.0 (the "License");
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
set -e
if [[ "$EUID" -ne 0 ]]; then
echo "Please run as root."
exit 1
fi
generate_token() {
local length=${1:-64}
head -c $((length / 2)) /dev/urandom | xxd -p | tr -d '\n'
}
# Save the directory where the script was started
INSTALLER_DIR="$(pwd)"
echo "=== Password Management Appliance Installer ==="
echo "An Open Source Appliance from Robbie Ferguson"
echo
if [[ "$1" == "--purge" ]]; then
echo "PURGE command passed on the command line."
echo
echo "WARNING: This will permanently remove Vaultwarden and all its data."
read -r -p "Are you sure you want to delete everything? [y/N]: " confirm
# Default to "No" if input is empty or not 'y' or 'Y'
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "Purge aborted."
exit 0
fi
echo "Purging previous Vaultwarden installation..."
set +e
echo "Removing MariaDB (server + client + common)..."
systemctl stop mariadb 2>/dev/null
apt purge -y mariadb-server mariadb-client mariadb-common mariadb-backup
apt autoremove -y
rm -rf /var/lib/mysql /etc/mysql
if id "vaultwarden" &>/dev/null; then
echo "Attempting to stop and remove vaultwarden user..."
echo "Killing any processes owned by vaultwarden..."
pkill -u vaultwarden 2>/dev/null || true
echo "Stopping Vaultwarden service if running..."
systemctl stop vaultwarden 2>/dev/null || true
systemctl disable vaultwarden 2>/dev/null || true
rm -f /etc/systemd/system/vaultwarden.service
echo "Unmounting or clearing home directory if needed..."
umount /opt/vaultwarden 2>/dev/null || true
rm -rf /opt/vaultwarden /var/lib/vaultwarden
rm -f /usr/local/bin/vaultwarden-wrapper
echo "Deleting vaultwarden user..."
userdel vaultwarden && echo "vaultwarden user removed." || echo "Failed to remove vaultwarden user."
else
echo "vaultwarden user does not exist. Skipping."
fi
echo "Purging the vaultgroup gruop"
groupdel vaultgroup
getent group vaultgroup
echo "Cleaning www-data writable config and setup..."
rm -f /var/lib/vaultwarden/.env.user
rm -f /var/lib/vaultwarden/.setup-complete
rm -rf /var/www/html/setup/
echo "Removing sudoers config for www-data..."
rm -f /etc/sudoers.d/vaultwarden-www-restart
echo "Purge complete. You can now re-run installer.sh safely."
exit 0
fi
# Update system and install dependencies
echo "Installing dependencies..."
apt update && apt upgrade -y
apt install -y curl gnupg2 software-properties-common apt-transport-https lsb-release mariadb-server mariadb-client nginx unzip ufw git build-essential pkg-config libssl-dev libmariadb-dev libmariadb-dev-compat sudo xxd
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw --force enable
DB_NAME="vaultwarden"
DB_USER="vaultuser"
#DB_PASS=$(openssl rand -base64 24)
DB_PASS=$(generate_token 64)
echo "Creating MariaDB database and user..."
mysql -u root </dev/null 2>&1 || {
echo "ERROR: Cannot verify MariaDB access."
exit 1
}
echo "MariaDB credentials verified."
echo "Installing Rust and Vaultwarden..."
curl https://sh.rustup.rs -sSf | sh -s -- -y
export PATH="$HOME/.cargo/bin:$PATH"
useradd -r -d /opt/vaultwarden -s /usr/sbin/nologin vaultwarden || true
mkdir -p /opt/vaultwarden
chown vaultwarden:vaultwarden /opt/vaultwarden
cd /opt
git clone https://github.com/dani-garcia/vaultwarden.git
cd vaultwarden
echo "Building Vaultwarden with MySQL support..."
if ! cargo build --release --locked --features mysql; then
echo "Cargo build failed."
exit 1
fi
echo "Verifying that Vaultwarden binary was built..."
if [[ ! -x target/release/vaultwarden ]]; then
echo "Vaultwarden binary not found after build."
exit 1
fi
echo "Checking for MySQL support in the built binary..."
if ldd target/release/vaultwarden | grep -qE 'libmysqlclient|libmariadb'; then
echo "Vaultwarden binary is linked with MySQL/MariaDB support."
if [[ -e /usr/local/bin/vaultwarden ]]; then
rm -f /usr/local/bin/vaultwarden
fi
echo "Placing binary in /usr/local/bin"
cp target/release/vaultwarden /usr/local/bin/
chown vaultwarden:vaultwarden /usr/local/bin/vaultwarden
else
echo "Built Vaultwarden binary does not link MySQL/MariaDB. Something went wrong."
exit 1
fi
# Ensure vaultwarden data directory exists
if [ ! -d /var/lib/vaultwarden ]; then
echo "Creating /var/lib/vaultwarden directory..."
mkdir -p /var/lib/vaultwarden
chown www-data:www-data /var/lib/vaultwarden
fi
# Ensure .env.user file exists
if [ ! -f /var/lib/vaultwarden/.env.user ]; then
echo "Creating .env.user file..."
touch /var/lib/vaultwarden/.env.user
chown www-data:www-data /var/lib/vaultwarden/.env.user
fi
mkdir -p /opt/vaultwarden/data
chown -R vaultwarden:vaultwarden /opt/vaultwarden
cat < /opt/vaultwarden/.env
DATABASE_URL=mysql://${DB_USER}:${DB_PASS}@localhost:3306/${DB_NAME}
DATA_FOLDER=/opt/vaultwarden/data
DOMAIN=https://localhost
ROCKET_PORT=8080
ROCKET_ADDRESS=0.0.0.0
WEBSOCKET_ENABLED=true
WEB_VAULT_FOLDER=/opt/vaultwarden/web-vault
EOF
chown vaultwarden:vaultwarden /opt/vaultwarden/.env
chmod 600 /opt/vaultwarden/.env
# Create vaultwarden-wrapper to support layered .env config
cat <<"EOF" > /usr/local/bin/vaultwarden-wrapper
#!/bin/bash
ENV_MAIN="/opt/vaultwarden/.env"
ENV_USER="/var/lib/vaultwarden/.env.user"
ENV_MERGED="/opt/vaultwarden/.env.merged"
cat "$ENV_MAIN" > "$ENV_MERGED"
[ -f "$ENV_USER" ] && cat "$ENV_USER" >> "$ENV_MERGED"
# Load environment variables from merged file
set -a
source "$ENV_MERGED"
set +a
exec /usr/local/bin/vaultwarden
EOF
chmod +x /usr/local/bin/vaultwarden-wrapper
cat < /etc/systemd/system/vaultwarden.service
[Unit]
Description=Vaultwarden Password Server
After=network.target mariadb.service
[Service]
User=vaultwarden
Group=vaultwarden
WorkingDirectory=/opt/vaultwarden
ExecStart=/usr/local/bin/vaultwarden-wrapper
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reexec
systemctl enable --now vaultwarden
echo "Checking Vaultwarden service..."
systemctl is-active --quiet vaultwarden || {
echo "Vaultwarden service failed to start."
journalctl -u vaultwarden --no-pager | tail -n 10
exit 1
}
echo "Vaultwarden service is active."
# Remove default nginx site if it exists to avoid duplicate default_server errors
if [ -f /etc/nginx/sites-enabled/default ]; then
echo "Removing default Nginx site to avoid conflict..."
rm -f /etc/nginx/sites-enabled/default
fi
# Basic NGINX placeholder config
cat <<"EOF" > /etc/nginx/sites-available/vaultwarden
server {
listen 80 default_server;
server_name _;
root /var/www/html/vaultinfo;
index index.php;
# Main landing page
location / {
try_files $uri $uri/ /index.php?$args;
}
# Serve PHP files
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
# Setup interface
location /setup {
root /var/www/html;
index index.php;
location ~ ^/setup/.*\.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
}
# Vaultwarden Admin Panel
location ^~ /admin/ {
proxy_pass http://127.0.0.1:8080/admin/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Vaultwarden static files (referenced from root!)
location ~ ^/(bootstrap|admin|vaultwarden|.*\.(css|js|png|ico|woff2?)$) {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
# WebSocket
location /notifications/hub {
proxy_pass http://127.0.0.1:3012;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
EOF
ln -sf /etc/nginx/sites-available/vaultwarden /etc/nginx/sites-enabled/vaultwarden
echo "Testing Nginx configuration..."
if nginx -t; then
echo "Reloading Nginx..."
systemctl reload nginx
else
echo "Nginx config test failed. Please check manually."
fi
mkdir -p /var/www/html/vaultinfo
chown -R www-data:www-data /var/www/html/vaultinfo
# Setup a group that allows Vaultwarden and www-data to share configs
groupadd vaultgroup
usermod -aG vaultgroup vaultwarden
usermod -aG vaultgroup www-data
# Allow web interface to save initial config
touch /var/lib/vaultwarden/.env.user
chown www-data:vaultgroup /var/lib/vaultwarden/.env.user
chmod 640 /var/lib/vaultwarden/.env.user
# Download and deploy setup wizard
echo "Installing PHP and deploying setup page..."
apt install -y php php-fpm php-cli php-common php-mbstring php-json php-curl php-xml php-zip php-gd
cd "$INSTALLER_DIR"
cp -R ./setup /var/www/html/
chown -R www-data:www-data /var/www/html/setup
# Welcome page with /setup condition check
cat <<"EOF" > /var/www/html/vaultinfo/index.php
Password Management Appliance
Password Management Appliance
This server is running Vaultwarden on a Password Management Appliance.
✓ Installation Complete
Access the admin panel at: /admin
'; } else { echo 'Continue to activation.
'; } ?>
An Open Source Appliance from Robbie Ferguson
EOF
echo "Setup system ready. Visit http:///setup to begin configuration."
# Allow www-data to restart Vaultwarden without password
echo "www-data ALL=NOPASSWD: /bin/systemctl restart vaultwarden" > /etc/sudoers.d/vaultwarden-www-restart
chmod 440 /etc/sudoers.d/vaultwarden-www-restart
echo "Configuring default web-vault."
cd /opt/vaultwarden
curl -L https://github.com/dani-garcia/bw_web_builds/releases/download/v2025.6.0/bw_web_v2025.6.0.tar.gz | tar xz
chown -R vaultwarden:vaultwarden web-vault
chmod -R 755 web-vault
systemctl restart vaultwarden
echo
echo "Installation complete!"
echo "Visit http:///setup to begin configuration."