A proxmox install script for Vaultwarden

shodan

Member
Sep 1, 2022
98
33
23
Hello,

I don't know where else to post this.

But I just created a proxmox install script to create a Vaultwarden server in an LXC container, inside of a debian LXC template

Code:
#----------------------------------------
# stop in case of errors
#set -e # this breaks on or soon after pct create 
# shell function to create --net variable
addnet() { CT_net_count=${CT_net_count:-0}; local net_name=$1; shift; local _CT_new_config="--net$CT_net_count name=$net_name"; local valid_params=("bridge" "firewall" "gw" "gw6" "hwaddr" "ip" "ip6" "link_down" "mtu" "rate" "tag" "trunks" "type"); while [ $# -gt 0 ]; do key=$1; value=$2; if [[ " ${valid_params[*]} " =~ " $key " ]]; then _CT_new_config="$_CT_new_config,$key=$value"; shift 2; else shift 1; fi; done; _CT_net_config="$_CT_net_config $_CT_new_config"; echo "Interface added: $_CT_new_config"; CT_net_count=$((CT_net_count + 1)); }
# shell function to create user variables
adduser() { user_ubound=$((user_ubound + 1)); local user_and_group=$1; IFS=':' read -r username group <<< "$user_and_group"; eval "user_name_${user_ubound}=\"$username\""; [ -n "$group" ] && eval "user_group_${user_ubound}=\"$group\""; shift 1; while [ $# -gt 0 ]; do case $1 in nologin) eval "user_shell_${user_ubound}='/usr/sbin/nologin'" ;; shell) shift 1; eval "user_shell_${user_ubound}=\"$1\"" ;; groups) shift 1; eval "user_groups_${user_ubound}=\"$1\"" ;; nopassword) eval "user_password_${user_ubound}=$(printf '%s' '!')" ;; password) shift 1; eval "user_password_${user_ubound}=\"$1\"" ;; lock) eval "user_lock_${user_ubound}=true" ;; *) echo "Warning: Unrecognized option '$1'" ;; esac; shift 1; done; }
createuser() { local i=$1; local username=$(eval echo "\$user_name_${i}"); local group=$(eval echo "\$user_group_${i}"); local shell=$(eval echo "\$user_shell_${i}"); local password=$(eval echo "\$user_password_${i}"); local groups=$(eval echo "\$user_groups_${i}"); local lock=$(eval echo "\$user_lock_${i}"); [ -n "$shell" ] && shell_option="-s $shell" || shell_option="-s /bin/bash"; [ -n "$group" ] && group_option="-g $group" || group_option=""; pct_exec useradd $shell_option $group_option "$username"; [ -n "$password" ] && pct_set_password "$username:$password"; [ "$password" = "!" ] && pct_exec usermod -L "$username"; [ "$lock" = "true" ] && pct_exec usermod -L "$username"; pct_exec mkdir -p "/home/$username"; [ -n "$group" ] && pct_exec chown "$username:$group" "/home/$username" || pct_exec chown "$username" "/home/$username"; [ -n "$groups" ] && pct_exec usermod -aG "$groups" "$username"; unset username group shell password groups lock; }
# shell function for create each file line variables
addline() { file_line_ubound=$((file_line_ubound + 1)); eval "file_line_${file_line_ubound}_${file_ubound}=\"$1\""; eval "file_line_count_${file_ubound}=$file_line_ubound"; }
addfile() { file_ubound=$((file_ubound + 1)); eval "file_name_${file_ubound}=\"$1\""; [ -n "$2" ] && [[ "$2" =~ ^[0-9]+$ ]] && eval "filepermission_${file_ubound}=\"$2\""; [ -n "$3" ] && eval "fileowner_${file_ubound}=\"$3\""; unset file_line_ubound; }
pct_append_text() { local file=$1; local text_or_var=$2; if [ -n "${!text_or_var}" ]; then local text=${!text_or_var}; elif [[ "$text_or_var" == file_line* ]]; then local text=$(eval echo \${$text_or_var}); else local text="$text_or_var"; fi; local command="echo \"$text\" >> \"$file\""; [ "$VERBOSE" -gt 0 ] && echo "pct exec $CT_ID -- /bin/sh -c \"$command\""; pct exec $CT_ID -- /bin/sh -c "$command"; }
writefile() { local file_index=$1; local file_name=$(eval echo \${file_name_${file_index}}); local file_permission=$(eval echo \${filepermission_${file_index}}); local file_owner=$(eval echo \${fileowner_${file_index}}); local file_line_count=$(eval echo \${file_line_count_${file_index}}); pct_exec mkdir -p "$(dirname "$file_name")"; for i in $(seq 1 $file_line_count); do local file_line_var="file_line_${i}_${file_index}"; pct_append_text "$file_name" "$file_line_var"; done; [ -n "$file_permission" ] && pct_exec "chmod $file_permission $file_name"; [ -n "$file_owner" ] && pct_exec "chown $file_owner $file_name"; }
# shell functions for user port binding permissions using authbind
add_port_bind() { port_bind_ubound=$((port_bind_ubound + 1)); eval "port_bind_number_${port_bind_ubound}=\"${1%%:*}\""; eval "port_bind_username_${port_bind_ubound}=\"${1##*:}\""; }
create_port_bind() { [ -z "$authbind_installed" ] && pct_install_package authbind && authbind_installed=1; local i=$1; local port_number=$(eval echo "\$port_bind_number_${i}"); local portbind_username=$(eval echo "\$port_bind_username_${i}"); [ -z "$portbind_username" ] && { echo "Error: Username is empty for port $port_number. Skipping."; return 1; }; pct_exec touch "/etc/authbind/byport/${port_number}"; pct_exec chmod 500 "/etc/authbind/byport/${port_number}"; pct_exec chown "$portbind_username" "/etc/authbind/byport/${port_number}"; unset portbind_username; }
# shell function to type pct exec commands
pct_exec() { [ "$VERBOSE" -gt 0 ] && echo "pct exec $CT_ID -- /bin/sh -c \"$*\""; pct exec $CT_ID -- /bin/sh -c "$*"; }
pct_install_package() { local packages="$*"; case "$CT_os_type" in centos|almalinux|amazonlinux|openeuler|oracle|rockylinux|springdalelinux) install_command="yum install -y" ;; debian|devuan|kali|ubuntu|mint) install_command="apt -qq install -y" ;; alpine) install_command="apk add --quiet" ;; archlinux) install_command="pacman --noconfirm -S" ;; fedora) install_command="dnf install -y" ;; gentoo|funtoo) install_command="emerge --quiet" ;; opensuse) install_command="zypper --quiet install -y" ;; nixos) install_command="nix-env -i" ;; openwrt|busybox) install_command="opkg install" ;; voidlinux) install_command="xbps-install -y" ;; slackware) install_command="slackpkg install" ;; plamo) install_command="pkginstall" ;; alt) install_command="apt-get install -y" ;; *) echo "Error: Unknown or unsupported OS type '$CT_os_type'."; return 1 ;; esac; [ -n "$install_command" ] && pct_exec "$install_command $packages"; }
pct_update_package_manager() { case "$CT_os_type" in centos|almalinux|amazonlinux|openeuler|oracle|rockylinux|springdalelinux) pct_exec "yum -q -y update" ;; debian|devuan|kali|ubuntu|mint) pct_exec "apt -qq update" ;; alpine) pct_exec "apk update" ;; archlinux) pct_exec "pacman -Sy --noconfirm" ;; fedora) pct_exec "dnf -q -y update" ;; gentoo|funtoo) pct_exec "emerge --sync" ;; opensuse) pct_exec "zypper --gpg-auto-import-keys refresh" ;; nixos) pct_exec "nix-channel --update && nix-env -u '*'" ;; openwrt|busybox) pct_exec "opkg update" ;; voidlinux) pct_exec "xbps-install -Sy" ;; slackware) pct_exec "slackpkg update" ;; plamo) pct_exec "pkginstall --update" ;; alt) pct_exec "apt-get update" ;; *) echo "Error: Unknown or unsupported OS type '$CT_os_type'. Cannot update package manager."; return 1 ;; esac; }
pct_set_password() { for user_pass in "$@"; do user=$(echo "$user_pass" | cut -d':' -f1); pass=$(echo "$user_pass" | cut -d':' -f2); pct_exec bash -c "'printf \"%s\n\" \"${user}:${pass}\" | chpasswd'"; done; }
addcommand() { pct_command_ubound=$((pct_command_ubound + 1)); eval "pct_command_${pct_command_ubound}=\"\$*\""; }
runcommand() { local i=$1; eval "pct_exec \${pct_command_${i}}"; }

