#!/bin/sh oci=$(which podman || which docker) test -n "$APPNAME" || export APPNAME=XRForge test -n "$TAGLINE" || export TAGLINE="Publish single-file XR experiences to immersive networks" 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 test -n "$CORS_PROXY" || export CORS_PROXY=1 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; } ${oci} rm -f xrforge } 1>&2 #-e NO_OVERLAYFS=true \ #-e NO_DEFAULTDB=true \ #-e PUBLIC_HOSTNAME=localhost \ #-e PUBLIC_PORT=80 \ echo ${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';") set_global model_path_template "replace('--- \"{creator}/{modelId} \"\\n','\\n',char(10))" sqlite3 $db "INSERT INTO settings VALUES(6,'default_library',$id); INSERT INTO sqlite_sequence VALUES('settings',6);" } 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_global(){ echocolor "[$APPNAME]" "setting $1 to '$2'" debug sqlite3 /config/manyfold.sqlite3 'CREATE TABLE IF NOT EXISTS "settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "var" varchar NOT NULL, "value" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);' debug sqlite3 /config/manyfold.sqlite3 " INSERT OR REPLACE INTO settings (id, var, value, created_at, updated_at) VALUES ( (SELECT id FROM settings WHERE var = '$1'), '$1', $2, COALESCE((SELECT created_at FROM settings WHERE var = '$1'), datetime('now')), datetime('now') ); " } set_admin(){ echocolor "[$APPNAME]" "adding xrforge admin" read -r -d '' QUERY <Manyfold, XR Fragments, JanusWeb 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(){ cd /usr/src/app echocolor "scanning libraries" BOOT_SCAN=$BOOT_SCAN flock -n /tmp/.scanlibrary bin/manyfold libraries scan rm /tmp/.scanlibrary } scan_experience(){ # don't do this when all libraries are already being scanned test -f /tmp/.scanlibrary && return 0 id=$1 cd /usr/src/app rails_query 'Model.find('$id').add_new_files_later(include_all_subfolders:false)' } 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/xrfragments /mnt/templates/xrfragments cd /tmp timeout 50 wget "https://codeberg.org/coderofsalvation/xrfragment/archive/main.zip" unzip main.zip cp -r xrfragment/assets/library /mnt/asset/xrfragments/\#1 find xrfragment/assets/template -maxdepth 1 -mindepth 1 -type d | awk '{ system("cp -r "$0" /mnt/templates/xrfragments/#"(NR+1)) }' } add_lib_to_db /mnt/asset add_lib_to_db /mnt/templates } init_database(){ test -f ${db}.xrforgeinit && exit 0 # already inited sleep 3 test -z "$THEME" || set_global theme "'$THEME'" set_admin set_global site_name "'$APPNAME'" set_global site_tagline "'$TAGLINE'" set_global model_tags_auto_tag_new "replace('--- \"\"\\n','\\n',char(10))" set_global model_path_template "replace('--- \"{creator}/{modelId} \"\\n','\\n',char(10))" set_upload_path #set_global about "$ABOUT" get_xrfragment_assets mount_dir BOOT_SCAN=1 scan_libraries & touch ${db}.xrforgeinit } # The new entrypoint of the docker boot(){ echocolor "[$APPNAME]" "booting..." test -z "$NO_OVERLAYFS" && overlayfs start_syslog rename_app set_homepage start_hook_daemon mount_rclone force_public & # enable development mode (disables template caching etc) test -n "$DEV" && { 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