xrforge/manyfold/cli/manyfold.sh
2025-08-12 17:03:26 +02:00

220 lines
6.9 KiB
Bash
Executable file

#!/bin/sh
oci=$(which podman || which docker)
test -n "$APPNAME" || APPNAME=xrforge
test -n "$UPLOAD_PATH" || UPLOAD_PATH=/mnt/experiences
test -n "$THEME" || THEME=vapor # xrforge (public/assets/themes/xrforge.css gives err)
test -n "$HOMEPAGE" || HOMEPAGE=/models
db=/config/manyfold.sqlite3
# utility funcs
error(){ echocolor "[error]" "$*"; exit 1; }
echocolor(){ printf "\033[96m%s\033[0m \033[95m%s\033[0m %s\n" "$1" "$2" "$3"; }
debug(){ set -x; "$@"; set +x; }
create_config(){
test -d ./config || mkdir config
test -d ./experiences || mkdir experiences
}
run(){
test -x ${oci} || { echo "warning: not running manyfold OCI container [install podman/docker/etc]"; exit; }
echocolor "[$APPNAME]" "$(basename ${oci}) detected..starting OCI container"
${oci} rm -f xrforge
#-e NO_OVERLAYFS=true \
#-e NO_DEFAULTDB=true \
#-e PUBLIC_HOSTNAME=localhost \
#-e PUBLIC_PORT=80 \
debug ${oci} run "$@" -p 8790:3214 -p 8791:3215 --name xrforge \
-e SECRET_KEY_BASE=lkjwljlkwejrlkjek34k234l \
-e DATABASE_ADAPTER=sqlite3 \
-e SUDO_RUN_UNSAFELY=enabled \
-e MULTIUSER=enabled \
-e FEDERATION=enabled \
-e THEME=$THEME \
-e HOMEPAGE=$HOMEPAGE \
-e FEDERATE_DRIVE_CACHE=5s \
--cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse \
xrforge
#ghcr.io/manyfold3d/manyfold-solo:latest
}
overlayfs(){
test -d /manyfold || return 0; # nothing to override
echocolor "[$APPNAME]" "applying filesystem overlay"
cd /manyfold
rsync -rvzi * /.
}
# cron-like function using sleep (./manifold.sh infinite 3600 zip -r /backup.zip /)
infinite(){
trap 'echocolor "/bin/infinite $*: process ended..infinite does not care.."; sleep 2s' INT
interval=$1
shift
loop(){
echocolor "[/bin/infinite $*]" "$(date) started"
while sleep ${interval}; do
echocolor "[/bin/infinite $*]" "$(date) executing"
"$@"
done;
}
loop "$@"
}
# flexible unixy hook-mechanism which executes all files in ~/hook.d/foo/* when
# calling 'hook foo bar'
hook(){
test -z "$1" && { echo "usage: hook <cmd_or_jsfunction> [args]"; return 0; }
logger "$ hook $*"
cmd=$1
shift
test -d ~/hook.d/$cmd && {
find -L ~/hook.d/$cmd/ -type f -executable -maxdepth 1 | while read hook; do
logger " |+ hook $hook $*"
{ $hook "$@" || true; } 2>&1 | awk '{ gsub(/\/root\/\//,"",$1); $1 = sprintf("%-40s", $1)} 1' | logger
done
}
}
start_hook_daemon(){
# every day call scripts in ~/hook.d/cleanup_daily/*
# 86400 secs = 1 day 3600 = 1 hour
$0 infinite 86400 hook daily &
$0 infinite 3600 hook hourly &
# trigger hooks when files change in /mnt/experiences
inotifywait -r -m $UPLOAD_PATH | awk '$2 ~ /(CREATE|MODIFY|MOVED_TO|DELETE)/ { system("'$0' hook inotify_"$2" "$1""$3) }' &
}
db(){
default(){
test -f /manyfold/manyfold.sql && ! test -f /config/manyfold.sql && {
echocolor "[$APPNAME]" "copying default database"
test -d /config || mkdir /config
cat /manyfold/manyfold.sql | sqlite3 $db
}
}
dump(){
sqlite3 source.db ".dump" > /manyfold/manyfold.sql
}
import(){
sqlite3 $db < "$1" #/manyfold/manyfold.sql
}
"$@"
}
add_lib_to_db(){
name=$(basename $1)
sqlite3 $db "INSERT INTO libraries SELECT NULL, '$1', DATE('NOW'), DATE('NOW'), '', '', '$name', NULL, '', 'filesystem', '', '', '', '', '', '$name', 1 WHERE NOT EXISTS (SELECT 1 FROM libraries WHERE path = '$1');"
}
set_upload_path(){
echocolor "[$APPNAME]" "configuring upload library"
test -d $UPLOAD_PATH || mkdir $UPLOAD_PATH
add_lib_to_db $UPLOAD_PATH
id=$(sqlite3 $db "select id from libraries where path = '$UPLOAD_PATH';")
sqlite3 $db "UPDATE settings set value = $id where var = 'default_library';"
}
mount_dir(){
find /mnt -type d -mindepth 1 -maxdepth 1 | while read dir; do
echocolor "[$APPNAME]" "mounting $dir as library"
add_lib_to_db "$dir"
done
}
mount_rclone(){
libraries(){
rclone listremotes | while read remote; do
dir="${remote/:/}"
test -d /mnt/$dir || mkdir /mnt/$dir
echocolor "[$APPNAME]" "rclone: mounting $remote to /mnt/$dir"
debug rclone mount --daemon $remote /mnt/$dir -vv
add_lib_to_db /mnt/$dir
done
}
library(){
echocolor "[$APPNAME]" "rclone: mounting $RCLONE_REMOTE to /mnt/$RCLONE_REMOTE"
test -d /mnt/$RCLONE_REMOTE || mkdir /mnt/$RCLONE_REMOTE
debug rclone mount --daemon ${RCLONE_REMOTE}: /mnt/$RCLONE_REMOTE -vv
add_lib_to_db /mnt/$RCLONE_REMOTE
}
test -n "$RCLONE_REMOTE" && library
test -n "$RCLONE_REMOTE" || libraries
}
set_theme(){
test -z "$THEME" && return 0 # nothing to do
echocolor "[$APPNAME]" "setting theme to '$THEME'"
debug sqlite3 /config/manyfold.sqlite3 "UPDATE settings SET value = '"$THEME"' WHERE var == 'theme';"
}
set_modelpath(){
echocolor "[$APPNAME]" "enforcing modelpath"
debug sqlite3 /config/manyfold.sqlite3 "UPDATE settings SET value = replace('--- \"{creator}/{modelId}\"\n','\n',char(10)) WHERE var == 'model_path_template';"
}
set_homepage(){
test "$HOMEPAGE" = "/" && return 0 # nothing to do
echocolor "[$APPNAME]" "enforcing homepage path"
debug sed -i 's|root to:.*|root to: redirect("'$HOMEPAGE'")|g' /usr/src/app/config/routes.rb
}
rename_app(){
echocolor "[$APPNAME]" "renaming manyfold to $APPNAME"
sed -i 's/title: Manyfold/title: '$APPNAME'/g' /usr/src/app/config/locales/*.yml
sed -i 's|powered_by_html:.*|powered_by_html: Radically opensource-powered by <a href="https://forgejo.isvery.ninja/coderofsalvation/xrforge">XR Forge</a>, <a href="https://manifold.app" target="_blank">Manyfold</a>, <a href="https://xrfragment.org">XR Fragments</a> and <a href="https://nixos.org" target="_blank">NIX</a>|g' /usr/src/app/config/locales/*.yml
sed -i 's|Models|Experiences|g' /usr/src/app/config/locales/*.yml /usr/src/app/config/locales/*/*.yml
sed -i 's|Model|Experience|g' /usr/src/app/config/locales/*.yml /usr/src/app/config/locales/*.yml
}
start_syslog(){
touch /var/log/messages
syslogd -n & # start syslogd
echocolor started syslog | logger
tail -f /var/log/messages &
}
scan_libraries(){
sleep 20 # wait for manyfold/redis to boot first
cd /usr/src/app
echocolor "scanning libraries"
bin/manyfold libraries scan
}
# The new entrypoint of the docker
boot(){
echocolor "[$APPNAME]" "booting..."
test -z "$NO_OVERLAYFS" && overlayfs
test -z "$NO_DEFAULTDB" && db default
start_syslog
rename_app
set_homepage
set_theme
set_modelpath
mount_dir
mount_rclone
set_upload_path
start_hook_daemon
scan_libraries &
hook boot # emit unixy hook-event (/root/hook.d/boot/* scripts)
exec "$@" # exec prevents error 's6-overlay-suexec: fatal: can only run as pid 1'
}
is_inside_container(){
test -d /package/admin && return 0 || return 1
}
usage(){
echocolor "Usage:" manyfold.sh "<cmd>"
echocolor "Cmds:"
echocolor " " "run [-d] " "# runs a OCI container (needs podman/docker)"
exit 0
}
test -n "$1" && "$@"
test -n "$1" || usage