diff --git a/manyfold/README.md b/manyfold/README.md index 4847875..07545a1 100644 --- a/manyfold/README.md +++ b/manyfold/README.md @@ -53,7 +53,7 @@ $ docker run -t xrforge docker.io/coderofsalvation/xrforge:latest -v ./config:/c | `APPNAME` | `XRForge` | manyfold instance name | | `HOMEPAGE` | `/models` | show '/models' URL as homepage (use `/` for manyfold default) | | `THEME` | `default` | bootstrap theme | -| 'JANUSXR' | `` | run local JanusXR stack (janus-server, janus-gateway, janusweb) | +| 'IMPORT_INSTANCES' | `1` | integrate cherrypicked content from other JanusXR instances (see [manyfold/root/instances](root/instances) | | `AFRAME_VERSION` | `1.7.0` | AFRAME version | | `GODOT_VERSION` | `4.4.1-stable`| godot editor version | | `GODOT_TEMPLATE_ZIP` | `` | godot template zip URL or file (default is empty godot project) | @@ -74,6 +74,8 @@ $ docker run -t xrforge docker.io/coderofsalvation/xrforge:latest -v ./config:/c | `FEDERATE_DRIVE_CACHE`| `1m0s` | specify interval to re-check all models/directories | | `FEDERATE_DRIVE_KEY` | `` | specify path to TLS PEM private key file (`-v ./key.pem:/key.pem -e FEDERATE_DRIVE_KEY=/key.pem` dockerflag e.g.) | | `FEDERATE_DRIVE_CERT` | `` | specify path to TLS PEM public key certificate/CA/intermediate file (`-v ./cert.pem:/cert.pem -e FEDERATE_DRIVE_KEY=/cert.pem` dockerflag e.g.) | +| `SERVER_CORS` | `http://localhost:5577`| CORS-ANYWHERE server for JanusWeb | +| `SERVER_JANUS` | `http://localhost:5566`| JANUS presence server for JanusWeb | > NOTE: if you have nix installed, you can easily try out environment-flags by running: `docker load < $(nix-build nix/docker.nix) && manyfold/cli/manyfold run -e RUNTESTS=1` e.g. @@ -135,8 +137,9 @@ The quickest way is: * TIP2: use env-var `RCLONE_REMOTE` to mount only one specific remote (in case of a [combined](https://rclone.org/combine/) or [union](https://rclone.org/union/) rclone remote e.g.). * TIP2: use **alphanumeric** names for rclone remotes (manyfold libraries choke on dot- or other special-characters) -By default environment-flag `FEDERATE_DRIVE_PATH` will share path `/mnt/experiences` as an open web directory. -Make sure that the URL (and credentials if configure) of step 3 are setup properly, so it matches your reverse proxy/ or SSL configuration (via `FEDERATE_DRIVE_CERT` and `FEDERATE_DRIVE_KEY` flags) +By default environment-flag `FEDERATE_DRIVE_PATH` will share path `/mnt/experiences` as an open web directory.
+Make sure that the URL (and credentials if configure) of step 3 are setup properly, so it matches your reverse proxy/ or SSL configuration (via `FEDERATE_DRIVE_CERT` and `FEDERATE_DRIVE_KEY` flags).
+For more info see the SSL section below. # Git libraries @@ -192,29 +195,46 @@ $ manyfold/cli/manyfold.sh run -e DEV=1 # JanusXR -When running xrforge with the `JANUSXR=1` env-flag, the opensource [JanusXR](https://janusxr.org) stack will be installed and started: +By default the xrforge docker will run an opensource [JanusXR](https://janusxr.org) stack in the background: -* [janus-server](https://github.com/janusvr/janus-server) for chat + syncing avatar positions -* [janus-gateway](https://github.com/meetecho/janus-gateway) for video/audio * [janusweb](https://github.com/meetecho/janus-gateway) the viewer using the above services +* [janus-server](https://github.com/janusvr/janus-server) at port `5566`, for chat + syncing avatar positions +* [cors-anywhere](https://github.com/Rob--W/cors-anywhere) at port `5577`for a hasslefree deep immersive web +* TODO: [janus-gateway](https://github.com/meetecho/janus-gateway) for video/audio -> NOTE: consider this a fingers-crossed 'rolling release' installation, as this is not officially part of XRForge (just a helper for intranets). +For SSL, you need to configure your reverse proxy as following: -Note that janus-server exposes a http websocket at port 5566, so you need to configure your reverse proxy as following: +* wss://presence.foo.bar.com => localhost:5566 +* https://cors.foo.bar.com > localhost:5577 -* https://presence.foo.bar.com => 5566 +> This assumes environment-var `FEDERATE_DRIVE_HOST` is set to `https://foo.bar.com` (`presence` / `cors` subdomain is automatically prefixed when running xrforge over https) -> This assumes environment-var `FEDERATE_DRIVE_HOST` is set to `https://foo.bar.com` (`presence` subdomain is automatically prefixed by the installer) - -#### persist JanusXR stack - -When running the container run the following cmds to speed up the boot-time: +# Developing ``` -$ docker cp xrforge:/mnt/janusweb . -$ docker cp xrforge:/root/janus-server . +$ podman load < $(nix-build nix/docker.nix) # build xrforge-overlay on manyfold-image +$ $(./manyfold/cli/manyfold.sh run -e DEV=1) # start docker ``` -then add the following flags to your docker cmd: `-v ./janusweb:/mnt/janusweb -v ./janus-server:/root/janus-server` +> Profit! now point your browser to 'https://localhost:8080' -> That way JanusXR does not have to be installed every time during boot +### With SSL (for WebXR/JanusXR testing) + +> NOTE: WebXR requires SSL, so we need ngrok or [caddy](https://caddyserver.com) to run the docker over SSL: + +``` +$ $(CADDY=10.171.13.61 ./manyfold/cli/manyfold.sh run -e DEV=1) +``` + +make sure you have caddy installed before running this as root: + +``` +# /manyfold/cli/manyfold caddy 10.171.13.61 +``` + +> Profit! now point your browser to `https://` + +you can edit/copy files via: + +* `podman exec -it xrforge vim /manyfold/cli/manyfold.sh` e.g. +* `podman cp xrforge:/foo .` and vice versa `podman cp foo xrforge:/.` e.g. diff --git a/manyfold/cli/manyfold.sh b/manyfold/cli/manyfold.sh index 981c29e..17c67b2 100755 --- a/manyfold/cli/manyfold.sh +++ b/manyfold/cli/manyfold.sh @@ -6,7 +6,16 @@ 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 +test -n "$IMPORT_INSTANCES" || export IMPORT_INSTANCES=1 +test -n "$SERVER_CORS" || export SERVER_CORS=http://localhost:5577 +test -n "$SERVER_JANUS" || export SERVER_JANUS=http://localhost:5566 +test -n "$HTTPS_ONLY" || export HTTPS_ONLY=disabled +test -n "$CADDY" && { + export HTTPS_ONLY=enabled + export SERVER_CORS=https://$CADDY:5577 + export SERVER_JANUS=https://$CADDY:5566 + export PUBLIC_HOSTNAME=localhost +} db=/config/manyfold.sqlite3 # utility funcs @@ -29,15 +38,25 @@ run(){ #-e PUBLIC_HOSTNAME=localhost \ #-e PUBLIC_PORT=80 \ #-e JANUSXR=1 \ - echo ${oci} run "$@" -p 8790:3214 -p 8791:3215 -p 5566:5566 -p 5577:5577 --name xrforge \ + ports="-p 8080:3214 -p 8081:8081 -p 5566:5566 -p 5577:5577" + # shift ports with 1000 if caddy is used as SSL reverse proxy + test -n "$CADDY" && { + ports='-p 8080:3214 -p 9081:8081 -p 6566:5566 -p 6577:5577' + } + + echo ${oci} run "$@" ${ports} --name xrforge \ -e SECRET_KEY_BASE=lkjwljlkwejrlkjek34k234l \ -e DATABASE_ADAPTER=sqlite3 \ - -e FEDERATE_DRIVE_HOST=http://localhost:8791 \ + -e FEDERATE_DRIVE_HOST=http://localhost:8081 \ + -e PUBLIC_HOSTNAME=$PUBLIC_HOSTNAME \ + -e SERVER_JANUS=$SERVER_JANUS \ + -e SERVER_CORS=$SERVER_CORS \ -e SUDO_RUN_UNSAFELY=enabled \ -e MULTIUSER=enabled \ -e FEDERATION=enabled \ -e THEME=$THEME \ -e HOMEPAGE=$HOMEPAGE \ + -e UPLOAD_PATH=$UPLOAD_PATH \ -e GODOT_VERSION=4.4.1-stable \ -e FEDERATE_DRIVE_CACHE=5s \ --cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse \ @@ -48,8 +67,13 @@ run(){ overlayfs(){ test -d /manyfold || return 0; # nothing to override echocolor "[$APPNAME]" "applying filesystem overlay" + # compose overlays + cd / + mkdir /usr/src/app/public/view || true + rsync -ravuzi /nix/store/*-janus/janusweb/* /usr/src/app/public/view/. cd /manyfold - rsync -rvzi * /. + rsync -rvziL * /. + ln -fs /usr/src/app/public/view /mnt/view # /mnt is one big zippable runnable backup #apply_patches } @@ -79,7 +103,7 @@ hook(){ 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 + { $hook "$@" 2>&1 || true; } | awk '{ gsub(/\/root\/\//,"",$1); $1 = sprintf("%-40s", $1)} 1' | logger done } } @@ -142,14 +166,13 @@ 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);" + set_global default_library "replace('--- $id\\n','\\n',char(10))" } mount_dir(){ - find /mnt -type d -mindepth 1 -maxdepth 1 | grep -v janusweb | while read dir; do + find /mnt -type d -mindepth 1 -maxdepth 1 | grep -vE '(janusweb|view)' | while read dir; do echocolor "[$APPNAME]" "mounting $dir as library" add_lib_to_db "$dir" done @@ -214,8 +237,25 @@ 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 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 + echocolor "[$APPNAME]" "renaming 'model' to 'experience'" + for dir in /usr/src/app/config/locales/*.yml /usr/src/app/config/locales/*/*.yml; do + sed -i 's|Models|Experiences|g' "$dir" + sed -i 's|Model|Experience|g' "$dir" + #sed -i 's|model|experience|g' "$dir" + #sed -i 's|models|experiences|g' "$dir" + done + sed -i 's|File(s) uploaded succesfully|File(s) uploaded succesfully. The experience is now being (re)generated, please be patient and check back later|g' /usr/src/app/config/locales/models/en.yml + sed -i 's|File(s) uploaded succesfully|File(s) uploaded succesfully. The experience is now being (re)generated, please be patient and check back later|g' /usr/src/app/config/locales/model_files/en.yml +} + +allow_csp(){ + test -n "$FEDERATE_DRIVE_HOST" || return 0 + # allow iframes + other federated servers + servers='"https://web.janusxr.org", "https://vesta.janusxr.org"' + sed -i "s|:self,|:self, $servers, ENV['FEDERATE_DRIVE_HOST'], ENV['SERVER_CORS'], ENV['SERVER_JANUS'],|g" /usr/src/app/app/controllers/application_controller.rb + sed -i "s|content_security_policy.connect_src(\*origins)|content_security_policy.connect_src(\*origins);content_security_policy.connect_src( $servers, ENV['FEDERATE_DRIVE_HOST'], ENV['SERVER_CORS'], ENV['SERVER_JANUS'])|g" /usr/src/app/app/controllers/application_controller.rb + # content_security_policy.clear_directives() + # content_security_policy.default_src '*' } start_syslog(){ @@ -253,7 +293,10 @@ force_public(){ import_assets(){ test -n "$NO_ASSETS" && return 0 # nothing to do here - add_lib_to_db /mnt/asset + # mount because rclone does support symlinks outside of the served folder + mkdir /mnt/templates && mount --bind /nix/store/*-xrfragments/xrf/templates /mnt/templates + mkdir /mnt/assets && mount --bind /nix/store/*-xrfragments/xrf/assets /mnt/assets + add_lib_to_db /mnt/assets add_lib_to_db /mnt/templates } @@ -267,31 +310,11 @@ janusxr(){ cd /root/corsanywhere npm install - PORT=5577 start_server ~/.run-corsanywhere node server.js & + PORT=${SERVER_CORS/*:/} start_server ~/.run-corsanywhere node server.js & cd /root chmod +x janus_server-linux - PORT=5566 start_server ~/.run-janus-server ./janus_server-linux & - - ## we should do this in nix/docker.nix but the image gets into GB's :/ - #which git || apk add git - #which janus || apk add janus-gateway - #which node || apk add nodejs - #which bash || apk add bash - #cd /root - - ## install server - #test -d janus-server || git clone --depth 1 https://github.com/janusvr/janus-server - #cd janus-server - #test -d node_modules || { apk add npm && npm install; } - #test -f config.js || ln -f /root/.config/janus-server/config.js . - #start_server(){ - # while sleep 2s; do - # flock -n ~/.janus-server node server.js - # echocolor "[janus-server]" "'node server.js' exited (why?)...restarting" - # done - #} - #test -f ~/.janus-server || start_server & + PORT=${SERVER_CORS/*:/} start_server ~/.run-janus-server ./janus_server-linux & } init_database(){ @@ -302,7 +325,7 @@ init_database(){ 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_global model_path_template "replace('--- \"{creator}/{modelId}\"\\n','\\n',char(10))" set_upload_path #set_global about "$ABOUT" import_assets @@ -311,17 +334,25 @@ init_database(){ touch ${db}.xrforgeinit } +caddy(){ + cat Caddyfile | sed 's|ip:|'$1':|g' > /tmp/Caddyfile + set -x + cat /tmp/Caddyfile + cd /tmp + $(which caddy) run +} + # The new entrypoint of the docker boot(){ echocolor "[$APPNAME]" "booting..." test -z "$NO_OVERLAYFS" && overlayfs start_syslog rename_app + allow_csp set_homepage start_hook_daemon mount_rclone janusxr - cp /root/templates/ARhome/* /mnt/janusweb/. force_public & # enable development mode (disables template caching etc) diff --git a/manyfold/root/hook.d/experience_updated/250-package-audiovisual.rb b/manyfold/root/hook.d/experience_updated/250-package-audiovisual.rb new file mode 100644 index 0000000..070136e --- /dev/null +++ b/manyfold/root/hook.d/experience_updated/250-package-audiovisual.rb @@ -0,0 +1,61 @@ +#!/usr/bin/env ruby + +require 'json' +require_relative './../../xrforge.rb' + +# Check if a filename is provided +if ARGV.length != 1 + puts "Usage: #{$0} " + exit 1 +end + +filename = ARGV[0] + +begin + + # dont run for each file-update + if ! filename.end_with?("datapackage.json") + exit 0 + end + + # Change the directory + dir = File.dirname(filename) + dirPublic = dir.gsub("/mnt/","").gsub("#","%23") + Dir.chdir( File.dirname(filename) ) + # Read and parse the JSON file + data = JSON.parse( File.read( "datapackage.json" ) ) + logfile = File.join( File.dirname(filename), ".xrforge/log.txt" ) + + XRForge.log("✅ starting generating audiovisual experience", logfile) + + # Check if a model file was found after the loop + #model_file = XRForge.getExperienceFile(data,dir,logfile) + #if ! model_file + # XRForge.log("❌ No suitable 3D file found for JanusXR-compatible experience", logfile) + # exit + #end + + # Get the value of the environment variable FEDERATE_DRIVE_HOST + federate_drive_host = ENV['FEDERATE_DRIVE_HOST'] + + autogenerate = true + + if ! data['description'] + + end + XRForge.log("✅ generated audiovisual experience", logfile) + XRForge.log(" ", logfile) + + # tag it! + if ! data['keywords'].include?('audiovisual') + data['keywords'].push('audiovisual') + File.write("datapackage.json", JSON.pretty_generate(data) ) + end + +rescue Errno::ENOENT + puts "File #{filename} not found" +rescue JSON::ParserError + puts "Error parsing JSON from #{filename}" +rescue => e + puts "An error occurred: #{e.message}" +end diff --git a/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb b/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb index de913bb..004db62 100755 --- a/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb +++ b/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb @@ -2,6 +2,7 @@ require 'json' require 'erb' +require 'cgi' require_relative './../../xrforge.rb' # Check if a filename is provided @@ -21,7 +22,6 @@ def addLink(url) return url end - begin # dont run for each file-update @@ -49,9 +49,6 @@ begin XRForge.log("❌ No suitable 3D file found for JanusXR-compatible experience", logfile) end - # Get the value of the environment variable FEDERATE_DRIVE_HOST - federate_drive_host = ENV['FEDERATE_DRIVE_HOST'] - autogenerate = true janusweb_src = ENV['DEV'] ? "/janusweb/janusweb.js" : "/janusweb/janusweb.min.js" @@ -104,16 +101,19 @@ begin xrf_base = model_file ? File.basename(model_file, xrf_ext) : "" # remove the metadata from the description - description = data['description'].gsub(metadataRegex,"").strip() + description = CGI.escapeHTML( + data['description'].gsub(metadataRegex,"") + .strip() + ) data['resources'].each do |resource| ext = File.extname(resource['path']) - if ext.match(/mp3/i) + if ext.match(/(mp3|ogg)/i) audiovisual = model_file ? false : true base = File.basename(resource['path'], ext) - soundtrackUrl = addLink("#{federate_drive_host}/#{dirPublic}/#{resource["path"]}") - assets += " \n" + soundtrackUrl = addLink("model_files/#{resource["path"]}") + assets += " \n" objects += "\n" end @@ -124,7 +124,7 @@ begin audiovisual = model_file ? false : true base = File.basename(resource['path'], ext) - videoSrc = addLink("#{federate_drive_host}/#{dirPublic}/#{resource["path"]}") + videoSrc = addLink("model_files/#{resource["path"]}") assets += "\n" objects += " \n" end @@ -133,7 +133,7 @@ begin if ext.match(/mp4/i) || ext.match(/webm/i) audiovisual = model_file ? false : true base = File.basename(resource['path'], ext) - videoSrc = addLink("#{federate_drive_host}/#{dirPublic}/#{resource["path"]}") + videoSrc = addLink("model_files/#{resource["path"]}") assets += "\n" objects += " \n" end @@ -143,7 +143,7 @@ begin # TODO: copy files from a heuristic-based folder instead hardcoding it here startpos = "pos=\"0 0 -10\"" use_local_asset = "room5" - rplaneSrc = addLink("#{federate_drive_host}/#{dirPublic}/roundedplane.glb") + rplaneSrc = addLink("model_files/roundedplane.glb") system("cp /root/templates/audiovisual/roundedplane.glb #{dir}/.") assets += " \n" objects += " \n" @@ -164,11 +164,11 @@ begin end if description && !data['keywords'].include?('nodescription') - objects += " #{description}\n" + objects += " #{description}\n" end if model_file - objectSrc = "#{federate_drive_host}/#{dirPublic}/#{model_file.gsub("#","%23")}" + objectSrc = "model_files/#{model_file.gsub("#","%23")}" if ! $links.include?(objectSrc) objectSrc = addLink(objectSrc) assets += " \n" diff --git a/manyfold/root/hook.d/experience_updated/500-mastodon-post.rb b/manyfold/root/hook.d/experience_updated/500-mastodon-post.rb index ebe968c..be3a868 100755 --- a/manyfold/root/hook.d/experience_updated/500-mastodon-post.rb +++ b/manyfold/root/hook.d/experience_updated/500-mastodon-post.rb @@ -13,6 +13,7 @@ if ARGV.length != 1 end filename = ARGV[0] + # dont run for each file-update if ! filename.end_with?("datapackage.json") @@ -68,6 +69,7 @@ begin logfile = File.join( File.dirname(filename), ".xrforge/log.txt" ) data['keywords'].each do |tag| + puts tag if tag.match?(/^@.*@.*\./) # scan for activitypub handle (@foo@mastodon.online e.g.) generate(tag,filename,dir,data,logfile) end