@bikegremlin said:
Sums it up well - though my eye starts twitching when I hear "Bluehost" - LOL
Who here, on the low end sites, have never used hosts like bluehost, 25mb host, 000webhosting and all those crappy sites?
Yup.
bikegremlin.com was first registered with 1-and-1 for $1 for the first year (then I read the terms, saw the renewal price, and moved it in time to Namecheap, and a few years after that to Porkbun).
Hosting was first with a suspiciously cheap shared hosting provider (unlimited all LOL), that got sold to Justhost (which was owned by the Bluehost umbrella company). And it took a while to figure out how the hosting is not very good.
It took me a while to realize that time is the most limited resource and that I should pay a bit more to not worry about hosting, and spend the time (and effort) on writing articles.
Similar goes for WordPress itself - while it is free and open source, it has its costs in terms of both time and money, depending on what you're using it for.
To this discussion, I spent a couple of hours with chatGPT and grok last night, while visiting a family member at an assisted living facility. (Story for another time)...
I now have hopefully working scripts for installing ClassicPress (or WordPress if you prefer) on Debian, using Caddy/ Lighttpd and Openlitespeed as the "control" for LES Budget friendly specs.
Idea came up when I asked chatGPT "why do installer scripts like Webinoly only support Ubuntu? Why can't i get something with Debian 12 (or 13) ? Pasting the "optimized version" of the conversation and the script below. As always, comments and feedback appreciated. I might do an actual testing later in the day.
What this script is supposed to do:
Support Interactive or Defaults mode.
Support Dry-run mode (simulate everything; do not change the system).
Run pre-flight checks (OS, RAM, CPU, free disk) and warn if low — but allow continuing.
Prompt for domain, admin user/email/password (defaults if Enter), install path (default /var/www/classicpress), DB prefix (default cp1_).
Install only required packages (skip if already installed) and show progress messages.
Install WP-CLI, download ClassicPress, set permissions, generate wp-config.php via WP-CLI, run the ClassicPress install non-interactively.
Install optional plugins/themes via WP-CLI (defaults pre-set but editable).
Configure SSL:
Caddy: automatic (Caddy handles TLS once DNS points).
OLS: uses certbot to obtain cert (script checks DNS).
Print final summary (login URL, admin creds, DB creds, paths).
Plenty of inline comments and terminal output so you can follow what is happening.
Note: these scripts attempt to be conservative and safe. Always review before running on a production machine. Run first in --dry-run to verify.
if (( TOTAL_RAM_MB < MIN_RAM_MB )); then
warn "Detected RAM: ${TOTAL_RAM_MB} MB — recommended >= ${MIN_RAM_MB} MB."
if ! confirm "Proceed anyway?"; then
log "User aborted due to low RAM."
exit 1
fi
fi
if (( FREE_DISK_GB < MIN_DISK_GB )); then
warn "Detected free disk: ${FREE_DISK_GB} GB — recommended >= ${MIN_DISK_GB} GB."
if ! confirm "Proceed anyway?"; then
log "User aborted due to low disk."
exit 1
fi
fi
if [[ "$DB_ENGINE" == "mariadb" ]]; then
if ! command_exists mysql; then
log "Installing MariaDB..."
apt install -y mariadb-server mariadb-client
log "Securing MariaDB (interactive may appear)..."
# attempt non-interactive secure install: set root auth to unix_socket (Debian default)
# create DB and user later
else
log "MariaDB present."
fi
else
log "SQLite selected; PHP sqlite extension will be used."
apt install -y "php${PHP_VERSION}-sqlite3"
fi
if [[ "$DB_ENGINE" == "mariadb" ]]; then
log "Creating database and user..."
mysql -e "CREATE DATABASE IF NOT EXISTS `${DB_NAME}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -e "CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';"
mysql -e "GRANT ALL PRIVILEGES ON `${DB_NAME}`.* TO '${DB_USER}'@'localhost';"
mysql -e "FLUSH PRIVILEGES;"
log "Database ${DB_NAME} and user ${DB_USER} created."
fi
log "Installing default plugins/themes if requested..."
if $INSTALL_PLUGINS; then
for pl in "${DEFAULT_PLUGINS[@]}"; do
wp plugin install "$pl" --activate --path="$WEBROOT" --allow-root || warn "Failed to install plugin $pl"
done
fi
for th in "${DEFAULT_THEMES[@]}"; do
wp theme install "$th" --activate --path="$WEBROOT" --allow-root || warn "Failed to install theme $th"
done
-------------------------
SSL (Caddy auto handles TLS) and reminder
-------------------------
log "Caddy will manage TLS automatically when DNS for ${DOMAIN} points to this server's IP."
log "If DNS is not yet pointed, TLS will be obtained automatically when it is."
The usual questions came to mind- why not github repo/ Why not use pastebin, etc... before I posted the script here. Anyways, formatting in vanilla drove me nuts.
Since much of the code was AI tested/ suggested, not sure about license either. Lets' call it a bored middle aged man project license.
(gosh, am I really that old? OR MAybe.. use it ONLY if you are that old..lol)
Sorry @bikegremlin, did not meant to hijack thread.
To this discussion, I spent a couple of hours with chatGPT and grok last night, while visiting a family member at an assisted living facility. (Story for another time)...
I now have hopefully working scripts for installing ClassicPress (or WordPress if you prefer) on Debian, using Caddy/ Lighttpd and Openlitespeed as the "control" for LES Budget friendly specs.
Idea came up when I asked chatGPT "why do installer scripts like Webinoly only support Ubuntu? Why can't i get something with Debian 12 (or 13) ? Pasting the "optimized version" of the conversation and the script below. As always, comments and feedback appreciated. I might do an actual testing later in the day.
What this script is supposed to do:
Support Interactive or Defaults mode.
Support Dry-run mode (simulate everything; do not change the system).
Run pre-flight checks (OS, RAM, CPU, free disk) and warn if low — but allow continuing.
Prompt for domain, admin user/email/password (defaults if Enter), install path (default /var/www/classicpress), DB prefix (default cp1_).
Install only required packages (skip if already installed) and show progress messages.
Install WP-CLI, download ClassicPress, set permissions, generate wp-config.php via WP-CLI, run the ClassicPress install non-interactively.
Install optional plugins/themes via WP-CLI (defaults pre-set but editable).
Configure SSL:
Caddy: automatic (Caddy handles TLS once DNS points).
OLS: uses certbot to obtain cert (script checks DNS).
Print final summary (login URL, admin creds, DB creds, paths).
Plenty of inline comments and terminal output so you can follow what is happening.
Note: these scripts attempt to be conservative and safe. Always review before running on a production machine. Run first in --dry-run to verify.
Save as install_cp_caddy.sh, then
$ chmod +x install_cp_caddy.sh, and finally, run with
$ sudo ./install_cp_caddy.sh.
>
> ####!/usr/bin/env bash
>
> set -euo pipefail
> IFS=$'\n\t'
>
> # -------------------------
>
> ##### Defaults & Configurable Vars
>
> # -------------------------
>
> DEFAULT_DOMAIN="example.com"
> DEFAULT_ADMIN_USER="admin"
> DEFAULT_ADMIN_EMAIL="[email protected]"
> DEFAULT_INSTALL_FOLDER="classicpress"
> DEFAULT_WEBROOT_BASE="/var/www"
> DEFAULT_DB_PREFIX="cp1"
> PHP_VERSION="8.3"
> MIN_DISK_GB=5
> MIN_RAM_MB=1024
>
> # Default plugin/theme lists (empty by default; edit if you want auto-install)
> DEFAULT_PLUGINS=("classic-editor") # Example; add slugs you want
> DEFAULT_THEMES=()
>
> # -------------------------
>
> ###### Helper functions
>
> # -------------------------
> log() { echo -e "\e[1;34m[ INFO ]\e[0m $*"; }
> warn() { echo -e "\e[1;33m[ WARN ]\e[0m $*"; }
> err() { echo -e "\e[1;31m[ ERROR ]\e[0m $*"; }
>
> confirm() {
> # prompt with default no
> read -p "$1 (y/N): " ans
> [[ "$ans" == "y" || "$ans" == "Y" ]]
> }
>
> command_exists() { command -v "$1" >/dev/null 2>&1; }
>
> # -------------------------
>
> ##### CLI flags: --dry-run
>
> # -------------------------
>
> DRY_RUN=false
> while [[ $# -gt 0 ]]; do
> case $1 in
> --dry-run) DRY_RUN=true; shift ;;
> --help) echo "Usage: sudo $0 [--dry-run]"; exit 0 ;;
> *) echo "Unknown arg: $1"; exit 1 ;;
> esac
> done
>
> # -------------------------
>
> ##### Mode selection: interactive or defaults
>
> # -------------------------
>
> echo "ClassicPress + Caddy Installer"
> echo "Interactive or defaults?"
> echo " 1) Interactive (step-by-step prompts)"
> echo " 2) Defaults (use sensible defaults, minimal prompts)"
> read -p "Choose mode [1-2, default 1]: " MODE_CHOICE
> MODE_CHOICE=${MODE_CHOICE:-1}
>
> INTERACTIVE=true
> if [[ "$MODE_CHOICE" == "2" ]]; then
> INTERACTIVE=false
> fi
>
> # Dry-run message
> if $DRY_RUN; then
> log "DRY RUN mode enabled — no changes will be made."
> fi
>
> # -------------------------
>
> ##### Pre-flight checks
>
> # -------------------------
>
> log "Running pre-flight checks..."
>
> #### OS check (Debian)
> if [ -f /etc/debian_version ]; then
> OS_NAME="Debian"
> OS_VER=$(cat /etc/debian_version)
> log "Detected OS: $OS_NAME $OS_VER"
> else
> err "This installer is intended for Debian. Aborting."
> exit 1
> fi
>
> ##### Hardware checks (warn, not abort)
> TOTAL_RAM_MB=$(free -m | awk '/^Mem:/{print $2}')
> FREE_DISK_GB=$(df -BG --output=avail / | tail -1 | tr -dc '0-9')
> CPU_CORES=$(nproc)
>
> if (( TOTAL_RAM_MB < MIN_RAM_MB )); then
> warn "Detected RAM: ${TOTAL_RAM_MB} MB — recommended >= ${MIN_RAM_MB} MB."
> if ! confirm "Proceed anyway?"; then
> log "User aborted due to low RAM."
> exit 1
> fi
> fi
>
> if (( FREE_DISK_GB < MIN_DISK_GB )); then
> warn "Detected free disk: ${FREE_DISK_GB} GB — recommended >= ${MIN_DISK_GB} GB."
> if ! confirm "Proceed anyway?"; then
> log "User aborted due to low disk."
> exit 1
> fi
> fi
>
> log "CPU cores: $CPU_CORES; RAM: ${TOTAL_RAM_MB} MB; Free disk: ${FREE_DISK_GB} GB"
>
> # -------------------------
>
> ##### Interactive prompts (or defaults)
>
> # -------------------------
>
> if $INTERACTIVE; then
> read -p "Enter domain (FQDN) [${DEFAULT_DOMAIN}]: " DOMAIN
> DOMAIN=${DOMAIN:-$DEFAULT_DOMAIN}
> read -p "Install folder name [${DEFAULT_INSTALL_FOLDER}]: " FOLDER_NAME
> FOLDER_NAME=${FOLDER_NAME:-$DEFAULT_INSTALL_FOLDER}
> read -p "Admin username [${DEFAULT_ADMIN_USER}]: " ADMIN_USER
> ADMIN_USER=${ADMIN_USER:-$DEFAULT_ADMIN_USER}
> read -p "Admin email [${DEFAULT_ADMIN_EMAIL}]: " ADMIN_EMAIL
> ADMIN_EMAIL=${ADMIN_EMAIL:-$DEFAULT_ADMIN_EMAIL}
> read -p "Admin password (leave blank to auto-generate): " ADMIN_PASS
> if [[ -z "$ADMIN_PASS" ]]; then
> ADMIN_PASS=$(openssl rand -base64 12)
> log "Generated admin password: $ADMIN_PASS"
> fi
> read -p "DB prefix [${DEFAULT_DB_PREFIX}]: " DB_PREFIX
> DB_PREFIX=${DB_PREFIX:-$DEFAULT_DB_PREFIX}
> read -p "Use MariaDB (y) or SQLite (n)? [y]: " DB_CHOICE
> DB_CHOICE=${DB_CHOICE:-y}
> if [[ "$DB_CHOICE" == "y" || "$DB_CHOICE" == "Y" ]]; then
> DB_ENGINE="mariadb"
> else
> DB_ENGINE="sqlite"
> fi
> read -p "Install default plugins? (classic-editor) [y/N]: " INSTALL_PLUGINS_ANS
> INSTALL_PLUGINS=false
> if [[ "$INSTALL_PLUGINS_ANS" == "y" || "$INSTALL_PLUGINS_ANS" == "Y" ]]; then
> INSTALL_PLUGINS=true
> fi
> else
> # defaults
> DOMAIN=$DEFAULT_DOMAIN
> FOLDER_NAME=$DEFAULT_INSTALL_FOLDER
> ADMIN_USER=$DEFAULT_ADMIN_USER
> ADMIN_EMAIL=$DEFAULT_ADMIN_EMAIL
> ADMIN_PASS=$(openssl rand -base64 12)
> DB_PREFIX=$DEFAULT_DB_PREFIX
> DB_ENGINE="mariadb"
> INSTALL_PLUGINS=false
> fi
>
> WEBROOT="${DEFAULT_WEBROOT_BASE}/${FOLDER_NAME}"
> DB_NAME="${DB_PREFIX}db"
> DB_USER="${DB_PREFIX}user"
> DB_PASS=$(openssl rand -base64 16)
>
> log "Summary of choices:"
> echo " Domain: $DOMAIN"
> echo " Webroot: $WEBROOT"
> echo " Admin: $ADMIN_USER / $ADMIN_EMAIL"
> echo " DB engine: $DB_ENGINE"
> echo " DB name/user: $DB_NAME / $DB_USER"
> echo " Install plugins/themes: $INSTALL_PLUGINS"
> echo " PHP version target: $PHP_VERSION"
>
> if $DRY_RUN; then
> echo
> log "DRY RUN summary complete. No changes will be made."
> exit 0
> fi
>
> # -------------------------
>
> ##### Install prerequisites (only what is missing)
>
> # -------------------------
>
> log "Installing prerequisites (only missing packages)..."
> ##### Add Sury repo for PHP 8.3 if needed
> if ! command_exists php || [[ "$(php -r 'echo PHP_VERSION;')" != "$PHP_VERSION"* ]]; then
> log "Installing PHP ${PHP_VERSION} and extensions..."
> apt update
> apt install -y ca-certificates apt-transport-https lsb-release gnupg
> echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/sury-php.list
> wget -qO - https://packages.sury.org/php/apt.gpg | gpg --dearmor -o /usr/share/keyrings/sury-php.gpg
> apt update
> apt install -y "php${PHP_VERSION}" "php${PHP_VERSION}-fpm" "php${PHP_VERSION}-mysql" \
> "php${PHP_VERSION}-xml" "php${PHP_VERSION}-mbstring" "php${PHP_VERSION}-curl" \
> "php${PHP_VERSION}-zip" "php${PHP_VERSION}-gd" unzip wget curl
> else
> log "PHP ${PHP_VERSION} appears installed."
> fi
>
> ##### Caddy installation (official)
> if ! command_exists caddy; then
> log "Installing Caddy..."
> apt install -y debian-keyring debian-archive-keyring apt-transport-https
> curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable.gpg
> curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
> apt update
> apt install -y caddy
> else
> log "Caddy already installed."
> fi
>
> #### Database
> if [[ "$DB_ENGINE" == "mariadb" ]]; then
> if ! command_exists mysql; then
> log "Installing MariaDB..."
> apt install -y mariadb-server mariadb-client
> log "Securing MariaDB (interactive may appear)..."
> # attempt non-interactive secure install: set root auth to unix_socket (Debian default)
> # create DB and user later
> else
> log "MariaDB present."
> fi
> else
> log "SQLite selected; PHP sqlite extension will be used."
> apt install -y "php${PHP_VERSION}-sqlite3"
> fi
>
> #### WP-CLI
> if ! command_exists wp; then
> log "Installing wp-cli..."
> curl -sO https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
> php wp-cli.phar --info >/dev/null 2>&1 || true
> chmod +x wp-cli.phar
> mv wp-cli.phar /usr/local/bin/wp
> else
> log "wp-cli present."
> fi
>
> # -------------------------
>
> ##### Database setup
>
> # -------------------------
>
> if [[ "$DB_ENGINE" == "mariadb" ]]; then
> log "Creating database and user..."
> mysql -e "CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
> mysql -e "CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';"
> mysql -e "GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_USER}'@'localhost';"
> mysql -e "FLUSH PRIVILEGES;"
> log "Database ${DB_NAME} and user ${DB_USER} created."
> fi
>
> # -------------------------
>
> ##### Download and place ClassicPress
>
> # -------------------------
>
> log "Downloading ClassicPress..."
> mkdir -p "$WEBROOT"
> cd /tmp
> CP_RELEASE_URL="https://github.com/ClassicPress/ClassicPress-release/archive/refs/tags/1.6.3.tar.gz"
> wget -qO cp.tar.gz "$CP_RELEASE_URL"
> tar -xzf cp.tar.gz
> shopt -s dotglob
> mv ClassicPress-release-*/* "$WEBROOT"/
> rm -rf /tmp/ClassicPress-release-* cp.tar.gz
> shopt -u dotglob
>
> log "Setting ownership and permissions..."
> chown -R www-data:www-data "$WEBROOT"
> find "$WEBROOT" -type d -exec chmod 755 {} \;
> find "$WEBROOT" -type f -exec chmod 644 {} \;
>
> # -------------------------
>
> ##### Caddy config
>
> # -------------------------
>
> log "Creating Caddyfile for ${DOMAIN}..."
> cat > /etc/caddy/Caddyfile <<EOF
> ${DOMAIN} {
> root * ${WEBROOT}
> encode gzip zstd
> php_fastcgi unix//run/php/php${PHP_VERSION}-fpm.sock
> file_server
> }
> EOF
>
> log "Reloading Caddy..."
> systemctl reload caddy || systemctl restart caddy
>
> # -------------------------
>
> ##### ClassicPress install via WP-CLI
>
> # -------------------------
>
> log "Preparing wp-config.php and running site install..."
> # Create wp-config.php via wp-cli using DB engine chosen
> if [[ "$DB_ENGINE" == "mariadb" ]]; then
> wp config create --path="$WEBROOT" --dbname="$DB_NAME" --dbuser="$DB_USER" --dbpass="$DB_PASS" --dbhost="localhost" --dbprefix="${DB_PREFIX}_" --skip-check --allow-root
> else
> # use SQLite db.php drop-in approach
> # ensure php sqlite extension present
> WP_DB_DIR="$WEBROOT/wp-content/database"
> mkdir -p "$WP_DB_DIR"
> chown -R www-data:www-data "$WP_DB_DIR"
> # download db.php drop-in if available (wp-sqlite-db)
> wget -qO "$WEBROOT/wp-content/db.php" "https://raw.githubusercontent.com/aaemnnosttv/wp-sqlite-db/v1.3.2/src/db.php"
> wp config create --path="$WEBROOT" --skip-check --allow-root
> fi
>
> #### Install the site
> wp core install --path="$WEBROOT" --url="https://${DOMAIN}" --title="ClassicPress Site" --admin_user="${ADMIN_USER}" --admin_password="${ADMIN_PASS}" --admin_email="${ADMIN_EMAIL}" --skip-email --allow-root
>
> log "Installing default plugins/themes if requested..."
> if $INSTALL_PLUGINS; then
> for pl in "${DEFAULT_PLUGINS[@]}"; do
> wp plugin install "$pl" --activate --path="$WEBROOT" --allow-root || warn "Failed to install plugin $pl"
> done
> fi
> for th in "${DEFAULT_THEMES[@]}"; do
> wp theme install "$th" --activate --path="$WEBROOT" --allow-root || warn "Failed to install theme $th"
> done
>
> # -------------------------
> ##### SSL (Caddy auto handles TLS) and reminder
>
> # -------------------------
>
> log "Caddy will manage TLS automatically when DNS for ${DOMAIN} points to this server's IP."
> log "If DNS is not yet pointed, TLS will be obtained automatically when it is."
>
> # -------------------------
>
> ##### Final summary
>
> # -------------------------
>
> echo
>
> echo "===================================================="
>
> echo " ClassicPress install complete (Caddy mode)"
> echo " Domain: https://${DOMAIN}"
> echo " Admin user: ${ADMIN_USER}"
> echo " Admin password: ${ADMIN_PASS}"
> if [[ "$DB_ENGINE" == "mariadb" ]]; then
> echo " DB: ${DB_NAME} / ${DB_USER} / ${DB_PASS}"
> else
> echo " DB: SQLite (file stored in wp-content/database by default)"
> fi
> echo " Webroot: ${WEBROOT}"
> echo " Caddyfile: /etc/caddy/Caddyfile"
> echo " WP-CLI: /usr/local/bin/wp"
>
> echo "===================================================="
>
>
Note
** (or not - if you ask my wife)
The usual questions came to mind- why not github repo/ Why not use pastebin, etc... before I posted the script here. Anyways, formatting in vanilla drove me nuts.
Since much of the code was AI tested/ suggested, not sure about license either. Lets' call it a bored middle aged man project license.
(gosh, am I really that old? OR MAybe.. use it ONLY if you are that old..lol)
Sorry @bikegremlin, did not meant to hijack thread.
Pretty cool - though it would make more sense to move it to a separate thread for easier finding in the future (and for any updates and feedback).
On serious note, you may kindly move to cest pit. If we do separate thread, Accidentaly we might trigger a storm of AI bot “guided” scripts and make a mess out of les. Don’t want that now, do we ?
On serious note, you may kindly move to cest pit. If we do separate thread, Accidentaly we might trigger a storm of AI bot “guided” scripts and make a mess out of les. Don’t want that now, do we ?
Cheers
IMO it would make more sense to move it to a normal theme under the WordPress section - and act differently if we do run into any problems.
On serious note, you may kindly move to cest pit. If we do separate thread, Accidentaly we might trigger a storm of AI bot “guided” scripts and make a mess out of les. Don’t want that now, do we ?
Cheers
What? What's wrong with AI generated scripts (other then it not working)?
I mean we can even have a AI generated script to post AI generated scripts to LES and have other AI generated scripts to reply and fix the AI generated scripts! Viva la AI generated scripts!
I've managed WordPress websites with templates and plugins I built myself, handling spikes of 2k concurrent users on $3/month VPSes.
Later, another company hired me to do something similar for more than 10 million unique visitors per month, and it cost them around $2k/month including a larger server.
I've managed WordPress websites with templates and plugins I built myself, handling spikes of 2k concurrent users on $3/month VPSes.
Later, another company hired me to do something similar for more than 10 million unique visitors per month, and it cost them around $2k/month including a larger server.
1998Euros profit - 2 euros infrastrcture
I believe in good luck. Harder that I work ,luckier i get.
I've managed WordPress websites with templates and plugins I built myself, handling spikes of 2k concurrent users on $3/month VPSes.
Later, another company hired me to do something similar for more than 10 million unique visitors per month, and it cost them around $2k/month including a larger server.
1998Euros profit - 2 euros infrastrcture
Actually I moved everything to bare metal to have peace of mind. I slept better after that.
I've managed WordPress websites with templates and plugins I built myself, handling spikes of 2k concurrent users on $3/month VPSes.
Later, another company hired me to do something similar for more than 10 million unique visitors per month, and it cost them around $2k/month including a larger server.
1998Euros profit - 2 euros infrastrcture
Actually I moved everything to bare metal to have peace of mind. I slept better after that.
Baremetal never gives me piece of mind...
A dedicated server running my own VMs on proxmos + onsite snapshot backup + offsite snapshot backup + offsite code backup is what gives me piece of mind. And yes, I know it's overkill, but this is what helps me sleep better at night so it's all worth it
@imok said:
Yes, that’s what I meant to say. I mixed up the words. I meant a dedicated server (plus a VPS for backups). I always run Proxmox on my dedis.
And a few idlers used to monitor uptime, right?
Ya, proxmox is awesome. I dunno what to do if proxmox ever removes their community version...
Comments
Yup.
bikegremlin.com was first registered with 1-and-1 for $1 for the first year (then I read the terms, saw the renewal price, and moved it in time to Namecheap, and a few years after that to Porkbun).
Hosting was first with a suspiciously cheap shared hosting provider (unlimited all LOL), that got sold to Justhost (which was owned by the Bluehost umbrella company). And it took a while to figure out how the hosting is not very good.
It took me a while to realize that time is the most limited resource and that I should pay a bit more to not worry about hosting, and spend the time (and effort) on writing articles.
Similar goes for WordPress itself - while it is free and open source, it has its costs in terms of both time and money, depending on what you're using it for.
🔧 BikeGremlin guides & resources
Thanks**
To this discussion, I spent a couple of hours with chatGPT and grok last night, while visiting a family member at an assisted living facility. (Story for another time)...
I now have hopefully working scripts for installing ClassicPress (or WordPress if you prefer) on Debian, using Caddy/ Lighttpd and Openlitespeed as the "control" for LES Budget friendly specs.
Idea came up when I asked chatGPT "why do installer scripts like Webinoly only support Ubuntu? Why can't i get something with Debian 12 (or 13) ? Pasting the "optimized version" of the conversation and the script below. As always, comments and feedback appreciated. I might do an actual testing later in the day.
What this script is supposed to do:
Support Interactive or Defaults mode.
Support Dry-run mode (simulate everything; do not change the system).
Run pre-flight checks (OS, RAM, CPU, free disk) and warn if low — but allow continuing.
Prompt for domain, admin user/email/password (defaults if Enter), install path (default /var/www/classicpress), DB prefix (default cp1_).
Install only required packages (skip if already installed) and show progress messages.
Install WP-CLI, download ClassicPress, set permissions, generate wp-config.php via WP-CLI, run the ClassicPress install non-interactively.
Install optional plugins/themes via WP-CLI (defaults pre-set but editable).
Configure SSL:
Caddy: automatic (Caddy handles TLS once DNS points).
OLS: uses certbot to obtain cert (script checks DNS).
Print final summary (login URL, admin creds, DB creds, paths).
Plenty of inline comments and terminal output so you can follow what is happening.
Note: these scripts attempt to be conservative and safe. Always review before running on a production machine. Run first in --dry-run to verify.
The Script itself: simplenote link - http://simp.ly/p/KMkQVp
Or read below at your own scrolling risk:
Example: Classic Press + Caddy
Save as install_cp_caddy.sh, then
$ chmod +x install_cp_caddy.sh, and finally, run with
$ sudo ./install_cp_caddy.sh.
<
pre>
!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
-------------------------
Defaults & Configurable Vars
-------------------------
DEFAULT_DOMAIN="example.com"
DEFAULT_ADMIN_USER="admin"
DEFAULT_ADMIN_EMAIL="[email protected]"
DEFAULT_INSTALL_FOLDER="classicpress"
DEFAULT_WEBROOT_BASE="/var/www"
DEFAULT_DB_PREFIX="cp1"
PHP_VERSION="8.3"
MIN_DISK_GB=5
MIN_RAM_MB=1024
Default plugin/theme lists (empty by default; edit if you want auto-install)
DEFAULT_PLUGINS=("classic-editor") # Example; add slugs you want
DEFAULT_THEMES=()
-------------------------
Helper functions
-------------------------
log() { echo -e "\e[1;34m[ INFO ]\e[0m $"; }
warn() { echo -e "\e[1;33m[ WARN ]\e[0m $"; }
err() { echo -e "\e[1;31m[ ERROR ]\e[0m $*"; }
confirm() {
# prompt with default no
read -p "$1 (y/N): " ans
[[ "$ans" == "y" || "$ans" == "Y" ]]
}
command_exists() { command -v "$1" >/dev/null 2>&1; }
-------------------------
CLI flags: --dry-run
-------------------------
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run) DRY_RUN=true; shift ;;
--help) echo "Usage: sudo $0 [--dry-run]"; exit 0 ;;
*) echo "Unknown arg: $1"; exit 1 ;;
esac
done
-------------------------
Mode selection: interactive or defaults
-------------------------
echo "ClassicPress + Caddy Installer"
echo "Interactive or defaults?"
echo " 1) Interactive (step-by-step prompts)"
echo " 2) Defaults (use sensible defaults, minimal prompts)"
read -p "Choose mode [1-2, default 1]: " MODE_CHOICE
MODE_CHOICE=${MODE_CHOICE:-1}
INTERACTIVE=true
if [[ "$MODE_CHOICE" == "2" ]]; then
INTERACTIVE=false
fi
Dry-run message
if $DRY_RUN; then
log "DRY RUN mode enabled — no changes will be made."
fi
-------------------------
Pre-flight checks
-------------------------
log "Running pre-flight checks..."
OS check (Debian)
if [ -f /etc/debian_version ]; then
OS_NAME="Debian"
OS_VER=$(cat /etc/debian_version)
log "Detected OS: $OS_NAME $OS_VER"
else
err "This installer is intended for Debian. Aborting."
exit 1
fi
Hardware checks (warn, not abort)
TOTAL_RAM_MB=$(free -m | awk '/^Mem:/{print $2}')
FREE_DISK_GB=$(df -BG --output=avail / | tail -1 | tr -dc '0-9')
CPU_CORES=$(nproc)
if (( TOTAL_RAM_MB < MIN_RAM_MB )); then
warn "Detected RAM: ${TOTAL_RAM_MB} MB — recommended >= ${MIN_RAM_MB} MB."
if ! confirm "Proceed anyway?"; then
log "User aborted due to low RAM."
exit 1
fi
fi
if (( FREE_DISK_GB < MIN_DISK_GB )); then
warn "Detected free disk: ${FREE_DISK_GB} GB — recommended >= ${MIN_DISK_GB} GB."
if ! confirm "Proceed anyway?"; then
log "User aborted due to low disk."
exit 1
fi
fi
log "CPU cores: $CPU_CORES; RAM: ${TOTAL_RAM_MB} MB; Free disk: ${FREE_DISK_GB} GB"
-------------------------
Interactive prompts (or defaults)
-------------------------
if $INTERACTIVE; then
read -p "Enter domain (FQDN) [${DEFAULT_DOMAIN}]: " DOMAIN
DOMAIN=${DOMAIN:-$DEFAULT_DOMAIN}
read -p "Install folder name [${DEFAULT_INSTALL_FOLDER}]: " FOLDER_NAME
FOLDER_NAME=${FOLDER_NAME:-$DEFAULT_INSTALL_FOLDER}
read -p "Admin username [${DEFAULT_ADMIN_USER}]: " ADMIN_USER
ADMIN_USER=${ADMIN_USER:-$DEFAULT_ADMIN_USER}
read -p "Admin email [${DEFAULT_ADMIN_EMAIL}]: " ADMIN_EMAIL
ADMIN_EMAIL=${ADMIN_EMAIL:-$DEFAULT_ADMIN_EMAIL}
read -p "Admin password (leave blank to auto-generate): " ADMIN_PASS
if [[ -z "$ADMIN_PASS" ]]; then
ADMIN_PASS=$(openssl rand -base64 12)
log "Generated admin password: $ADMIN_PASS"
fi
read -p "DB prefix [${DEFAULT_DB_PREFIX}]: " DB_PREFIX
DB_PREFIX=${DB_PREFIX:-$DEFAULT_DB_PREFIX}
read -p "Use MariaDB (y) or SQLite (n)? [y]: " DB_CHOICE
DB_CHOICE=${DB_CHOICE:-y}
if [[ "$DB_CHOICE" == "y" || "$DB_CHOICE" == "Y" ]]; then
DB_ENGINE="mariadb"
else
DB_ENGINE="sqlite"
fi
read -p "Install default plugins? (classic-editor) [y/N]: " INSTALL_PLUGINS_ANS
INSTALL_PLUGINS=false
if [[ "$INSTALL_PLUGINS_ANS" == "y" || "$INSTALL_PLUGINS_ANS" == "Y" ]]; then
INSTALL_PLUGINS=true
fi
else
# defaults
DOMAIN=$DEFAULT_DOMAIN
FOLDER_NAME=$DEFAULT_INSTALL_FOLDER
ADMIN_USER=$DEFAULT_ADMIN_USER
ADMIN_EMAIL=$DEFAULT_ADMIN_EMAIL
ADMIN_PASS=$(openssl rand -base64 12)
DB_PREFIX=$DEFAULT_DB_PREFIX
DB_ENGINE="mariadb"
INSTALL_PLUGINS=false
fi
WEBROOT="${DEFAULT_WEBROOT_BASE}/${FOLDER_NAME}"
DB_NAME="${DB_PREFIX}db"
DB_USER="${DB_PREFIX}user"
DB_PASS=$(openssl rand -base64 16)
log "Summary of choices:"
echo " Domain: $DOMAIN"
echo " Webroot: $WEBROOT"
echo " Admin: $ADMIN_USER / $ADMIN_EMAIL"
echo " DB engine: $DB_ENGINE"
echo " DB name/user: $DB_NAME / $DB_USER"
echo " Install plugins/themes: $INSTALL_PLUGINS"
echo " PHP version target: $PHP_VERSION"
if $DRY_RUN; then
echo
log "DRY RUN summary complete. No changes will be made."
exit 0
fi
-------------------------
Install prerequisites (only what is missing)
-------------------------
log "Installing prerequisites (only missing packages)..."
Add Sury repo for PHP 8.3 if needed
if ! command_exists php || [[ "$(php -r 'echo PHP_VERSION;')" != "$PHP_VERSION"* ]]; then
log "Installing PHP ${PHP_VERSION} and extensions..."
apt update
apt install -y ca-certificates apt-transport-https lsb-release gnupg
echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/sury-php.list
wget -qO - https://packages.sury.org/php/apt.gpg | gpg --dearmor -o /usr/share/keyrings/sury-php.gpg
apt update
apt install -y "php${PHP_VERSION}" "php${PHP_VERSION}-fpm" "php${PHP_VERSION}-mysql" \
"php${PHP_VERSION}-xml" "php${PHP_VERSION}-mbstring" "php${PHP_VERSION}-curl" \
"php${PHP_VERSION}-zip" "php${PHP_VERSION}-gd" unzip wget curl
else
log "PHP ${PHP_VERSION} appears installed."
fi
Caddy installation (official)
if ! command_exists caddy; then
log "Installing Caddy..."
apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install -y caddy
else
log "Caddy already installed."
fi
Database
if [[ "$DB_ENGINE" == "mariadb" ]]; then
if ! command_exists mysql; then
log "Installing MariaDB..."
apt install -y mariadb-server mariadb-client
log "Securing MariaDB (interactive may appear)..."
# attempt non-interactive secure install: set root auth to unix_socket (Debian default)
# create DB and user later
else
log "MariaDB present."
fi
else
log "SQLite selected; PHP sqlite extension will be used."
apt install -y "php${PHP_VERSION}-sqlite3"
fi
WP-CLI
if ! command_exists wp; then
log "Installing wp-cli..."
curl -sO https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info >/dev/null 2>&1 || true
chmod +x wp-cli.phar
mv wp-cli.phar /usr/local/bin/wp
else
log "wp-cli present."
fi
-------------------------
Database setup
-------------------------
if [[ "$DB_ENGINE" == "mariadb" ]]; then
log "Creating database and user..."
mysql -e "CREATE DATABASE IF NOT EXISTS `${DB_NAME}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -e "CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';"
mysql -e "GRANT ALL PRIVILEGES ON `${DB_NAME}`.* TO '${DB_USER}'@'localhost';"
mysql -e "FLUSH PRIVILEGES;"
log "Database ${DB_NAME} and user ${DB_USER} created."
fi
-------------------------
Download and place ClassicPress
-------------------------
log "Downloading ClassicPress..."
mkdir -p "$WEBROOT"
cd /tmp
CP_RELEASE_URL="https://github.com/ClassicPress/ClassicPress-release/archive/refs/tags/1.6.3.tar.gz"
wget -qO cp.tar.gz "$CP_RELEASE_URL"
tar -xzf cp.tar.gz
shopt -s dotglob
mv ClassicPress-release-/ "$WEBROOT"/
rm -rf /tmp/ClassicPress-release-* cp.tar.gz
shopt -u dotglob
log "Setting ownership and permissions..."
chown -R www-data:www-data "$WEBROOT"
find "$WEBROOT" -type d -exec chmod 755 {} \;
find "$WEBROOT" -type f -exec chmod 644 {} \;
-------------------------
Caddy config
-------------------------
log "Creating Caddyfile for ${DOMAIN}..."
cat > /etc/caddy/Caddyfile <<EOF
${DOMAIN} {
root * ${WEBROOT}
encode gzip zstd
php_fastcgi unix//run/php/php${PHP_VERSION}-fpm.sock
file_server
}
EOF
log "Reloading Caddy..."
systemctl reload caddy || systemctl restart caddy
-------------------------
ClassicPress install via WP-CLI
-------------------------
log "Preparing wp-config.php and running site install..."
Create wp-config.php via wp-cli using DB engine chosen
if [[ "$DB_ENGINE" == "mariadb" ]]; then
wp config create --path="$WEBROOT" --dbname="$DB_NAME" --dbuser="$DB_USER" --dbpass="$DB_PASS" --dbhost="localhost" --dbprefix="${DB_PREFIX}_" --skip-check --allow-root
else
# use SQLite db.php drop-in approach
# ensure php sqlite extension present
WP_DB_DIR="$WEBROOT/wp-content/database"
mkdir -p "$WP_DB_DIR"
chown -R www-data:www-data "$WP_DB_DIR"
# download db.php drop-in if available (wp-sqlite-db)
wget -qO "$WEBROOT/wp-content/db.php" "https://raw.githubusercontent.com/aaemnnosttv/wp-sqlite-db/v1.3.2/src/db.php"
wp config create --path="$WEBROOT" --skip-check --allow-root
fi
Install the site
wp core install --path="$WEBROOT" --url="https://${DOMAIN}" --title="ClassicPress Site" --admin_user="${ADMIN_USER}" --admin_password="${ADMIN_PASS}" --admin_email="${ADMIN_EMAIL}" --skip-email --allow-root
log "Installing default plugins/themes if requested..."
if $INSTALL_PLUGINS; then
for pl in "${DEFAULT_PLUGINS[@]}"; do
wp plugin install "$pl" --activate --path="$WEBROOT" --allow-root || warn "Failed to install plugin $pl"
done
fi
for th in "${DEFAULT_THEMES[@]}"; do
wp theme install "$th" --activate --path="$WEBROOT" --allow-root || warn "Failed to install theme $th"
done
-------------------------
SSL (Caddy auto handles TLS) and reminder
-------------------------
log "Caddy will manage TLS automatically when DNS for ${DOMAIN} points to this server's IP."
log "If DNS is not yet pointed, TLS will be obtained automatically when it is."
-------------------------
Final summary
-------------------------
echo
echo "===================================================="
echo " ClassicPress install complete (Caddy mode)"
echo " Domain: https://${DOMAIN}"
echo " Admin user: ${ADMIN_USER}"
echo " Admin password: ${ADMIN_PASS}"
if [[ "$DB_ENGINE" == "mariadb" ]]; then
echo " DB: ${DB_NAME} / ${DB_USER} / ${DB_PASS}"
else
echo " DB: SQLite (file stored in wp-content/database by default)"
fi
echo " Webroot: ${WEBROOT}"
echo " Caddyfile: /etc/caddy/Caddyfile"
echo " WP-CLI: /usr/local/bin/wp"
echo "===================================================="
Note
** (or not - if you ask my wife)
The usual questions came to mind- why not github repo/ Why not use pastebin, etc... before I posted the script here. Anyways, formatting in vanilla drove me nuts.
Since much of the code was AI tested/ suggested, not sure about license either. Lets' call it a
bored middle aged man project
license.(gosh, am I really that old? OR MAybe.. use it ONLY if you are that old..lol)
Sorry @bikegremlin, did not meant to hijack thread.
blog | exploring visually |
Pretty cool - though it would make more sense to move it to a separate thread for easier finding in the future (and for any updates and feedback).
🔧 BikeGremlin guides & resources
:-)
On serious note, you may kindly move to cest pit. If we do separate thread, Accidentaly we might trigger a storm of AI bot “guided” scripts and make a mess out of les. Don’t want that now, do we ?
Cheers
blog | exploring visually |
IMO it would make more sense to move it to a normal theme under the WordPress section - and act differently if we do run into any problems.
🔧 BikeGremlin guides & resources
What? What's wrong with AI generated scripts (other then it not working)?
I mean we can even have a AI generated script to post AI generated scripts to LES and have other AI generated scripts to reply and fix the AI generated scripts! Viva la AI generated scripts!
Never make the same mistake twice. There are so many new ones to make.
It’s OK if you disagree with me. I can’t force you to be right.
I've managed WordPress websites with templates and plugins I built myself, handling spikes of 2k concurrent users on $3/month VPSes.
Later, another company hired me to do something similar for more than 10 million unique visitors per month, and it cost them around $2k/month including a larger server.
1998Euros profit - 2 euros infrastrcture
I believe in good luck. Harder that I work ,luckier i get.
Actually I moved everything to bare metal to have peace of mind. I slept better after that.
Baremetal never gives me piece of mind...
A dedicated server running my own VMs on proxmos + onsite snapshot backup + offsite snapshot backup + offsite code backup is what gives me piece of mind. And yes, I know it's overkill, but this is what helps me sleep better at night so it's all worth it
Never make the same mistake twice. There are so many new ones to make.
It’s OK if you disagree with me. I can’t force you to be right.
Yes, that’s what I meant to say. I mixed up the words. I meant a dedicated server (plus a VPS for backups). I always run Proxmox on my dedis.
And a few idlers used to monitor uptime, right?
Ya, proxmox is awesome. I dunno what to do if proxmox ever removes their community version...
Never make the same mistake twice. There are so many new ones to make.
It’s OK if you disagree with me. I can’t force you to be right.
I use Hetrixtools (free plan), otherwise I would need another server to monitor my monitoring server and so on...
@bikegremlin oh I gots problems bud BIG problems!!!!
Free Hosting at YetiNode | MicroNode| Cryptid Security | URL Shortener | LaunchVPS | ExtraVM | Host-C | In the Node, or Out of the Loop?
Should I send Rocky and Knuckles over?
🔧 BikeGremlin guides & resources
Not unless they have a j2534 passthru and a GM SPS subscription lol
Free Hosting at YetiNode | MicroNode| Cryptid Security | URL Shortener | LaunchVPS | ExtraVM | Host-C | In the Node, or Out of the Loop?
We got SPS - since the very start of the 90s!
🔧 BikeGremlin guides & resources
I don't know what you have confused SPS for but it is not a VD bud!!!
Free Hosting at YetiNode | MicroNode| Cryptid Security | URL Shortener | LaunchVPS | ExtraVM | Host-C | In the Node, or Out of the Loop?