VERBOSE=1  # Enable verbose mode
[ "$VERBOSE" -gt 0 ] && echo "------------------------------ Creation of debian LXC container ------------------------------"
CT_ID="900"  # Adjust the container ID, doesn't have to be set
CT_hostname="vaultwarden"
CT_network_suffix=".lan"
CT_root_password="qwerty"
CT_memory="1024"
CT_cores="4"
CT_rootfs_size="2"

CT_install_packages="sudo screen wget htop docker-compose"

CT_enable_root_login="true"
CT_template_download="http://download.proxmox.com/images/system/debian-12-standard_12.7-1_amd64.tar.zst"
CT_template_filename="debian-12-standard_12.7-1_amd64.tar.zst"
CT_template_file="local:vztmpl/$CT_template_filename"
CT_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8Eg5xSbsyLySMCH5K1eb8ZzLTLwPrXDmgyGh9OAi/kofhR6UrTtuVzViAxBV8i+52pgMkRFoX2q/wDKkX7bJk0HXGzs26Npz40BCOEO6hf8MlTc/Kdu288sxVKPhnMofJ1UGy4vZjy2AHoAEe0NbazoEiBNZNO+EpXAGnaxnSM2KQFDDidZydeFMaGKWPb0wYXnGeKbnjxA2rASbX2Rd515FC5ZkYVAK0KFjRV41q+xJebBDhGhgLpHynusmFINM/RdoUswD+c2lwRpdIL+yU+DIPt4J7pM6h0Tj8hTFlqIzemwKKFi4UkzL53oQFYpCK2qAHiBTHAfOdL8gcF5Kv rsa-key-20240131"
CT_key_file="/ssh_key.$CT_hostname.pub"

