base on boxxy puts bad Linux applications in a box with only their files. # boxxy boxxy (case-sensitive) is a tool for boxing up misbehaving Linux applications and forcing them to put their files and directories in the right place, **without symlinks!** boxxy is a part of the [amyware discord server](https://discord.gg/7WgSTwh). If you like what I make, consider supporting me on Patreon: [<img src="https://i.imgur.com/YFjoCd1.png" width="162" height="38" />](https://patreon.com/amyware) Linux-only! boxxy uses Linux namespaces for its functionality. For example, consider tmux. It wants to put its config in `~/.tmux.conf`. With boxxy, you can put its config in `~/.config/tmux/tmux.conf` instead: ```yaml # ~/.config/boxxy/boxxy.yaml rules: - name: "redirect tmux config from ~/.tmux.conf to ~/.config/tmux/tmux.conf" target: "~/.tmux.conf" rewrite: "~/.config/tmux/tmux.conf" mode: "file" ``` [![asciicast](https://asciinema.org/a/558679.svg)](https://asciinema.org/a/558679) ## maintenance status I am on a break from maintaining open-source projects due to health reasons. PRs will still be accepted and issues will still be looked at, but there are no promises about when this will happen. ## motivation I recently had to use the AWS CLI. It wants to save data in `~/.aws`, but I don't want it to just clutter up my `$HOME` however it wants. boxxy lets me force it to puts its data somewhere nice and proper. ## features - box any program and force it to put its files/directories where you want it to - context-dependent boxing, ie different rules apply in different directories depending on your configuration - minimal overhead - opt-in immutable fs outside of rule rewrites, ie only the files/directories you specify in rules are writable - `0.5.0`: boxxy can scan your homedir to automatically suggest rules for you! ![image of boxxy scan](https://amyware.nyc3.digitaloceanspaces.com/2023/03/25/G6hrd3iQjEy65.png) - `0.6.0`: boxxy can use project-local `boxxy.yaml` files, and can load `.env` files for you! ![image of 0.6.0 features](https://amyware.nyc3.digitaloceanspaces.com/2023/03/28/Jawp5It1xrnWN.png) - `0.6.1`: boxxy rules can inject env vars: ![image of 0.6.1 features](https://amyware.nyc3.digitaloceanspaces.com/2023/03/29/ukcWuiYdtI8yq.png) - `0.7.2`: boxxy can fork the boxxed process into the background with the `--daemon` flag. - `0.8.0`: boxxy can pass rules at the command line with `--rule`, and disable loading config files with `--no-config`. - `0.8.2`: Explain how to run AppImages properly: ![image of 0.8.2 features](https://amyware.nyc3.digitaloceanspaces.com/2023/10/31/yMiHJaURUud6E.png) ### potential drawbacks - new project, 0.x.y, comes with all those warnings - **cannot** use sudo inside the container (see [#6](https://github.com/queer/boxxy/issues/6)) - primarily tested for my use-cases ## example usage ```sh git:(mistress) | ▶ cat ~/.config/boxxy/boxxy.yaml rules: - name: "Store AWS CLI config in ~/.config/aws" target: "~/.aws" rewrite: "~/.config/aws" git:(mistress) | ▶ boxxy aws configure INFO boxxy > loaded 1 rules INFO boxxy::enclosure > applying rule 'Store AWS CLI config in ~/.config/aws' INFO boxxy::enclosure > redirect: ~/.aws -> ~/.config/aws INFO boxxy::enclosure > boxed "aws" ♥ AWS Access Key ID [****************d]: a AWS Secret Access Key [****************c]: b Default region name [b]: c Default output format [a]: d git:(mistress) | ▶ ls ~/.aws git:(mistress) | ▶ ls ~/.config/aws config credentials git:(mistress) | ▶ cat ~/.config/aws/config [default] region = c output = d git:(mistress) | ▶ ``` ### suggested usage - `alias aws="boxxy aws"` (repeat for other tools) - use contexts to keep project configs separate on disk - dotfiles! - stop using symlinks!!! - no more dev config files when writing code ## requirements boxxy requires `newuidmap` to function, which is not included by default in all distributions. To install: Alpine: ```sh $ apk add shadow-uidmap ``` Debian / Ubuntu: ```sh $ apt install uidmap ``` RHEL / Fedora: ```sh $ yum install shadow-utils ``` ## configuration The boxxy configuration file lives in `~/.config/boxxy/boxxy.yaml`. If none exists, an empty one will be created for you. ```yaml rules: # The name of the rule. User-friendly name for your reference - name: "redirect aws-cli from ~/.aws to ~/.config/aws" # The target of the rule, ie the file/directory that will be shadowed by the # rewrite. target: "~/.aws" # The rewrite of the rule, ie the file/directory that will be used instead of # the target. rewrite: "~/.config/aws" - name: "use different k8s configs when in ~/Projects/my-cool-startup" target: "~/.kube/config" rewrite: "~/Projects/my-cool-startup/.kube/config" # The context for the rule. Any paths listed in the context are paths where # this rule will apply. If no context is specified, the rule applies # globally. context: - "~/Projects/my-cool-startup" # The mode of this rule, either `directory` or `file`. `directory` is the # default. Must be specified for the correct behaviour when the target is a # file. Required because the target file/directory may not exist yet. mode: "file" # The list of commands that this rule applies to. If no commands are # specified, the rule applies to all programs run with boxxy. only: - "kubectl" ``` ### syntax ```yaml rules: - name: "any valid string" # required target: "path" # required rewrite: "path" # required context: # optional - "path" - "path" mode: "directory | file" # optional only: # optional - "binary name" - "binary name" env: # optional KEY: "value" ``` ## developing 1. set up pre-commit: `pre-commit install` 2. make sure it builds: `cargo build` 3. do the thing! 4. test with the command of your choice, ex. `cargo run -- ls -lah ~/.config` ### how does it work? - create temporary directory in /tmp - set up new user/mount namespace - bind-mount `/` to tmp directory - bind-mount rule mounts rw so that target programs can use them - remount `/` ro - run! ## credits - `fixtures/helloworld-appimage-x86_84.AppImage`: https://github.com/ClonedRepos/hello-world-appimage ", Assign "at most 3 tags" to the expected json: {"id":"11611","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"