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 += "