addnet eth0 hwaddr "DE:AD:BE:EF:99:55" ip dhcp ip6 manual firewall 0 bridge vmbr0 #link_down 1 # LAN interface (eth0)

adduser user nopassword nologin lock groups docker
add_port_bind "443:user"

addfile "/opt/vaultwarden/docker-compose.yml" "644" "root:root"
addline "version: '3.7'"
addline "services:"
addline "  vaultwarden:"
addline "    image: vaultwarden/server:latest"
addline "    container_name: vaultwarden"
addline "    environment:"
addline "      DOMAIN: 'https://$CT_hostname$CT_network_suffix'"
addline "      ROCKET_PORT: 443"
addline "      ENABLE_SSL: 'true'"
addline "      SIGNUPS_ALLOWED: 'true'"
addline "      ROCKET_TLS: '{certs=/certs/fullchain.pem,key=/certs/privkey.pem}'"
addline "    volumes:"
addline "      - /opt/vaultwarden-data/:/data/"
addline "      - /opt/vaultwarden-certs/:/certs/:ro"
addline "    ports:"
addline "      - 443:443"
addline "    restart: unless-stopped"

addcommand mkdir -p /opt/vaultwarden/ /opt/vaultwarden-certs/ /opt/vaultwarden-data/ 
addcommand openssl req -x509 -newkey rsa:4096 -keyout /opt/vaultwarden-certs/privkey.pem -out /opt/vaultwarden-certs/fullchain.pem -sha256 -days 36500 -nodes -subj '/CN=$CT_hostname$CT_network_suffix'
addcommand sudo -u user docker-compose -f /opt/vaultwarden/docker-compose.yml up -d

# Only download template file if it is not already present
[ ! -f /var/lib/vz/template/cache/$CT_template_filename ] && wget "$CT_template_download" -O /var/lib/vz/template/cache/$CT_template_filename
# Set the right OS Type
case "$CT_template_filename" in *almalinux*|*amazonlinux*|*centos*|*openeuler*|*oracle*|*rockylinux*|*springdalelinux*) CT_os_type="centos" ;; *alpine*) CT_os_type="alpine" ;; *alt*|*busybox*|*plamo*|*slackware*|*voidlinux*|*openwrt*) CT_os_type="unmanaged" ;; *archlinux*) CT_os_type="archlinux" ;; *debian*|*devuan*|*kali*) CT_os_type="debian" ;; *fedora*) CT_os_type="fedora" ;; *funtoo*|*gentoo*) CT_os_type="gentoo" ;; *mint*) CT_os_type="ubuntu" ;; *nixos*) CT_os_type="nixos" ;; *opensuse*) CT_os_type="opensuse" ;; *ubuntu*) CT_os_type="ubuntu" ;; *) CT_os_type="unmanaged" ;; esac
# Obtain the next CT_ID if not already set
: ${CT_ID:=100}; CT_ID=$((CT_ID < 100 ? 100 : CT_ID)); existing_ids=$(pct list | awk 'NR>1 {print $1}' | sort -n); while echo "$existing_ids" | grep -qw "$CT_ID"; do CT_ID=$((CT_ID + 1)); done
# Writing the public key to a file, as pct requires
echo "$CT_key" > $CT_key_file

