diff --git a/.env b/.env
index 1afc1dd..f0395e0 100644
--- a/.env
+++ b/.env
@@ -1,6 +1,6 @@
# aliases
-build(){ $(pwd)/util/xrforge.sh build; }
-rebuild(){ $(pwd)/util/xrforge.sh rebuild; }
+build(){ $(pwd)/src/util/xrforge.sh build; }
+rebuild(){ $(pwd)/src/util/xrforge.sh rebuild; }
# forgejo server hook
#test "$GITEA_ROOT_URL" = "https://xrforge.isvery.ninja/" && {
diff --git a/README.md b/README.md
index 5c6f312..913fae6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,65 @@
XRForge is a tiny, powerful tool to interconnect XR experiences.
+
+An open, seamless XR web built from simple URLs and directories, without gatekeepers.
+
+
+XRForge transforms standard local or remote file directories into interconnected 3D spaces.
+ Users can seamlessly "surf" the spatial web, turning everyday directories into 3D portals.
+
+```
+ ┌───────────────────────────────────┐ ┌──────────────────────────────────────────┐
+ │ local disk │ │ Surf via 3D portals │
+ ├───────────────────────────────────┤ └──────────────────────────────────────────┘
+ │ │
+ │ /myverse │ https://my.org/myverse
+ │ /elearnings │ https://my.org/myverse/elearnings
+ │ /elearning1/1.glb │ https://my.org/myverse/elearning/elearning1
+ │ /elearning2/2.glb │ https://my.org/myverse/elearning/elearning2
+ │ │
+ │ ┌──────────────────────────────┐ │
+ │ │ github / webdav / dropbox etc│ │
+ │ │ │ │
+ │ │ /mygames │ │ https://my.org/myverse/mygames
+ │ │ /biggusgateWebXR │ │ https://my.org/myverse/mygames/biggusgate
+ │ │ /spasca │ │ https://my.org/myverse/mygames/spasca
+ │ │ /otherxrforge │ │ https://my.org/myverse/otherxrforge
+ │ │ │ │
+ │ │ ┌───────────┐ ┌───────────┐ │ │
+ │ │ │/manyfold │ │archive.org│ │ │ https://my.org/myverse/manyfold
+ │ │ └───────────┘ └───────────┘ │ │ https://my.org/myverse/myarchive
+ │ │ │ │
+ │ │ +40 integrations! │ │
+ │ └──────────────────────────────┘ │
+ └───────────────────────────────────┘
+```
+
+
+## Usage / Radical Opensource
+
+```bash
+$ ./xrforge.com
+Usage: ./xrforge.com [opts]
+
+ ./xrforge.com test run testsuite (selftest)
+ ./xrforge.com admin launch adminpanel to configure stuff (/admin)
+ ./xrforge.com generate generate www-folder with XR experiences
+ ./xrforge.com help display all possible flags
+ ./xrforge.com createplugin generates skeleton plugin
+ ./xrforge.com server starts the webserver [without --nobrowser]
+ ./xrforge.com build build
+
+$ unzip xrforge.com
+Archive: result/bin/xrforge.com
+ Length Date Time Name
+--------- ---------- ----- ----
+ 3992 08-03-2026 09:54 .init.lua
+ 242 08-03-2026 09:46 cmd/server.lua
+ 1014 07-23-2026 16:13 cmd/createplugin.lua
+ 580 07-24-2026 16:12 cmd/help.lua
+ ....
+```
+
+> Profit! you have aqcuired the sourcecode from the binary: run and distribute modified versions if needed ♥
## Adding content
diff --git a/src/.init.lua b/src/.init.lua
index 07bf501..0277a9c 100644
--- a/src/.init.lua
+++ b/src/.init.lua
@@ -43,6 +43,8 @@ app = require("soakbean") {
end
}
+util.fetchInstall(app) -- setup internal fetch callback mechanism
+
-- api skeleton (implemented via soakbean's app.on(...))
app.set = function(k,v) end
app.init = function() end
@@ -55,10 +57,10 @@ app.plugin.diskscan = app.addPlugin('plugin/diskscan')
app.plugin.moderation = app.addPlugin('plugin/moderation')
app.plugin.xrfragments = app.addPlugin('plugin/xrfragments')
app.plugin.janusxr = app.addPlugin('plugin/janusxr')
-app.plugin.xrforges = app.addPlugin('plugin/xrforges')
+app.plugin.clouddrive = app.addPlugin('plugin/clouddrive')
+app.plugin.federation = app.addPlugin('plugin/federation')
app.plugin.manyfold = app.addPlugin('plugin/manyfold')
app.plugin.git = app.addPlugin('plugin/git')
-app.plugin.clouddrive = app.addPlugin('plugin/clouddrive')
app.plugin.webhook = app.addPlugin('plugin/webhook')
app.plugin.markdown = app.addPlugin('plugin/markdown')
@@ -104,6 +106,11 @@ end)
function OnHttpRequest() app.run() end
+OnServerListen = function(socketdescriptor, serverip, serverport)
+ app.ip = FormatIp(serverip)
+ app.port = serverport
+end
+
OnServerStart = function()
app.init() -- this triggers init event
end
diff --git a/src/.lua/soakbean.lua b/src/.lua/soakbean.lua
index a6ecc7b..5d15066 100644
--- a/src/.lua/soakbean.lua
+++ b/src/.lua/soakbean.lua
@@ -188,7 +188,7 @@ sb = {
header=GetHeaders(),
url=GetPath(),
protocol=GetScheme(),
- body={}
+ body=GetBody(),
}
local res={
_status=nil,
diff --git a/src/.lua/util.lua b/src/.lua/util.lua
index bd64fe4..2aa5831 100644
--- a/src/.lua/util.lua
+++ b/src/.lua/util.lua
@@ -75,10 +75,17 @@ util.templateFile = function(file,data)
return util.template( LoadAsset(file), data)
end
-util.getURL = function()
- local port = ':' .. GetPort()
- if port == 80 then port = '' end
- return GetScheme() .. '://' .. GetHost() .. port .. GetPath()
+util.getURL = function(url,opts)
+ opts = opts or {}
+ local port = ''
+ if opts.server then
+ url = url or '/'
+ return 'http://localhost:' .. app.port .. url
+ else
+ port = ':' .. GetPort()
+ if port == 80 then port = '' end
+ return GetScheme() .. '://' .. GetHost() .. port .. (url or GetPath())
+ end
end
util.template = function(content,data)
@@ -287,6 +294,30 @@ function util.pluck(path, tbl, default)
return current
end
+function util.fetchInstall(app)
+ app.expectedreq = {}
+ app.post('/bus',function(req,res,next)
+ local reqid = GetParam('id')
+ local cb = app.expectedreq[reqid]
+ if cb then cb(req,res,next) end
+ end)
+end
+
+function util.fetch(url, opts, cb)
+ local reqid = tostring( Rdrand() )
+ app.expectedreq[reqid] = cb
+ local urlret = util.getURL('/bus?id=' .. reqid, {server=true} )
+ if assert(unix.fork()) == 0 then
+ local ok, headers, body = Fetch(url, opts)
+ if ok then
+ Fetch(urlret, body) -- post back to ourselves
+ else
+ print(headers)
+ end
+ unix.exit(0)
+ end
+end
+
function util.stripPath(str)
return str:gsub("^./",""):gsub("^/",""):gsub("/$","")
diff --git a/src/cmd/generate.lua b/src/cmd/generate.lua
index 4d694ea..57a050e 100644
--- a/src/cmd/generate.lua
+++ b/src/cmd/generate.lua
@@ -1,4 +1,6 @@
return function(app,argv)
- app.plugin.diskscan.scan()
+ print('generating..')
+ app.plugin.clouddrive.mount()
+ --app.plugin.diskscan.scan( app.opts.plugin_diskscan_dir )
os.exit()
end
diff --git a/src/css/xrforge.css b/src/css/xrforge.css
index cbca605..608794f 100644
--- a/src/css/xrforge.css
+++ b/src/css/xrforge.css
@@ -270,8 +270,12 @@ img.icon{ display:none; }
.info{
background:#224;
- padding:10px;
+ padding:10px 20px;
+ overflow:clip;
}
+.info .tag{
+ margin-bottom:7px;
+}
b.tag{
display:inline-block;
diff --git a/src/federation.json b/src/federation.json
new file mode 100644
index 0000000..7fb9503
--- /dev/null
+++ b/src/federation.json
@@ -0,0 +1,4 @@
+[
+ {"url":"https://xrforge.isvery.ninja", "name":"XRForge", "thumbnail":""},
+ {"url":"https://www.janusxr.org", "name":"JanusXR", "thumbnail":""}
+]
diff --git a/src/page/admin/index.lua b/src/page/admin/index.lua
index cb5a3cf..761813b 100644
--- a/src/page/admin/index.lua
+++ b/src/page/admin/index.lua
@@ -1,6 +1,6 @@
local util = require("util")
-local data = util.merge({notification=''},{ opts = app.opts })
+local data = {notification=''}
data.plugins = ""
data.tabs = {}
@@ -15,13 +15,13 @@ function pluginInput(opt)
]]
- local value = app.opts[ opt.key:gsub("^app.opts.","") ]
+ local key = opt.key:gsub("^app.opts.","")
+ local value = app.opts[ key ]
if type(opt.values) == 'table' then
html = html .. util.template('\n"
else
- html = html .. util.template('',{
+ html = html .. util.template('',{
key = opt.key,
- value = value
+ value = value,
+ placeholder= opt.placeholder or ''
})
end
html = html .. util.template([[
${info}
-
]],opt)
return html
end
-function pluginUI(seen)
+function pluginUI(seen,data)
return function(name,plugin)
if seen[ plugin.name ] then return end -- already processed
table.insert( data.tabs, plugin.name ) -- insert tabname
@@ -63,8 +63,8 @@ function saveData()
local params = GetParams()
local saveconfig = false
foreach(params, function(k,v)
- if v[1]:match("^opts.") then
- app.opts[ v[1]:gsub("^opts.","") ] = v[2] -- update
+ if v[1]:match("^app%.opts%.") then
+ app.opts[ v[1]:gsub("^app%.opts%.","") ] = v[2] -- update
saveconfig = true
end
end)
@@ -77,9 +77,11 @@ function saveData()
end
end
-local seen = {}
-foreach( app.plugin, pluginUI(seen) )
saveData()
+
+data = util.merge(data,{ opts = app.opts })
+local seen = {}
+foreach( app.plugin, pluginUI(seen,data) )
local html = util.template( LoadAsset("page/admin/index.html"),data)
diff --git a/src/page/admin/plugin.html b/src/page/admin/plugin.html
index 9a5bc3c..b426fd3 100644
--- a/src/page/admin/plugin.html
+++ b/src/page/admin/plugin.html
@@ -15,11 +15,21 @@
{if #plugin.info > 2 then
- print([[
-
- ${plugin.info}
-
- ]])
+ if plugin.infowide then
+ print([[
+
+
+
+ ${plugin.info}
+
+ ]])
+ else
+ print([[
+
+ ${plugin.info}
+
+ ]])
+ end
end}
diff --git a/src/page/dir.lua b/src/page/dir.lua
index 6819ddd..d4eec15 100644
--- a/src/page/dir.lua
+++ b/src/page/dir.lua
@@ -21,29 +21,30 @@ return {
local matchpath = urlpath
if urlpath == '/' then matchpath = rootpath end -- generate janusxr footer for root url
foreach( app.cache.items, function(k,v)
- if v.parent then
- trace( "path = " .. util.stripPath(v.path) .. " " ..
- "parent = " .. util.stripPath(v.parent.path) .. " " ..
- "matchpath = " .. util.stripPath(matchpath)
- )
- if match(v.path, matchpath) then -- show parent folder if any
- local parent = app.cache.items[ v.parent.id ]
- if v.portal then
- redirect = v.portal.url
+ if req.authenticated or app.plugin.moderation.isAllowed(v) then
+ if v.parent then
+ trace( "path = " .. util.stripPath(v.path) .. " " ..
+ "parent = " .. util.stripPath(v.parent.path) .. " " ..
+ "matchpath = " .. util.stripPath(matchpath)
+ )
+ if match(v.path, matchpath) then -- show parent folder if any
+ local parent = app.cache.items[ v.parent.id ]
+ if v.portal then
+ redirect = v.portal.url
+ end
+ if match(parent.path, app.opts.plugin_diskscan_dir) then -- add rootfolder if needed
+ local root = app.cache.items[ app.opts.plugin_diskscan_dir ]
+ --res.items[ root.id ] = root
+ end
end
- if match(parent.path, app.opts.plugin_diskscan_dir) then -- add rootfolder if needed
- local root = app.cache.items[ app.opts.plugin_diskscan_dir ]
- --res.items[ root.id ] = root
+
+ -- add items from dir
+ if match(v.parent.path, matchpath) and (v.dir or v.view3D) then
+ local parent = app.cache.items[ v.parent.id ]
+ res.items[ v.id ] = v
+ res.parent = parent
end
end
-
- -- add items from dir
- if match(v.parent.path, matchpath) and (v.dir or v.view3D) then
- local parent = app.cache.items[ v.parent.id ]
- res.items[ v.id ] = v
- res.parent = parent
- end
-
end
end)
local data = util.merge({
@@ -72,6 +73,7 @@ return {
if data.item then
res.template = 'page/index.html'
end
+ app.pub("dir", nil, {res=res,data=data, urlpath=urlpath, isdir=isdir})
app.cache.url[ urlpath ] = util.templateFile( res.template, data)
end
if redirect then
diff --git a/src/page/footer.janusxr.html b/src/page/footer.janusxr.html
index 834ead2..a0b32ec 100644
--- a/src/page/footer.janusxr.html
+++ b/src/page/footer.janusxr.html
@@ -7,9 +7,9 @@
end
end}
-
+
-
+
{if item then
print('\t')
end}
@@ -22,7 +22,7 @@
local center = -((#items*width)/1.3) + (i*width)
local url = v.url
if v.dir then url = url .. "/" end
- print('\t\t