mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-01-20 12:01:47 -05:00
2038 lines
77 KiB
Bash
Executable File
2038 lines
77 KiB
Bash
Executable File
#!/bin/bash
|
|
# @version 0.11.1
|
|
|
|
# Repository information used for downloading files and images from GitHub
|
|
REPO_OWNER="lanedirt"
|
|
REPO_NAME="AliasVault"
|
|
GITHUB_RAW_URL_REPO="https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}"
|
|
GITHUB_CONTAINER_REGISTRY="ghcr.io/$(echo "$REPO_OWNER" | tr '[:upper:]' '[:lower:]')/$(echo "$REPO_NAME" | tr '[:upper:]' '[:lower:]')"
|
|
|
|
# Required files and directories
|
|
REQUIRED_DIRS=(
|
|
"certificates/ssl"
|
|
"certificates/app"
|
|
"certificates/letsencrypt"
|
|
"certificates/letsencrypt/www"
|
|
"database"
|
|
"database/postgres"
|
|
"logs"
|
|
"logs/msbuild"
|
|
)
|
|
|
|
# Color codes for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
MAGENTA='\033[0;35m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
# File paths
|
|
ENV_FILE=".env"
|
|
ENV_EXAMPLE_FILE=".env.example"
|
|
|
|
# Function to show usage
|
|
show_usage() {
|
|
print_logo
|
|
printf "Usage: $0 [COMMAND] [OPTIONS]\n"
|
|
printf "\n"
|
|
printf "Commands:\n"
|
|
printf " install Install AliasVault by pulling pre-built images from GitHub Container Registry (recommended)\n"
|
|
printf " uninstall Uninstall AliasVault\n"
|
|
printf " update Update AliasVault to the latest version\n"
|
|
printf " update-installer Check and update install.sh script if newer version available\n"
|
|
printf " configure-hostname Configure the hostname where AliasVault can be accessed from\n"
|
|
printf " configure-ssl Configure SSL certificates (Let's Encrypt or self-signed)\n"
|
|
printf " configure-email Configure email domains for receiving emails\n"
|
|
printf " configure-registration Configure new account registration (enable or disable)\n"
|
|
printf " start Start AliasVault containers using remote images\n"
|
|
printf " stop Stop AliasVault containers using remote images\n"
|
|
printf " restart Restart AliasVault containers using remote images\n"
|
|
printf " reset-password Reset admin password\n"
|
|
printf " build [operation] Build AliasVault from source (takes longer and requires sufficient specs)\n"
|
|
printf " Optional operations: start|stop|restart (uses locally built images)\n"
|
|
printf "\n"
|
|
printf " db-export Export database to file\n"
|
|
printf " db-import Import database from file\n"
|
|
printf "\n"
|
|
printf " configure-dev-db Enable/disable development database (for local development only)\n"
|
|
printf " migrate-db Migrate data from SQLite to PostgreSQL when upgrading from a version prior to 0.10.0\n"
|
|
printf "\n"
|
|
printf "Options:\n"
|
|
printf " --verbose Show detailed output\n"
|
|
printf " -y, --yes Automatic yes to prompts\n"
|
|
printf " --dev Target development database for db import/export operations\n"
|
|
printf " --help Show this help message\n"
|
|
printf "\n"
|
|
|
|
}
|
|
|
|
# Function to parse command line arguments
|
|
parse_args() {
|
|
COMMAND=""
|
|
VERBOSE=false
|
|
FORCE_YES=false
|
|
COMMAND_ARG=""
|
|
DEV_DB=false
|
|
|
|
if [ $# -eq 0 ]; then
|
|
show_usage
|
|
exit 0
|
|
fi
|
|
|
|
# First argument is always the command
|
|
case $1 in
|
|
install|i)
|
|
COMMAND="install"
|
|
shift
|
|
# Check for version argument
|
|
if [ $# -gt 0 ] && [[ ! "$1" =~ ^- ]]; then
|
|
COMMAND_ARG="$1"
|
|
shift
|
|
fi
|
|
;;
|
|
build|b)
|
|
COMMAND="build"
|
|
shift
|
|
# Check for additional operation argument
|
|
if [ $# -gt 0 ] && [[ ! "$1" =~ ^- ]]; then
|
|
case $1 in
|
|
start|stop|restart)
|
|
COMMAND_ARG="$1"
|
|
shift
|
|
;;
|
|
*)
|
|
echo "Invalid build operation: $1"
|
|
echo "Valid operations are: start, stop, restart"
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
;;
|
|
uninstall|u)
|
|
COMMAND="uninstall"
|
|
shift
|
|
;;
|
|
reset-password|reset-admin-password|rp)
|
|
COMMAND="reset-password"
|
|
shift
|
|
;;
|
|
configure-hostname|hostname)
|
|
COMMAND="configure-hostname"
|
|
shift
|
|
;;
|
|
configure-ssl|ssl)
|
|
COMMAND="configure-ssl"
|
|
shift
|
|
;;
|
|
configure-email|email)
|
|
COMMAND="configure-email"
|
|
shift
|
|
;;
|
|
configure-registration|registration)
|
|
COMMAND="configure-registration"
|
|
shift
|
|
;;
|
|
start|s)
|
|
COMMAND="start"
|
|
shift
|
|
;;
|
|
stop|st)
|
|
COMMAND="stop"
|
|
shift
|
|
;;
|
|
restart|r)
|
|
COMMAND="restart"
|
|
shift
|
|
;;
|
|
update|up)
|
|
COMMAND="update"
|
|
shift
|
|
;;
|
|
update-installer|cs)
|
|
COMMAND="update-installer"
|
|
shift
|
|
;;
|
|
configure-dev-db|dev-db)
|
|
COMMAND="configure-dev-db"
|
|
shift
|
|
# Check for direct option argument
|
|
if [ $# -gt 0 ] && [[ ! "$1" =~ ^- ]]; then
|
|
COMMAND_ARG="$1"
|
|
shift
|
|
fi
|
|
;;
|
|
migrate-db|migrate)
|
|
COMMAND="migrate-db"
|
|
shift
|
|
;;
|
|
db-export)
|
|
COMMAND="db-export"
|
|
shift
|
|
;;
|
|
db-import)
|
|
COMMAND="db-import"
|
|
shift
|
|
;;
|
|
--help)
|
|
show_usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1"
|
|
show_usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Parse remaining flags
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--verbose)
|
|
VERBOSE=true
|
|
shift
|
|
;;
|
|
-y|--yes)
|
|
FORCE_YES=true
|
|
shift
|
|
;;
|
|
--dev)
|
|
DEV_DB=true
|
|
shift
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1"
|
|
show_usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Main function
|
|
main() {
|
|
parse_args "$@"
|
|
|
|
# Check if command is empty (should not happen with updated parse_args)
|
|
if [ -z "$COMMAND" ]; then
|
|
show_usage
|
|
exit 1
|
|
fi
|
|
|
|
print_logo
|
|
case $COMMAND in
|
|
"build")
|
|
handle_build
|
|
;;
|
|
"install")
|
|
handle_install "$COMMAND_ARG"
|
|
;;
|
|
"uninstall")
|
|
handle_uninstall
|
|
;;
|
|
"reset-password")
|
|
generate_admin_password
|
|
if [ $? -eq 0 ]; then
|
|
printf "${CYAN}> Restarting admin container...${NC}\n"
|
|
if [ "$VERBOSE" = true ]; then
|
|
$(get_docker_compose_command) up -d --force-recreate admin
|
|
else
|
|
$(get_docker_compose_command) up -d --force-recreate admin > /dev/null 2>&1
|
|
fi
|
|
print_password_reset_message
|
|
fi
|
|
;;
|
|
"configure-ssl")
|
|
handle_ssl_configuration
|
|
;;
|
|
"configure-email")
|
|
handle_email_configuration
|
|
;;
|
|
"configure-registration")
|
|
handle_registration_configuration
|
|
;;
|
|
"configure-hostname")
|
|
handle_hostname_configuration
|
|
;;
|
|
"start")
|
|
handle_start
|
|
;;
|
|
"stop")
|
|
handle_stop
|
|
;;
|
|
"restart")
|
|
handle_restart
|
|
;;
|
|
"update")
|
|
handle_update
|
|
;;
|
|
"update-installer")
|
|
check_install_script_update
|
|
exit $?
|
|
;;
|
|
"configure-dev-db")
|
|
configure_dev_database
|
|
;;
|
|
"migrate-db")
|
|
handle_migrate_db
|
|
;;
|
|
"db-export")
|
|
handle_db_export
|
|
;;
|
|
"db-import")
|
|
handle_db_import
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to create required directories
|
|
create_directories() {
|
|
printf "${CYAN}> Checking workspace...${NC}\n"
|
|
|
|
local dirs_needed=false
|
|
for dir in "${REQUIRED_DIRS[@]}"; do
|
|
if [ ! -d "$dir" ]; then
|
|
if [ "$dirs_needed" = false ]; then
|
|
printf " ${CYAN}> Creating required directories...${NC}\n"
|
|
dirs_needed=true
|
|
fi
|
|
mkdir -p "$dir"
|
|
chmod -R 755 "$dir"
|
|
if [ $? -ne 0 ]; then
|
|
printf " ${RED}> Failed to create directory: $dir${NC}\n"
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
if [ "$dirs_needed" = true ]; then
|
|
printf " ${GREEN}> Directories created successfully.${NC}\n"
|
|
else
|
|
printf " ${GREEN}> All required directories already exist.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
# Function to initialize workspace
|
|
initialize_workspace() {
|
|
create_directories
|
|
}
|
|
|
|
# Function to handle docker-compose.yml
|
|
handle_docker_compose() {
|
|
local version_tag="$1"
|
|
printf "${CYAN}> Downloading latest docker-compose files...${NC}\n"
|
|
|
|
# Download and overwrite docker-compose.yml
|
|
printf " ${GREEN}> Downloading docker-compose.yml for version ${version_tag}...${NC}"
|
|
if curl -sSf "${GITHUB_RAW_URL_REPO}/${version_tag}/docker-compose.yml" -o "docker-compose.yml.tmp" > /dev/null 2>&1; then
|
|
# Replace the :latest tag with the specific version if provided
|
|
if [ -n "$version_tag" ] && [ "$version_tag" != "latest" ]; then
|
|
sed "s/:latest/:$version_tag/g" docker-compose.yml.tmp > docker-compose.yml
|
|
rm docker-compose.yml.tmp
|
|
else
|
|
mv docker-compose.yml.tmp docker-compose.yml
|
|
fi
|
|
printf "\n ${CYAN}> docker-compose.yml downloaded successfully.${NC}\n"
|
|
else
|
|
printf "\n ${YELLOW}> Failed to download docker-compose.yml, please check your internet connection and try again. Alternatively, you can download it manually from ${GITHUB_RAW_URL_REPO}/${version_tag}/docker-compose.yml and place it in the root directory of AliasVault.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Download and overwrite docker-compose.letsencrypt.yml
|
|
printf " ${GREEN}> Downloading docker-compose.letsencrypt.yml for version ${version_tag}...${NC}"
|
|
if curl -sSf "${GITHUB_RAW_URL_REPO}/${version_tag}/docker-compose.letsencrypt.yml" -o "docker-compose.letsencrypt.yml" > /dev/null 2>&1; then
|
|
printf "\n ${CYAN}> docker-compose.letsencrypt.yml downloaded successfully.${NC}\n"
|
|
else
|
|
printf "\n ${YELLOW}> Failed to download docker-compose.letsencrypt.yml, please check your internet connection and try again. Alternatively, you can download it manually from ${GITHUB_RAW_URL_REPO}/${version_tag}/docker-compose.letsencrypt.yml and place it in the root directory of AliasVault.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Function to check and update install.sh for specific version
|
|
check_install_script_version() {
|
|
local target_version="$1"
|
|
printf "${CYAN}> Checking install script version for ${target_version}...${NC}\n"
|
|
|
|
# Get remote install.sh for target version
|
|
if ! curl -sSf "${GITHUB_RAW_URL_REPO}/${target_version}/install.sh" -o "install.sh.tmp"; then
|
|
printf "${RED}> Failed to check install script version. Continuing with current version.${NC}\n"
|
|
rm -f install.sh.tmp
|
|
return 1
|
|
fi
|
|
|
|
# Get versions
|
|
local current_version=$(extract_version "install.sh")
|
|
local target_script_version=$(extract_version "install.sh.tmp")
|
|
|
|
# Check if versions could be extracted
|
|
if [ -z "$current_version" ] || [ -z "$target_script_version" ]; then
|
|
printf "${YELLOW}> Could not determine script versions. Falling back to file comparison...${NC}\n"
|
|
if ! cmp -s "install.sh" "install.sh.tmp"; then
|
|
printf "${YELLOW}> Install script needs updating to match version ${target_version}${NC}\n"
|
|
return 2
|
|
fi
|
|
else
|
|
printf "${CYAN}> Current install script version: ${current_version}${NC}\n"
|
|
printf "${CYAN}> Target install script version: ${target_script_version}${NC}\n"
|
|
|
|
if [ "$current_version" != "$target_script_version" ]; then
|
|
printf "${YELLOW}> Install script needs updating to match version ${target_version}${NC}\n"
|
|
return 2
|
|
fi
|
|
fi
|
|
|
|
printf "${GREEN}> Install script is up to date for version ${target_version}.${NC}\n"
|
|
rm -f install.sh.tmp
|
|
return 0
|
|
}
|
|
|
|
# Function to print the logo
|
|
print_logo() {
|
|
printf "${MAGENTA}" >&2
|
|
printf " _ _ _ __ __ _ _ \n" >&2
|
|
printf " / \ | (_) __ _ ___ \ \ / /_ _ _ _| | |_\n" >&2
|
|
printf " / _ \ | | |/ _\` / __| \ \/\/ / _\` | | | | | __|\n" >&2
|
|
printf " / ___ \| | | (_| \__ \ \ / / (_| | |_| | | |_ \n" >&2
|
|
printf "/_/ \_\_|_|\__,_|___/ \/ \__,__|\__,_|_|\__|\n" >&2
|
|
printf "${NC}\n" >&2
|
|
}
|
|
|
|
# Function to create .env file
|
|
create_env_file() {
|
|
printf "${CYAN}> Checking .env file...${NC}\n"
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
if [ -f "$ENV_EXAMPLE_FILE" ]; then
|
|
cp "$ENV_EXAMPLE_FILE" "$ENV_FILE"
|
|
printf " ${GREEN}> New.env file created from .env.example.${NC}\n"
|
|
else
|
|
touch "$ENV_FILE"
|
|
printf " ${YELLOW}> New blank .env file created.${NC}\n"
|
|
fi
|
|
else
|
|
printf " ${GREEN}> .env file already exists.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
populate_hostname() {
|
|
if ! grep -q "^HOSTNAME=" "$ENV_FILE" || [ -z "$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
while true; do
|
|
read -p "Enter the (public) hostname where this AliasVault instance can be accessed from (e.g. aliasvault.net): " USER_HOSTNAME
|
|
if [ -n "$USER_HOSTNAME" ]; then
|
|
HOSTNAME="$USER_HOSTNAME"
|
|
break
|
|
else
|
|
printf "${YELLOW}> Hostname cannot be empty. Please enter a valid hostname.${NC}\n"
|
|
fi
|
|
done
|
|
update_env_var "HOSTNAME" "$HOSTNAME"
|
|
else
|
|
HOSTNAME=$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)
|
|
printf " ${GREEN}> HOSTNAME already exists.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
# Environment setup functions
|
|
populate_jwt_key() {
|
|
printf "${CYAN}> Checking JWT_KEY...${NC}\n"
|
|
if ! grep -q "^JWT_KEY=" "$ENV_FILE" || [ -z "$(grep "^JWT_KEY=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
JWT_KEY=$(openssl rand -base64 32)
|
|
update_env_var "JWT_KEY" "$JWT_KEY"
|
|
else
|
|
printf " ${GREEN}> JWT_KEY already exists.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
populate_data_protection_cert_pass() {
|
|
printf "${CYAN}> Checking DATA_PROTECTION_CERT_PASS...${NC}\n"
|
|
if ! grep -q "^DATA_PROTECTION_CERT_PASS=" "$ENV_FILE" || [ -z "$(grep "^DATA_PROTECTION_CERT_PASS=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
CERT_PASS=$(openssl rand -base64 32)
|
|
update_env_var "DATA_PROTECTION_CERT_PASS" "$CERT_PASS"
|
|
else
|
|
printf " ${GREEN}> DATA_PROTECTION_CERT_PASS already exists.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
populate_postgres_credentials() {
|
|
printf "${CYAN}> Checking Postgres credentials...${NC}\n"
|
|
|
|
if ! grep -q "^POSTGRES_DB=" "$ENV_FILE" || [ -z "$(grep "^POSTGRES_DB=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
update_env_var "POSTGRES_DB" "aliasvault"
|
|
else
|
|
printf " ${GREEN}> POSTGRES_DB already exists.${NC}\n"
|
|
fi
|
|
|
|
if ! grep -q "^POSTGRES_USER=" "$ENV_FILE" || [ -z "$(grep "^POSTGRES_USER=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
update_env_var "POSTGRES_USER" "aliasvault"
|
|
else
|
|
printf " ${GREEN}> POSTGRES_USER already exists.${NC}\n"
|
|
fi
|
|
|
|
if ! grep -q "^POSTGRES_PASSWORD=" "$ENV_FILE" || [ -z "$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
# Generate a strong random password with 32 characters
|
|
POSTGRES_PASS=$(openssl rand -base64 32)
|
|
update_env_var "POSTGRES_PASSWORD" "$POSTGRES_PASS"
|
|
else
|
|
printf " ${GREEN}> POSTGRES_PASSWORD already exists.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
set_private_email_domains() {
|
|
printf "${CYAN}> Checking PRIVATE_EMAIL_DOMAINS...${NC}\n"
|
|
if ! grep -q "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" || [ -z "$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
update_env_var "PRIVATE_EMAIL_DOMAINS" "DISABLED.TLD"
|
|
fi
|
|
|
|
private_email_domains=$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2)
|
|
if [ "$private_email_domains" = "DISABLED.TLD" ]; then
|
|
printf " ${GREEN}> Email server is disabled. To enable use ./install.sh configure-email command.${NC}\n"
|
|
else
|
|
printf " ${GREEN}> PRIVATE_EMAIL_DOMAINS already exists. Email server is enabled.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
set_smtp_tls_enabled() {
|
|
printf "${CYAN}> Checking SMTP_TLS_ENABLED...${NC}\n"
|
|
if ! grep -q "^SMTP_TLS_ENABLED=" "$ENV_FILE"; then
|
|
update_env_var "SMTP_TLS_ENABLED" "false"
|
|
else
|
|
printf " ${GREEN}> SMTP_TLS_ENABLED already exists.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
set_support_email() {
|
|
printf "${CYAN}> Checking SUPPORT_EMAIL...${NC}\n"
|
|
if ! grep -q "^SUPPORT_EMAIL=" "$ENV_FILE"; then
|
|
read -p "Enter server admin support email address that is shown on contact page (optional, press Enter to skip): " SUPPORT_EMAIL
|
|
update_env_var "SUPPORT_EMAIL" "$SUPPORT_EMAIL"
|
|
else
|
|
printf " ${GREEN}> SUPPORT_EMAIL already exists.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
set_public_registration() {
|
|
printf "${CYAN}> Checking PUBLIC_REGISTRATION_ENABLED...${NC}\n"
|
|
if ! grep -q "^PUBLIC_REGISTRATION_ENABLED=" "$ENV_FILE" || [ -z "$(grep "^PUBLIC_REGISTRATION_ENABLED=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
update_env_var "PUBLIC_REGISTRATION_ENABLED" "true"
|
|
else
|
|
printf " ${GREEN}> PUBLIC_REGISTRATION_ENABLED already exists.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
# Function to generate admin password
|
|
generate_admin_password() {
|
|
printf "${CYAN}> Generating admin password...${NC}\n"
|
|
PASSWORD=$(openssl rand -base64 12)
|
|
|
|
# Build locally if in build mode or if pre-built image is not available
|
|
if grep -q "^DEPLOYMENT_MODE=build" "$ENV_FILE" 2>/dev/null || ! docker pull ${GITHUB_CONTAINER_REGISTRY}-installcli:latest > /dev/null 2>&1; then
|
|
printf "${CYAN}> Building InstallCli locally...${NC}"
|
|
if [ "$VERBOSE" = true ]; then
|
|
docker build -t installcli -f src/Utilities/AliasVault.InstallCli/Dockerfile .
|
|
else
|
|
(
|
|
docker build -t installcli -f src/Utilities/AliasVault.InstallCli/Dockerfile . > install_build_output.log 2>&1 &
|
|
BUILD_PID=$!
|
|
while kill -0 $BUILD_PID 2>/dev/null; do
|
|
printf "."
|
|
sleep 1
|
|
done
|
|
printf "\n"
|
|
wait $BUILD_PID
|
|
BUILD_EXIT_CODE=$?
|
|
if [ $BUILD_EXIT_CODE -ne 0 ]; then
|
|
printf "\n${RED}> Error building Docker image. Check install_build_output.log for details.${NC}\n"
|
|
exit $BUILD_EXIT_CODE
|
|
fi
|
|
)
|
|
fi
|
|
HASH=$(docker run --rm installcli hash-password "$PASSWORD")
|
|
else
|
|
HASH=$(docker run --rm ${GITHUB_CONTAINER_REGISTRY}-installcli:latest hash-password "$PASSWORD")
|
|
fi
|
|
|
|
if [ -z "$HASH" ]; then
|
|
printf "${RED}> Error: Failed to generate password hash${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
update_env_var "ADMIN_PASSWORD_HASH" "$HASH"
|
|
update_env_var "ADMIN_PASSWORD_GENERATED" "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
printf " ==> New admin password: $PASSWORD\n"
|
|
}
|
|
|
|
# Function to set default ports
|
|
set_default_ports() {
|
|
printf "${CYAN}> Checking default ports...${NC}\n"
|
|
|
|
# Web ports
|
|
if ! grep -q "^HTTP_PORT=" "$ENV_FILE" || [ -z "$(grep "^HTTP_PORT=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
update_env_var "HTTP_PORT" "80"
|
|
else
|
|
printf " ${GREEN}> HTTP_PORT already exists.${NC}\n"
|
|
fi
|
|
|
|
if ! grep -q "^HTTPS_PORT=" "$ENV_FILE" || [ -z "$(grep "^HTTPS_PORT=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
update_env_var "HTTPS_PORT" "443"
|
|
else
|
|
printf " ${GREEN}> HTTPS_PORT already exists.${NC}\n"
|
|
fi
|
|
|
|
# SMTP ports
|
|
if ! grep -q "^SMTP_PORT=" "$ENV_FILE" || [ -z "$(grep "^SMTP_PORT=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
update_env_var "SMTP_PORT" "25"
|
|
else
|
|
printf " ${GREEN}> SMTP_PORT already exists.${NC}\n"
|
|
fi
|
|
|
|
if ! grep -q "^SMTP_TLS_PORT=" "$ENV_FILE" || [ -z "$(grep "^SMTP_TLS_PORT=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
update_env_var "SMTP_TLS_PORT" "587"
|
|
else
|
|
printf " ${GREEN}> SMTP_TLS_PORT already exists.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
# Helper function to update environment variables
|
|
update_env_var() {
|
|
local key=$1
|
|
local value=$2
|
|
|
|
if [ -f "$ENV_FILE" ]; then
|
|
sed -i.bak "/^${key}=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
|
|
fi
|
|
|
|
echo "$key=$value" >> "$ENV_FILE"
|
|
printf " ${GREEN}> $key has been set in $ENV_FILE.${NC}\n"
|
|
}
|
|
|
|
|
|
# Helper function to delete environment variables
|
|
delete_env_var() {
|
|
local key=$1
|
|
|
|
if [ -f "$ENV_FILE" ]; then
|
|
sed -i.bak "/^${key}=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
|
|
printf " ${GREEN}> $key has been removed from $ENV_FILE.${NC}\n"
|
|
fi
|
|
}
|
|
|
|
# Function to print success message
|
|
print_success_message() {
|
|
printf "\n"
|
|
printf "${MAGENTA}=========================================================${NC}\n"
|
|
printf "\n"
|
|
printf "${GREEN}AliasVault is successfully installed!${NC}\n"
|
|
printf "\n"
|
|
printf "${CYAN}To configure the server, login to the admin panel:${NC}\n"
|
|
printf "\n"
|
|
if [ -n "$PASSWORD" ]; then
|
|
printf "Admin Panel: https://localhost/admin\n"
|
|
printf "Username: admin\n"
|
|
printf "Password: $PASSWORD\n"
|
|
printf "\n"
|
|
printf "${YELLOW}(!) Caution: Make sure to backup the above credentials in a safe place, they won't be shown again!${NC}\n"
|
|
else
|
|
printf "Admin Panel: https://localhost/admin\n"
|
|
printf "Username: admin\n"
|
|
printf "Password: (Previously set. Use ./install.sh reset-password to generate new one.)\n"
|
|
fi
|
|
printf "\n"
|
|
printf "${CYAN}===========================${NC}\n"
|
|
printf "\n"
|
|
printf "${CYAN}In order to start using AliasVault, log into the client website:${NC}\n"
|
|
printf "\n"
|
|
printf "Client Website: https://localhost/\n"
|
|
printf "\n"
|
|
printf "${MAGENTA}=========================================================${NC}\n"
|
|
}
|
|
|
|
# Function to recreate (restart) Docker containers
|
|
recreate_docker_containers() {
|
|
printf "${CYAN}> (Re)creating Docker containers...${NC}\n"
|
|
|
|
if [ "$VERBOSE" = true ]; then
|
|
$(get_docker_compose_command) up -d --force-recreate
|
|
else
|
|
$(get_docker_compose_command) up -d --force-recreate > /dev/null 2>&1
|
|
fi
|
|
printf "${GREEN}> Docker containers (re)created successfully.${NC}\n"
|
|
}
|
|
|
|
# Function to print password reset success message
|
|
print_password_reset_message() {
|
|
printf "\n"
|
|
printf "${MAGENTA}=========================================================${NC}\n"
|
|
printf "\n"
|
|
printf "${GREEN}The admin password has been successfully reset, see the output above.${NC}\n"
|
|
printf "\n"
|
|
printf "${MAGENTA}=========================================================${NC}\n"
|
|
printf "\n"
|
|
}
|
|
|
|
# Function to get docker compose command with appropriate config files
|
|
get_docker_compose_command() {
|
|
local base_command="docker compose -f docker-compose.yml"
|
|
|
|
# Check if using build configuration
|
|
if grep -q "^DEPLOYMENT_MODE=build" "$ENV_FILE" 2>/dev/null; then
|
|
base_command="$base_command -f docker-compose.build.yml"
|
|
fi
|
|
|
|
# Check if Let's Encrypt is enabled
|
|
if grep -q "^LETSENCRYPT_ENABLED=true" "$ENV_FILE" 2>/dev/null; then
|
|
base_command="$base_command -f docker-compose.letsencrypt.yml"
|
|
fi
|
|
|
|
echo "$base_command"
|
|
}
|
|
|
|
# Add this new function for handling registration configuration
|
|
handle_registration_configuration() {
|
|
printf "${YELLOW}+++ Public Registration Configuration +++${NC}\n"
|
|
printf "\n"
|
|
|
|
# Check if AliasVault is installed
|
|
if [ ! -f "docker-compose.yml" ]; then
|
|
printf "${RED}Error: AliasVault must be installed first.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Get current registration setting
|
|
CURRENT_SETTING=$(grep "^PUBLIC_REGISTRATION_ENABLED=" "$ENV_FILE" | cut -d '=' -f2)
|
|
|
|
printf "${CYAN}About Public Registration:${NC}\n"
|
|
printf "Public registration allows new users to create their own accounts on your AliasVault instance.\n"
|
|
printf "When disabled, no new accounts can be created.\n"
|
|
printf "\n"
|
|
printf "${CYAN}Current Configuration:${NC}\n"
|
|
if [ "$CURRENT_SETTING" = "true" ]; then
|
|
printf "Public Registration: ${GREEN}Enabled${NC}\n"
|
|
else
|
|
printf "Public Registration: ${RED}Disabled${NC}\n"
|
|
fi
|
|
|
|
printf "\n"
|
|
printf "Options:\n"
|
|
printf "1) Enable public registration\n"
|
|
printf "2) Disable public registration\n"
|
|
printf "3) Cancel\n"
|
|
printf "\n"
|
|
|
|
read -p "Select an option [1-3]: " reg_option
|
|
|
|
case $reg_option in
|
|
1)
|
|
update_env_var "PUBLIC_REGISTRATION_ENABLED" "true"
|
|
printf "${GREEN}> Public registration has been enabled.${NC}\n"
|
|
|
|
printf "\n${YELLOW}Warning: Docker containers need to be restarted to apply these changes.${NC}\n"
|
|
read -p "Restart now? (y/n): " restart_confirm
|
|
|
|
if [ "$restart_confirm" != "y" ] && [ "$restart_confirm" != "Y" ]; then
|
|
printf "${YELLOW}Please restart manually to apply the changes.${NC}\n"
|
|
exit 0
|
|
fi
|
|
|
|
handle_restart
|
|
;;
|
|
2)
|
|
update_env_var "PUBLIC_REGISTRATION_ENABLED" "false"
|
|
printf "${YELLOW}> Public registration has been disabled.${NC}\n"
|
|
|
|
printf "\n${YELLOW}Warning: Docker containers need to be restarted to apply these changes.${NC}\n"
|
|
read -p "Restart now? (y/n): " restart_confirm
|
|
|
|
if [ "$restart_confirm" != "y" ] && [ "$restart_confirm" != "Y" ]; then
|
|
printf "${YELLOW}Please restart manually to apply the changes.${NC}\n"
|
|
exit 0
|
|
fi
|
|
|
|
handle_restart
|
|
;;
|
|
3)
|
|
printf "${YELLOW}Registration configuration cancelled.${NC}\n"
|
|
exit 0
|
|
;;
|
|
*)
|
|
printf "${RED}Invalid option selected.${NC}\n"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to handle initial installation or reinstallation
|
|
handle_install() {
|
|
local specified_version="$1"
|
|
|
|
# If version specified, install that version directly
|
|
if [ -n "$specified_version" ]; then
|
|
handle_install_version "$specified_version"
|
|
return
|
|
fi
|
|
|
|
# Check if .env exists before reading
|
|
if [ -f "$ENV_FILE" ]; then
|
|
if grep -q "^ALIASVAULT_VERSION=" "$ENV_FILE"; then
|
|
current_version=$(grep "^ALIASVAULT_VERSION=" "$ENV_FILE" | cut -d '=' -f2)
|
|
printf "${CYAN}> Current AliasVault version: ${current_version}${NC}\n"
|
|
printf "${YELLOW}> AliasVault is already installed.${NC}\n"
|
|
printf "1. To reinstall the current version (${current_version}), continue with this script\n"
|
|
printf "2. To check for updates and to install the latest version, use: ./install.sh update\n"
|
|
printf "3. To install a specific version, use: ./install.sh install <version>\n\n"
|
|
|
|
read -p "Would you like to reinstall the current version? [y/N]: " REPLY
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
printf "${YELLOW}> Installation cancelled.${NC}\n"
|
|
exit 0
|
|
fi
|
|
|
|
handle_install_version "$current_version"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
handle_install_version "latest"
|
|
}
|
|
|
|
# Function to handle build
|
|
handle_build() {
|
|
printf "${YELLOW}+++ Building AliasVault from source +++${NC}\n"
|
|
# Set deployment mode to build to ensure container lifecycle uses build configuration
|
|
set_deployment_mode "build"
|
|
printf "\n"
|
|
|
|
# Initialize workspace which makes sure all required directories and files exist
|
|
initialize_workspace
|
|
|
|
# Check for required build files
|
|
if [ ! -f "docker-compose.build.yml" ] || [ ! -d "src" ]; then
|
|
printf "${RED}Error: Required files for building from source are missing.${NC}\n"
|
|
printf "\n"
|
|
printf "To build AliasVault from source, you need:\n"
|
|
printf "1. docker-compose.build.yml file\n"
|
|
printf "2. src/ directory with the complete source code\n"
|
|
printf "\n"
|
|
printf "Please clone the complete repository using:\n"
|
|
printf "git clone https://github.com/${REPO_OWNER}/${REPO_NAME}.git\n"
|
|
printf "\n"
|
|
printf "Alternatively, you can use './install.sh install' to pull pre-built images.\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Initialize environment with proper error handling
|
|
create_env_file || { printf "${RED}> Failed to create .env file${NC}\n"; exit 1; }
|
|
set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; }
|
|
populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; }
|
|
populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; }
|
|
populate_postgres_credentials || { printf "${RED}> Failed to set PostgreSQL credentials${NC}\n"; exit 1; }
|
|
set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; }
|
|
set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; }
|
|
set_default_ports || { printf "${RED}> Failed to set default ports${NC}\n"; exit 1; }
|
|
set_public_registration || { printf "${RED}> Failed to set public registration${NC}\n"; exit 1; }
|
|
|
|
# Only generate admin password if not already set
|
|
if ! grep -q "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" || [ -z "$(grep "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
generate_admin_password || { printf "${RED}> Failed to generate admin password${NC}\n"; exit 1; }
|
|
fi
|
|
|
|
printf "\n${YELLOW}+++ Building and starting services +++${NC}\n"
|
|
printf "\n"
|
|
|
|
printf "${CYAN}> Building Docker Compose stack...${NC}"
|
|
if [ "$VERBOSE" = true ]; then
|
|
$(get_docker_compose_command "build") build || {
|
|
printf "\n${RED}> Failed to build Docker Compose stack${NC}\n"
|
|
exit 1
|
|
}
|
|
else
|
|
(
|
|
$(get_docker_compose_command "build") build > install_compose_build_output.log 2>&1 &
|
|
BUILD_PID=$!
|
|
while kill -0 $BUILD_PID 2>/dev/null; do
|
|
printf "."
|
|
sleep 1
|
|
done
|
|
wait $BUILD_PID
|
|
BUILD_EXIT_CODE=$?
|
|
if [ $BUILD_EXIT_CODE -ne 0 ]; then
|
|
printf "\n${RED}> Failed to build Docker Compose stack. Check install_compose_build_output.log for details.${NC}\n"
|
|
exit 1
|
|
fi
|
|
)
|
|
fi
|
|
printf "\n${GREEN}> Docker Compose stack built successfully.${NC}\n"
|
|
|
|
printf "${CYAN}> Starting Docker Compose stack...${NC}\n"
|
|
|
|
recreate_docker_containers
|
|
|
|
printf "${GREEN}> Docker Compose stack started successfully.${NC}\n"
|
|
|
|
# Only show success message if we made it here without errors
|
|
print_success_message
|
|
}
|
|
|
|
# Function to handle uninstall
|
|
handle_uninstall() {
|
|
printf "${YELLOW}+++ Uninstalling AliasVault +++${NC}\n"
|
|
printf "\n"
|
|
|
|
# Check if -y flag was passed
|
|
if [ "$FORCE_YES" != "true" ]; then
|
|
# Ask for confirmation before proceeding
|
|
read -p "Are you sure you want to uninstall AliasVault? This will remove all containers and images. [y/N]: " REPLY
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
printf "${YELLOW}> Uninstall cancelled.${NC}\n"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
printf "${CYAN}> Stopping and removing Docker containers...${NC}\n"
|
|
if [ "$VERBOSE" = true ]; then
|
|
docker compose -f docker-compose.yml down -v || {
|
|
printf "${RED}> Failed to stop and remove Docker containers${NC}\n"
|
|
exit 1
|
|
}
|
|
else
|
|
docker compose -f docker-compose.yml down -v > /dev/null 2>&1 || {
|
|
printf "${RED}> Failed to stop and remove Docker containers${NC}\n"
|
|
exit 1
|
|
}
|
|
fi
|
|
printf "${GREEN}> Docker containers stopped and removed.${NC}\n"
|
|
|
|
# Remove version from .env
|
|
delete_env_var "ALIASVAULT_VERSION" ""
|
|
|
|
printf "${CYAN}> Removing Docker images...${NC}\n"
|
|
if [ "$VERBOSE" = true ]; then
|
|
docker compose -f docker-compose.yml down --rmi all || {
|
|
printf "${RED}> Failed to remove Docker images${NC}\n"
|
|
exit 1
|
|
}
|
|
else
|
|
docker compose -f docker-compose.yml down --rmi all > /dev/null 2>&1 || {
|
|
printf "${RED}> Failed to remove Docker images${NC}\n"
|
|
exit 1
|
|
}
|
|
fi
|
|
printf "${GREEN}> Docker images removed.${NC}\n"
|
|
|
|
printf "${CYAN}> Pruning Docker system...${NC}\n"
|
|
if [ "$VERBOSE" = true ]; then
|
|
docker system prune -af || {
|
|
printf "${RED}> Failed to prune Docker system${NC}\n"
|
|
exit 1
|
|
}
|
|
else
|
|
docker system prune -af > /dev/null 2>&1 || {
|
|
printf "${RED}> Failed to prune Docker system${NC}\n"
|
|
exit 1
|
|
}
|
|
fi
|
|
printf "${GREEN}> Docker system pruned.${NC}\n"
|
|
|
|
# Only show success message if we made it here without errors
|
|
printf "\n"
|
|
printf "${MAGENTA}=========================================================${NC}\n"
|
|
printf "\n"
|
|
printf "${GREEN}AliasVault has been successfully uninstalled!${NC}\n"
|
|
printf "\n"
|
|
printf "All Docker containers and images related to AliasVault have been removed.\n"
|
|
printf "The current directory, including logs and .env files, has been left intact.\n"
|
|
printf "\n"
|
|
printf "If you wish to remove the remaining files, you can do so manually.\n"
|
|
printf "\n"
|
|
printf "Thank you for using AliasVault!\n"
|
|
printf "\n"
|
|
printf "${MAGENTA}=========================================================${NC}\n"
|
|
}
|
|
|
|
# Function to handle SSL configuration
|
|
handle_ssl_configuration() {
|
|
printf "${YELLOW}+++ SSL Certificate Configuration +++${NC}\n"
|
|
printf "\n"
|
|
|
|
# Check if AliasVault is installed
|
|
if [ ! -f "docker-compose.yml" ]; then
|
|
printf "${RED}Error: AliasVault must be installed first.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; }
|
|
|
|
# Get the current hostname and SSL config from .env
|
|
CURRENT_HOSTNAME=$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)
|
|
LETSENCRYPT_ENABLED=$(grep "^LETSENCRYPT_ENABLED=" "$ENV_FILE" | cut -d '=' -f2)
|
|
|
|
printf "${CYAN}About SSL Certificates:${NC}\n"
|
|
printf "A default installation of AliasVault comes with a self-signed SSL certificate.\n"
|
|
printf "While self-signed certificates provide encryption, they will show security warnings in browsers.\n"
|
|
printf "\n"
|
|
printf "AliasVault also supports generating valid SSL certificates via Let's Encrypt.\n"
|
|
printf "Let's Encrypt certificates are trusted by browsers and will not show security warnings.\n"
|
|
printf "However, Let's Encrypt requires that:\n"
|
|
printf " - AliasVault is reachable from the internet via port 80/443\n"
|
|
printf " - You have configured a valid domain name (not localhost)\n"
|
|
printf "\n"
|
|
printf "Let's Encrypt certificates will be automatically renewed before expiry.\n"
|
|
printf "\n"
|
|
printf "${CYAN}Current Configuration:${NC}\n"
|
|
if [ "$LETSENCRYPT_ENABLED" = "true" ]; then
|
|
printf "Currently using: ${GREEN}Let's Encrypt certificates${NC}\n"
|
|
else
|
|
printf "Currently using: ${YELLOW}Self-signed certificates${NC}\n"
|
|
fi
|
|
|
|
printf "Current hostname: ${CYAN}${CURRENT_HOSTNAME}${NC} (To change this, run: ./install.sh configure-hostname)\n"
|
|
printf "\n"
|
|
printf "SSL Options:\n"
|
|
printf "1) Activate and/or request new Let's Encrypt certificate (recommended for production)\n"
|
|
printf "2) Activate and/or generate new self-signed certificate\n"
|
|
printf "3) Cancel\n"
|
|
printf "\n"
|
|
|
|
read -p "Select an option [1-3]: " ssl_option
|
|
|
|
case $ssl_option in
|
|
1)
|
|
configure_letsencrypt
|
|
;;
|
|
2)
|
|
generate_self_signed_cert
|
|
;;
|
|
3)
|
|
printf "${YELLOW}SSL configuration cancelled.${NC}\n"
|
|
exit 0
|
|
;;
|
|
*)
|
|
printf "${RED}Invalid option selected.${NC}\n"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to handle email server configuration
|
|
handle_email_configuration() {
|
|
# Setup trap for Ctrl+C and other interrupts
|
|
trap 'printf "\n${YELLOW}Configuration cancelled by user.${NC}\n"; exit 1' INT TERM
|
|
|
|
printf "${YELLOW}+++ Email Server Configuration +++${NC}\n"
|
|
printf "\n"
|
|
|
|
# Check if AliasVault is installed
|
|
if [ ! -f "docker-compose.yml" ]; then
|
|
printf "${RED}Error: AliasVault must be installed first.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Get current email domains from .env
|
|
CURRENT_DOMAINS=$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2)
|
|
|
|
printf "${CYAN}About Email Server:${NC}\n"
|
|
printf "AliasVault includes a built-in email server for handling virtual email addresses.\n"
|
|
printf "When enabled, it can receive emails for one or more configured domains.\n"
|
|
printf "Each domain must have an MX record in DNS configuration pointing to this server's hostname.\n"
|
|
printf "\n"
|
|
printf "${CYAN}Current Configuration:${NC}\n"
|
|
|
|
if [ "$CURRENT_DOMAINS" = "DISABLED.TLD" ]; then
|
|
printf "Email Server Status: ${RED}Disabled${NC}\n"
|
|
else
|
|
printf "Email Server Status: ${GREEN}Enabled${NC}\n"
|
|
printf "Active Domains: ${CYAN}${CURRENT_DOMAINS}${NC}\n"
|
|
fi
|
|
|
|
printf "\n"
|
|
printf "Email Server Options:\n"
|
|
printf "1) Enable email server / Update domains\n"
|
|
printf "2) Disable email server\n"
|
|
printf "3) Cancel\n"
|
|
printf "\n"
|
|
|
|
read -p "Select an option [1-3]: " email_option
|
|
|
|
case $email_option in
|
|
1)
|
|
while true; do
|
|
printf "\n${CYAN}Enter domain(s) for email server${NC}\n"
|
|
printf "For multiple domains, separate with commas (e.g. domain1.com,domain2.com)\n"
|
|
printf "IMPORTANT: Each domain must have an MX record in DNS pointing to this server.\n"
|
|
read -p "Domains: " new_domains
|
|
|
|
if [ -z "$new_domains" ]; then
|
|
printf "${RED}Error: Domains cannot be empty${NC}\n"
|
|
continue
|
|
fi
|
|
|
|
printf "\n${CYAN}You entered the following domains:${NC}\n"
|
|
IFS=',' read -ra DOMAIN_ARRAY <<< "$new_domains"
|
|
for domain in "${DOMAIN_ARRAY[@]}"; do
|
|
printf " - ${GREEN}${domain}${NC}\n"
|
|
done
|
|
printf "\n"
|
|
|
|
read -p "Are these domains correct? (y/n): " confirm
|
|
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Update .env file and restart
|
|
if ! update_env_var "PRIVATE_EMAIL_DOMAINS" "$new_domains"; then
|
|
printf "${RED}Failed to update configuration.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
printf "${GREEN}Email server configuration updated${NC}\n"
|
|
|
|
printf "\n${YELLOW}Warning: Docker containers need to be restarted to apply these changes.${NC}\n"
|
|
read -p "Restart now? (y/n): " restart_confirm
|
|
|
|
if [ "$restart_confirm" != "y" ] && [ "$restart_confirm" != "Y" ]; then
|
|
printf "${YELLOW}Please restart manually to apply the changes.${NC}\n"
|
|
exit 0
|
|
fi
|
|
|
|
printf "Restarting AliasVault services...\n"
|
|
|
|
if ! handle_restart; then
|
|
printf "${RED}Failed to restart services.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Only show next steps if everything succeeded
|
|
printf "\n${CYAN}The email server is now succesfully configured.${NC}\n"
|
|
printf "\n"
|
|
printf "To test the email server:\n"
|
|
printf " a. Log in to your AliasVault account\n"
|
|
printf " b. Create a new alias using one of your configured private domains\n"
|
|
printf " c. Send a test email from an external email service (e.g., Gmail)\n"
|
|
printf " d. Check if the email appears in your AliasVault inbox\n"
|
|
printf "\n"
|
|
printf "If emails don't arrive, please verify:\n"
|
|
printf " > DNS MX records are correctly configured\n"
|
|
printf " > Your server's firewall allows incoming traffic on port 25 and 587\n"
|
|
printf " > Your ISP/hosting provider doesn't block SMTP traffic\n"
|
|
printf "\n"
|
|
;;
|
|
2)
|
|
printf "${YELLOW}Warning: Docker containers need to be restarted after disabling the email server.${NC}\n"
|
|
read -p "Continue with disable and restart? (y/n): " disable_confirm
|
|
|
|
if [ "$disable_confirm" != "y" ] && [ "$disable_confirm" != "Y" ]; then
|
|
printf "${YELLOW}Configuration cancelled.${NC}\n"
|
|
exit 0
|
|
fi
|
|
|
|
# Disable email server
|
|
if ! update_env_var "PRIVATE_EMAIL_DOMAINS" "DISABLED.TLD"; then
|
|
printf "${RED}Failed to update configuration.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
printf "${YELLOW}Email server disabled${NC}\n"
|
|
printf "Restarting AliasVault services...\n"
|
|
|
|
if ! handle_restart; then
|
|
printf "${RED}Failed to restart services.${NC}\n"
|
|
exit 1
|
|
fi
|
|
;;
|
|
3)
|
|
printf "${YELLOW}Email configuration cancelled.${NC}\n"
|
|
exit 0
|
|
;;
|
|
*)
|
|
printf "${RED}Invalid option selected.${NC}\n"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Remove the trap before normal exit
|
|
trap - INT TERM
|
|
}
|
|
|
|
# Function to configure Let's Encrypt
|
|
configure_letsencrypt() {
|
|
printf "${CYAN}> Configuring Let's Encrypt SSL certificate...${NC}\n"
|
|
|
|
# Check if hostname is localhost
|
|
if [ "$CURRENT_HOSTNAME" = "localhost" ]; then
|
|
printf "${RED}Error: Let's Encrypt certificates cannot be issued for 'localhost'.${NC}\n"
|
|
printf "${YELLOW}Please configure a valid publically resolvable domain name (e.g. mydomain.com) before setting up Let's Encrypt.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if hostname is a valid domain
|
|
if ! [[ "$CURRENT_HOSTNAME" =~ \.[a-zA-Z]{2,}$ ]]; then
|
|
printf "${RED}Error: Invalid hostname '${CURRENT_HOSTNAME}'.${NC}\n"
|
|
printf "${YELLOW}Please configure a valid publically resolvable domain name (e.g. mydomain.com) before setting up Let's Encrypt.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Verify DNS is properly configured
|
|
printf "\n${YELLOW}Important: Before proceeding, ensure that:${NC}\n"
|
|
printf "1. AliasVault is currently running and accessible at ${CYAN}https://${CURRENT_HOSTNAME}${NC}\n"
|
|
printf "2. Your domain (${CYAN}${CURRENT_HOSTNAME}${NC}) is externally resolvable to this server's IP address\n"
|
|
printf "3. Ports 80 and 443 are open and accessible from the internet\n"
|
|
printf "\n"
|
|
|
|
read -p "Have you completed these steps? [y/N]: " REPLY
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
printf "${YELLOW}> Let's Encrypt configuration cancelled.${NC}\n"
|
|
exit 0
|
|
fi
|
|
|
|
# Get contact email for Let's Encrypt
|
|
SUPPORT_EMAIL=$(grep "^SUPPORT_EMAIL=" "$ENV_FILE" | cut -d '=' -f2)
|
|
LETSENCRYPT_EMAIL=""
|
|
|
|
while true; do
|
|
printf "\nPlease enter a valid email address that will be used for Let's Encrypt certificate notifications:\n"
|
|
read -p "Email: " LETSENCRYPT_EMAIL
|
|
if [[ "$LETSENCRYPT_EMAIL" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
|
|
printf "Confirm using ${CYAN}${LETSENCRYPT_EMAIL}${NC} for Let's Encrypt notifications? [y/N] "
|
|
read REPLY
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
break
|
|
fi
|
|
else
|
|
printf "${RED}Invalid email format. Please try again.${NC}\n"
|
|
fi
|
|
done
|
|
|
|
# Create certbot directories
|
|
printf "${CYAN}> Creating Let's Encrypt directories...${NC}\n"
|
|
mkdir -p ./certificates/letsencrypt/www
|
|
|
|
# Request certificate using a temporary certbot container
|
|
printf "${CYAN}> Requesting Let's Encrypt certificate...${NC}\n"
|
|
docker run --rm \
|
|
-v ./certificates/letsencrypt:/etc/letsencrypt:rw \
|
|
-v ./certificates/letsencrypt/www:/var/www/certbot:rw \
|
|
certbot/certbot certonly \
|
|
--webroot \
|
|
--webroot-path=/var/www/certbot \
|
|
--email "$LETSENCRYPT_EMAIL" \
|
|
--agree-tos \
|
|
--no-eff-email \
|
|
--non-interactive \
|
|
--domains ${CURRENT_HOSTNAME} \
|
|
--force-renewal
|
|
|
|
if [ $? -ne 0 ]; then
|
|
printf "${RED}Failed to obtain Let's Encrypt certificate.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Fix permissions on Let's Encrypt directories and files
|
|
sudo chmod -R 755 ./certificates/letsencrypt
|
|
|
|
# Ensure private keys remain secure
|
|
sudo find ./certificates/letsencrypt -type f -name "privkey*.pem" -exec chmod 600 {} \;
|
|
sudo find ./certificates/letsencrypt -type f -name "fullchain*.pem" -exec chmod 644 {} \;
|
|
|
|
# Update .env to indicate Let's Encrypt is enabled
|
|
update_env_var "LETSENCRYPT_ENABLED" "true"
|
|
|
|
# Restart only the reverse proxy with new configuration so it loads the new certificate
|
|
printf "${CYAN}> Restarting reverse proxy with Let's Encrypt configuration...${NC}\n"
|
|
$(get_docker_compose_command) up -d reverse-proxy --force-recreate
|
|
|
|
# Starting certbot container to renew certificates automatically
|
|
printf "${CYAN}> Starting new certbot container to renew certificates automatically...${NC}\n"
|
|
$(get_docker_compose_command) up -d certbot
|
|
|
|
printf "${GREEN}> Let's Encrypt SSL certificate has been configured successfully!${NC}\n"
|
|
}
|
|
|
|
# Function to generate self-signed certificate
|
|
generate_self_signed_cert() {
|
|
printf "${CYAN}> Generating new self-signed certificate...${NC}\n"
|
|
|
|
# Disable Let's Encrypt
|
|
update_env_var "LETSENCRYPT_ENABLED" "false"
|
|
|
|
# Stop existing containers
|
|
printf "${CYAN}> Stopping existing containers...${NC}\n"
|
|
docker compose down
|
|
|
|
# Remove existing certificates
|
|
rm -f ./certificates/ssl/cert.pem ./certificates/ssl/key.pem
|
|
|
|
# Remove Let's Encrypt directories
|
|
rm -rf ./certificates/letsencrypt
|
|
|
|
# Start containers (which will generate new self-signed certs)
|
|
printf "${CYAN}> Restarting services...${NC}\n"
|
|
docker compose up -d
|
|
|
|
printf "${GREEN}> New self-signed certificate has been generated successfully!${NC}\n"
|
|
}
|
|
|
|
# New functions to handle container lifecycle:
|
|
handle_start() {
|
|
printf "${CYAN}> Starting AliasVault containers...${NC}\n"
|
|
$(get_docker_compose_command) up -d
|
|
printf "${GREEN}> AliasVault containers started successfully.${NC}\n"
|
|
}
|
|
|
|
handle_stop() {
|
|
printf "${CYAN}> Stopping AliasVault containers...${NC}\n"
|
|
if ! docker compose ps --quiet 2>/dev/null | grep -q .; then
|
|
printf "${YELLOW}> No containers are currently running.${NC}\n"
|
|
exit 0
|
|
fi
|
|
|
|
$(get_docker_compose_command) down
|
|
printf "${GREEN}> AliasVault containers stopped successfully.${NC}\n"
|
|
}
|
|
|
|
handle_restart() {
|
|
printf "${CYAN}> Restarting AliasVault containers...${NC}\n"
|
|
$(get_docker_compose_command) down
|
|
$(get_docker_compose_command) up -d
|
|
printf "${GREEN}> AliasVault containers restarted successfully.${NC}\n"
|
|
}
|
|
|
|
# Function to handle updates
|
|
handle_update() {
|
|
printf "${YELLOW}+++ Checking for AliasVault updates +++${NC}\n"
|
|
printf "\n"
|
|
|
|
# First check for install.sh updates
|
|
check_install_script_update || true
|
|
|
|
# Check current version
|
|
if ! grep -q "^ALIASVAULT_VERSION=" "$ENV_FILE"; then
|
|
printf "${YELLOW}> No version information found. Running first-time update check...${NC}\n"
|
|
handle_install_version "latest"
|
|
return
|
|
fi
|
|
|
|
current_version=$(grep "^ALIASVAULT_VERSION=" "$ENV_FILE" | cut -d '=' -f2)
|
|
latest_version=$(curl -s "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
|
|
|
if [ -z "$latest_version" ]; then
|
|
printf "${RED}> Failed to check for updates. Please try again later.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
printf "${CYAN}> Current AliasVault version: ${current_version}${NC}\n"
|
|
printf "${CYAN}> Latest AliasVault version: ${latest_version}${NC}\n"
|
|
printf "\n"
|
|
|
|
if [ "$current_version" = "$latest_version" ]; then
|
|
printf "${GREEN}> You are already running the latest version of AliasVault!${NC}\n"
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$FORCE_YES" = true ]; then
|
|
printf "${CYAN}> Updating AliasVault to the latest version...${NC}\n"
|
|
handle_install_version "$latest_version"
|
|
printf "${GREEN}> Update completed successfully!${NC}\n"
|
|
return
|
|
fi
|
|
|
|
printf "${YELLOW}> A new version of AliasVault is available!${NC}\n"
|
|
printf "\n"
|
|
printf "${MAGENTA}Important:${NC}\n"
|
|
printf "1. It's recommended to backup your database before updating\n"
|
|
printf "2. The update process will restart all containers\n"
|
|
printf "\n"
|
|
|
|
read -p "Would you like to update to the latest version? [y/N]: " REPLY
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
printf "${YELLOW}> Update cancelled.${NC}\n"
|
|
exit 0
|
|
fi
|
|
|
|
printf "${CYAN}> Updating AliasVault...${NC}\n"
|
|
handle_install_version "$latest_version"
|
|
|
|
printf "${GREEN}> Update completed successfully!${NC}\n"
|
|
}
|
|
|
|
# Function to extract version
|
|
extract_version() {
|
|
local file="$1"
|
|
local version=$(head -n 2 "$file" | grep '@version' | cut -d' ' -f3)
|
|
echo "$version"
|
|
}
|
|
|
|
# Function to compare semantic versions
|
|
compare_versions() {
|
|
local version1="$1"
|
|
local version2="$2"
|
|
|
|
# Split versions into arrays
|
|
IFS='.' read -ra v1_parts <<< "$version1"
|
|
IFS='.' read -ra v2_parts <<< "$version2"
|
|
|
|
# Compare each part numerically
|
|
for i in {0..2}; do
|
|
# Default to 0 if part doesn't exist
|
|
local v1_part=${v1_parts[$i]:-0}
|
|
local v2_part=${v2_parts[$i]:-0}
|
|
|
|
# Compare numerically
|
|
if [ "$v1_part" -gt "$v2_part" ]; then
|
|
echo "1" # version1 is greater
|
|
return
|
|
elif [ "$v1_part" -lt "$v2_part" ]; then
|
|
echo "-1" # version1 is lesser
|
|
return
|
|
fi
|
|
done
|
|
|
|
echo "0" # versions are equal
|
|
}
|
|
|
|
# Function to check if install.sh needs updating
|
|
check_install_script_update() {
|
|
printf "${CYAN}> Checking for install script updates...${NC}\n"
|
|
|
|
# Get latest release version
|
|
local latest_version=$(curl -s "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
|
|
|
if [ -z "$latest_version" ]; then
|
|
printf "${RED}> Failed to check for install script updates. Continuing with current version.${NC}\n"
|
|
return 1
|
|
fi
|
|
|
|
if ! curl -sSf "${GITHUB_RAW_URL_REPO}/${latest_version}/install.sh" -o "install.sh.tmp"; then
|
|
printf "${RED}> Failed to check for install script updates. Continuing with current version.${NC}\n"
|
|
rm -f install.sh.tmp
|
|
return 1
|
|
fi
|
|
|
|
# Get versions
|
|
local current_version=$(extract_version "install.sh")
|
|
local new_version=$(extract_version "install.sh.tmp")
|
|
|
|
# Check if versions could be extracted
|
|
if [ -z "$current_version" ] || [ -z "$new_version" ]; then
|
|
printf "${YELLOW}> Could not determine script versions. Falling back to file comparison...${NC}\n"
|
|
# Fall back to file comparison
|
|
if ! cmp -s "install.sh" "install.sh.tmp"; then
|
|
printf "${YELLOW}> Changes detected in install script.${NC}\n"
|
|
else
|
|
printf "${GREEN}> Install script is up to date.${NC}\n"
|
|
rm -f install.sh.tmp
|
|
return 0
|
|
fi
|
|
else
|
|
printf "${CYAN}> Current install script version: ${current_version}${NC}\n"
|
|
printf "${CYAN}> Latest install script version: ${new_version}${NC}\n"
|
|
|
|
# Compare versions using semver comparison
|
|
if [ "$current_version" = "$new_version" ]; then
|
|
printf "${GREEN}> Install script is up to date.${NC}\n"
|
|
rm -f install.sh.tmp
|
|
return 0
|
|
else
|
|
local compare_result=$(compare_versions "$current_version" "$new_version")
|
|
|
|
if [ "$compare_result" -ge "0" ]; then
|
|
printf "${GREEN}> Install script is up to date.${NC}\n"
|
|
rm -f install.sh.tmp
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# If we get here, an update is available
|
|
if [ "$FORCE_YES" = true ]; then
|
|
printf "${CYAN}> Updating install script...${NC}\n"
|
|
cp "install.sh" "install.sh.backup"
|
|
mv "install.sh.tmp" "install.sh"
|
|
chmod +x "install.sh"
|
|
printf "${GREEN}> Install script updated successfully.${NC}\n"
|
|
printf "${GREEN}> Backup of previous version saved as install.sh.backup${NC}\n"
|
|
exit 0
|
|
fi
|
|
|
|
printf "${YELLOW}> A new version of the install script is available.${NC}\n"
|
|
printf "Would you like to update the install script before proceeding? [Y/n]: "
|
|
read -r reply
|
|
|
|
if [[ ! $reply =~ ^[Nn]$ ]]; then
|
|
# Create backup of current script
|
|
cp "install.sh" "install.sh.backup"
|
|
|
|
if mv "install.sh.tmp" "install.sh"; then
|
|
chmod +x "install.sh"
|
|
printf "${GREEN}> Install script updated successfully.${NC}\n"
|
|
printf "${GREEN}> Backup of previous version saved as install.sh.backup${NC}\n"
|
|
printf "${YELLOW}> Please run the update command again to continue with the update process.${NC}\n"
|
|
exit 0
|
|
else
|
|
printf "${RED}> Failed to update install script. Continuing with current version.${NC}\n"
|
|
# Restore from backup if update failed
|
|
mv "install.sh.backup" "install.sh"
|
|
rm -f install.sh.tmp
|
|
return 1
|
|
fi
|
|
else
|
|
printf "${YELLOW}> Continuing with current install script version.${NC}\n"
|
|
rm -f install.sh.tmp
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# Function to perform the actual installation with specific version
|
|
handle_install_version() {
|
|
local target_version="$1"
|
|
|
|
# If latest, get actual version number from GitHub API
|
|
if [ "$target_version" = "latest" ]; then
|
|
local actual_version=$(curl -s "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
|
if [ -n "$actual_version" ]; then
|
|
target_version="$actual_version"
|
|
fi
|
|
fi
|
|
|
|
printf "${YELLOW}+++ Installing AliasVault ${target_version} +++${NC}\n"
|
|
# Set deployment mode to install to ensure container lifecycle uses install configuration
|
|
set_deployment_mode "install"
|
|
printf "\n"
|
|
|
|
# Initialize workspace which makes sure all required directories and files exist
|
|
initialize_workspace
|
|
|
|
# Check if install script needs updating for this version
|
|
check_install_script_version "$target_version"
|
|
local check_result=$?
|
|
|
|
if [ $check_result -eq 2 ]; then
|
|
if [ "$FORCE_YES" = true ]; then
|
|
printf "${CYAN}> Updating install script to match version ${target_version}...${NC}\n"
|
|
else
|
|
printf "${YELLOW}> A different version of the install script is required for installing version ${target_version}.${NC}\n"
|
|
read -p "Would you like to self-update the install script before proceeding? [Y/n]: " reply
|
|
if [[ $reply =~ ^[Nn]$ ]]; then
|
|
printf "${YELLOW}> Continuing with current install script version.${NC}\n"
|
|
rm -f install.sh.tmp
|
|
fi
|
|
fi
|
|
|
|
if [ "$FORCE_YES" = true ] || [[ ! $reply =~ ^[Nn]$ ]]; then
|
|
# Create backup of current script
|
|
cp "install.sh" "install.sh.backup"
|
|
|
|
if mv "install.sh.tmp" "install.sh"; then
|
|
chmod +x "install.sh"
|
|
printf "${GREEN}> Install script updated successfully.${NC}\n"
|
|
printf "${GREEN}> Backup of previous version saved as install.sh.backup${NC}\n"
|
|
printf "${YELLOW}> Please run the same install command again to continue with the installation.${NC}\n"
|
|
exit 0
|
|
else
|
|
printf "${RED}> Failed to update install script. Continuing with current version.${NC}\n"
|
|
mv "install.sh.backup" "install.sh"
|
|
rm -f install.sh.tmp
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Update docker-compose files with correct version so we pull the correct images
|
|
handle_docker_compose "$target_version"
|
|
|
|
# Initialize environment
|
|
create_env_file || { printf "${RED}> Failed to create .env file${NC}\n"; exit 1; }
|
|
set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; }
|
|
populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; }
|
|
populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; }
|
|
populate_postgres_credentials || { printf "${RED}> Failed to set PostgreSQL credentials${NC}\n"; exit 1; }
|
|
set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; }
|
|
set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; }
|
|
set_default_ports || { printf "${RED}> Failed to set default ports${NC}\n"; exit 1; }
|
|
set_public_registration || { printf "${RED}> Failed to set public registration${NC}\n"; exit 1; }
|
|
|
|
# Only generate admin password if not already set
|
|
if ! grep -q "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" || [ -z "$(grep "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
|
generate_admin_password || { printf "${RED}> Failed to generate admin password${NC}\n"; exit 1; }
|
|
fi
|
|
|
|
# Pull images from GitHub Container Registry
|
|
printf "\n${YELLOW}+++ Pulling Docker images +++${NC}\n"
|
|
printf "\n"
|
|
|
|
printf "${CYAN}> Installing version: ${target_version}${NC}\n"
|
|
|
|
images=(
|
|
"${GITHUB_CONTAINER_REGISTRY}-postgres:${target_version}"
|
|
"${GITHUB_CONTAINER_REGISTRY}-reverse-proxy:${target_version}"
|
|
"${GITHUB_CONTAINER_REGISTRY}-api:${target_version}"
|
|
"${GITHUB_CONTAINER_REGISTRY}-client:${target_version}"
|
|
"${GITHUB_CONTAINER_REGISTRY}-admin:${target_version}"
|
|
"${GITHUB_CONTAINER_REGISTRY}-smtp:${target_version}"
|
|
"${GITHUB_CONTAINER_REGISTRY}-task-runner:${target_version}"
|
|
)
|
|
|
|
for image in "${images[@]}"; do
|
|
printf "${CYAN}> Pulling $image...${NC}\n"
|
|
if [ "$VERBOSE" = true ]; then
|
|
docker pull $image || printf "${YELLOW}> Warning: Failed to pull image: $image - continuing anyway${NC}\n"
|
|
else
|
|
docker pull $image > /dev/null 2>&1 || printf "${YELLOW}> Warning: Failed to pull image: $image - continuing anyway${NC}\n"
|
|
fi
|
|
done
|
|
|
|
# Save version to .env
|
|
update_env_var "ALIASVAULT_VERSION" "$target_version"
|
|
|
|
# Start containers
|
|
printf "\n${YELLOW}+++ Starting services +++${NC}\n"
|
|
printf "\n"
|
|
if [ "$VERBOSE" = true ]; then
|
|
docker compose up -d --force-recreate
|
|
else
|
|
docker compose up -d --force-recreate > /dev/null 2>&1
|
|
fi
|
|
printf "${GREEN}> Docker containers recreated.${NC}\n"
|
|
|
|
# Only show success message if we made it here without errors
|
|
print_success_message
|
|
}
|
|
|
|
# Function to handle development database configuration
|
|
configure_dev_database() {
|
|
printf "${YELLOW}+++ Development Database Configuration +++${NC}\n"
|
|
printf "\n"
|
|
|
|
if [ ! -f "docker-compose.dev.yml" ]; then
|
|
printf "${RED}> The docker-compose.dev.yml file is missing. This file is required to start the development database. Please checkout the full GitHub repository and try again.${NC}\n"
|
|
return 1
|
|
fi
|
|
|
|
# Check if direct option was provided
|
|
if [ -n "$COMMAND_ARG" ]; then
|
|
case $COMMAND_ARG in
|
|
1|start)
|
|
if docker compose -f docker-compose.dev.yml -p aliasvault-dev ps --status running 2>/dev/null | grep -q postgres-dev; then
|
|
printf "${YELLOW}> Development database is already running.${NC}\n"
|
|
else
|
|
printf "${CYAN}> Starting development database...${NC}\n"
|
|
docker compose -p aliasvault-dev -f docker-compose.dev.yml up -d --wait --wait-timeout 60
|
|
printf "${GREEN}> Development database started successfully.${NC}\n"
|
|
fi
|
|
print_dev_db_details
|
|
return
|
|
;;
|
|
0|stop)
|
|
if ! docker compose -f docker-compose.dev.yml -p aliasvault-dev ps --status running 2>/dev/null | grep -q postgres-dev; then
|
|
printf "${YELLOW}> Development database is already stopped.${NC}\n"
|
|
else
|
|
printf "${CYAN}> Stopping development database...${NC}\n"
|
|
docker compose -p aliasvault-dev -f docker-compose.dev.yml down
|
|
printf "${GREEN}> Development database stopped successfully.${NC}\n"
|
|
fi
|
|
return
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Check current status
|
|
if docker compose -f docker-compose.dev.yml -p aliasvault-dev ps --status running 2>/dev/null | grep -q postgres-dev; then
|
|
DEV_DB_STATUS="running"
|
|
else
|
|
DEV_DB_STATUS="stopped"
|
|
fi
|
|
|
|
printf "${CYAN}About Development Database:${NC}\n"
|
|
printf "A separate PostgreSQL instance for development purposes that:\n"
|
|
printf " - Runs on port 5433 (to avoid conflicts)\n"
|
|
printf " - Uses simple credentials (password: 'password')\n"
|
|
printf " - Stores data separately from production\n"
|
|
printf "\n"
|
|
printf "${CYAN}Current Status:${NC}\n"
|
|
if [ "$DEV_DB_STATUS" = "running" ]; then
|
|
printf "Development Database: ${GREEN}Running${NC}\n"
|
|
else
|
|
printf "Development Database: ${YELLOW}Stopped${NC}\n"
|
|
fi
|
|
printf "\n"
|
|
printf "Options:\n"
|
|
printf "1) Start development database\n"
|
|
printf "2) Stop development database\n"
|
|
printf "3) View connection details\n"
|
|
printf "4) Cancel\n"
|
|
printf "\n"
|
|
|
|
read -p "Select an option [1-4]: " dev_db_option
|
|
|
|
case $dev_db_option in
|
|
1)
|
|
if [ "$DEV_DB_STATUS" = "running" ]; then
|
|
printf "${YELLOW}> Development database is already running.${NC}\n"
|
|
else
|
|
printf "${CYAN}> Starting development database...${NC}\n"
|
|
docker compose -p aliasvault-dev -f docker-compose.dev.yml up -d --wait --wait-timeout 60
|
|
printf "${GREEN}> Development database started successfully.${NC}\n"
|
|
fi
|
|
print_dev_db_details
|
|
;;
|
|
2)
|
|
if [ "$DEV_DB_STATUS" = "stopped" ]; then
|
|
printf "${YELLOW}> Development database is already stopped.${NC}\n"
|
|
else
|
|
printf "${CYAN}> Stopping development database...${NC}\n"
|
|
docker compose -p aliasvault-dev -f docker-compose.dev.yml down
|
|
printf "${GREEN}> Development database stopped successfully.${NC}\n"
|
|
fi
|
|
;;
|
|
3)
|
|
print_dev_db_details
|
|
;;
|
|
4)
|
|
printf "${YELLOW}Configuration cancelled.${NC}\n"
|
|
exit 0
|
|
;;
|
|
*)
|
|
printf "${RED}Invalid option selected.${NC}\n"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to print development database connection details
|
|
print_dev_db_details() {
|
|
printf "\n"
|
|
printf "${MAGENTA}=========================================================${NC}\n"
|
|
printf "\n"
|
|
printf "${CYAN}Development Database Connection Details:${NC}\n"
|
|
printf "Host: localhost\n"
|
|
printf "Port: 5433\n"
|
|
printf "Database: aliasvault\n"
|
|
printf "Username: aliasvault\n"
|
|
printf "Password: password\n"
|
|
printf "\n"
|
|
printf "Connection string:\n"
|
|
printf "Host=localhost;Port=5433;Database=aliasvault;Username=aliasvault;Password=password\n"
|
|
printf "\n"
|
|
printf "${MAGENTA}=========================================================${NC}\n"
|
|
}
|
|
|
|
# Function to handle database migration. This is a one-time operation necessary when upgrading from <= 0.9.x to 0.10.0+ and only needs to be run once.
|
|
handle_migrate_db() {
|
|
printf "${YELLOW}+++ Database Migration Tool +++${NC}\n"
|
|
printf "\n"
|
|
|
|
# Check for old SQLite database
|
|
SQLITE_DB="database/AliasServerDb.sqlite"
|
|
if [ ! -f "$SQLITE_DB" ]; then
|
|
printf "${RED}Error: SQLite database not found at ${SQLITE_DB}${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Get the absolute path of the SQLite database
|
|
SQLITE_DB_ABS=$(realpath "$SQLITE_DB")
|
|
SQLITE_DB_DIR=$(dirname "$SQLITE_DB_ABS")
|
|
SQLITE_DB_NAME=$(basename "$SQLITE_DB_ABS")
|
|
|
|
# Get PostgreSQL password from .env file
|
|
POSTGRES_PASSWORD=$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d= -f2-)
|
|
if [ -z "$POSTGRES_PASSWORD" ]; then
|
|
printf "${RED}Error: POSTGRES_PASSWORD not found in .env file${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Get network name in lowercase
|
|
NETWORK_NAME="$(pwd | xargs basename)_default"
|
|
NETWORK_NAME=$(echo "$NETWORK_NAME" | tr '[:upper:]' '[:lower:]')
|
|
|
|
printf "\n${YELLOW}Warning: This will migrate data from your SQLite database to PostgreSQL.${NC}\n"
|
|
printf "\n"
|
|
printf "This is a one-time operation necessary when upgrading from <= 0.9.x to 0.10.0+ and only needs to be run once.\n"
|
|
printf "\n"
|
|
printf "Source database: ${CYAN}${SQLITE_DB_ABS}${NC}\n"
|
|
printf "Target: PostgreSQL database (using connection string from docker-compose.yml)\n"
|
|
printf "Make sure you have backed up your data before proceeding.\n"
|
|
|
|
printf "\n${RED}WARNING: This operation will DELETE ALL EXISTING DATA in the PostgreSQL database.${NC}\n"
|
|
printf "${RED}Only proceed if you understand that any current PostgreSQL data will be permanently lost.${NC}\n"
|
|
printf "${RED}This operation will stop all services and restart them after the migration is complete.${NC}\n"
|
|
printf "\n"
|
|
|
|
read -p "Continue with migration? [y/N]: " confirm
|
|
if [[ ! $confirm =~ ^[Yy]$ ]]; then
|
|
printf "${YELLOW}Migration cancelled.${NC}\n"
|
|
exit 0
|
|
fi
|
|
|
|
printf "${CYAN}> Stopping services to ensure database is not in use...${NC}\n"
|
|
docker compose stop api admin task-runner smtp
|
|
|
|
if ! docker pull ${GITHUB_CONTAINER_REGISTRY}-installcli:0.10.3 > /dev/null 2>&1; then
|
|
printf "${YELLOW}> Pre-built image not found, building locally...${NC}"
|
|
if [ "$VERBOSE" = true ]; then
|
|
docker build -t installcli -f src/Utilities/AliasVault.InstallCli/Dockerfile .
|
|
else
|
|
(
|
|
docker build -t installcli -f src/Utilities/AliasVault.InstallCli/Dockerfile . > install_build_output.log 2>&1 &
|
|
BUILD_PID=$!
|
|
while kill -0 $BUILD_PID 2>/dev/null; do
|
|
printf "."
|
|
sleep 1
|
|
done
|
|
printf "\n"
|
|
wait $BUILD_PID
|
|
BUILD_EXIT_CODE=$?
|
|
if [ $BUILD_EXIT_CODE -ne 0 ]; then
|
|
printf "\n${RED}> Error building Docker image. Check install_build_output.log for details.${NC}\n"
|
|
exit $BUILD_EXIT_CODE
|
|
fi
|
|
)
|
|
fi
|
|
|
|
# Run migration with volume mount and connection string
|
|
docker run --rm \
|
|
--network="${NETWORK_NAME}" \
|
|
-v "${SQLITE_DB_DIR}:/sqlite" \
|
|
installcli migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}"
|
|
else
|
|
# Run migration with volume mount using pre-built image
|
|
docker run --rm \
|
|
--network="${NETWORK_NAME}" \
|
|
-v "${SQLITE_DB_DIR}:/sqlite" \
|
|
${GITHUB_CONTAINER_REGISTRY}-installcli:0.10.0 migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}"
|
|
fi
|
|
|
|
# Starting services again
|
|
printf "${CYAN}> Starting services...${NC}\n"
|
|
docker compose start api admin task-runner smtp reverse-proxy
|
|
|
|
printf "${GREEN}> Check migration output above for details.${NC}\n"
|
|
}
|
|
|
|
# Function to set deployment mode in .env
|
|
set_deployment_mode() {
|
|
local mode=$1
|
|
if [ "$mode" != "build" ] && [ "$mode" != "install" ]; then
|
|
printf "${RED}Invalid deployment mode: $mode${NC}\n"
|
|
exit 1
|
|
fi
|
|
update_env_var "DEPLOYMENT_MODE" "$mode"
|
|
}
|
|
|
|
# Function to handle database export
|
|
handle_db_export() {
|
|
# Print logo and headers to stderr
|
|
printf "${YELLOW}+++ Exporting Database +++${NC}\n" >&2
|
|
printf "\n" >&2
|
|
|
|
# Check if output redirection is present
|
|
if [ -t 1 ]; then
|
|
printf "${RED}Error: Output redirection is required.${NC}\n" >&2
|
|
printf "Usage: ./install.sh db-export [--dev] > backup.sql.gz\n" >&2
|
|
printf "\n" >&2
|
|
printf "Options:\n" >&2
|
|
printf " --dev Export from development database\n" >&2
|
|
printf "\n" >&2
|
|
printf "Example:\n" >&2
|
|
printf " ./install.sh db-export > my_backup_$(date +%Y%m%d).sql.gz\n" >&2
|
|
printf " ./install.sh db-export --dev > my_dev_backup_$(date +%Y%m%d).sql.gz\n" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$DEV_DB" = true ]; then
|
|
# Check if dev containers are running
|
|
if ! docker compose -f docker-compose.dev.yml -p aliasvault-dev ps postgres-dev --quiet 2>/dev/null | grep -q .; then
|
|
printf "${RED}Error: Development database container is not running. Start it first with: ./install.sh configure-dev-db${NC}\n" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Check if postgres-dev container is healthy
|
|
if ! docker compose -f docker-compose.dev.yml -p aliasvault-dev ps postgres-dev | grep -q "healthy"; then
|
|
printf "${RED}Error: Development PostgreSQL container is not healthy. Please check the logs.${NC}\n" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf "${CYAN}> Exporting development database...${NC}\n" >&2
|
|
docker compose -f docker-compose.dev.yml -p aliasvault-dev exec postgres-dev pg_dump -U aliasvault aliasvault | gzip
|
|
else
|
|
# Production database export logic
|
|
if ! docker compose ps --quiet 2>/dev/null | grep -q .; then
|
|
printf "${RED}Error: AliasVault containers are not running. Start them first with: ./install.sh start${NC}\n" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! docker compose ps postgres | grep -q "healthy"; then
|
|
printf "${RED}Error: PostgreSQL container is not healthy. Please check the logs with: docker compose logs postgres${NC}\n" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf "${CYAN}> Exporting production database...${NC}\n" >&2
|
|
docker compose exec postgres pg_dump -U aliasvault aliasvault | gzip
|
|
fi
|
|
|
|
if [ $? -eq 0 ]; then
|
|
printf "${GREEN}> Database exported successfully.${NC}\n" >&2
|
|
else
|
|
printf "${RED}> Failed to export database.${NC}\n" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Function to handle database import
|
|
handle_db_import() {
|
|
printf "${YELLOW}+++ Importing Database +++${NC}\n"
|
|
|
|
# Check if containers are running
|
|
if [ "$DEV_DB" = true ]; then
|
|
if ! docker compose -f docker-compose.dev.yml -p aliasvault-dev ps postgres-dev | grep -q "healthy"; then
|
|
printf "${RED}Error: Development PostgreSQL container is not healthy.${NC}\n"
|
|
exit 1
|
|
fi
|
|
else
|
|
if ! docker compose ps postgres | grep -q "healthy"; then
|
|
printf "${RED}Error: PostgreSQL container is not healthy.${NC}\n"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Check if we're getting input from a pipe
|
|
if [ -t 0 ]; then
|
|
printf "${RED}Error: No input file provided${NC}\n"
|
|
printf "Usage: ./install.sh db-import [--dev] < backup.sql.gz\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Save stdin to file descriptor 3
|
|
exec 3<&0
|
|
|
|
printf "${RED}Warning: This will DELETE ALL EXISTING DATA in the "
|
|
if [ "$DEV_DB" = true ]; then
|
|
printf "development database"
|
|
else
|
|
printf "database"
|
|
fi
|
|
printf ".${NC}\n"
|
|
|
|
if [ "$FORCE_YES" != true ]; then
|
|
# Use /dev/tty to read from terminal even when stdin is redirected
|
|
if [ -t 1 ] && [ -t 2 ] && [ -e /dev/tty ]; then
|
|
# Temporarily switch stdin to tty for confirmation
|
|
exec < /dev/tty
|
|
read -p "Continue? [y/N]: " confirm
|
|
# Switch back to original stdin
|
|
exec 0<&3
|
|
if [[ ! $confirm =~ ^[Yy]$ ]]; then
|
|
exec 3<&- # Close fd 3
|
|
exit 1
|
|
fi
|
|
else
|
|
printf "${RED}Error: Cannot read confirmation from terminal. Use -y flag to bypass confirmation.${NC}\n"
|
|
exec 3<&- # Close fd 3
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ "$DEV_DB" != true ]; then
|
|
printf "${CYAN}> Stopping dependent services...${NC}\n"
|
|
if [ "$VERBOSE" = true ]; then
|
|
docker compose stop api admin task-runner smtp
|
|
else
|
|
docker compose stop api admin task-runner smtp > /dev/null 2>&1
|
|
fi
|
|
fi
|
|
|
|
printf "${CYAN}> Importing "
|
|
if [ "$DEV_DB" = true ]; then
|
|
printf "development "
|
|
fi
|
|
printf "database...${NC}\n"
|
|
|
|
# Create a temporary file to verify the gzip input
|
|
temp_file=$(mktemp)
|
|
cat <&3 > "$temp_file" # Read from fd 3 instead of stdin
|
|
exec 3<&- # Close fd 3
|
|
|
|
if ! gzip -t "$temp_file" 2>/dev/null; then
|
|
printf "${RED}Error: Input is not a valid gzip file${NC}\n"
|
|
rm "$temp_file"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$DEV_DB" = true ]; then
|
|
if [ "$VERBOSE" = true ]; then
|
|
docker compose -f docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'aliasvault' AND pid <> pg_backend_pid();" && \
|
|
docker compose -f docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "DROP DATABASE IF EXISTS aliasvault;" && \
|
|
docker compose -f docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "CREATE DATABASE aliasvault OWNER aliasvault;" && \
|
|
gunzip -c "$temp_file" | docker compose -f docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault aliasvault
|
|
else
|
|
docker compose -f docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'aliasvault' AND pid <> pg_backend_pid();" > /dev/null 2>&1 && \
|
|
docker compose -f docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "DROP DATABASE IF EXISTS aliasvault;" > /dev/null 2>&1 && \
|
|
docker compose -f docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault postgres -c "CREATE DATABASE aliasvault OWNER aliasvault;" > /dev/null 2>&1 && \
|
|
gunzip -c "$temp_file" | docker compose -f docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev psql -U aliasvault aliasvault > /dev/null 2>&1
|
|
fi
|
|
else
|
|
if [ "$VERBOSE" = true ]; then
|
|
docker compose exec -T postgres psql -U aliasvault postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'aliasvault' AND pid <> pg_backend_pid();" && \
|
|
docker compose exec -T postgres psql -U aliasvault postgres -c "DROP DATABASE IF EXISTS aliasvault;" && \
|
|
docker compose exec -T postgres psql -U aliasvault postgres -c "CREATE DATABASE aliasvault OWNER aliasvault;" && \
|
|
gunzip -c "$temp_file" | docker compose exec -T postgres psql -U aliasvault aliasvault
|
|
else
|
|
docker compose exec -T postgres psql -U aliasvault postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'aliasvault' AND pid <> pg_backend_pid();" > /dev/null 2>&1 && \
|
|
docker compose exec -T postgres psql -U aliasvault postgres -c "DROP DATABASE IF EXISTS aliasvault;" > /dev/null 2>&1 && \
|
|
docker compose exec -T postgres psql -U aliasvault postgres -c "CREATE DATABASE aliasvault OWNER aliasvault;" > /dev/null 2>&1 && \
|
|
gunzip -c "$temp_file" | docker compose exec -T postgres psql -U aliasvault aliasvault > /dev/null 2>&1
|
|
fi
|
|
fi
|
|
|
|
import_status=$?
|
|
rm "$temp_file"
|
|
|
|
if [ $import_status -eq 0 ]; then
|
|
printf "${GREEN}> Database imported successfully.${NC}\n"
|
|
if [ "$DEV_DB" != true ]; then
|
|
printf "${CYAN}> Starting services...${NC}\n"
|
|
if [ "$VERBOSE" = true ]; then
|
|
docker compose restart api admin task-runner smtp reverse-proxy
|
|
else
|
|
docker compose restart api admin task-runner smtp reverse-proxy > /dev/null 2>&1
|
|
fi
|
|
fi
|
|
else
|
|
printf "${RED}> Import failed. Please check that your backup file is valid.${NC}\n"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Function to handle hostname configuration
|
|
handle_hostname_configuration() {
|
|
printf "${YELLOW}+++ Hostname Configuration +++${NC}\n"
|
|
printf "\n"
|
|
|
|
# Check if AliasVault is installed
|
|
if [ ! -f "docker-compose.yml" ]; then
|
|
printf "${RED}Error: AliasVault must be installed first.${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
# Get current hostname
|
|
CURRENT_HOSTNAME=$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)
|
|
printf "${CYAN}Removing current hostname ${CURRENT_HOSTNAME}${NC}...\n"
|
|
printf "\n"
|
|
|
|
# Force hostname to be empty so populate_hostname will ask for a new one
|
|
sed -i.bak "/^HOSTNAME=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
|
|
|
|
# Reuse existing hostname population logic
|
|
populate_hostname
|
|
|
|
if [ $? -eq 0 ]; then
|
|
printf "New hostname: ${CYAN}${HOSTNAME}${NC}\n"
|
|
printf "\n"
|
|
printf "${MAGENTA}=========================================================${NC}\n"
|
|
else
|
|
printf "${RED}> Failed to update hostname. Please try again.${NC}\n"
|
|
printf "\n"
|
|
printf "${MAGENTA}=========================================================${NC}\n"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
main "$@"
|