-
-
Notifications
You must be signed in to change notification settings - Fork 26
Description
Booklore only provides a docker image for installation, so this was a bit tricky.
The basic steps are (updated as of 02/27/2026):
- Install the dependencies: Java, MariaDB, crane (for help extracting the docker image)
- Setup MariaDB
- Extract the booklore jar from the docker image using crane (to avoid having to build Booklore locally)
- Initialize booklore
-Unfortunately MariaDB specifically is required, so the installation directory is bulky (about 1GB, of which MariaDB is ~730MB). I've tried to slim down MariaDB after the download by removing some unused plugins and binaries.
-It is possible to extract the necessary booklore files from their docker image without using crane, but the logic was tricky and the download links seem fragile. crane makes the logic a lot simpler and future-proof if booklore decides to change something on their end.
Thanks for helping in the discord with how to get started. I did my best to follow the advice given (not binding to localhost, etc). I also tried to follow along the basic format of the other scripts here and stole some code from there (like the little function to generate an open port). I looked at the ports other apps were using here and tried to pick a range outside of them.
I'm a novice at this stuff, so please give feedback or adjust it however you like before merging.
booklore.sh (updated 02/27/2026)
#!/bin/bash
mkdir -p "$HOME/.logs/"
export log="$HOME/.logs/booklore.log"
touch "$log"
SUBNET_IP=$(cat "$HOME/.install/subnet.lock")
BASE="$HOME/booklore"
# crane is helpful to extract the necessary files from the Booklore docker image.
# it's possible without it, but the hardcoded links seemed fragile, and crane makes
# the logic MUCH easier anyway.
function crane_install() {
if [[ -x "$BASE/crane" ]]; then
echo "crane is already installed. Skipping download."
return 0
fi
echo "Downloading crane (for Booklore docker extraction)..."
local dl_file=$(mktemp)
curl -sLf "https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz" -o "$dl_file"
tar -xzf "$dl_file" -C "$BASE" crane
rm -f "$dl_file"
}
function java_install() {
# get the docker config using crane and extract the required java version
local DOCKER_CONFIG=$("$BASE/crane" config ghcr.io/booklore-app/booklore:latest 2>> "$log")
local JAVA_VER=$(echo "$DOCKER_CONFIG" | grep -oP '"JAVA_VERSION[^"]*"' | grep -oP '\d+' | head -n 1)
if [[ -z "$JAVA_VER" ]]; then
echo "ERROR: Could not detect Java version from the Booklore docker image."
exit 1
fi
if [[ -x "$BASE/java/bin/java" ]] && "$BASE/java/bin/java" -version 2>&1 | grep -q "version \"$JAVA_VER"; then
echo "Java $JAVA_VER is already installed. Skipping download."
return 0
fi
echo "Detected Booklore requires Java $JAVA_VER. Downloading..."
rm -rf "$BASE/java"
mkdir -p "$BASE/java"
local dl_file=$(mktemp)
curl -sLf "https://api.adoptium.net/v3/binary/latest/${JAVA_VER}/ga/linux/x64/jre/hotspot/normal/eclipse" -o "$dl_file"
tar -xzf "$dl_file" --strip-components=1 -C "$BASE/java"
rm -f "$dl_file"
}
function maria_install() {
echo "Downloading MariaDB..."
local dl_file=$(mktemp)
curl -sLf "https://archive.mariadb.org/mariadb-12.3.1/bintar-linux-systemd-x86_64/mariadb-12.3.1-linux-systemd-x86_64.tar.gz" -o "$dl_file"
tar -xzf "$dl_file" --strip-components=1 -C "$BASE/mariadb"
rm -f "$dl_file"
# remove useless directories (tests and such)
rm -rf "$BASE/mariadb"/{mariadb-test,sql-bench,include,man,docs}
# remove massive storage engines not used by Booklore (MyRocks, Mroonga, etc.)
rm -f "$BASE/mariadb/lib/plugin"/{ha_rocksdb.so,ha_mroonga.so,ha_spider.so,ha_connect.so,ha_oqgraph.so,libgalera_smm.so}
# remove some unused binaries (mariadb-dump is kept instead of mariadb-backup)
rm -f "$BASE/mariadb/bin/"{mariadb-backup,garbd,mariadb-ldb,sst_dump,mariadb-slap,mariadb-test,mariadb-client-test,aria_s3_copy,mbstream}
}
function port() {
LOW_BOUND=$1
UPPER_BOUND=$2
comm -23 <(seq ${LOW_BOUND} ${UPPER_BOUND} | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1
}
function _docker_extract() {
local tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' RETURN
echo "Extracting Booklore via crane..."
# extract the booklore .jar from the docker image using crane
"$BASE/crane" export ghcr.io/booklore-app/booklore:latest - | tar -xvf - -C "$tmp" --wildcards "app/*.jar" >> "$log" 2>&1
# verify and move the file to the install directory. crane gives full paths so the jar is likely at /app/app.jar
if [[ -f "$tmp/app/app.jar" ]]; then
echo "Files verified. Installing to $BASE..."
mkdir -p "$BASE/app"
# Backup old jar
[[ -f "$BASE/app/app.jar" ]] && mv "$BASE/app/app.jar" "$BASE/app/app.jar.bak"
# Move new jar
mv "$tmp/app/app.jar" "$BASE/app/app.jar"
return 0
else
echo "ERROR: crane export failed or file structure is different than expected. script needs to be updated."
return 1
fi
}
function _install() {
mkdir -p "$BASE/"{app,bookdrop,books,config,java,tmp,mariadb,mariadb/data,mariadb/tmp}
# dependency installs
crane_install
java_install
maria_install
DB_PORT=$(port 17000 17400)
APP_PORT=$(port 17401 17800)
DB_PASS=$(openssl rand -hex 16)
# configure MariaDB
echo "Setting up MariaDB on $SUBNET_IP:$DB_PORT..."
cat <<EOF > "$BASE/mariadb/my.cnf"
[mysqld]
port = $DB_PORT
bind-address = $SUBNET_IP
socket = $BASE/mariadb/mysql.sock
datadir = $BASE/mariadb/data
tmpdir = $BASE/mariadb/tmp
skip-log-bin
innodb_log_file_size = 16M
innodb_buffer_pool_size = 128M
pid-file = $BASE/mariadb/pid
log-error = $BASE/mariadb/error.log
EOF
# initialize database
"$BASE/mariadb/scripts/mariadb-install-db" --defaults-file="$BASE/mariadb/my.cnf" --basedir="$BASE/mariadb" >> "$log" 2>&1
cat <<EOF > "$HOME/.config/systemd/user/booklore-db.service"
[Unit]
Description=Booklore MariaDB
[Service]
ExecStart=$BASE/mariadb/bin/mariadbd --defaults-file=$BASE/mariadb/my.cnf
Restart=on-failure
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now booklore-db
# wait for the DB to be ready
if ! "$BASE/mariadb/bin/mariadb-admin" --socket="$BASE/mariadb/mysql.sock" --wait=30 ping --silent; then
echo "ERROR: DB failed to start."; tail -n 20 "$BASE/mariadb/error.log"; exit 1
fi
# initialize and try root user first, then current user if that fails
SQL="CREATE DATABASE IF NOT EXISTS booklore; GRANT ALL PRIVILEGES ON booklore.* TO 'booklore'@'%' IDENTIFIED BY '$DB_PASS'; FLUSH PRIVILEGES;"
"$BASE/mariadb/bin/mariadb" --socket="$BASE/mariadb/mysql.sock" -u root -e "$SQL" 2>/dev/null || \
"$BASE/mariadb/bin/mariadb" --socket="$BASE/mariadb/mysql.sock" -u "$(whoami)" -e "$SQL"
# extract jar from Booklore docker via crane
if ! _docker_extract; then
exit 1
fi
# Booklore configuration
cat <<EOF > "$BASE/application.yml"
server:
port: $APP_PORT
spring:
datasource:
url: jdbc:mariadb://$SUBNET_IP:$DB_PORT/booklore
username: booklore
password: $DB_PASS
app:
path-config: $BASE/config
bookdrop-folder: $BASE/bookdrop
EOF
cat <<EOF > "$HOME/.config/systemd/user/booklore.service"
[Unit]
Description=Booklore
After=booklore-db.service
[Service]
WorkingDirectory=$BASE
ExecStart=$BASE/java/bin/java -Xmx1g -Djava.io.tmpdir=$BASE/tmp -jar $BASE/app/app.jar --spring.config.additional-location=file:$BASE/application.yml
Restart=on-failure
[Install]
WantedBy=default.target
EOF
systemctl --user enable --now booklore
touch "$HOME/.install/.booklore.lock"
echo "Booklore has been installed and should be running at http://$(hostname -f):$APP_PORT (give it a minute or so to initialize)"
}
function _remove() {
systemctl --user stop booklore booklore-db
systemctl --user disable booklore booklore-db
rm -f "$HOME/.config/systemd/user/booklore"*.service
rm -f "$HOME/.install/.booklore.lock"
rm -rf "$BASE/"
systemctl --user daemon-reload
echo "Uninstall Complete."
}
function _upgrade() {
if [ ! -f "$HOME/.install/.booklore.lock" ]; then
echo "Booklore is not installed."
exit 1
fi
echo "Upgrading Booklore..."
systemctl --user stop booklore
crane_install
if _docker_extract; then
# if extraction works, update java to the required version
java_install
systemctl --user start booklore
echo "Upgrade complete. Services restarted."
else
# if extraction fails, restart the current Booklore
echo "Extraction failed. Restarting the old version..."
systemctl --user start booklore
exit 1
fi
}
echo 'This is unsupported software. You will not get help with this, please answer `yes` if you understand and wish to proceed'
if [[ -z ${eula} ]]; then
read -r eula
fi
if ! [[ $eula =~ yes ]]; then
echo "You did not accept the above. Exiting..."
exit 1
else
echo "Proceeding with installation"
fi
echo "Welcome to the Booklore Installer"
echo ""
echo "What do you like to do?"
echo "Logs are stored at ${log}"
echo "install = Install Booklore"
echo "upgrade = Upgrade Booklore to the latest version"
echo "uninstall = Completely removes Booklore (including the database! backup yourself if needed)"
echo "exit = Exit"
while true; do
read -r -p "Enter choice: " choice
case $choice in
"install")
_install
break
;;
"upgrade")
_upgrade
break
;;
"uninstall")
_remove
break
;;
"exit")
break
;;
*)
echo "Unknown Option."
;;
esac
done
exit