I'm still working on it but the gist looks like this.
Two new programs: `url` and `run`. I am creating reference implementations but they're simple and there's a spec, so they could be reimplemented in your language of choice.
`url https://...` resolves the given URL into a disk cache in $HOME and then prints the path. `url` understands archives and can resolve inside them:
$ url https://a.b/program.zip/bin/program
/home/foo/.cache/..../bin/program
It implements the HTTP caching spec, and - as browsers can do with HTML - can extract cache override headers from the given program if it's a script with hashbang. It does a few other things, like set +x automatically if the program is intended to be executable, sets quarantine bits on macOS and so on:
$ open $(url https://downloads.mac-app.com/MacApp-1.2.zip/MacApp.app)
(scanned by gatekeeper and then run)
The disk cache is cleaned up automatically as disk space falls low or the cache exceeds the configured maximum.
Running programs this way is possible, but not ideal. This is where `run` enters the picture:
$ run cool-tool.org --help
or if you have the zsh integration set up, you can skip the `run` prefix and just treat URLs as program names:
$ cool-tool.org --help
`run` resolves this to
https://cool-tool.org/run.zip/ which is a small metadata archive containing instructions for how to start the app. The instructions come in the form of a JavaScript that's run inside a restrictive sandbox. It is allowed to resolve URLs into the disk cache, to learn the current host OS and CPU architecture, and to yield an execution plan (basically the path to the binary/script to execute + optionally transformed CLI arguments). So by publishing a small run.zip file to the root of cool-tool.org it becomes possible for users on any OS, including macOS and Windows, to start the program purely by domain name. The script can do hash locking and override cache headers for URLs if the file server isn't cooperative. The tools will keep the app up to date and optimize out cache checks to keep startup fast when the cache is fresh enough.
The run script has a few other options, like figuring out what system libraries are available and what their versions are, thus - if it wishes - it can choose to either download dependencies into the cache and use them (e.g. by setting LD_LIBRARY_PATH), or use the OS provided versions.
At the moment I'm also working on adding a form of sandboxing. The execution plans produced by run scripts can contain permission requests, similar in spirit to macOS entitlements. The permissions aren't tied to any specific sandboxing system or OS. The implementation of `run` may, at its leisure, use this information to sandbox the program before it executes it. The run script may also adapt the permissions requested based on things like what the host OS is, or even what the command line arguments are. For example:
$ cool-app.org --input ./in.mp4 --output ./out.mp4
or
$ dry-run cool-app.org --input ./in.mp4 --output ./out.mp4
Requests permissions:
1. Read $PWD/in.mp4
2. Read/write $PWD/out.mp4
3. Read ~/.config/org.cool-app/config.yml
The run script can look at the command line arguments for this invocation, then produce a permission set that only allows access to the in.mp4 and out.mp4 paths, and a config in an OS specific location, but nothing else. Whether the implementation of run actually tightens things that much will depend on things like what the host OS is capable of, but the information to do so is there. For example on Linux it might use landlock, on macOS it might compare the declared permissions against the entitlements in the Mach-O and abort if there's a mismatch, and so on.
run.zip files don't have to be produced by the upstream author of the code. They can come from anywhere and resolve URLs from anywhere. So if you are worried about the upstream code being malicious, you could use a run.zip from a third party app store or catalogue, and if you aren't, you can use the upstream's version which just sandboxes to limit the blast radius of compromises.
The goals here are deliberately modest, something like:
- Learn from the web, by making starting native CLI apps as convenient as starting a web app.
- Have a small spec that allows for cross-platform deployment without forcing a large declarative schema for how to assemble files and URLs.
- Don't require the user to manually manage installation/uninstallation.
- Experiment with a cross-platform declarative permissions schema that's computed imperatively, where the mapping to OS sandboxing primitives can flex and adapt over time as OS kernels change.