config.toml -> pkg.toml, get cleanup working
This commit is contained in:
parent
735a3c65d8
commit
aa2ee00134
12 changed files with 116 additions and 29 deletions
|
@ -4,61 +4,116 @@
|
||||||
|
|
||||||
An alternative to GNU Stow, more some notion of packages with dependencies and install scripts.
|
An alternative to GNU Stow, more some notion of packages with dependencies and install scripts.
|
||||||
|
|
||||||
Think an Ansible or Puppet but anyarch and lite enough to check in with your dotfiles.
|
Think an Ansible, Puppet or even NixOS but anyarch and lite enough to check in with your dotfiles.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Somewhat like Stow, Cram operates in terms of packages, which are directories with the following structure -
|
Cram operates on a directory of packages called `packages.d/`, and two directories of metapackages called `profiles.d` and `hosts.d`.
|
||||||
|
|
||||||
```
|
### Packages
|
||||||
/REQUIRES # A list of other packages this one requires.
|
|
||||||
/BUILD # 1. Perform any compile or package management tasks.
|
|
||||||
/PRE_INSTALL # 2. Any other tasks required before installation occurs.
|
|
||||||
/INSTALL # 3. Do whatever constitutes installation.
|
|
||||||
# This supercedes the default copying of files.
|
|
||||||
/POST_INSTALL # 4. Any cleanup or other tasks following installation
|
|
||||||
... # Any other files are treated as package contents.
|
|
||||||
|
|
||||||
|
A Cram package consists of a directory containing a `pkg.toml` file with the following format -
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[cram]
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
[package]
|
||||||
|
# The package.require list names depended artifacts.
|
||||||
|
[[package.require]]
|
||||||
|
name = packages.d/some-other-package
|
||||||
|
|
||||||
|
# (optional) The package.build list enumerates either
|
||||||
|
# inline scripts or script files. These are run as a
|
||||||
|
# package is 'built' before it is installed.
|
||||||
|
[[package.build]]
|
||||||
|
run = some-build-command
|
||||||
|
|
||||||
|
# (optional) Hook script(s) which occur before installation.
|
||||||
|
[[package.pre_install]]
|
||||||
|
run = some-hook
|
||||||
|
|
||||||
|
# (optional) Override installation scrpt(s).
|
||||||
|
# By default, everthing under the package directory
|
||||||
|
# (the `pkg.toml` excepted) treated is as a file to be
|
||||||
|
# installed and stow is emulated using symlinks.
|
||||||
|
[[package.install]]
|
||||||
|
run = some-install-command
|
||||||
|
|
||||||
|
# (optional) Hook script(s) which after installation.
|
||||||
|
[[package.post_install]]
|
||||||
|
run = some-other-hook
|
||||||
```
|
```
|
||||||
|
|
||||||
Cram reads a config dir with three groups of packages
|
To take a somewhat real example from my own dotfiles -
|
||||||
- `packages.d/<packagename>` contains a package that installs but probably shouldn't configure a given tool, package or group of files.
|
|
||||||
Configuration should be left to profiles.
|
|
||||||
- `profiles.d/<profilename>` contains a profile; a group of related profiles and packages that should be installed together.
|
|
||||||
- `hosts.d/<hostname>` contains one package for each host, and should pull in a list of profiles.
|
|
||||||
- Both profiles and hosts entries may specify their own "inline" packages as a convenience.
|
|
||||||
|
|
||||||
The intent of this tool is to keep GNU Stow's intuitive model of deploying configs via symlinks, and augment it with a useful pattern for talking about "layers" / "packages" of related configs.
|
```shell
|
||||||
|
$ tree -a packages.d/tmux
|
||||||
|
packages.d/tmux
|
||||||
|
├── pkg.toml
|
||||||
|
└── .tmux.conf
|
||||||
|
```
|
||||||
|
|
||||||
Cram installs the package `hosts.d/$(hostname)`, and `profiles.d/default` by default.
|
This TMUX package provides only my `.tmux.conf` file, and a stub `pkg.toml` that does nothing.
|
||||||
|
A fancier setup could use `pkg.toml` to install TMUX either as a `pre_install` task or by using a separate TMUX package and providing the config in a profile.
|
||||||
|
|
||||||
|
### Metapackages
|
||||||
|
|
||||||
|
Writing lots of packages gets cumbersome quickly, as does managing long lists of explicit dependencies.
|
||||||
|
To try and manage this, Cram provides metapackages - packages which contain no stowable files, but instad contain subpackages.
|
||||||
|
|
||||||
|
To take a somewhat real example from my own dotfiles -
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ tree -a -L 1 profiles.d/macos
|
||||||
|
profiles.d/macos
|
||||||
|
├── pkg.toml
|
||||||
|
├── emacs/
|
||||||
|
├── homebrew/
|
||||||
|
└── zsh/
|
||||||
|
```
|
||||||
|
|
||||||
|
The `profiles.d/macos` package depends AUTOMATICALLY on the contents of the `profiles.d/macos/emacs`, `profiles.d/macos/homebrew` and `profiles.d/macos/zsh` packages, which are normal packages.
|
||||||
|
These sub-packages can have normal dependencies on other packages both within and without the profile and install files or run scripts.
|
||||||
|
|
||||||
|
Profiles allow users to write groups of related packages, especially configs, which go together and allows for scoped reuse of meaningful names.
|
||||||
|
|
||||||
|
Likewise the `hosts.d/` tree allows users to store host-specific packages.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cram apply [--dry-run|--execute] [--optimize] <configdir> <destdir>
|
$ cram apply [--dry-run|--execute] [--optimize] [--require <package>] <configdir> <destdir>
|
||||||
```
|
```
|
||||||
|
|
||||||
The `apply` task applies a configuration to a destination directory.
|
The `apply` task applies a configuration to a destination directory.
|
||||||
The most common uses of this would be `--dry-run`, which functions as a `diff` or `--execute ~/conf ~/` for emulating Stow and installing dotfiles.
|
The most common uses of this would be `--dry-run` (the default), which functions as a `diff` or `--execute ~/conf ~/` for emulating Stow and installing dotfiles.
|
||||||
|
|
||||||
|
By default `cram` installs two packages - `profiles.d/default` and `hosts.d/$(hostname -s)`.
|
||||||
|
This default can be overriden by providing `--require <package>` one or more times to enumerate specific packages to install.
|
||||||
|
|
||||||
Cram always reads the `.cram.log` state file and diffs the current state against the configured state.
|
Cram always reads the `.cram.log` state file and diffs the current state against the configured state.
|
||||||
Files and directories no longer defined by the configured state are cleaned up automatically.
|
Files and directories no longer defined by the configured state are cleaned up automatically.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cram show <configdir>
|
$ cram state <configdir>
|
||||||
```
|
```
|
||||||
|
|
||||||
The `list` task loads up and prints the `.cram.log` state file generated by any previous `cram apply --execute` so you can read a manifest of what cram thinks it did.
|
The `state` task loads up and prints the `.cram.log` state file generated by any previous `cram apply --execute` so you can read a manifest of what cram thinks it did.
|
||||||
|
This is useful because `cram` attempts to optimize repeated executions and implement change detection using the state file.
|
||||||
|
|
||||||
|
This cache can be busted if needed by using `apply --execute --no-optimize`, which will cause cram to take all actions it deems presently required.
|
||||||
|
This can result in dangling symlinks in the filesystem.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cram list <configdir>
|
$ cram list <configdir> [package]
|
||||||
```
|
```
|
||||||
|
|
||||||
The `show` task lists out all available packages (eg. packages, profiles, hosts, and subpackages) as a dependency graph.
|
The `list` task lists out all available packages (eg. packages, profiles, hosts, and subpackages) as a dependency graph.
|
||||||
|
When provided a specific package, the details of that package (its requirements and installation task log) will be printed.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright Reid 'arrdem' McKenzie, 31/10/2021.
|
Copyright Reid 'arrdem' McKenzie, 15/02/2022.
|
||||||
|
|
||||||
Published under the terms of the MIT license.
|
Published under the terms of the Anticapitalist Software License (https://anticapitalist.software).
|
||||||
See the included `LICENSE` file for more.
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
__author__ = "Reid D. 'arrdem' McKenzie <me@arrdem.com>"
|
||||||
|
__copyright__ = "Copyright 2020"
|
||||||
|
__license__ = "https://anticapitalist.software/"
|
|
@ -8,6 +8,7 @@ import pickle
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from . import __version__, __author__, __license__, __copyright__
|
||||||
from .v0 import PackageV0, ProfileV0
|
from .v0 import PackageV0, ProfileV0
|
||||||
from .v1 import PackageV1, ProfileV1
|
from .v1 import PackageV1, ProfileV1
|
||||||
|
|
||||||
|
@ -148,6 +149,21 @@ def scrub(old_fs: Vfs, new_fs: Vfs) -> Vfs:
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
|
@click.version_option(version=1, message=f"""Cram {__version__}
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
https://github.com/arrdem/source/tree/trunk/projects/cram/
|
||||||
|
|
||||||
|
Features
|
||||||
|
- 0.0.0 legacy config format
|
||||||
|
- 0.1.0 TOML config format
|
||||||
|
- 0.1.0 log based optimizer
|
||||||
|
- 0.1.0 idempotent default for scripts
|
||||||
|
|
||||||
|
About
|
||||||
|
{__copyright__}, {__author__}.
|
||||||
|
Published under the terms of the {__license__} license.
|
||||||
|
""")
|
||||||
def cli():
|
def cli():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -175,7 +191,7 @@ def do_apply(confdir, destdir, state_file, execute, optimize, require, exec_idem
|
||||||
# Middleware processing of the resulting filesystem(s)
|
# Middleware processing of the resulting filesystem(s)
|
||||||
executable_fs = scrub(old_fs, new_fs)
|
executable_fs = scrub(old_fs, new_fs)
|
||||||
if optimize:
|
if optimize:
|
||||||
executable_fs = simplify(old_fs, new_fs,
|
executable_fs = simplify(old_fs, executable_fs,
|
||||||
exec_idempotent=exec_idempotent)
|
exec_idempotent=exec_idempotent)
|
||||||
|
|
||||||
# Dump the new state.
|
# Dump the new state.
|
||||||
|
|
|
@ -10,6 +10,7 @@ from vfs import Vfs
|
||||||
|
|
||||||
SHELL = "/bin/sh"
|
SHELL = "/bin/sh"
|
||||||
|
|
||||||
|
|
||||||
def sh(cmd: List[str], /,
|
def sh(cmd: List[str], /,
|
||||||
env: Optional[dict] = None):
|
env: Optional[dict] = None):
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ def tempf(name):
|
||||||
class PackageV1(Package):
|
class PackageV1(Package):
|
||||||
"""The v1 package format."""
|
"""The v1 package format."""
|
||||||
|
|
||||||
SPECIAL_FILES = ["config.toml"]
|
SPECIAL_FILES = ["pkg.toml"]
|
||||||
_config = None
|
_config = None
|
||||||
|
|
||||||
def config(self):
|
def config(self):
|
||||||
|
|
|
@ -83,3 +83,14 @@ rm -r "${dest}"/*
|
||||||
|
|
||||||
# If multiple packages provide the same _effective_ script, do it once
|
# If multiple packages provide the same _effective_ script, do it once
|
||||||
./cram apply --require packages.d/p6 --require packages.d/p7 --execute test/ "${dest}" | sort | uniq -c | grep "/tmp/stow/b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b.sh" | grep "1 - exec"
|
./cram apply --require packages.d/p6 --require packages.d/p7 --execute test/ "${dest}" | sort | uniq -c | grep "/tmp/stow/b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b.sh" | grep "1 - exec"
|
||||||
|
|
||||||
|
# Test log-based cleanup
|
||||||
|
./cram apply --require packages.d/p1 --require packages.d/p2 --execute test/ "${dest}"
|
||||||
|
[ -L "${dest}"/foo ]
|
||||||
|
[ -L "${dest}"/bar ]
|
||||||
|
# And how bar shouldn't be installed...
|
||||||
|
./cram state test/
|
||||||
|
./cram apply --require packages.d/p1 --execute test/ "${dest}"
|
||||||
|
./cram state test/
|
||||||
|
[ -L "${dest}"/foo ]
|
||||||
|
[ ! -L "${dest}"/bar ]
|
||||||
|
|
Loading…
Reference in a new issue