2025-07-22 13:16:53 +02:00
#!/bin/sh
oci = $( which podman || which docker)
2025-11-10 21:25:39 +01:00
test -n " $APPNAME " || export APPNAME = XRForge
test -n " $TAGLINE " || export TAGLINE = "Publish single-file XR experiences to immersive networks"
2025-10-03 17:21:58 +02:00
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
2025-11-18 17:46:33 +01:00
test -n " $CORS_PROXY " || export CORS_PROXY = 1
2025-07-28 17:52:11 +02:00
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; }
2025-07-22 13:16:53 +02:00
create_config( ) {
test -d ./config || mkdir config
test -d ./experiences || mkdir experiences
}
run( ) {
2025-11-18 17:46:33 +01:00
{
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 \
2025-07-22 13:16:53 +02:00
-e SECRET_KEY_BASE = lkjwljlkwejrlkjek34k234l \
-e DATABASE_ADAPTER = sqlite3 \
2025-08-22 11:30:34 +02:00
-e FEDERATE_DRIVE_HOST = http://localhost:8791 \
2025-07-22 13:16:53 +02:00
-e SUDO_RUN_UNSAFELY = enabled \
-e MULTIUSER = enabled \
-e FEDERATION = enabled \
2025-08-11 16:19:41 +02:00
-e THEME = $THEME \
-e HOMEPAGE = $HOMEPAGE \
2025-08-22 11:30:34 +02:00
-e GODOT_VERSION = 4.4.1-stable \
2025-08-05 18:59:03 +02:00
-e FEDERATE_DRIVE_CACHE = 5s \
2025-07-28 17:52:11 +02:00
--cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse \
2025-08-01 11:41:59 +02:00
xrforge
#ghcr.io/manyfold3d/manyfold-solo:latest
2025-07-28 17:52:11 +02:00
}
overlayfs( ) {
test -d /manyfold || return 0; # nothing to override
echocolor " [ $APPNAME ] " "applying filesystem overlay"
cd /manyfold
2025-08-01 16:20:48 +02:00
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 " $@ "
2025-07-28 17:52:11 +02:00
}
2025-08-04 18:06:41 +02:00
# 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
2025-11-25 12:48:50 +01:00
2025-08-04 18:06:41 +02:00
test -d ~/hook.d/$cmd && {
2025-10-28 17:57:34 +01:00
find -L ~/hook.d/$cmd / -type f -executable -maxdepth 1 | sort -V | while read hook; do
2025-08-04 18:06:41 +02:00
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 &
2025-10-24 21:09:41 +02:00
# trigger hooks when files change in /mnt
2025-10-29 17:57:16 +01:00
#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
2025-10-24 21:09:41 +02:00
2025-10-29 17:57:16 +01:00
## force-trigger processing hooks in /mnt
#find /mnt | grep datapackage | xargs -n1 $0 hook inotify_MODIFY
2025-08-04 18:06:41 +02:00
}
2025-07-28 17:52:11 +02:00
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
}
" $@ "
}
2025-07-30 15:09:03 +02:00
add_lib_to_db( ) {
name = $( basename $1 )
2025-08-12 15:41:45 +02:00
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 '); "
2025-07-30 15:09:03 +02:00
}
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 '; " )
2025-11-18 17:46:33 +01:00
sqlite3 $db " INSERT INTO settings VALUES(6,'default_library',replace('--- $id \n','\n',char(10);
INSERT INTO sqlite_sequence VALUES( 'settings' ,6) ; "
2025-07-30 15:09:03 +02:00
}
2025-08-12 15:41:45 +02:00
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( ) {
2025-07-28 17:52:11 +02:00
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
2025-07-30 15:09:03 +02:00
add_lib_to_db /mnt/$dir
2025-07-28 17:52:11 +02:00
done
}
library( ) {
2025-07-30 15:09:03 +02:00
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
2025-07-28 17:52:11 +02:00
}
test -n " $RCLONE_REMOTE " && library
test -n " $RCLONE_REMOTE " || libraries
2025-07-22 13:16:53 +02:00
}
2025-11-10 21:25:39 +01:00
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' )
) ;
"
2025-07-22 13:16:53 +02:00
}
2025-11-10 21:25:39 +01:00
set_admin( ) {
echocolor " [ $APPNAME ] " "adding xrforge admin"
read -r -d '' QUERY <<EOF
INSERT INTO users VALUES( 1,'xrforge@localhost' ,'\$2a\$12\$u/j8LRzbPiJRHmi1eV/fvOXXiKxN2vBGtNd.Pt28w.wOnq3rnfpzO' ,'2025-07-25 10:52:56.989975' ,'2025-07-25 12:46:27.338917' ,'xrforge' ,'{"models":true,"creators":true,"collections":true,"per_page":12}' ,'{"grid_width":200,"grid_depth":200,"show_grid":true,"enable_pan_zoom":false,"background_colour":"#000000","object_colour":"#ffffff","render_style":"original"}' ,'{"threshold":2,"heatmap":true,"keypair":true,"sorting":"frequency"}' ,'{"missing":"danger","empty":"info","nesting":"warning","inefficient":"info","duplicate":"warning","no_image":"silent","no_3d_model":"silent","non_manifold":"warning","inside_out":"warning","no_license":"silent","no_links":"silent","no_creator":"silent","no_tags":"silent"}' ,'{"hide_presupported_versions":true}' ,NULL,'2025-07-25 12:46:27.325287' ,NULL,NULL,0,NULL,NULL,NULL,NULL,'71863vkppj6k' ,1,1,1) ;
EOF
debug sqlite3 /config/manyfold.sqlite3 " $QUERY "
debug sqlite3 /config/manyfold.sqlite3 'INSERT INTO users_roles VALUES(1,1);'
#debug sqlite3 /config/manyfold.sqlite3 'INSERT INTO users_roles VALUES(1,4);'
#debug sqlite3 /config/manyfold.sqlite3 "INSERT INTO sqlite_sequence VALUES('users',1);"
2025-07-28 18:02:11 +02:00
}
2025-08-11 16:19:41 +02:00
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
}
2025-07-28 17:52:11 +02:00
rename_app( ) {
echocolor " [ $APPNAME ] " " renaming manyfold to $APPNAME "
sed -i 's/title: Manyfold/title: ' $APPNAME '/g' /usr/src/app/config/locales/*.yml
2025-11-18 17:46:33 +01:00
sed -i 's|powered_by_html:.*|powered_by_html: Radically opensource-powered by <a href="https://manifold.app" target="_blank">Manyfold</a>, <a href="https://xrfragment.org">XR Fragments</a>, <a href="https://github.com/jbaicoianu/janusweb" target="_blank">JanusWeb</a> and <a href="https://nixos.org" target="_blank">NIX</a>|g' /usr/src/app/config/locales/*.yml
2025-08-06 17:22:29 +02:00
sed -i 's|Models|Experiences|g' /usr/src/app/config/locales/*.yml /usr/src/app/config/locales/*/*.yml
2025-08-27 14:11:20 +02:00
sed -i 's|Model|Experience|g' /usr/src/app/config/locales/*.yml /usr/src/app/config/locales/*/*.yml
2025-07-28 17:52:11 +02:00
}
2025-08-04 18:06:41 +02:00
start_syslog( ) {
2025-08-06 09:45:55 +02:00
touch /var/log/messages
2025-08-04 18:06:41 +02:00
syslogd -n & # start syslogd
echocolor started syslog | logger
tail -f /var/log/messages &
2025-08-01 16:20:48 +02:00
}
2025-08-12 15:41:45 +02:00
scan_libraries( ) {
cd /usr/src/app
2025-08-12 17:03:26 +02:00
echocolor "scanning libraries"
2025-11-25 12:48:50 +01:00
BOOT_SCAN = $BOOT_SCAN bin/manyfold libraries scan
2025-08-12 15:41:45 +02:00
}
2025-11-12 18:18:45 +01:00
scan_experience( ) {
id = $1
cd /usr/src/app
# don't do this when all libraries are already being scanned
rails_query 'Model.find(' $id ').add_new_files_later(include_all_subfolders:false)'
}
2025-08-13 10:52:28 +02:00
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
2025-08-22 11:30:34 +02:00
test -d /mnt/asset || {
echocolor "fetching XR Fragments asset & templates"
mkdir -p /mnt/asset/xrfragment /mnt/templates/xrfragment
2025-08-13 10:52:28 +02:00
cd /tmp
timeout 50 wget "https://codeberg.org/coderofsalvation/xrfragment/archive/main.zip"
unzip main.zip
2025-08-22 11:30:34 +02:00
cp -r xrfragment/assets/library /mnt/asset/xrfragment/\# 1
2025-08-14 15:01:59 +02:00
cp -r xrfragment/assets/template /mnt/templates/xrfragment/\# 2
2025-08-13 10:52:28 +02:00
}
2025-08-22 11:30:34 +02:00
add_lib_to_db /mnt/asset
2025-08-14 15:01:59 +02:00
add_lib_to_db /mnt/templates
2025-08-13 10:52:28 +02:00
}
2025-11-10 21:25:39 +01:00
init_database( ) {
2025-11-20 11:06:23 +01:00
test -f ${ db } .xrforgeinit && exit 0 # already inited
2025-11-10 21:25:39 +01:00
sleep 3
test -z " $THEME " || set_global theme " ' $THEME ' "
set_admin
set_global site_name " ' $APPNAME ' "
set_global site_tagline " ' $TAGLINE ' "
2025-11-18 17:46:33 +01:00
set_global model_tags_auto_tag_new "replace('--- \"\"\\n','\\n',char(10))"
2025-11-10 21:25:39 +01:00
set_global model_path_template "replace('--- \"{creator}/{modelId} \"\\n','\\n',char(10))"
#set_global about "$ABOUT"
set_upload_path &
get_xrfragment_assets
mount_dir
2025-11-25 12:48:50 +01:00
BOOT_SCAN = 1 scan_libraries &
2025-11-20 11:06:23 +01:00
touch ${ db } .xrforgeinit
2025-11-10 21:25:39 +01:00
}
2025-07-28 17:52:11 +02:00
# The new entrypoint of the docker
boot( ) {
echocolor " [ $APPNAME ] " "booting..."
test -z " $NO_OVERLAYFS " && overlayfs
2025-08-04 18:06:41 +02:00
start_syslog
2025-07-28 17:52:11 +02:00
rename_app
2025-08-11 16:19:41 +02:00
set_homepage
2025-10-24 18:42:55 +02:00
start_hook_daemon
2025-11-20 11:06:23 +01:00
mount_rclone
force_public &
2025-08-01 16:20:48 +02:00
2025-08-22 11:30:34 +02:00
# 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
}
2025-07-28 17:52:11 +02:00
exec " $@ " # exec prevents error 's6-overlay-suexec: fatal: can only run as pid 1'
2025-07-22 13:16:53 +02:00
}
is_inside_container( ) {
test -d /package/admin && return 0 || return 1
}
usage( ) {
2025-07-28 17:52:11 +02:00
echocolor "Usage:" manyfold.sh "<cmd>"
echocolor "Cmds:"
echocolor " " "run [-d] " "# runs a OCI container (needs podman/docker)"
2025-07-22 13:16:53 +02:00
exit 0
}
2025-07-28 17:52:11 +02:00
test -n " $1 " && " $@ "
test -n " $1 " || usage