metapac 0.8.0

multi-backend declarative package manager
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
# metapac

multi-backend declarative package manager

`metapac` allows you to maintain a consistent set of packages across
multiple machines. It also makes setting up a new system with your
preferred packages from your preferred package managers much easier.

## Obligatory XKCDs

[<img src="https://imgs.xkcd.com/comics/standards_2x.png" title="How Standards Proliferate" height="300"/>](https://xkcd.com/927/)
[<img src="https://imgs.xkcd.com/comics/universal_install_script_2x.png" title="Universal Install Script" height="300"/>](https://xkcd.com/1654/)

## Installation

### With Cargo

```shell
cargo install metapac
```

### With Arch User Repository

```shell
paru -S metapac
```

## Meta

`metapac` is a meta package manager, that means it does not directly
implement the functionality to install packages on your system, instead it
provides a standardised interface for installing packages from other
package managers. See the [Supported Backends](#supported-backends) section
for a list of the currently supported backend package managers.

## Declarative

`metapac` is also a declarative package manager, that means that you
declare in `.toml` group files the packages you would like installed on
your system and then run one of the `metapac` commands which read these
group files and then operate on your system to do some function such as
install packages in your group files that are not present on your system
yet (`metapac sync`), or remove packages present on your system but not in
your group files (`metapac clean`).

The group files are then stored with your other system configuration files
and so can be tracked with version control.

## Usage

### Enable backends

By default all backends are disabled. Enable the backends you want
`metapac` to manage in the config file. See the [`Config`](#config) section
for more details.

### Migrating a default system into `metapac`

Run `metapac unmanaged` and save the output into a group file in
`metapac`'s `groups/` folder, see the [`Group Files`](#group-files)
section for the exact location of this folder on your operating system.

For example, on linux this would mean:

```console
mkdir -p ~/.config/metapac/groups
metapac unmanaged > ~/.config/metapac/groups/all.toml
```

Now `metapac` won't try to remove any of your explicitly installed packages
when you run `metapac clean`.

> [!CAUTION]
> If you run `metapac clean` without first configuring your group files
> with the packages you want installed then `metapac` will attempt to
> remove all of your packages from your enabled backends.
>
> `metapac clean` will always show you which packages it intends to remove
> and ask for confirmation, so make sure to double check that the expected
> packages are being removed before confirming.

### Adding a new package

1. Edit your group files with a text editor to add the package to an
   existing group file or create a new group file and add the package to
   it. See the [`Group Files`]#group-files section for the group file
   syntax
2. Run the `metapac add` command, see `metapac add --help` for arguments
3. Run the `metapac install` command, see `metapac install --help` for
   arguments

After the first two options you will then need to run `metapac sync` for
the newly added package to be installed, whereas for `metapac install` it
also installs the package while adding it to a group file.

> [!TIP]
> The first option is recommended since then you can group or organize the
> order of packages in your group files in a way that is meaningful to you
> and even add comments using the `toml` format.

### Removing a package

Do the opposite of [`Adding a new package`](#adding-a-new-package). The
opposite of `metapac add` is `metapac remove`, the opposite of `metapac
install` is `metapac uninstall` and the opposite of `metapac sync` is
`metapac clean`.

### Hooks

Hooks are commands that you can add per-package in your group files. They
get run by `metapac` at various stages in some of `metapac`'s commands.

One of the main use-cases for hooks is to allow you to declaratively
maintain your enabled `systemd` services alongside each package in your
group files. See the [`Group Files`](#group-files) section for some
examples.

- `before_install`: Run before a package is installed. Only applies to the
  `metapac sync` command.
- `after_install`: Run after a package is installed. Only applies to the
  `metapac sync` command.
- `before_sync`: Run before installing any packages, regardless of whether
  the package is already installed or not. Only applies to the `metapac
sync` command.
- `after_sync`: Run after installing all packages, regardless of whether
  the package was already installed or not. Only applies to the `metapac
sync` command.

### Enable more logs for debugging

You can enable additional log levels by setting the `RUST_LOG` environment
variable, this can be useful to see which commands are being run on the
backends by `metapac`. For example, `RUST_LOG=trace metapac unmanaged`. See
<https://docs.rs/env_logger> for more information.

### Advanced usage

For more advanced usage read through the remaining sections, especially the
[`Config`](#config) section. You can also run `metapac --help` to get a
list of all of the available commands.

## Supported Backends

At the moment, these are the supported backends. Pull requests and issues
for additional backends are always welcome!

| Backend               |
| --------------------- |
| [`apt`]#apt         |
| [`arch`]#arch       |
| [`brew`]#brew       |
| [`bun`]#bun         |
| [`cargo`]#cargo     |
| [`dnf`]#dnf         |
| [`flatpak`]#flatpak |
| [`mas`]#mas         |
| [`mise`]#mise       |
| [`npm`]#npm         |
| [`pipx`]#pipx       |
| [`pnpm`]#pnpm       |
| [`scoop`]#scoop     |
| [`snap`]#snap       |
| [`uv`]#uv           |
| [`vscode`]#vscode   |
| [`winget`]#winget   |
| [`xbps`]#xbps       |
| [`yarn`]#yarn       |
| [`zypper`]#zypper       |

### apt

### arch

#### Package Groups

Arch has two special types of packages called meta packages and package
groups. (See
<https://wiki.archlinux.org/title/Meta_package_and_package_group>).
`metapac` only supports meta packages in group files since they are "real"
packages whereas groups are not "real". This is because meta packages are
normal PKGBUILD files with no content of themselves but which have several
dependencies, whereas package groups are special cases that don't have a
corresponding PKGBUILD file. For example, running `pacman -Si nerd-fonts`
returns "error: package 'nerd-fonts' was not found".

If you still want the behavior of a meta package you have two options.

Firstly, consider creating your own meta package with the same packages as
the group. Consider also publishing this package to the AUR so other users
can also benefit from it. Convention has it that meta packages end in
`-meta`, for example, the meta package version of `nerd-fonts` might be
called `nerd-fonts-meta` (Although `nerd-fonts-meta` does not yet exist at
the time of writing, 2025-09-03).

Alternatively, you could create a new group file using the packages from
the package group, which you can get from the command: `pacman -Sgq
<group_name>`.

#### Yay Bug

Due to a bug in `yay`: <https://github.com/Jguer/yay/issues/2288>,
`metapac` will sometimes keep trying to install some packages when doing
`metapac sync`. To fix this, either switch to `paru` or use `pacman` to
mark the packages in question as installed explicitly using `pacman
--database --asexplicit <packages...>`.

Reported in #152.

### brew

### bun

### cargo

### dnf

### flatpak

### mas

### mise

### npm

If on linux you might need to first run `npm config set prefix ~/.local`.

### pipx

### pnpm

You might need to first run `pnpm setup`.

### scoop

`scoop` doesn't differentiate between implicit and explicit packages.
Therefore, you will need to list all packages and their dependencies in
your group files. See
<https://github.com/ScoopInstaller/Scoop/issues/4276>.

### snap

### uv

### vscode

### winget

### xbps

### yarn

### zypper

## Config

```toml
# metapac's config.toml file (like this one) should be placed in the following location
# dependent on the operating system as specified in the `dirs` crate:
# | Platform | Value                                                 | Example                                                      |
# | -------- | ----------------------------------------------------- | ------------------------------------------------------------ |
# | Linux    | $XDG_CONFIG_HOME or $HOME/.config/metapac/config.toml | /home/alice/.config/metapac/config.toml                      |
# | macOS    | $HOME/Library/Application Support/metapac/config.toml | /Users/Alice/Library/Application Support/metapac/config.toml |
# | Windows  | {FOLDERID_RoamingAppData}\metapac\config.toml         | C:\Users\Alice\AppData\Roaming\metapac\config.toml           |

# Backends to enable. These will be merged with any hostname-specific backends
# from the [hostname_enabled_backends] config table.
# Default: []
enabled_backends = ["arch", "cargo"]

# If this is `false` all toml files recursively found in the groups folder
# will be used as group files.
# If this is `true` then the [hostname_groups] config table will be used to
# decide which group files to use per hostname.
# Default: false
hostname_groups_enabled = false

# Backends to enable per hostname. These will be merged with the base
# `enabled_backends` config.
# Default: None
[hostname_enabled_backends]
pc = ["winget", "cargo"]
laptop = ["arch", "cargo"]
server = ["apt"]

# Which group files will be used per hostname. Subject to `hostname_groups_enabled`.
# Relative paths are relative to the groups folder.
# Default: None
[hostname_groups]
pc = ["relative_group", "/etc/absolute_group"]
laptop = ["relative_group"]
server = ["relative_group"]

[arch]
# Since pacman, pamac, paru, pikaur and yay all operate on the same package database
# they are mutually exclusive and so you must pick which one you want
# metapac to use.
# Must be one of: ["pacman", "pamac", "paru", "pikaur", "yay"]
# Default: "pacman"
package_manager = "paru"

[brew]
# If this is `true` then brew packages default to using the `--quarantine`
# option.
# If this is `false` then brew packages default to using the `--no-quarantine`
# option.
# Default: true
quarantine = true

[cargo]
# Whether to default to installing cargo packages with the `--locked` option.
# Default: false
locked = false

# Whether to use `cargo-binstall` instead of `cargo install` for installing packages.
# When `true`, metapac will use `cargo binstall --no-confirm` instead of `cargo install`.
# This can be faster for installing packages as it downloads pre-built binaries.
# Default: false
binstall = false

[flatpak]
# If this is `true` then flatpak packages default to using the `--system`
# option.
# If this is `false` then flatpak packages default to using the `--user`
# option.
# Default: true
systemwide = true

[vscode]
# Since VSCode and VSCodium both operate on the same package database
# they are mutually exclusive and so you must pick which one you want
# metapac to use.
# Must be one of: ["code", "codium"]
# Default: "code"
variant = "code"

[zypper]
# Since OpenSUSE Leap and Tumbleweed should be updated with different commands
# (see https://en.opensuse.org/System_Updates for more details), you can set how
# metapac updates system packages.
# If this is `false` then the system is updated with the subcommand `update` (`up`).
# If this is `true` then the system is updated with the subcommand `dist-upgrade` (`dup`).
# Default: false
distribution_upgrade = false

```

## Group Files

```toml
# metapac's group files (like this one) should be placed in the following location
# dependent on the operating system as specified in the `dirs` crate:
# | Platform | Value                                     | Example                                                  |
# | -------- | ----------------------------------------- | -------------------------------------------------------- |
# | Linux    | $XDG_CONFIG_HOME or $HOME/.config/groups/ | /home/alice/.config/metapac/groups/                      |
# | macOS    | $HOME/Library/Application Support/groups/ | /Users/Alice/Library/Application Support/metapac/groups/ |
# | Windows  | {FOLDERID_RoamingAppData}\groups\         | C:\Users\Alice\AppData\Roaming\metapac\groups\           |
#
# The packages for each backend in group files can come in two formats, short-form
# and long-form:
#
# short-form syntax is simply a string of the name of the package.
#
# long-form syntax is a table which contains several fields which can
# optionally be set to specify install options on a per-package basis.
# The "package" field in the table specifies the name of the package.
#
# For example, the following two packages are equivalent:
# arch = [
#  "metapac",
#  { package = "metapac" }
# ]

apt = ["package1", { package = "package2" }]
arch = [
  "package1",
  { package = "package2" },
  { package = "syncthing", hooks = { after_sync = [
    "sudo",
    "systemctl",
    "enable",
    "--now",
    "syncthing@ripytide",
  ] } },
  { package = "openssh", hooks = { after_sync = [
    "sudo",
    "systemctl",
    "enable",
    "--now",
    "sshd",
  ] } },
  { package = "fastfetch", hooks = { before_install = [
    "echo",
    "before_install",
  ], after_install = [
    "echo",
    "after_install",
  ], before_sync = [
    "echo",
    "before_sync",
  ], after_sync = [
    "echo",
    "after_sync",
  ] } },
]
brew = ["package1", { package = "package2", options = { quarantine = false } }]
bun = ["package1", { package = "package2" }]
cargo = [
  "package1",
  { package = "package2", options = { git = "https://github.com/ripytide/metapac", all_features = true, no_default_features = false, features = [
    "feature1",
  ], locked = true } },
]
dnf = ["package1", { package = "package2" }]
flatpak = [
  "package1",
  { package = "package2", options = { remote = "flathub", systemwide = false } },
]
mas = ["package1", { package = "package2" }]
mise = [
  "package1",
  { package = "package2", options = { version = "1.0.0" } },
  { package = "package3", options = { version = "lts" } },
]
npm = ["package1", { package = "package2" }]
pipx = ["package1", { package = "package2" }]
pnpm = ["package1", { package = "package2" }]
scoop = ["main/metapac1", { package = "main/package2" }]
snap = [
  "package1",
  { package = "package2" },
  { package = "package3", options = { confinement = "strict" } },
  { package = "package4", options = { confinement = "classic" } },
  { package = "package5", options = { confinement = "dangerous" } },
  { package = "package6", options = { confinement = "devmode" } },
  { package = "package7", options = { confinement = "jailmode" } },
]
uv = ["package1", { package = "package2", options = { python = "3.11" } }]
vscode = ["package1", { package = "package2" }]
winget = ["ripytide.package1", { package = "ripytide.package2" }]
xbps = ["package1", { package = "package2" }]
yarn = ["package1", { package = "package2" }]
zypper = ["package1", { package = "package2" }]
```

## Wishlist

Here is a list of package managers we would like to support along with any
reasons why we can't yet if any. Feel free to add to this list if you know
of any other package managers we should be aware of.

- [`apk`]https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper: no
  attempt made yet
- [`cgwin`]https://cygwin.com/: no attempt made yet
- [`choco`]https://github.com/chocolatey/choco: no attempt made yet
- [`deno`]https://github.com/denoland/deno: can't list installed global
  packages <https://github.com/denoland/deno/discussions/28230>
- [`emerge`]https://wiki.gentoo.org/wiki/Emerge: no attempt made yet
- [`guix`]https://codeberg.org/guix/guix: no attempt made yet
- [`nala`]https://github.com/volitank/nala: no attempt made yet
- [`nix`]https://github.com/NixOS/nix: no attempt made yet
- [`opkg`]https://github.com/oe-mirrors/opkg: no attempt made yet
- [`pip`]https://pypi.org/project/pip/: we support `pipx` instead which
  only allows you to install cli programs which makes sense for a global
  package manager
- [`pkg`]https://github.com/freebsd/pkg: no attempt made yet
- [`ports`]https://github.com/openbsd/ports: no attempt made yet
- [`pkgsrc`]https://github.com/NetBSD/pkgsrc: no attempt made yet
- [`sdk`]https://github.com/sdkman/sdkman-cli: can't list installed
  packages <https://github.com/sdkman/sdkman-cli/issues/466>. The project
  is being rewritten in rust with the intention to implement the command in
  the new version <https://github.com/sdkman/sdkman-cli-native>, also see
  <https://github.com/ripytide/metapac/issues/86>
- [`yum`]https://github.com/rpm-software-management/yum: project
  deprecated in favor of `dnf`

## Similar Projects

- [decman]https://github.com/kiviktnm/decman: written in python,
  archlinux specific, supports installing dotfiles
- [declaro]https://github.com/mantinhas/declaro: written in shell script,
  currently provides support for `apt`, `dnf`, `pacman`, `paru` and `yay`
  but is extensible
- [pacdef]https://github.com/steven-omaha/pacdef: written in rust, custom
  file format, unmaintained, supported `pacman`, `apt`, `dnf`, `flatpak`,
  `pip`, `cargo`, `rustup` and `xbps`
- [upt]https://github.com/sigoden/upt/tree/main: written in rust,
  supports 28 package managers! Designed for manual package management
  rather than declarative.

## Credits

This project was forked from <https://github.com/steven-omaha/pacdef> so
credits to the author(s) of that project for all their prior work.