Guest isolation
This commit is contained in:
@@ -26,7 +26,7 @@ The following are set on `ctx` before the kernel starts (unless noted as overwri
|
|||||||
| **`bareOsCtxApiVersion`** | String semver for the documented **`ctx`** contract (e.g. **`1.0.0`**). Bump in [`bare-os-ctx-api.js`](../packages/bare-os-booter/lib/bare-os-ctx-api.js) when you make breaking changes to stable fields. |
|
| **`bareOsCtxApiVersion`** | String semver for the documented **`ctx`** contract (e.g. **`1.0.0`**). Bump in [`bare-os-ctx-api.js`](../packages/bare-os-booter/lib/bare-os-ctx-api.js) when you make breaking changes to stable fields. |
|
||||||
| **`disk`** | Disk bundle used during boot (includes drives and helpers); advanced use |
|
| **`disk`** | Disk bundle used during boot (includes drives and helpers); advanced use |
|
||||||
| **`drive`** | **System** Hyperdrive (`ctx.drive` is the OS image: `/bin`, `/boot`, …) |
|
| **`drive`** | **System** Hyperdrive (`ctx.drive` is the OS image: `/bin`, `/boot`, …) |
|
||||||
| **`personalDrive`** | **Personal** Hyperdrive (mutable per-user state; also reachable via VFS under `$HOME`) |
|
| **`personalDrive`** | **Personal** Hyperdrive (mutable per-user state; VFS maps **`$HOME`** to **`/.bare-os/home/<HOME-basename>/…`** and session **`/var/log`** to **`/.bare-os/var/log/<basename>/…`** so guest vs unlocked trees do not share the same keys) |
|
||||||
| **`vfs`** | Path layer: resolves logical paths, routes to system vs personal drive, implements `mkdir`, `readFile`, etc. See [`vfs.js`](../packages/bare-os-booter/lib/vfs.js) |
|
| **`vfs`** | Path layer: resolves logical paths, routes to system vs personal drive, implements `mkdir`, `readFile`, etc. See [`vfs.js`](../packages/bare-os-booter/lib/vfs.js) |
|
||||||
| **`env`** | Shell environment object (`HOME`, `PATH`, `USER`, …), same object as **`vfs.env`**. Mutated by builtins (`export`, `cd` updates `PWD`, identity unlock updates user fields). After each **`execLine`**, **`BARE_OS_EXIT_STATUS`** holds the last command’s exit code as a decimal string (POSIX **`$?`** parity); use **`$?`** or **`${?}`** in shell words for expansion. |
|
| **`env`** | Shell environment object (`HOME`, `PATH`, `USER`, …), same object as **`vfs.env`**. Mutated by builtins (`export`, `cd` updates `PWD`, identity unlock updates user fields). After each **`execLine`**, **`BARE_OS_EXIT_STATUS`** holds the last command’s exit code as a decimal string (POSIX **`$?`** parity); use **`$?`** or **`${?}`** in shell words for expansion. |
|
||||||
| **`b4a`** | **`b4a`** module (byte helpers); used to convert Hyperdrive buffers to strings |
|
| **`b4a`** | **`b4a`** module (byte helpers); used to convert Hyperdrive buffers to strings |
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Cleanup path closes swarm/drives and calls **`session.cleanup()`**, which runs *
|
|||||||
|
|
||||||
`lib/vfs.js` implements **`resolveLogical`** with **`unix-path-resolve(cwd, userPath)`** (two arguments only—important when reading the code).
|
`lib/vfs.js` implements **`resolveLogical`** with **`unix-path-resolve(cwd, userPath)`** (two arguments only—important when reading the code).
|
||||||
|
|
||||||
- Paths under **`$HOME`** resolve to the **personal** Hyperdrive (mutable `writeFile` / `unlink` where policy allows).
|
- Paths under **`$HOME`** resolve to the **personal** Hyperdrive under **`/.bare-os/home/<HOME-basename>/…`** (mutable `writeFile` / `unlink` where policy allows); writable **`/var/log`** uses **`/.bare-os/var/log/<same-basename>/…`** on that drive.
|
||||||
- Other absolute paths hit the **system** drive (OS image).
|
- Other absolute paths hit the **system** drive (OS image).
|
||||||
- Hyperdrive rejects **`/`** as a filename; the VFS special-cases **logical root** for `stat`, `chdir`, `exists`.
|
- Hyperdrive rejects **`/`** as a filename; the VFS special-cases **logical root** for `stat`, `chdir`, `exists`.
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ On boot, **`applyGuestEnv`** sets:
|
|||||||
|
|
||||||
The personal drive still persists: guest data is **not** anonymous to the drive—it is simply the **unauthenticated** profile.
|
The personal drive still persists: guest data is **not** anonymous to the drive—it is simply the **unauthenticated** profile.
|
||||||
|
|
||||||
|
**Home and session logs on disk:** logical **`$HOME`** and **`/var/log`** map to the personal Hyperdrive under **`/.bare-os/home/<basename>`** and **`/.bare-os/var/log/<basename>`**, where **`<basename>`** is the first segment of **`HOME`** (e.g. `guest` or the 12-hex display name). That keeps guest and unlocked trees separate on the same drive. Shared machine metadata (**`/.bare/account`**, **`/.bare/hdms/`**, vault blobs, etc.) stays outside those prefixes. On first boot after an upgrade from older booters, a best-effort migration may move non-reserved files from the personal drive root into the current session’s home prefix when that prefix is still empty.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Account blob: `/.bare/account`
|
## Account blob: `/.bare/account`
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -12,7 +12,7 @@ export const INITD_LOG = `${BARE_OS_VAR_LOG_DIR}/initd.log`
|
|||||||
|
|
||||||
const README_REL = `${BARE_OS_VAR_LOG_DIR}/README`
|
const README_REL = `${BARE_OS_VAR_LOG_DIR}/README`
|
||||||
|
|
||||||
const README_TEXT = `Bare OS session logs (mirrored on your personal drive under /.bare-os/var/log).
|
const README_TEXT = `Bare OS session logs (mirrored on your personal drive under /.bare-os/var/log/<HOME-basename>/).
|
||||||
kernel-console.log — console.log / console.error from the kernel session
|
kernel-console.log — console.log / console.error from the kernel session
|
||||||
cron.log — bare-cron job errors
|
cron.log — bare-cron job errors
|
||||||
initd.log — bare-initd service start failures
|
initd.log — bare-initd service start failures
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export async function applyUnlockedEnv(ctx, publicKey, secretKey) {
|
|||||||
env.LOGNAME = name
|
env.LOGNAME = name
|
||||||
env.HOME = home
|
env.HOME = home
|
||||||
env.PWD = home
|
env.PWD = home
|
||||||
|
await migrateLegacyPersonalHomeIfNeeded(ctx)
|
||||||
const uid = uidFromPublicKey(publicKey)
|
const uid = uidFromPublicKey(publicKey)
|
||||||
env.UID = uid
|
env.UID = uid
|
||||||
env.GID = uid
|
env.GID = uid
|
||||||
@@ -145,9 +146,10 @@ export async function ensureBareDir(ctx) {
|
|||||||
*/
|
*/
|
||||||
export async function ensureGuestHome(ctx) {
|
export async function ensureGuestHome(ctx) {
|
||||||
await ensureBareDir(ctx)
|
await ensureBareDir(ctx)
|
||||||
|
await migrateLegacyPersonalHomeIfNeeded(ctx)
|
||||||
const drive = ctx.personalDrive
|
const drive = ctx.personalDrive
|
||||||
if (!drive || typeof drive.put !== 'function') return
|
if (!drive || typeof drive.put !== 'function') return
|
||||||
const keep = '/.keep_guest'
|
const keep = '/.bare-os/home/guest/.keep_guest'
|
||||||
try {
|
try {
|
||||||
const existing = await drive.get(keep)
|
const existing = await drive.get(keep)
|
||||||
if (existing) return
|
if (existing) return
|
||||||
@@ -217,6 +219,8 @@ const VAULT_EXCLUDE_PREFIXES = [
|
|||||||
'bare',
|
'bare',
|
||||||
'.bare/',
|
'.bare/',
|
||||||
'.bare',
|
'.bare',
|
||||||
|
'.bare-os/var/',
|
||||||
|
'.bare-os/var',
|
||||||
'.vault/',
|
'.vault/',
|
||||||
'.vault',
|
'.vault',
|
||||||
'bin/',
|
'bin/',
|
||||||
@@ -238,6 +242,106 @@ function shouldVaultSkip(short) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top-level personal-drive names we do not lift from `/` into `/.bare-os/home/<seg>/`
|
||||||
|
* during legacy migration (machine metadata, new layout root, old guest marker).
|
||||||
|
* @param {string} name single path segment (no slashes)
|
||||||
|
*/
|
||||||
|
function legacyRootMigrateSkip(name) {
|
||||||
|
if (shouldVaultSkip(name)) return true
|
||||||
|
if (name === '.bare-os' || name === '.keep_guest') return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('hyperdrive').default} drive
|
||||||
|
* @param {string} prefix absolute e.g. /.bare-os/home/guest
|
||||||
|
*/
|
||||||
|
async function personalHomePrefixIsEmpty(drive, prefix) {
|
||||||
|
try {
|
||||||
|
const names = await driveReaddirNames(drive, prefix)
|
||||||
|
return names.length === 0
|
||||||
|
} catch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('hyperdrive').default} drive
|
||||||
|
* @param {string} fromAbs
|
||||||
|
* @param {string} toAbs
|
||||||
|
*/
|
||||||
|
async function moveDriveSubtree(drive, fromAbs, toAbs) {
|
||||||
|
const ent = await drive.entry(fromAbs, { follow: false })
|
||||||
|
if (!ent?.value) return
|
||||||
|
const v = ent.value
|
||||||
|
if (v.linkname != null) {
|
||||||
|
await drive.putEntry(toAbs, {
|
||||||
|
linkname: v.linkname,
|
||||||
|
executable: !!v.executable,
|
||||||
|
metadata: v.metadata ?? null
|
||||||
|
})
|
||||||
|
await drive.del(fromAbs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (v.blob) {
|
||||||
|
const data = await drive.get(fromAbs, { follow: true })
|
||||||
|
if (data) {
|
||||||
|
await drive.put(toAbs, data, {
|
||||||
|
executable: !!v.executable,
|
||||||
|
metadata: v.metadata ?? null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await drive.del(fromAbs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const names = await driveReaddirNames(drive, fromAbs)
|
||||||
|
for (const n of names) {
|
||||||
|
const f = fromAbs === '/' ? `/${n}` : `${fromAbs}/${n}`
|
||||||
|
const t = toAbs === '/' ? `/${n}` : `${toAbs}/${n}`
|
||||||
|
await moveDriveSubtree(drive, f, t)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await drive.del(fromAbs)
|
||||||
|
} catch {
|
||||||
|
/* directory may have no tombstone */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-time best-effort: flat personal-root files from older booters → `/.bare-os/home/<seg>/`.
|
||||||
|
* @param {Record<string, unknown>} ctx
|
||||||
|
*/
|
||||||
|
export async function migrateLegacyPersonalHomeIfNeeded(ctx) {
|
||||||
|
const drive = ctx.personalDrive
|
||||||
|
const vfs = ctx.vfs
|
||||||
|
if (!drive || typeof drive.put !== 'function' || !vfs?.env) return
|
||||||
|
const home = vfs.env.HOME
|
||||||
|
if (typeof home !== 'string' || !home.startsWith('/home/')) return
|
||||||
|
const seg = home.slice('/home/'.length).split('/')[0]
|
||||||
|
if (!seg) return
|
||||||
|
const prefix = `/.bare-os/home/${seg}`
|
||||||
|
if (!(await personalHomePrefixIsEmpty(drive, prefix))) return
|
||||||
|
|
||||||
|
const rootNames = await driveReaddirNames(drive, '/')
|
||||||
|
for (const name of rootNames) {
|
||||||
|
if (legacyRootMigrateSkip(name)) continue
|
||||||
|
const from = `/${name}`
|
||||||
|
const to = `${prefix}/${name}`
|
||||||
|
try {
|
||||||
|
await moveDriveSubtree(drive, from, to)
|
||||||
|
} catch {
|
||||||
|
/* best-effort */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const oldGuest = await drive.get('/.keep_guest')
|
||||||
|
if (oldGuest) await drive.del('/.keep_guest')
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('hyperdrive').default} drive
|
* @param {import('hyperdrive').default} drive
|
||||||
* @param {string} dir
|
* @param {string} dir
|
||||||
|
|||||||
@@ -18,13 +18,10 @@ import {
|
|||||||
/** Empty-directory marker (must match {@link ./git-fs-adapter.js}). */
|
/** Empty-directory marker (must match {@link ./git-fs-adapter.js}). */
|
||||||
const DIR_MARKER = '.bareos_empty'
|
const DIR_MARKER = '.bareos_empty'
|
||||||
|
|
||||||
/** Personal-drive backing for logical `/var/log/…` (system drive is read-only). */
|
|
||||||
const VAR_LOG_STORAGE = '/.bare-os/var/log'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unified path view: system Hyperdrive for OS paths, personal Hyperdrive under $HOME,
|
* Unified path view: system Hyperdrive for OS paths, personal Hyperdrive under $HOME,
|
||||||
* optional HDMS mounts under /mnt/<label>/…, virtual /var with writable /var/log/…
|
* optional HDMS mounts under /mnt/<label>/…, virtual /var with writable /var/log/…
|
||||||
* on the personal drive at /.bare-os/var/log/…
|
* on the personal drive under /.bare-os/var/log/<home-seg>/… (session-isolated).
|
||||||
* @param {import('hyperdrive').default} systemDrive
|
* @param {import('hyperdrive').default} systemDrive
|
||||||
* @param {import('hyperdrive').default} personalDrive
|
* @param {import('hyperdrive').default} personalDrive
|
||||||
* @param {Record<string, string>} env
|
* @param {Record<string, string>} env
|
||||||
@@ -47,6 +44,18 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
|
|||||||
return seg || null
|
return seg || null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Session home tree on the personal drive (isolates guest vs unlocked users). */
|
||||||
|
function personalHomeStorageRoot() {
|
||||||
|
const seg = activeHomeBasename()
|
||||||
|
return seg ? `/.bare-os/home/${seg}` : '/.bare-os/home/_nosession'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Personal-drive backing for logical `/var/log/…` (per session segment). */
|
||||||
|
function varLogStorageRoot() {
|
||||||
|
const seg = activeHomeBasename()
|
||||||
|
return seg ? `/.bare-os/var/log/${seg}` : '/.bare-os/var/log/_nosession'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `cd ~`, `touch ~/x`, etc. must map to $HOME — otherwise `unix-path-resolve(cwd, '~')`
|
* `cd ~`, `touch ~/x`, etc. must map to $HOME — otherwise `unix-path-resolve(cwd, '~')`
|
||||||
* becomes `/~` on the system drive (read-only).
|
* becomes `/~` on the system drive (read-only).
|
||||||
@@ -107,12 +116,13 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
|
|||||||
if (varNorm === '/var') {
|
if (varNorm === '/var') {
|
||||||
return { virtualVarRoot: true }
|
return { virtualVarRoot: true }
|
||||||
}
|
}
|
||||||
|
const varLogRoot = varLogStorageRoot()
|
||||||
if (varNorm === '/var/log' || absPath === '/var/log/') {
|
if (varNorm === '/var/log' || absPath === '/var/log/') {
|
||||||
return { drive: personalDrive, path: VAR_LOG_STORAGE }
|
return { drive: personalDrive, path: varLogRoot }
|
||||||
}
|
}
|
||||||
if (absPath.startsWith('/var/log/')) {
|
if (absPath.startsWith('/var/log/')) {
|
||||||
const rel = absPath.slice('/var/log/'.length).replace(/^\/+/, '')
|
const rel = absPath.slice('/var/log/'.length).replace(/^\/+/, '')
|
||||||
const p = rel ? unixPathResolve(VAR_LOG_STORAGE, rel) : VAR_LOG_STORAGE
|
const p = rel ? unixPathResolve(varLogRoot, rel) : varLogRoot
|
||||||
return { drive: personalDrive, path: p }
|
return { drive: personalDrive, path: p }
|
||||||
}
|
}
|
||||||
if (absPath.startsWith('/var/')) {
|
if (absPath.startsWith('/var/')) {
|
||||||
@@ -121,6 +131,7 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
|
|||||||
|
|
||||||
const h = normalizeHome()
|
const h = normalizeHome()
|
||||||
const activeSeg = activeHomeBasename()
|
const activeSeg = activeHomeBasename()
|
||||||
|
const homeRoot = personalHomeStorageRoot()
|
||||||
|
|
||||||
if (activeSeg && absPath === '/home') {
|
if (activeSeg && absPath === '/home') {
|
||||||
return { virtualHomeDir: true }
|
return { virtualHomeDir: true }
|
||||||
@@ -132,19 +143,22 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
|
|||||||
const seg = slash === -1 ? after : after.slice(0, slash)
|
const seg = slash === -1 ? after : after.slice(0, slash)
|
||||||
const rest = slash === -1 ? '' : after.slice(slash + 1)
|
const rest = slash === -1 ? '' : after.slice(slash + 1)
|
||||||
if (seg === activeSeg) {
|
if (seg === activeSeg) {
|
||||||
const sub = rest ? '/' + rest.replace(/^\//, '') : '/'
|
if (!rest) {
|
||||||
const p = unixPathResolve('/', sub)
|
return { drive: personalDrive, path: homeRoot }
|
||||||
|
}
|
||||||
|
const rel = rest.replace(/^\/+/, '')
|
||||||
|
const p = unixPathResolve(homeRoot, rel)
|
||||||
return { drive: personalDrive, path: p }
|
return { drive: personalDrive, path: p }
|
||||||
}
|
}
|
||||||
return { drive: systemDrive, path: absPath }
|
return { drive: systemDrive, path: absPath }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (absPath === h || absPath.startsWith(h + '/')) {
|
if (absPath === h || absPath.startsWith(h + '/')) {
|
||||||
const sub =
|
if (absPath === h) {
|
||||||
absPath === h
|
return { drive: personalDrive, path: homeRoot }
|
||||||
? '/'
|
}
|
||||||
: '/' + absPath.slice(h.length + 1).replace(/^\//, '')
|
const rel = absPath.slice(h.length + 1).replace(/^\/+/, '')
|
||||||
const p = unixPathResolve('/', sub)
|
const p = unixPathResolve(homeRoot, rel)
|
||||||
return { drive: personalDrive, path: p }
|
return { drive: personalDrive, path: p }
|
||||||
}
|
}
|
||||||
return { drive: systemDrive, path: absPath }
|
return { drive: systemDrive, path: absPath }
|
||||||
@@ -277,10 +291,16 @@ export function createVfs(systemDrive, personalDrive, env, mntRef = null) {
|
|||||||
if (
|
if (
|
||||||
normLog === '/var/log' &&
|
normLog === '/var/log' &&
|
||||||
r.drive === personalDrive &&
|
r.drive === personalDrive &&
|
||||||
p === VAR_LOG_STORAGE
|
p === varLogStorageRoot()
|
||||||
) {
|
) {
|
||||||
return { ...synthesizeStat(abs, true, env, 'directory'), path: abs }
|
return { ...synthesizeStat(abs, true, env, 'directory'), path: abs }
|
||||||
}
|
}
|
||||||
|
const h = normalizeHome()
|
||||||
|
const homeNorm = h.replace(/\/+$/, '') || h
|
||||||
|
const absNorm = abs.replace(/\/+$/, '') || abs
|
||||||
|
if (activeSeg && absNorm === homeNorm) {
|
||||||
|
return { ...synthesizeStat(absNorm, true, env, 'directory'), path: abs }
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,13 @@ function testCtx(drive, personal, env) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Personal-drive path backing logical `/home/<seg>/…` (matches `vfs.js`). */
|
||||||
|
function personalHomeBacking(homePath, rel = '') {
|
||||||
|
const seg = homePath.replace(/^\/home\//, '').split('/')[0] || 'user'
|
||||||
|
const r = String(rel).replace(/^\//, '')
|
||||||
|
return r ? `/.bare-os/home/${seg}/${r}` : `/.bare-os/home/${seg}`
|
||||||
|
}
|
||||||
|
|
||||||
test('runKernelFromSource invokes start(ctx)', async (t) => {
|
test('runKernelFromSource invokes start(ctx)', async (t) => {
|
||||||
const calls = []
|
const calls = []
|
||||||
const source = `
|
const source = `
|
||||||
@@ -135,7 +142,7 @@ test('runBinCommand runs bare hello.js from cwd on personal drive', async (t) =>
|
|||||||
await drive.ready()
|
await drive.ready()
|
||||||
await personal.ready()
|
await personal.ready()
|
||||||
await personal.put(
|
await personal.put(
|
||||||
'/hello.js',
|
personalHomeBacking('/home/user', 'hello.js'),
|
||||||
b4a.from(`
|
b4a.from(`
|
||||||
async function run(ctx, argv) {
|
async function run(ctx, argv) {
|
||||||
ctx.out.push(argv.join(' '))
|
ctx.out.push(argv.join(' '))
|
||||||
@@ -159,7 +166,7 @@ test('runBinCommand strips shebang from user script', async (t) => {
|
|||||||
await drive.ready()
|
await drive.ready()
|
||||||
await personal.ready()
|
await personal.ready()
|
||||||
await personal.put(
|
await personal.put(
|
||||||
'/x.js',
|
personalHomeBacking('/home/user', 'x.js'),
|
||||||
b4a.from(`#!/usr/bin/env bare
|
b4a.from(`#!/usr/bin/env bare
|
||||||
async function run(ctx) { ctx.out.push('ok') }
|
async function run(ctx) { ctx.out.push('ok') }
|
||||||
`)
|
`)
|
||||||
@@ -180,7 +187,10 @@ test('runBinCommand user script error is caught and logged', async (t) => {
|
|||||||
const personal = new Hyperdrive(store.namespace('ptj'))
|
const personal = new Hyperdrive(store.namespace('ptj'))
|
||||||
await drive.ready()
|
await drive.ready()
|
||||||
await personal.ready()
|
await personal.ready()
|
||||||
await personal.put('/bad.js', b4a.from(`async function run() { test() }`))
|
await personal.put(
|
||||||
|
personalHomeBacking('/home/user', 'bad.js'),
|
||||||
|
b4a.from(`async function run() { test() }`)
|
||||||
|
)
|
||||||
const errs = []
|
const errs = []
|
||||||
const ctx = testCtx(drive, personal)
|
const ctx = testCtx(drive, personal)
|
||||||
ctx.console = {
|
ctx.console = {
|
||||||
@@ -206,8 +216,8 @@ test('ls hides dotfiles unless -a', async (t) => {
|
|||||||
await drive.ready()
|
await drive.ready()
|
||||||
await personal.ready()
|
await personal.ready()
|
||||||
await drive.put('/bin/ls', b4a.from(lsSrc))
|
await drive.put('/bin/ls', b4a.from(lsSrc))
|
||||||
await personal.put('/shown.txt', b4a.from(''))
|
await personal.put(personalHomeBacking('/home/user', 'shown.txt'), b4a.from(''))
|
||||||
await personal.put('/.hidden', b4a.from(''))
|
await personal.put(personalHomeBacking('/home/user', '.hidden'), b4a.from(''))
|
||||||
const lines = []
|
const lines = []
|
||||||
const ctx = testCtx(drive, personal)
|
const ctx = testCtx(drive, personal)
|
||||||
ctx.console = {
|
ctx.console = {
|
||||||
@@ -346,7 +356,7 @@ test('vfs routes HOME to personal drive', async (t) => {
|
|||||||
const env = { HOME: '/home/user', PWD: '/home/user', PATH: '/bin' }
|
const env = { HOME: '/home/user', PWD: '/home/user', PATH: '/bin' }
|
||||||
const vfs = createVfs(sys, personal, env)
|
const vfs = createVfs(sys, personal, env)
|
||||||
await vfs.writeFile('f.txt', b4a.from('hi'))
|
await vfs.writeFile('f.txt', b4a.from('hi'))
|
||||||
const buf = await personal.get('/f.txt')
|
const buf = await personal.get(personalHomeBacking('/home/user', 'f.txt'))
|
||||||
t.ok(buf)
|
t.ok(buf)
|
||||||
t.is(b4a.toString(buf), 'hi')
|
t.is(b4a.toString(buf), 'hi')
|
||||||
const sysTry = await sys.get('/home/user/f.txt')
|
const sysTry = await sys.get('/home/user/f.txt')
|
||||||
@@ -387,7 +397,7 @@ test('vfs expands ~ and ~/ to $HOME (not read-only /~)', async (t) => {
|
|||||||
await vfs.chdir('~')
|
await vfs.chdir('~')
|
||||||
t.is(vfs.getcwd(), '/home/zuser')
|
t.is(vfs.getcwd(), '/home/zuser')
|
||||||
await vfs.writeFile('~/tilde.txt', b4a.from('ok'))
|
await vfs.writeFile('~/tilde.txt', b4a.from('ok'))
|
||||||
const buf = await personal.get('/tilde.txt')
|
const buf = await personal.get(personalHomeBacking('/home/zuser', 'tilde.txt'))
|
||||||
t.ok(buf)
|
t.ok(buf)
|
||||||
t.is(b4a.toString(buf), 'ok')
|
t.is(b4a.toString(buf), 'ok')
|
||||||
await store.close()
|
await store.close()
|
||||||
@@ -416,12 +426,66 @@ test('vfs lists virtual /home at root; /home shows only active session dir', asy
|
|||||||
t.alike(await vfsU.readdir('/home'), ['eeb18de988e9'])
|
t.alike(await vfsU.readdir('/home'), ['eeb18de988e9'])
|
||||||
t.is(await vfsU.stat('/home/guest'), null)
|
t.is(await vfsU.stat('/home/guest'), null)
|
||||||
await vfsU.writeFile('/home/eeb18de988e9/x', b4a.from('1'))
|
await vfsU.writeFile('/home/eeb18de988e9/x', b4a.from('1'))
|
||||||
const buf = await personal.get('/x')
|
const buf = await personal.get(
|
||||||
|
personalHomeBacking('/home/eeb18de988e9', 'x')
|
||||||
|
)
|
||||||
t.ok(buf)
|
t.ok(buf)
|
||||||
await store.close()
|
await store.close()
|
||||||
rmSync(dir, { recursive: true, force: true })
|
rmSync(dir, { recursive: true, force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('vfs isolates home and /var/log per HOME basename on personal drive', async (t) => {
|
||||||
|
const dir = testCorestoreDir('vfsisol')
|
||||||
|
const store = new Corestore(dir)
|
||||||
|
const sys = new Hyperdrive(store)
|
||||||
|
const personal = new Hyperdrive(store.namespace('pvis'))
|
||||||
|
await sys.ready()
|
||||||
|
await personal.ready()
|
||||||
|
const vfsGuest = createVfs(sys, personal, {
|
||||||
|
HOME: '/home/guest',
|
||||||
|
PWD: '/home/guest',
|
||||||
|
PATH: '/bin'
|
||||||
|
})
|
||||||
|
await vfsGuest.writeFile('shared-name.txt', b4a.from('guest-data'))
|
||||||
|
await vfsGuest.writeFile('/var/log/bare-os/g.log', b4a.from('glog'))
|
||||||
|
const vfsUser = createVfs(sys, personal, {
|
||||||
|
HOME: '/home/alice12hex',
|
||||||
|
PWD: '/home/alice12hex',
|
||||||
|
PATH: '/bin'
|
||||||
|
})
|
||||||
|
t.is(await vfsUser.readFile('/home/alice12hex/shared-name.txt'), null)
|
||||||
|
await vfsUser.writeFile('shared-name.txt', b4a.from('user-data'))
|
||||||
|
t.is(
|
||||||
|
b4a.toString(
|
||||||
|
await personal.get(personalHomeBacking('/home/guest', 'shared-name.txt'))
|
||||||
|
),
|
||||||
|
'guest-data'
|
||||||
|
)
|
||||||
|
t.is(
|
||||||
|
b4a.toString(
|
||||||
|
await personal.get(
|
||||||
|
personalHomeBacking('/home/alice12hex', 'shared-name.txt')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'user-data'
|
||||||
|
)
|
||||||
|
t.is(
|
||||||
|
b4a.toString(
|
||||||
|
await personal.get('/.bare-os/var/log/guest/bare-os/g.log')
|
||||||
|
),
|
||||||
|
'glog'
|
||||||
|
)
|
||||||
|
await vfsUser.writeFile('/var/log/bare-os/u.log', b4a.from('ulog'))
|
||||||
|
t.is(
|
||||||
|
b4a.toString(
|
||||||
|
await personal.get('/.bare-os/var/log/alice12hex/bare-os/u.log')
|
||||||
|
),
|
||||||
|
'ulog'
|
||||||
|
)
|
||||||
|
await store.close()
|
||||||
|
rmSync(dir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
test('vfs /mnt lists HDMS mounts and allows writable put', async (t) => {
|
test('vfs /mnt lists HDMS mounts and allows writable put', async (t) => {
|
||||||
const dir = testCorestoreDir('vfsmnt')
|
const dir = testCorestoreDir('vfsmnt')
|
||||||
const store = new Corestore(dir)
|
const store = new Corestore(dir)
|
||||||
@@ -464,10 +528,12 @@ test('vfs /var in root readdir; /var/log empty lstat; writes map to personal', a
|
|||||||
t.is(stLog.type, 'directory')
|
t.is(stLog.type, 'directory')
|
||||||
await vfs.mkdir('/var/log/bare-os', { recursive: true })
|
await vfs.mkdir('/var/log/bare-os', { recursive: true })
|
||||||
await vfs.writeFile('/var/log/bare-os/test-vfs.log', b4a.from('hello'))
|
await vfs.writeFile('/var/log/bare-os/test-vfs.log', b4a.from('hello'))
|
||||||
const buf = await personal.get('/.bare-os/var/log/bare-os/test-vfs.log')
|
const buf = await personal.get(
|
||||||
|
'/.bare-os/var/log/u/bare-os/test-vfs.log'
|
||||||
|
)
|
||||||
t.ok(buf)
|
t.ok(buf)
|
||||||
t.is(b4a.toString(buf), 'hello')
|
t.is(b4a.toString(buf), 'hello')
|
||||||
const sysTry = await sys.get('/.bare-os/var/log/bare-os/test-vfs.log')
|
const sysTry = await sys.get('/.bare-os/var/log/u/bare-os/test-vfs.log')
|
||||||
t.is(sysTry, null)
|
t.is(sysTry, null)
|
||||||
await store.close()
|
await store.close()
|
||||||
rmSync(dir, { recursive: true, force: true })
|
rmSync(dir, { recursive: true, force: true })
|
||||||
@@ -577,7 +643,7 @@ test('loadBarerc applies export and alias from personal ~/.barerc', async (t) =>
|
|||||||
await drive.ready()
|
await drive.ready()
|
||||||
await personal.ready()
|
await personal.ready()
|
||||||
await personal.put(
|
await personal.put(
|
||||||
'/.barerc',
|
personalHomeBacking('/home/user', '.barerc'),
|
||||||
b4a.from('export MYRC=1\nalias dog=echo woof\n')
|
b4a.from('export MYRC=1\nalias dog=echo woof\n')
|
||||||
)
|
)
|
||||||
const ctx = testCtx(drive, personal)
|
const ctx = testCtx(drive, personal)
|
||||||
@@ -1418,8 +1484,14 @@ test('tier-1 rm -rf removes directory tree on personal drive', async (t) => {
|
|||||||
const ctx = testCtx(drive, personal)
|
const ctx = testCtx(drive, personal)
|
||||||
ctx.exitCode = 0
|
ctx.exitCode = 0
|
||||||
ctx.console = { log() {}, error() {} }
|
ctx.console = { log() {}, error() {} }
|
||||||
await personal.put('/nest/leaf/x.txt', b4a.from('x'))
|
await personal.put(
|
||||||
await personal.put('/nest/other/y.txt', b4a.from('y'))
|
personalHomeBacking('/home/user', 'nest/leaf/x.txt'),
|
||||||
|
b4a.from('x')
|
||||||
|
)
|
||||||
|
await personal.put(
|
||||||
|
personalHomeBacking('/home/user', 'nest/other/y.txt'),
|
||||||
|
b4a.from('y')
|
||||||
|
)
|
||||||
t.ok((await ctx.vfs.readdir('nest')).includes('leaf'))
|
t.ok((await ctx.vfs.readdir('nest')).includes('leaf'))
|
||||||
await runBinCommand(ctx, ['rm', '-rf', 'nest'])
|
await runBinCommand(ctx, ['rm', '-rf', 'nest'])
|
||||||
t.is(ctx.exitCode, 0)
|
t.is(ctx.exitCode, 0)
|
||||||
@@ -1482,7 +1554,7 @@ test('runBinCommand runs ./git from cwd instead of booter delegate', async (t) =
|
|||||||
await drive.ready()
|
await drive.ready()
|
||||||
await personal.ready()
|
await personal.ready()
|
||||||
await personal.put(
|
await personal.put(
|
||||||
'/git',
|
personalHomeBacking('/home/user', 'git'),
|
||||||
b4a.from(`
|
b4a.from(`
|
||||||
async function run(ctx) {
|
async function run(ctx) {
|
||||||
ctx.out.push('local-git-script')
|
ctx.out.push('local-git-script')
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user