machines.fs works on a machine’s files from your code the way you work on local ones. Every call runs as the machine’s user and sees the same files its commands see, so a file written here is there for the next exec, and the other way round.
Every call
The async Python client (
AsyncBoxd) has the same calls, awaited, and its write also takes an async iterable.
Paths
A path is either absolute, or relative to the machine user’s home directory,/home/boxd:
There is no shell in between, so nothing is expanded:
~ and $HOME are ordinary characters in a name, and "~/notes.md" means a directory called ~ inside the home directory.
Writing files
write replaces the whole file. Text is written as UTF-8. Missing parent directories are created, with mode 0755.
- A new file gets mode
0644, ormodeif you pass one, and belongs to the machine’s user. - An existing file keeps its mode and owner. Pass
modeto change the mode. - A symlink is written through: the link stays a link, and the file it points to gets the new contents, keeping its own mode.
Reading files
Only regular files can be read. A symlink is followed to its target.Large files
read and write hold the whole file in memory. For files of any size, stream instead: read_stream / readStream hands you chunks as they arrive, and write takes chunks to send as they are produced. There is no size limit either way.
readStream loop early cancels the transfer. In TypeScript the browser entry (@boxd-sh/sdk/web) collects a streamed write in memory before sending it.
Entries: stat, exists, list
stat, list and write describe files with an FsEntry:
exists follows symlinks, so a symlink that leads nowhere does not exist. It is also false for a path the machine’s user isn’t allowed to look at. It still raises when the machine itself can’t be reached.
list returns every entry of the directory, in no particular order, with no cap on how many. Large directories arrive in batches: iter_list / listIter gives you each entry as it arrives instead of waiting for all of them.
Directories, renames and removal
mkdirgives new directories mode0755, ormode. A file already at the path is an error.renamewithoverwriteoff fails if the destination exists, instead of replacing it.removeon a symlink removes the link, never what it points to, even withrecursive. A directory needsrecursive. The root directory is refused. A recursive removal never follows symlinks out of the tree. Disks mounted inside it are left alone, and the call then fails withcross_deviceonce everything else is gone.realpathworks like Python’sos.path.realpath: the parts of the path that don’t exist yet are kept as written.
Filenames that aren’t UTF-8
On the machine, a filename is a sequence of bytes. Any name works, including ones with tabs, newlines or bytes that aren’t valid UTF-8.- Python decodes names the way
os.fsdecodedoes: bytes that aren’t UTF-8 are kept as surrogate escapes, so passingentry.pathback to any call reaches the same file. Every path argument also acceptsbytes. - TypeScript decodes
nameandpathas UTF-8 for display, replacing invalid bytes.nameBytes,pathBytesandsymlinkTargetByteshold the exact bytes, and every path argument also accepts aUint8Array.realpathBytesisrealpathas exact bytes.
Errors
A failure inside the machine raises a filesystem error that says what went wrong and names the path. Each carries areason:
In Python, every filesystem error is a
MachineFilesystemError, which is both a BoxdError and an OSError, with errno, strerror and filename set the way a local failure sets them. The five common ones are also the matching builtin, so code written for local files handles them unchanged:
FilesystemError. Its code is the error name Node’s own fs uses, reason is the exact reason, and path is the path the call was about. The five common ones throw a subclass of it, so you can test the class or the code:
NotFoundError for a machine that doesn’t exist. In Python the five common filesystem classes also extend the matching status class (MachineFileNotFoundError is a NotFoundError, MachinePermissionError a PermissionDeniedError, the other three a ConflictError), so catch the filesystem class when you need to tell a missing file from a missing machine. In TypeScript FilesystemError extends BoxdError only. See the error sections of the Python and TypeScript references.
Machines started before the filesystem API
A machine that was last started beforemachines.fs was released can’t answer these calls. They raise MachineAgentOutdatedError in Python and AgentOutdatedError in TypeScript, with reason agent_outdated. Rebooting the machine fixes it, and only needs doing once:
OSError in Python, not a FilesystemError in TypeScript): nothing was attempted on the machine’s disk.