e94038481e
- File Explorer (Files in the sidebar, /files; also from a workspace's Files tab, portalled so no panel clips it), laid out like GNOME Files: places (Home, workspaces, Trash), Back/Forward/Up, path bar, search, icon grid with image thumbnails or list, labelled New/Upload, selection action bar, hidden files behind Ctrl+H, right-click menus and the usual keys. Make, rename, move, copy (within a workspace, between workspaces, home ↔ workspace), delete into a per-account trash restorable for 7 days, streamed upload of files and whole folders (≤ 500 MB a batch, Replace / Keep both / Skip on a taken name), download a file or a zip of anything. server/lib/file-explorer.js, /api/lanes/:id/fs/*, /api/homes/:who/fs/*. - The user's home: every dot entry at its top stays hidden and refused (also via a symlink, download, zip or as a copy source); an admin may view other users' homes read-only, audited. - write-as learns move/copy (re-run `sudo agenthub server install-system`). - Hardening: a write to a workspace re-checks its owner by the live role; S3 keeps its server.conf if the file stops reading and a tmux call never falls back to the host's server; the AI Proxy gateway marks what it relays and the dashboard refuses it; a NUL in a pane's argv/env is refused. - OpenAPI for the new routes (server/openapi-extra/files.js); docs (API, SERVER-MODE, CLAUDE.md) updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
2.4 KiB
Bash
59 lines
2.4 KiB
Bash
#!/bin/sh
|
|
# @file write-as.sh
|
|
# @description Server mode, one Linux user per account (S3): installed as
|
|
# /opt/agenthub/libexec/write-as. The dashboard runs it AS a member through
|
|
# sudo (`sudo -n -u ah-<name> /opt/agenthub/libexec/write-as <path> [mode]`)
|
|
# for every change it makes inside that member's home — the file editor, a
|
|
# `.gitignore` line, the files a pane reads (prompt, proxy settings, context
|
|
# copies, drops, a session's launch file) — because the dashboard itself may
|
|
# never write, chmod or delete there: the member can swap any name in their
|
|
# own home, and a write by another account would follow the swap. As the
|
|
# member this can only do what the member could. Content comes on stdin,
|
|
# never in argv (sudo logs argv). Modes:
|
|
# (none) stdin into the file in place (keeps its mode and owner)
|
|
# append stdin appended
|
|
# replace stdin into a new 0644 file renamed over the path (atomic)
|
|
# private same, 0600
|
|
# mkdir the directory, 0755, parents included
|
|
# remove the path, recursively (rm never follows a symlink it removes)
|
|
# move the path to the 3rd argument (rename; never onto an existing
|
|
# name — checked just before, and the dashboard checks first)
|
|
# copy the path, recursively, to the 3rd argument (symlinks copied as
|
|
# links, never followed; mode and times kept; never onto an
|
|
# existing name)
|
|
# @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
set -eu
|
|
usage() {
|
|
echo "usage: write-as <absolute path> [append|replace|private|mkdir|remove] | <path> move|copy <absolute dest>" >&2
|
|
exit 2
|
|
}
|
|
case "${2:-}" in
|
|
move|copy) [ $# -eq 3 ] || usage ;;
|
|
*) { [ $# -ge 1 ] && [ $# -le 2 ]; } || usage ;;
|
|
esac
|
|
for p in "$1" "${3:-/}"; do
|
|
case "$p" in
|
|
/*) ;;
|
|
*) echo "write-as: an absolute path is required" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
swap() {
|
|
t=$(mktemp "$1.XXXXXX")
|
|
cat >"$t" && chmod "$2" "$t" && mv -f "$t" "$1" || { rm -f "$t"; exit 1; }
|
|
}
|
|
case "${2:-}" in
|
|
"") exec cat >"$1" ;;
|
|
append) exec cat >>"$1" ;;
|
|
replace) swap "$1" 0644 ;;
|
|
private) swap "$1" 0600 ;;
|
|
mkdir) exec mkdir -p -m 0755 -- "$1" ;;
|
|
remove) exec rm -rf -- "$1" ;;
|
|
move)
|
|
[ -e "$3" ] || [ -L "$3" ] && { echo "write-as: $3 exists" >&2; exit 1; }
|
|
exec mv -T -- "$1" "$3" ;;
|
|
copy)
|
|
[ -e "$3" ] || [ -L "$3" ] && { echo "write-as: $3 exists" >&2; exit 1; }
|
|
exec cp -R -P -T --preserve=mode,timestamps -- "$1" "$3" ;;
|
|
*) echo "write-as: unknown mode: $2" >&2; exit 2 ;;
|
|
esac
|