262 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			262 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
#!/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 <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
 | 
						|
  find /mnt -type d -mindepth 1 -maxdepth 1 | while read dir; do 
 | 
						|
    echocolor "[$APPNAME]" "listening to inotify events in $dir"
 | 
						|
    inotifywait -r -m $dir | awk '$2 ~ /(CREATE|MODIFY|MOVED_TO|DELETE)/ { system("'$0' hook inotify_"$2" "$1""$3) }' &
 | 
						|
  done
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
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 <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 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 "<cmd>"
 | 
						|
  echocolor "Cmds:"
 | 
						|
  echocolor "     " "run [-d]    " "# runs a OCI container (needs podman/docker)"
 | 
						|
  exit 0
 | 
						|
}
 | 
						|
 | 
						|
test -n "$1" && "$@"
 | 
						|
test -n "$1" || usage
 |