Files
AgentHub/scripts/server/write-as.sh
T
nntrivi2001 0547488f13 chore: v2.8.13 — Artifacts reinstall/edit/installs, agent steps in chat, pick a local file to attach, Windows paths as links
- Chat: an Agent step shows its task, status and tool calls and opens its conversation in place; Windows paths (E:\…, C:/…) in replies open as files
- Prompt: attach a file on this computer by path (native picker), clearer too-large messages
- Artifacts: edit title/description and a skill's files (new snapshot), Reinstall, install history, Hub-only rows, skills installed into ~/.claude/skills and ~/.agents/skills
- Files: batch upload of small files in one extract
- AI Tools: npm install falls back to --prefix ~/.local when the global prefix is not writable
2026-10-20 16:44:55 +07:00

63 lines
2.7 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)
# extract a tar on stdin unpacked into the directory (the dashboard's
# batch upload, a tar it wrote itself: folders and regular files
# only); an existing file is never replaced (`-k` fails instead)
# @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
set -eu
usage() {
echo "usage: write-as <absolute path> [append|replace|private|mkdir|remove|extract] | <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" ;;
extract) exec tar -x -k -p --no-same-owner -f - -C "$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