feat: Phase 1 — extract the client (web SPA + desktop) to dtoro/oikos-web
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled

Problem: the hexagonal refactor churns the backend tree for nine more
phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper,
compose/web image) must move to its own repo first so doc/layout
rewrites land once on a backend-only tree.

Change:
- New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/
  (updateURL repointed to oikos-web releases), compose/, own CI (web +
  desktop jobs), own deploy script (CI-green gate, TOCTOU guard,
  version-tagged images, prune-to-3), own webhook receiver on :9798 +
  launchd unit, own compose project publishing the same 8091:80.
- Cutover executed on mac-mini in order: oikos stack's web service
  stopped+removed, oikos-web project brought up on 8091; outer Caddy
  untouched (targets the published port) — serving + Authentik flow +
  /wails 404 quirk verified post-cutover.
- Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI
  workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install
  targets, the compose web service, oikos-web from deploy.sh's fallback
  prune list; wails + go-keyring dropped from go.mod, vendor synced.
- README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now
  point at the new repo; mbse + mascot design docs carry a path note.

Risk: production SPA serving depends on the new pipeline now; rollback
is versioned-image re-up of the old web service from a pre-split
checkout (port 8091). Desktop builds installed before the split still
check dtoro/oikos releases — one manual reinstall, noted in the
oikos-web release notes.

Verification: go vet, make test (race), make generate-check, golangci
(no new findings; baseline down 400→365); post-cutover curls —
localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
This commit is contained in:
2026-08-15 22:27:52 +02:00
parent e074f04bdf
commit d4d99a7473
18854 changed files with 2615729 additions and 173735 deletions

2
web/node_modules/isexe/.npmignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
.nyc_output/
coverage/

15
web/node_modules/isexe/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,15 @@
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

51
web/node_modules/isexe/README.md generated vendored Normal file
View File

@@ -0,0 +1,51 @@
# isexe
Minimal module to check if a file is executable, and a normal file.
Uses `fs.stat` and tests against the `PATHEXT` environment variable on
Windows.
## USAGE
```javascript
var isexe = require('isexe')
isexe('some-file-name', function (err, isExe) {
if (err) {
console.error('probably file does not exist or something', err)
} else if (isExe) {
console.error('this thing can be run')
} else {
console.error('cannot be run')
}
})
// same thing but synchronous, throws errors
var isExe = isexe.sync('some-file-name')
// treat errors as just "not executable"
isexe('maybe-missing-file', { ignoreErrors: true }, callback)
var isExe = isexe.sync('maybe-missing-file', { ignoreErrors: true })
```
## API
### `isexe(path, [options], [callback])`
Check if the path is executable. If no callback provided, and a
global `Promise` object is available, then a Promise will be returned.
Will raise whatever errors may be raised by `fs.stat`, unless
`options.ignoreErrors` is set to true.
### `isexe.sync(path, [options])`
Same as `isexe` but returns the value and throws any errors raised.
### Options
* `ignoreErrors` Treat all errors as "no, this is not executable", but
don't raise them.
* `uid` Number to use as the user id
* `gid` Number to use as the group id
* `pathExt` List of path extensions to use instead of `PATHEXT`
environment variable on Windows.

57
web/node_modules/isexe/index.js generated vendored Normal file
View File

@@ -0,0 +1,57 @@
var fs = require('fs')
var core
if (process.platform === 'win32' || global.TESTING_WINDOWS) {
core = require('./windows.js')
} else {
core = require('./mode.js')
}
module.exports = isexe
isexe.sync = sync
function isexe (path, options, cb) {
if (typeof options === 'function') {
cb = options
options = {}
}
if (!cb) {
if (typeof Promise !== 'function') {
throw new TypeError('callback not provided')
}
return new Promise(function (resolve, reject) {
isexe(path, options || {}, function (er, is) {
if (er) {
reject(er)
} else {
resolve(is)
}
})
})
}
core(path, options || {}, function (er, is) {
// ignore EACCES because that just means we aren't allowed to run it
if (er) {
if (er.code === 'EACCES' || options && options.ignoreErrors) {
er = null
is = false
}
}
cb(er, is)
})
}
function sync (path, options) {
// my kingdom for a filtered catch
try {
return core.sync(path, options || {})
} catch (er) {
if (options && options.ignoreErrors || er.code === 'EACCES') {
return false
} else {
throw er
}
}
}

