https://github.com/caifs-org/caifs
CAIFS - Config And Installers For Sofware
https://github.com/caifs-org/caifs
bash configuration-management container containers dotfiles dotfiles-installer dotfiles-linux dotfiles-macos dotfiles-resources shell stow-gnu zsh
Last synced: about 10 hours ago
JSON representation
CAIFS - Config And Installers For Sofware
- Host: GitHub
- URL: https://github.com/caifs-org/caifs
- Owner: caifs-org
- Created: 2026-01-19T12:02:34.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2026-07-03T08:58:10.000Z (2 days ago)
- Last Synced: 2026-07-03T10:29:34.477Z (2 days ago)
- Topics: bash, configuration-management, container, containers, dotfiles, dotfiles-installer, dotfiles-linux, dotfiles-macos, dotfiles-resources, shell, stow-gnu, zsh
- Language: Shell
- Homepage:
- Size: 158 KB
- Stars: 3
- Watchers: 0
- Forks: 1
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Config And Installers For Software - CAIFS v(0.9.0)
CAIFS is a tool to handle installing software across various unix-like operating systems. If you work with multiple
flavours of linux, build docker containers and work with macs, then this tool will help you consistently install
software and matching configuration across all of them.
CAIFS takes inspiration from Stow and especially Tuckr, in that it is a dotfile manager, with the ability to run
scripts. Unlike Tuckr though, CAIFS takes it a step further and allows you to define different installs for different
operating systems and even architectures. This is done via hook scripts and defining custom functions per os-flavour
For example, the following hook script (a `pre.sh` in this case), demonstrates how a single hook script can be defined
to work across operating systems, in this case installing curl in three different ways.
``` shell
fedora() {
rootdo dnf install -y curl
}
macos() {
brew install curl
}
debian() {
rootdo apt-get install -y curl
}
```
Running the hooks on your Fedora, MacOS or Debian host in this case would be performed by
`caifs add curl --hooks`
Running the equivalent in a docker file, after a bootstrap gives you consistency
``` Dockerfile
FROM debian:trixie-slim
RUN curl -sL https://raw.githubusercontent.com/caifs-org/caifs/refs/heads/main/install.sh | sh && \
caifs add docker-cli --hooks
# Your other docker image build
...
```
Alternatively, use the github image to simplify your docker builds
``` Dockerfile
FROM debian:trixie-slim
COPY --from=ghcr.io/caifs-org/caifs:latest /caifs/ /usr/local/
RUN caifs --version
```
Simplified dependency management in a GitHub Pipeline
``` yaml
...
steps:
- name: Add the dependencies to the runner
run: |
curl -sL https://raw.githubusercontent.com/caifs-org/caifs/refs/heads/main/install.sh | sh
caifs add uv ruff pre-commit rumdl docker-cli trivy just
- name: Run pre-commit checks
run: |
pre-commit run --all
```
## Other good reasons to use CAIFS
- 100% pure POSIX compliant shell. So it should run just about everywhere
- less than 60kb in size, so it won't take up precious space in your Docker builds
- It has zero dependencies, besides coreutils functions such as find, `sed`, `grep`, `dirname`, `realpath`, `pathchk`...
> [!NOTE]
> This CAIFS repo itself is a valid caifs collection, containing a single target, caifs!
> See a curated library of scores of more installers at
## Install and Usage
YOLO it onto your system to install locally within `~/.local/`
`curl -sL https://raw.githubusercontent.com/caifs-org/caifs/refs/heads/main/install.sh | sh`
OR
Install globally by using env var `INSTALL_PREFIX=/usr/local/` and root privileges
`INSTALL_PREFIX=/usr/local/ curl -sOL https://raw.githubusercontent.com/caifs-org/caifs/refs/heads/main/install.sh | sudo sh -c`
Check it's working and on your path with -
`caifs --version` or `caifs --help`
OR
Clone the repository and install CAIFS, using CAIFS
``` shell
git clone https://github.com/caifs-org/caifs/caifs.git
./caifs/config/bin/caifs add caifs -d . --link-root "$HOME/.local"
```
### Enable caifs-common collection (optional but recommended)
is a collection of curated installs of commonly used developer
software that can be enabled via the caifs library.
If you have installed caifs via the install.sh script, then you already have caifs-common installed
If are installing caifs via the git clone method, then you can simply run the caifs-common target
```shell
caifs add caifs -d .
caifs add caifs-common -d .
```
This will grab the latest `caifs-common` release and place it into `~/.local/share/caifs-collections/caifs-common` CAIFS
automatically looks for this library so there is no need to add it to the `$CAIFS_COLLECTIONS` environment variable or
specify it directly with the `caifs add --directory ` switch.
> ![TIP]
> Running `caifs add caifs-common` periodically will grab the latest version and keep it up to date
## Collection Structure
A CAIFS collection is a directory containing targets. Each target has a `config/` directory for files to symlink and an
optional `hooks/` directory for install scripts.
```text
my-dotfiles/
├── git/
│ ├── config/
│ │ ├── .gitconfig
│ │ └── .gitconfig.d/
│ │ └── aliases.config
│ └── hooks/
│ └── pre.sh
├── bash/
│ └── config/
│ ├── .bashrc
│ └── .bashrc.d/
│ └── aliases.bash
└── nvim/
└── config/
└── .config/
└── nvim/
└── init.lua
```
### Config files
**Config files** mirror their destination path relative to `$HOME` (or `$CAIFS_LINK_ROOT`):
- `git/config/.gitconfig` → `~/.gitconfig`
- `nvim/config/.config/nvim/init.lua` → `~/.config/nvim/init.lua`
### Hook scripts
Three types of hooks exist, `pre.sh`, `post.sh` and `rm.sh`. Following on from the above example, if you wanted to do a
`pre.sh` hook that installed git, before the configuration was symlinked across, then this would like like:
`git/hooks/pre.sh`
**Hook scripts** define functions named after OS identifiers. CAIFS detects the OS and calls the matching function:
``` shell
# git/hooks/pre.sh
fedora() {
rootdo dnf install -y git-core
}
ubuntu() {
rootdo apt-get install -y git
}
arch() {
rootdo pacman -S --noconfirm git
}
macos() {
brew install git
}
linux() {
# Runs on any Linux after the distro-specific function
echo "Git installed on Linux"
}
generic() {
# Runs on all platforms last
echo "Git setup complete"
}
```
Available function names:
- Distro-specific: `fedora`, `ubuntu`, `arch`, `debian`, etc. (from `/etc/os-release` ID)
- `linux` - any Linux system
- `macos` - macOS/Darwin
- `generic` - all platforms
- `container` - runs when inside a container (Docker, Podman, LXC, etc.)
- `portable` - runs when on a portable device (laptop, notebook, etc.)
### Writing your own hook scripts
A hook script occurs within it's own sub-shell, meaning that any variables declared and any temporary generated files
are deleted once the script exits. This is useful, as there is no manual cleanup required within a hook script.
If you want to install files manually, to say the default `$HOME/.local/` folder, which is the default for CAIFS, then
another temporary folder is provided via an environment variable, `${CAIFS_INSTALL_DIR}`. This folder has the following
structure of `${CAIFS_INSTALL_DIR}/{bin,lib,share}`, which when you run the utility function `caifs_install` at the end
of a hook function, will merge the contents of `${CAIFS_INSTALL_DIR}` into the `$CAIFS_LINK_ROOT` or equivalent CLI
flag `--link-root `.
This also works for root installs, that wish to target directories like `/usr/local/` or `/usr/`
### CA trust updates
It's often common in enterprise setups to require a custom certificate to be installed to maintain the certificate
trust chain. For these scenarios, any given target should create a certificate file within the following structure:
`/config/.local/share/certificates/my_cert.crt`
Of course, no OS updates their trust chain in the same way, so CAIFS provides a series of OS identifier wrapper
functions to manage the various OS specific tasks to get that cert into the system wide cert trust.
From a `post.sh` hook script (because we need it to run after the linking), call the `install_certs()` function, from
either of the handlers or as a fail safe, within the more generic `linux()` handler, like so:
``` shell
# enterprise-certs/hooks/post.sh
linux() {
install_certs
}
```
## Usage Examples
``` shell
# bootstrap your system the caifs-common library, which contains everything below
caifs add caifs-common -d .
# does symlinking and pre/post hooks for target uv
caifs add uv
# does only symlinking for target uv
caifs add uv --links
# does only hooks for target uv
caifs add uv --hooks
# run multiple hooks for targets uv, ruff and poetry in that order
caifs add uv ruff poetry --hooks
# force an override of bash config files if the links exist already
caifs add bash --links --force
# run over multiple collections, with a first-link wins scenario
caifs add git -d ~/my-personal-collection -d ~/my-work-collection
# same as above, but using the environment variable to replace the -d|--directory option
CAIFS_COLLECTIONS="~/my-personal-collection:~/my-work-collection" \
caifs add git
# remove symlinks for a target
caifs rm git -d ~/my-dotfiles --links
# run remove hook script
caifs rm git -d ~/my-dotfiles --hooks
# run a target from a specific collection within your installed collections in ~/.local/share/caifs-collections/
caifs add git@caifs-common fzf curl custom@my-dotfiles
```
## Environment Variables
| Variable | Default | Description |
|---------------------------|----------------------------------|--------------------------------------------------------------------------------|
| `CAIFS_COLLECTIONS` | `$PWD` | Colon-separated list of collection paths to search for targets |
| `CAIFS_LINK_ROOT` | `$HOME` | Destination root for symlinks (e.g., set to `/` for system-wide configs) |
| `CAIFS_VERBOSE` | `1` | Set to `0` to enable debug output |
| `CAIFS_RUN_FORCE` | `1` | Set to `0` to force overwrite existing files/links |
| `CAIFS_RUN_LINKS` | `0` | Set to `1` to skip symlinking (equivalent to `--hooks`) |
| `CAIFS_RUN_HOOKS` | `0` | Set to `1` to skip hooks (equivalent to `--links`) |
| `CAIFS_DRY_RUN` | `1` | Set to `0` to show what would run without making changes |
| `CAIFS_IN_CONTAINER` | unset | Set to `0` to set container config to run + triggers `container()` hooks). |
| | | Set to `1` to specify not in container, regardless of if in a container or not |
| `CAIFS_IN_WSL` | unset | Set to `0` to set WSL config to run. Set to `1` to force to run |
| `CAIFS_LOCAL_COLLECTIONS` | ~/.local/share/caifs-collections | A central store for collections that is automatically checked. |
| `CAIFS_USER` | `$USER` | Override the user that the links will be owned by |
| | | |
## Advanced Configuration
### SUDO requirements
Configuring `sudo` is generally recommended for ease of use, especially when working with docker containers. The default
in WSL2 is something akin to `echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers`
This is fine for a dedicated local dev machine, but if you working with containers then you might want to restrict sudo
or better, yet make use of the `CAIFS_LINK_ROOT` and `CAIFS_USER` variables to install within another users home
directory, or, provide the correct permissions.
Depending on your distro of choice, CAIFS requires the following for `sudo` access
| command | distro | justification |
|------------------------|------------------|------------------------------------------|
| cp | all | Moving files into root locations |
| ln | all | creating links to root locations |
| mkdir | all | creating parent directories for symlinks |
| update-ca-certificates | debian/ubuntu | adding certificates to the trust store |
| update-ca-trust | fedora/rhel/arch | adding certificates to the trust store |
| apt-get/apt | debian/ubuntu | install packages |
| pacman/yay | arch/steamos | install packages |
| dnf/rpm | rhel/fedora | install packages |
A simple entry for the set of these on debian might look something like:
```shell
echo '%sudo ALL=(ALL) NOPASSWD:/usr/bin/apt, \
/usr/bin/apt-get, \
/usr/bin/cp, \
/usr/bin/ln, \
/usr/bin/update-ca-certificates' >> /etc/sudoers
```
Or using a `sudoers.d` file
``` shell
# /etc/sudoers.d/caifs
%sudo ALL=(ALL) NOPASSWD:/usr/bin/apt, \
/usr/bin/apt-get, \
/usr/bin/mkdir, \
/usr/bin/cp, \
/usr/bin/ln, \
/usr/bin/update-ca-certificates
```
### Define multiple collections
Enabling multiple collections allows you to separate out your personal (and preferred) configuration into one collection
,then for instance, a work-specific collection defined, followed by the standard `caifs-common` library.
When you runs CAIFS, it will search all the collections, with the order you specify the collections in being the order
of operations.
There are a few options to support this.
#### CLI arguments
Using the `-d|--directory` arguments *will* override any `$CAIFS_COLLECTIONS` variable set, allowing you to work with a
collection in isolation.
#### CAIFS_COLLECTIONS environment variable
The environment variable, `$CAIFS_COLLECTIONS`, can be set with multiple `:`-delimited directory paths. Much like the
standard `$PATH` variable. Setting this variable is the equivalent of supplying multiple `-d|--directory`
arguments to the `caifs add|rm` command itself.
#### Built in mechanism aka caifs-ception
When `caifs` is run with no `-d|--directory` arguments and the `$CAIFS_COLLECTIONS` variable is empty, then CAIFS will
internally look to an XDG area of `~/.local/share/caifs-collections/` for collections to process.
CAIFS will look only 1 level deep in that directory and then attempt to validate that they are in-fact, caifs
compatible directories. It adds each collection it finds to the back of the queue (internally the queue is just the
`$CAIFS_COLLECTIONS` variable), that is to say, the order of the collections in `~/.local/share/caifs-collections/` is
important and is dictated by the `find` defaults.
The one exception to this, is the `caifs-common` library. If present, then this collection will always be at the back
of the line. Allowing people to override configuration if they wish.
### Install to non-$HOME area
By default, CAIFS configuration will be linked to the current `$HOME` variable. This is desirable for most use cases
where you want to manage personal dotfiles.
If you need to manage files beyond the `$HOME` area, perhaps you have some custom networking that is required to be
added underneath `/` - then CAIFS has two options.
#### Leading ^ character in config path
A config file under `/config/` with a leading `^` will be interpreted as being a `/` or root level file. For
example, `my_sudo/config/^etc/sudoers.d/01-mysudo.conf` will be attempted to be linked to
`/etc/sudoers.d/01-mysudo.conf`
Attempted, because CAIFS will attempt to escalate privileges
1. CAIFS is currently running as root, i.e. uid=0, run `` as is.
2. sudo is available and run `sudo `
3. fallback to `su -c ` to issue the command
> [!NOTE]
> Some of these options may prompt for passwords, depending on your setup
#### Altering the CAIFS_LINK_ROOT variable
It may be useful in certain situations, particularly in docker builds which generally run as root, to set an alternative
to the default `$HOME` destination for links.
You can specify this with the `-r|--link-root` flags for the `add|rm` commands or use the `$CAIFS_LINK_ROOT` environment
variable
In typical docker builds, or perhaps escalated automation scenarios where you are running as root, but want the
configuration to be placed into another users home directory.
``` Dockerfile
FROM debian:trixie-slim
# Add an app user with a home directory at /app
RUN useradd \
--create-home \
--home-dir /app \
--uid 1000 \
--shell /bin/sh \
appuser
# Copy over a collection, or perhaps curl one on from github
COPY my-docker-collection /usr/local/share/my-docker-collection
# install some software and add the config from a custom collection, but
# create the links at the link-root of /app/
RUN curl -sL https://github.com/caifs-org/caifs/install.sh | sh && \
caifs add uv git pre-commit ruff \
--link-root /app \
--user appuser:appuser \
-d /usr/local/share/my-docker-collection
```
#### WSL, Container, or Portable specific configuration
Besides the standard `/config` directory, CAIFS caters for environment-specific config. To enable a specific set
of configuration that should only be linked in a particular environment, provide an alternative directory:
- `/config_wsl` - for WSL environments
- `/config_container` - for container environments (Docker, Podman, LXC, etc.)
- `/config_portable` - for portable devices (laptops, notebooks, convertibles, etc.)
> [!NOTE]
> The order of precedence for multiple config directories is `config_portable/`, `config_container/`, `config_wsl/`, `config/`.
> This effectively allows you to prevent environment-specific configuration from being clobbered by similarly named configuration
> within the main `config/` directory.
### Command Options
| Option | Env Variable | Description |
|----------------------|---------------------|---------------------------------------------------------|
| `--verbose`, `-v` | `CAIFS_VERBOSE=0` | Show debug logs |
| `--force`, `-f` | `CAIFS_RUN_FORCE=0` | Remove existing links/files on conflict |
| `--links`, `-l` | `CAIFS_RUN_LINKS=0` | Run only links, disable hooks |
| `--hooks`, `-h` | `CAIFS_RUN_HOOKS=0` | Run only hooks, disable links |
| `--dry-run`, `-n` | `CAIFS_DRY_RUN=0` | Show what would run without making changes |
| `--collection`, `-c` | - | Constrain the targets to a single collection |
| `--user`, `-u` | `CAIFS_USER` | Apply the user permissions to links and instlaled files |
| | | |