#!/bin/sh oci=$(which podman || which docker) test -n "$APPNAME" || export APPNAME=xrforge test -n "$UPLOAD_PATH" || export UPLOAD_PATH=/mnt/experiences test -n "$THEME" || export THEME=slate test -n "$HOMEPAGE" || export HOMEPAGE=/models test -n "$GODOT_VERSION" || export GODOT_VERSION=4.4.1-stable 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 FEDERATE_DRIVE_HOST=http://localhost:8791 \ -e SUDO_RUN_UNSAFELY=enabled \ -e MULTIUSER=enabled \ -e FEDERATION=enabled \ -e THEME=$THEME \ -e HOMEPAGE=$HOMEPAGE \ -e GODOT_VERSION=4.4.1-stable \ -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 [args]"; return 0; } logger "$ hook $*" cmd=$1 shift test -d ~/hook.d/$cmd && { find -L ~/hook.d/$cmd/ -type f -executable -maxdepth 1 | sort -V | 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 #find /mnt -type d -mindepth 1 -maxdepth 1 | while read dir; do # echocolor "[$APPNAME]" "listening to inotify events in $dir" # # scan for '/mnt/experiences/creatorname/#234/ MODIFY foo.glb' e.g. # # scan for '/mnt/experiences/creatorname/#234/ MOVED_TO foo.glb' e.g. # inotifywait -r -m $dir | awk '/.*/ { print $0 }; $2 ~ /(CREATE|MODIFY|MOVED_TO|DELETE)/ && $3 ~ /datapackage/ { system("'$0' hook datapackage_"$2" "$1""$3) }' & #done ## force-trigger processing hooks in /mnt #find /mnt | grep datapackage | xargs -n1 $0 hook inotify_MODIFY } 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" ln -s "$dir" /usr/src/app/public/. 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 XR Forge, Manyfold, XR Fragments and NIX|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 10 # wait for manyfold/redis to boot first cd /usr/src/app echocolor "scanning libraries" bin/manyfold libraries scan } rails_query(){ cd /usr/src/app echo "$*" | bin/rails console } force_public(){ test -n "$NO_PUBLIC_ONLY" && return 0 # nothing to do echocolor "forcing public-only models" infinite 60 rails_query 'Model.find_each { |it| it.grant_permission_to("view", nil) }' & } get_xrfragment_assets(){ test -n "$NO_ASSETS" && return 0 # nothing to do here test -d /mnt/asset || { echocolor "fetching XR Fragments asset & templates" mkdir -p /mnt/asset/xrfragment /mnt/templates/xrfragment cd /tmp timeout 50 wget "https://codeberg.org/coderofsalvation/xrfragment/archive/main.zip" unzip main.zip cp -r xrfragment/assets/library /mnt/asset/xrfragment/\#1 cp -r xrfragment/assets/template /mnt/templates/xrfragment/\#2 } add_lib_to_db /mnt/asset add_lib_to_db /mnt/templates } # 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_rclone set_upload_path force_public start_hook_daemon get_xrfragment_assets mount_dir scan_libraries & hook boot # emit unixy hook-event (/root/hook.d/boot/* scripts) # enable development mode (disables template caching etc) test -n "$DEV" && { sed -i 's|^exec.*|exec s6-setuidgid $PUID:$PGID bin/dev|g' /usr/src/app/bin/docker-entrypoint.sh sed -i 's|config.enable_reloading = false|config.enable_reloading = true|g' /usr/src/app/config/environments/production.rb apk add vim } 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 "" echocolor "Cmds:" echocolor " " "run [-d] " "# runs a OCI container (needs podman/docker)" exit 0 } test -n "$1" && "$@" test -n "$1" || usage