41
web/node_modules/isexe/mode.js generated vendored Normal file
View File

@@ -0,0 +1,41 @@
module.exports = isexe
isexe.sync = sync
var fs = require('fs')
function isexe (path, options, cb) {
fs.stat(path, function (er, stat) {
cb(er, er ? false : checkStat(stat, options))
})
}
function sync (path, options) {
return checkStat(fs.statSync(path), options)
}
function checkStat (stat, options) {
return stat.isFile() && checkMode(stat, options)
}
function checkMode (stat, options) {
var mod = stat.mode
var uid = stat.uid
var gid = stat.gid
var myUid = options.uid !== undefined ?
options.uid : process.getuid && process.getuid()
var myGid = options.gid !== undefined ?
options.gid : process.getgid && process.getgid()
var u = parseInt('100', 8)
var g = parseInt('010', 8)
var o = parseInt('001', 8)
var ug = u | g
var ret = (mod & o) ||
(mod & g) && gid === myGid ||
(mod & u) && uid === myUid ||
(mod & ug) && myUid === 0
return ret
}

31
web/node_modules/isexe/package.json generated vendored Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "isexe",
"version": "2.0.0",
"description": "Minimal module to check if a file is executable.",
"main": "index.js",
"directories": {
"test": "test"
},
"devDependencies": {
"mkdirp": "^0.5.1",
"rimraf": "^2.5.0",
"tap": "^10.3.0"
},
"scripts": {
"test": "tap test/*.js --100",
"preversion": "npm test",
"postversion": "npm publish",
"postpublish": "git push origin --all; git push origin --tags"
},
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
"license": "ISC",
"repository": {
"type": "git",
"url": "git+https://github.com/isaacs/isexe.git"
},
"keywords": [],
"bugs": {
"url": "https://github.com/isaacs/isexe/issues"
},
"homepage": "https://github.com/isaacs/isexe#readme"
}

221
web/node_modules/isexe/test/basic.js generated vendored Normal file
View File

