Files
CloudDeploy/deploy.sh

507 lines
20 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
# Ensure script is run as root (required for apt + /etc)
if [[ "$EUID" -ne 0 ]]; then
echo "Please run as root (or use sudo)." >&2
exit 1
fi
#set script directory
scriptdir="$(dirname "$(realpath "$0")")"
# Capture the start time (Unix timestamp)
START_TIME=$(date +%s)
# recursive replace function
replace_string_recursive() {
local DIRECTORY="$1"
local OLD_STRING="$2"
local NEW_STRING="$3"
if [ ! -d "$DIRECTORY" ]; then
echo "Error: Directory '$DIRECTORY' does not exist."
return 1
fi
echo "Starting replacement of '$OLD_STRING' with '$NEW_STRING' in '$DIRECTORY'..."
find "$DIRECTORY" -type f -exec sed -i "s|$OLD_STRING|$NEW_STRING|g" {} +
if [ $? -eq 0 ]; then
echo "Replacement completed successfully."
return 0
else
echo "An error occurred during replacement."
return 1
fi
}
# example use
# replace_string_recursive "/path/to/directory" "oldstring" "newstring"
# sqlite db replace function
replace_in_sqlite_db() {
local DB_PATH="$1"
local OLD_STRING="$2"
local NEW_STRING="$3"
# Check if the database file exists
if [ ! -f "$DB_PATH" ]; then
echo "Error: Database file '$DB_PATH' does not exist."
exit 1
fi
echo "Starting replacement of '$OLD_STRING' with '$NEW_STRING' in '$DB_PATH'..."
# Create a backup of the original database
local BACKUP_PATH="${DB_PATH}.bak"
cp "$DB_PATH" "$BACKUP_PATH"
# Escape single quotes for SQL
local OLD_STRING_ESC=$(printf '%s\n' "$OLD_STRING" | sed "s/'/''/g")
local NEW_STRING_ESC=$(printf '%s\n' "$NEW_STRING" | sed "s/'/''/g")
# Get a list of all tables in the database
local TABLES
TABLES=$(sqlite3 "$DB_PATH" ".tables")
# Loop through each table
for TABLE in $TABLES; do
echo "Processing table: $TABLE"
# Escape table name if it's a reserved keyword
local ESCAPED_TABLE
if [[ "$TABLE" =~ ^(index|group|order|table|view|database|schema|trigger|transaction|commit|rollback|savepoint|release|alter|create|drop|insert|update|delete|select|from|where|join|union|intersect|except|limit|offset|order|by|group|having|as|with|replace|cast|case|when|then|else|end|and|or|not|is|null|between|in|like|glob|match|regexp|collate|exists|unique|primary|key|foreign|references|check|constraint|default|collate|asc|desc|on|using|natural|left|right|full|outer|cross|inner)$ ]]; then
ESCAPED_TABLE="\"$TABLE\""
else
ESCAPED_TABLE="$TABLE"
fi
# Get a list of all non-BLOB columns in the table
sqlite3 "$DB_PATH" "PRAGMA table_info($ESCAPED_TABLE);" 2>/dev/null | awk -F'|' '
{
if ($3 != "BLOB") {
print $2
}
}' | while read -r COLUMN; do
if [ -n "$COLUMN" ]; then
echo " Processing column: $COLUMN"
# Escape column name if it's a reserved keyword
local ESCAPED_COLUMN
if [[ "$COLUMN" =~ ^(index|group|order|table|view|database|schema|trigger|transaction|commit|rollback|savepoint|release|alter|create|drop|insert|update|delete|select|from|where|join|union|intersect|except|limit|offset|order|by|group|having|as|with|replace|cast|case|when|then|else|end|and|or|not|is|null|between|in|like|glob|match|regexp|collate|exists|unique|primary|key|foreign|references|check|constraint|default|collate|asc|desc|on|using|natural|left|right|full|outer|cross|inner)$ ]]; then
ESCAPED_COLUMN="\"$COLUMN\""
else
ESCAPED_COLUMN="$COLUMN"
fi
# Update only rows where the column contains OLD_STRING
# Run each update in its own connection to avoid locking issues
sqlite3 "$DB_PATH" "
UPDATE $ESCAPED_TABLE
SET $ESCAPED_COLUMN = replace(CAST($ESCAPED_COLUMN AS TEXT), '$OLD_STRING_ESC', '$NEW_STRING_ESC')
WHERE CAST($ESCAPED_COLUMN AS TEXT) LIKE '%$OLD_STRING_ESC%';
"
fi
done
done
echo "Replacement completed in '$DB_PATH'. Original database backed up to '$BACKUP_PATH'."
}
#example usage: replace_in_sqlite_db "database.sqlite" "Europe/Amsterdam" "UTC"
get_country_from_timezone() {
local timezone="$1"
# Extract the region and city from the timezone (e.g., "America/New_York" -> "New_York")
local city=$(echo "$timezone" | cut -d'/' -f2)
# Use a predefined mapping for common cities (fallback if no better method is found)
# This is a minimal mapping; you can expand it as needed.
local declare -A city_to_country=(
["New_York"]="United States"
["Toronto"]="Canada"
["Amsterdam"]="Netherlands"
["London"]="United Kingdom"
["Tokyo"]="Japan"
["Paris"]="France"
["Berlin"]="Germany"
["Sydney"]="Australia"
["Melbourne"]="Australia"
)
# Check if the city exists in the mapping
if [ -n "${city_to_country[$city]}" ]; then
echo "${city_to_country[$city]}"
else
# Fallback: Try to extract country from the timezone file (less reliable)
# This is a placeholder; actual implementation would require parsing timezone files.
echo "Unknown"
fi
}
oc_installapp() {
local appid="$1"
docker exec owncloud_server occ market:install $appid
echo "$appid installed for owncloud"
}
pause_if_enabled() {
if [ "$SHOULD_PAUSE" = true ]; then
read -n 1 -s -r -p "Press any key to continue..."
echo # Move to a new line after keypress
fi
}
# what this script needs to do:
#formatting variables:
green='\033[0;32m'
cyan='\033[0;36m'
cyanbold='\033[1;36m'
greenbold='\033[1;32m'
nc='\033[0m'
underline='\033[4;0m'
# request all install parameters needed from user
# ---- User input ----
read -rp "Admin email: " adminemail < /dev/tty
read -rsp "Admin password: " adminpass < /dev/tty
echo
read -rp "Timezone (e.g. Europe/Amsterdam): " timezone < /dev/tty
read -rp "Domain (e.g. example.com): " domain < /dev/tty
# Prompt the user to set the pause behavior (defaults to true)
echo -e "${cyan}pause prompts${nc} will ask you to finish configuration for each application as the script goes."
echo "disabling these means you'll have to do this after the script finishes, but allows you to run the script unattended."
read -p "Enable pause prompts? [Y/n] " -n 1 -r
echo # Move to a new line
if [[ $REPLY =~ ^[Nn]$ ]]; then
SHOULD_PAUSE=false
else
SHOULD_PAUSE=true
fi
# Public IP (tries multiple services)
publicip="$(curl -fsS https://api.ipify.org || curl -fsS https://ifconfig.me || echo "UNKNOWN")"
# Local IP (first non-loopback)
localip="$(hostname -I | awk '{print $1}')"
echo "--------------------------------------------------------"
echo -e "please double-check your ${cyan}DNS records${nc} to ensure they are set. the following dns records need to be set:"
echo ""
echo "|name |type |value "
echo "|@ |A |$publicip "
echo "|* |CNAME |@ "
echo ""
echo -e "once you've done this, press any key to ${underline}continue${nc}"
pause_if_enabled
# generate random passwords for DB hosts and other secrets
# ---- Random generators ----
rand_hex() { openssl rand -hex 24; }
rand_b64() { openssl rand -base64 32; }
ownclouddbpass="$(rand_hex)"
ownclouddbrootpass="$(rand_hex)"
convertxJWT="$(rand_hex)"
bookstackkey="$(rand_b64)" # base64 as requested
bookstackdbpass="$(rand_hex)"
bookstackdbrootpass="$(rand_hex)"
onlyofficeJWT="$(rand_hex)"
# install docker
echo "Updating apt and installing prerequisites..."
apt update
apt install -y ca-certificates curl git openssl curl gawk coreutils grep jq sqlite3 iso-codes
echo "Setting up Docker GPG key..."
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "Adding Docker apt repository..."
. /etc/os-release
cat > /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: ${UBUNTU_CODENAME:-$VERSION_CODENAME}
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF
echo "Updating apt and installing Docker..."
apt update
apt install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
echo "Docker installation complete."
docker --version
# install dockge
mkdir -p /opt/dockge
cd /opt/dockge
# Download your compose.yaml
curl "https://dockge.kuma.pet/compose.yaml?port=5001&stacksPath=%2Fopt%2Fstacks" --output compose.yaml
# write configuration to compose files
cp -r $scriptdir/stacks /opt
cp -r $scriptdir/setup /opt/stacks
chmod -R 775 /opt/stacks
cd /opt/stacks
git clone https://github.com/trantienloi2404/youtube-downloader.git downloader
#adminemail
replace_string_recursive "/opt/stacks" "?adminemail?" $adminemail
#adminpass
replace_string_recursive "/opt/stacks" "?adminpass?" $adminpass
#timezone
replace_string_recursive "/opt/stacks" "?timezone?" $timezone
#domain
replace_string_recursive "/opt/stacks" "?domain?" $domain
#publicip
replace_string_recursive "/opt/stacks" "?publicip?" $publicip
#localip
replace_string_recursive "/opt/stacks" "?localip?" $localip
#ownclouddbpass
replace_string_recursive "/opt/stacks" "?ownclouddbpass?" $ownclouddbpass
#ownclouddbrootpass
replace_string_recursive "/opt/stacks" "?ownclouddbrootpass?" $ownclouddbrootpass
#convertxJWT
replace_string_recursive "/opt/stacks" "?convertxJWT?" $convertxJWT
#bookstackkey
replace_string_recursive "/opt/stacks" "?bookstackkey?" $bookstackkey
#bookstackdbpass
replace_string_recursive "/opt/stacks" "?bookstackdbpass?" $bookstackdbpass
#bookstackdbrootpass
replace_string_recursive "/opt/stacks" "?bookstackdbrootpass?" $bookstackdbrootpass
#onlyofficeJWT
replace_string_recursive "/opt/stacks" "?onlyofficeJWT?" $onlyofficeJWT
# install mailcow to /opt/stacks/mailcow so it shows up in dockge
umask 0022
#cd /opt/stacks
#git clone https://github.com/mailcow/mailcow-dockerized mailcow
#cd /opt/stacks/mailcow
#bash ./generate_config.sh < /dev/tty
#disable stub listener
echo "DNSStubListener=no" | tee -a /etc/systemd/resolved.conf
systemctl restart systemd-resolved
sleep 5
systemctl restart docker
sleep 5
# up dockge
cd /opt/dockge
docker compose up -d
echo -e "${cyan}Dockge${nc} has been installed and launched. go to http://$localip:5001 and configure your username and password, then press any key to continue"
pause_if_enabled
# call replacements for nginx db
replace_in_sqlite_db "/opt/stacks/npm/data/database.sqlite" "<domain>" $domain
replace_in_sqlite_db "/opt/stacks/npm/data/database.sqlite" "<localip>" $localip
replace_in_sqlite_db "/opt/stacks/npm/data/database.sqlite" "<publicip>" $publicip
cd /opt/stacks/npm
docker compose up -d
echo -e "${cyan}Nginx Proxy Manager${nc} has been installed and launched. go to http://$localip:81 and configure your username and password, then press any key to continue"
pause_if_enabled
echo ""
echo -e "Multiple applications will now be installed in quick succession without continuous user prompting."
echo -e "when each application is installed, you will see an output providing you the url to verify it is online"
echo -e "do not worry if you miss it, you will be reminded later"
echo -e "you can skip verifying as the script runs, and take a short coffee break here."
echo -e "the script will continue automatically in 30 seconds"
sleep 30
cd /opt/stacks/dozzle
docker compose up -d
echo -e "${cyan}dozzle${nc} has been launched from http://dozzle.$domain, verify it is online, do not worry if the UI gives a time-out error, this is due to docker being busy while this script runs."
cd /opt/stacks/convertx
docker compose up -d
echo -e "${cyan}convertx${nc} has been launched from http://convert.$domain, verify it is online"
cd /opt/stacks/it-tools
docker compose up -d
echo -e "${cyan}it-tools${nc} has been launched from http://tools.$domain, verify it is online"
cd /opt/stacks/onlyoffice
docker compose up -d
echo -e "${cyan}onlyoffice${nc} has been launched from http://office.$domain, verify it is online"
#owncloud
# todo: use OC config commands to install addons and configure onlyoffice
cd /opt/stacks/owncloud
docker compose up -d
echo "awaiting owncloud boot"
sleep 120
oc_installapp bookmarks
oc_installapp calendar
oc_installapp camerarawpreviews
oc_installapp checksum
oc_installapp contacts
oc_installapp drawio
oc_installapp extract
oc_installapp files_3dviewer
oc_installapp files_clipboard
oc_installapp files_textviewer
oc_installapp gallery
oc_installapp metadata
oc_installapp music
oc_installapp notes
oc_installapp onlyoffice
oc_installapp tasks
docker exec owncloud_server occ config:app:set onlyoffice DocumentServerUrl --value="https://office.$domain/"
docker exec owncloud_server occ config:app:set onlyoffice jwt_secret --value="$onlyofficeJWT"
echo -e "${cyan}owncloud${nc} has been launched from http://cloud.$domain, check if it is online and working, it may take a bit to launch. 502 error = still starting"
cd /opt/stacks/site
docker compose up -d
echo -e "${cyan}site${nc} has been launched from http://www.$domain, verify it is online"
cd /opt/stacks/vaultwarden
docker compose up -d
echo -e "${cyan}vaultwarden${nc} has been launched from http://vault.$domain, verify it is online"
#pihole
cd /opt/stacks/pihole
docker compose up -d
echo -e "${cyan}pihole${nc} has been launched from http://dns.$domain, to use pihole as your DNS provider, set your DNS to $localip in your router for DHCP and on your device for any device with a static ip. Currently, the DNS is configured to use the joindns4.eu DNS, which *also* has built-in adblocking."
cd /opt/stacks/wireguard
docker compose up -d
echo -e "${cyan}wireguard${nc} has been launched from http://vpn.$domain, verify it is online"
cd /opt/stacks/downloader
docker compose up -d
echo -e "${cyan}downloader${nc} has been launched from http://download.$domain, verify it is online"
echo ""
echo -e "recap of all available services up to now:"
echo -e "${cyan}owncloud${nc} has been launched from http://cloud.$domain, check if it is online and working, it may take a bit to launch. feel free to add marketplace plugins"
echo -e "${cyan}vaultwarden${nc} has been launched from http://vault.$domain, verify it is online"
echo -e "${cyan}site${nc} has been launched from http://www.$domain, verify it is online"
echo -e "${cyan}onlyoffice${nc} has been launched from http://tools.$domain, verify it is online"
echo -e "${cyan}it-tools${nc} has been launched from http://tools.$domain, verify it is online"
echo -e "${cyan}wireguard${nc} has been launched from http://vpn.$domain, verify it is online"
echo -e "${cyan}convertx${nc} has been launched from http://convert.$domain, verify it is online"
echo -e "${cyan}downloader${nc} has been launched from http://download.$domain, verify it is online"
echo -e "${cyan}dozzle${nc} has been launched from http://dozzle.$domain, verify it is online, do not worry if the UI gives a time-out error, this is due to docker being busy while this script runs."
echo -e "${cyan}Nginx Proxy Manager${nc} has been installed, configured and launched from http://proxy.$domain"
echo -e "${cyan}Dockge${nc} has been installed, configured and launched from http://docker.$domain"
echo -e "${cyan}pihole${nc} has been launched from http://dns.$domain, to use pihole as your DNS provider, set your DNS to $localip in your router for DHCP and on your device for any device with a static ip. Currently, the DNS is configured to use the joindns4.eu DNS, which *also* has built-in adblocking."
echo ""
echo "you may have to go to settings > additional in owncloud and click "save" for the onlyoffice server settings."
#bookstack
cd /opt/stacks/bookstack
docker compose up -d
echo -e "${cyan}bookstack${nc} has been launched from http://docs.$domain, verify it is online (this may take a bit) and log in with email 'admin@admin.com' and password 'password', then reset this account to use $adminemail and your password. afterwards, press any key to continue"
pause_if_enabled
#browser
cd /opt/stacks/browser
docker compose up -d
echo -e "${cyan}filebrowser${nc} has been launched from http://browser.$domain, head to dockge (http://docker.$domain), open the filebrowser stack and check the logs for the initial admin password. make sure to change this in filebrowser's config, then press any key to continue"
pause_if_enabled
#jellyfin
cd /opt/stacks/jellyfin
docker compose up -d
mkdir /opt/stacks/jellyfin/media/shows
mkdir /opt/stacks/jellyfin/media/movies
echo -e "${cyan}jellyfin${nc} has launched from http://video.$domain, perform the initial setup and press any key to continue"
pause_if_enabled
#uptimekuma
cd /opt/stacks/kuma
docker compose up -d
echo -e "${cyan}uptime kuma${nc} has been launched from http://status.$domain, perform the initial setup, choosing embedded mariaDB, then press any key to continue"
pause_if_enabled
#dashboard
replace_in_sqlite_db "/opt/stacks/dashboard/config/www/app.sqlite" "<domain>" $domain
cd /opt/stacks/dashboard
docker compose up -d
echo -e "${cyan}dashboard${nc} has been launched from http://dash.$domain, verify it is online, check that its entries work and press any button to continue"
pause_if_enabled
cat >/opt/stacks/setup/npmcertlist.txt <<EOF
browser.$domain
cloud.$domain
convert.$domain
dash.$domain
dns.$domain
docker.$domain
docs.$domain
download.$domain
dozzle.$domain
office.$domain
proxy.$domain
status.$domain
tools.$domain
vault.$domain
video.$domain
vpn.$domain
www.$domain
test.$domain
temp.$domain
$domain
llm.$domain
mail.$domain
private.$domain
site.$domain
Add any other subdomains you want to use.
EOF
echo ""
echo ""
echo ""
echo -e "you can now access the dockge GUI interface at $localip:5001 and the proxy interface at $localip:81 once you start it from Dockge. be sure to forward ports 80, 443 and 51820 to $localip in your router settings"
echo -e "to use ${green}pihole${nc} as your DNS provider, set your DNS to $localip in your router for DHCP and on your device for any device with a static ip. Currently, the DNS is configured to use the joindns4.eu DNS, which also has built-in adblocking."
echo ""
echo -e "reminder, configure the following services if you have not done so already:"
echo "(this is only needed if you did not enable pause prompts or did not follow the pause prompts)"
echo -e "${cyan}uptime kuma${nc} has been launched from http://status.$domain, perform the initial setup, choosing embedded mariaDB"
echo -e "${cyan}jellyfin${nc} has launched from http://video.$domain, perform the initial setup"
echo -e "${cyan}filebrowser${nc} has been launched from http://browser.$domain, head to dockge (http://docker.$domain), open the filebrowser stack and check the logs for the initial admin password. make sure to change this in filebrowser's config"
echo -e "${cyan}bookstack${nc} has been launched from http://docs.$domain, log in with email 'admin@admin.com' and password 'password', then reset this account to use $adminemail and your password."
echo -e "${cyan}Nginx Proxy Manager${nc} has been installed and launched. go to http://proxy.$domain and configure your username and password"
echo -e "${cyan}Dockge${nc} has been installed and launched. go to http://docker.$domain and configure your username and password"
echo ""
echo "you may have to go to settings > additional in owncloud and click "save" for the onlyoffice server settings."
echo ""
echo "-------------------------------"
echo -e "the following set up steps must still be completed, you can find them in the "setup" folder at http://browser.$domain, this is required for vaultwarden to work"
echo -e "${cyan}Nginx Proxy Manager${nc}: lets-encrypt certificate needs to be set up"
echo -e "${cyan}Uptime Kuma${nc}: Monitoring and status page configuration"
# Capture the end time (Unix timestamp)
END_TIME=$(date +%s)
# Calculate the elapsed time
ELAPSED_TIME=$((END_TIME - START_TIME))
echo "Script took $ELAPSED_TIME seconds to complete."
echo "please reboot before further use"