# Create the container
echo ""; echo Creating LXC Container for $CT_template_filename
[ "$VERBOSE" -gt 0 ] && echo "pct create $CT_ID $CT_template_file --arch amd64 --cores $CT_cores --memory $CT_memory --hostname $CT_hostname $_CT_net_config --rootfs local-lvm:$CT_rootfs_size --features nesting=1 --unprivileged 1 --ostype $CT_os_type"
pct create $CT_ID $CT_template_file --arch amd64 --cores $CT_cores --memory $CT_memory --hostname $CT_hostname $_CT_net_config --rootfs local-lvm:$CT_rootfs_size --features nesting=1 --unprivileged 1 --ostype $CT_os_type

# delete key file
rm $CT_key_file

# Define the LXC configuration file path
LXC_CONF_FILE="/etc/pve/nodes/proxmox/lxc/$CT_ID.conf"

# to set console to shell mode
echo "cmode: shell" >> "$LXC_CONF_FILE"

# to add the mount points
#for i in $(seq 1 $mount_point_ubound); do create_mount_point $i; done

# Start the container
pct start $CT_ID

#Wait until container is finished booting
while [[ $(pct status $CT_ID) != *"running"* ]]; do echo "Waiting for container $CT_ID to start..."; sleep 2; done; echo "Container $CT_ID is running."

sleep 5

# Enable root login via ssh
[ "$CT_enable_root_login" = "true" ] && pct_exec "sed -i 's/^#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config"
# Enable login with password
[ -n "$CT_root_password" ] && pct_exec "sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config"
[ -n "$CT_root_password" ] && pct_set_password "root:$CT_root_password"
pct_exec systemctl restart sshd

pct_update_package_manager

# Install additional packages if specified
[ -n "$CT_install_packages" ] && pct_install_package $CT_install_packages

# create extra users
[ -n "$user_ubound" ] && for i in $(seq 1 $user_ubound); do createuser $i; done

# write all the files into the container
[ -n "$file_ubound" ] && for i in $(seq 1 $file_ubound); do writefile $i; done

# allow port binding by users if needed
[ -n "$port_bind_ubound" ] && for i in $(seq 1 $port_bind_ubound); do create_port_bind $i; done

# run the commands specified with addcommand
[ -n "$pct_command_ubound" ] && for i in $(seq 1 $pct_command_ubound); do runcommand $i; done
 
Last edited:
Unfortunately proxmox support forum doesn't have "copy text" button to copy the whole block

I have just tested it, copy and pasted from the above text

This is meant to be directly pasted into the root console of a proxmox server

It will create an LXC container, ID 900+, hostname vaultwarden.lan

This has self-signed SSL out of the box, but you could easily put your let's encrypt certs in /opt/vaultwarden-certs/ if you want to

You will want to change the following variables before using

CT_root_password
CT_key
and maybe
CT_hostname
CT_network_suffix

1734946131993.png

1734946146784.png

Beauty !
 
Why don't you just install deb file from vaultwarden?
https://github.com/greizgh/vaultwarden-debian

I don't know, the docker is the first thing I found

I agree installing it natively in the LXC would be lighter weight

However, I notice that

greizgh/vaultwarden-debian was last updated in 2021
while dani-garcia/vaultwarden was last updated 3 days ago

Also, in my version, I have setup SSL from rocket, so SSL works out of the box without a reverse proxy
This is important because vaultwarden refuses to work at all on plain HTTP
And I wanted it to work on my .lan
 
Yeah,scripts works okay, nevermind the date. It pulls from dani ,and builds .deb files. But yeah , you would need to install nginx as a reverse proxy.
 

About

The Proxmox community has been around for many years and offers help and support for Proxmox VE, Proxmox Backup Server, and Proxmox Mail Gateway.
We think our community is one of the best thanks to people like you!

Get your subscription!

The Proxmox team works very hard to make sure you are running the best software and getting stable updates and security enhancements, as well as quick enterprise support. Tens of thousands of happy customers have a Proxmox subscription. Get yours easily in our online shop.

Buy now!