@@ -0,0 +1,221 @@
var t = require('tap')
var fs = require('fs')
var path = require('path')
var fixture = path.resolve(__dirname, 'fixtures')
var meow = fixture + '/meow.cat'
var mine = fixture + '/mine.cat'
var ours = fixture + '/ours.cat'
var fail = fixture + '/fail.false'
var noent = fixture + '/enoent.exe'
var mkdirp = require('mkdirp')
var rimraf = require('rimraf')
var isWindows = process.platform === 'win32'
var hasAccess = typeof fs.access === 'function'
var winSkip = isWindows && 'windows'
var accessSkip = !hasAccess && 'no fs.access function'
var hasPromise = typeof Promise === 'function'
var promiseSkip = !hasPromise && 'no global Promise'
function reset () {
delete require.cache[require.resolve('../')]
return require('../')
}
t.test('setup fixtures', function (t) {
rimraf.sync(fixture)
mkdirp.sync(fixture)
fs.writeFileSync(meow, '#!/usr/bin/env cat\nmeow\n')
fs.chmodSync(meow, parseInt('0755', 8))
fs.writeFileSync(fail, '#!/usr/bin/env false\n')
fs.chmodSync(fail, parseInt('0644', 8))
fs.writeFileSync(mine, '#!/usr/bin/env cat\nmine\n')
fs.chmodSync(mine, parseInt('0744', 8))
fs.writeFileSync(ours, '#!/usr/bin/env cat\nours\n')
fs.chmodSync(ours, parseInt('0754', 8))
t.end()
})
t.test('promise', { skip: promiseSkip }, function (t) {
var isexe = reset()
t.test('meow async', function (t) {
isexe(meow).then(function (is) {
t.ok(is)
t.end()
})
})
t.test('fail async', function (t) {
isexe(fail).then(function (is) {
t.notOk(is)
t.end()
})
})
t.test('noent async', function (t) {
isexe(noent).catch(function (er) {
t.ok(er)
t.end()
})
})
t.test('noent ignore async', function (t) {
isexe(noent, { ignoreErrors: true }).then(function (is) {
t.notOk(is)
t.end()
})
})
t.end()
})
t.test('no promise', function (t) {
global.Promise = null
var isexe = reset()
t.throws('try to meow a promise', function () {
isexe(meow)
})
t.end()
})
t.test('access', { skip: accessSkip || winSkip }, function (t) {
runTest(t)
})
t.test('mode', { skip: winSkip }, function (t) {
delete fs.access
delete fs.accessSync
var isexe = reset()
t.ok(isexe.sync(ours, { uid: 0, gid: 0 }))
t.ok(isexe.sync(mine, { uid: 0, gid: 0 }))
runTest(t)
})
t.test('windows', function (t) {
global.TESTING_WINDOWS = true
var pathExt = '.EXE;.CAT;.CMD;.COM'
t.test('pathExt option', function (t) {
runTest(t, { pathExt: '.EXE;.CAT;.CMD;.COM' })
})
t.test('pathExt env', function (t) {
process.env.PATHEXT = pathExt
runTest(t)
})
t.test('no pathExt', function (t) {
// with a pathExt of '', any filename is fine.
// so the "fail" one would still pass.
runTest(t, { pathExt: '', skipFail: true })
})
t.test('pathext with empty entry', function (t) {
// with a pathExt of '', any filename is fine.
// so the "fail" one would still pass.
runTest(t, { pathExt: ';' + pathExt, skipFail: true })
})
t.end()
})
t.test('cleanup', function (t) {
rimraf.sync(fixture)
t.end()
})
function runTest (t, options) {
var isexe = reset()
var optionsIgnore = Object.create(options || {})
optionsIgnore.ignoreErrors = true
if (!options || !options.skipFail) {
t.notOk(isexe.sync(fail, options))
}
t.notOk(isexe.sync(noent, optionsIgnore))
if (!options) {
t.ok(isexe.sync(meow))
} else {
t.ok(isexe.sync(meow, options))
}
t.ok(isexe.sync(mine, options))
t.ok(isexe.sync(ours, options))
t.throws(function () {
isexe.sync(noent, options)
})
t.test('meow async', function (t) {
if (!options) {
isexe(meow, function (er, is) {
if (er) {
throw er
}
t.ok(is)
t.end()
})
} else {
isexe(meow, options, function (er, is) {
if (er) {
throw er
}
t.ok(is)
t.end()
})
}
})
t.test('mine async', function (t) {
isexe(mine, options, function (er, is) {
if (er) {
throw er
}
t.ok(is)
t.end()
})
})
t.test('ours async', function (t) {
isexe(ours, options, function (er, is) {
if (er) {
throw er
}
t.ok(is)
t.end()
})
})
if (!options || !options.skipFail) {
t.test('fail async', function (t) {
isexe(fail, options, function (er, is) {
if (er) {
throw er
}
t.notOk(is)
t.end()
})
})
}
t.test('noent async', function (t) {
isexe(noent, options, function (er, is) {
t.ok(er)
t.notOk(is)
t.end()
})
})
t.test('noent ignore async', function (t) {
isexe(noent, optionsIgnore, function (er, is) {
if (er) {
throw er
}
t.notOk(is)
t.end()
})
})
t.test('directory is not executable', function (t) {
isexe(__dirname, options, function (er, is) {
if (er) {
throw er
}
t.notOk(is)
t.end()
})
})
t.end()
}

42
web/node_modules/isexe/windows.js generated vendored Normal file
View File

@@ -0,0 +1,42 @@
module.exports = isexe
isexe.sync = sync
var fs = require('fs')
function checkPathExt (path, options) {
var pathext = options.pathExt !== undefined ?
options.pathExt : process.env.PATHEXT
if (!pathext) {
return true
}
pathext = pathext.split(';')
if (pathext.indexOf('') !== -1) {
return true
}
for (var i = 0; i < pathext.length; i++) {
var p = pathext[i].toLowerCase()
if (p && path.substr(-p.length).toLowerCase() === p) {
return true
}
}
return false
}
function checkStat (stat, path, options) {
if (!stat.isSymbolicLink() && !stat.isFile()) {
return false
}
return checkPathExt(path, options)
}
function isexe (path, options, cb) {
fs.stat(path, function (er, stat) {
cb(er, er ? false : checkStat(stat, path, options))
})
}
function sync (path, options) {
return checkStat(fs.statSync(path), path, options)
}