Further documentation and overhaul of the help menu CLI

- Shields.io badges added to README.md with clarification on
  functionality present in pacwrap.
- Stipulate the requirement for libalpm 14 or greater in README.md
- Module documentation written in markdown located in /docs/ for
  filesystems and permissions modules
- Help CLI refactored with format improvements and fixes.
- Lock assert calls for container composition, creation, and remote sync
- Added value opperand for process module in main frontend module
This commit is contained in:
Xavier Moffett 2024-03-30 21:36:06 -04:00
parent 7d0942496e
commit 91b5c79c5a
26 changed files with 1476 additions and 762 deletions

View file

@ -1,5 +1,10 @@
# pacwrap
[![pacwrap](https://shields.io/aur/version/pacwrap?style=for-the-badge&color=599ffb&archlinux&label=pacwrap)](https://aur.archlinux.org/packages/pacwrap/)
[![pacwrap-git](https://shields.io/aur/version/pacwrap-git?style=for-the-badge&color=599ffb&logo=archlinux&label=pacwrap-git)](https://aur.archlinux.org/packages/pacwrap-git/)
[![License](https://shields.io/crates/l/pacwrap/0.8.0?style=for-the-badge&color=6dfb59)](https://spdx.org/licenses/GPL-3.0-only.html)
![MSRV](https://shields.io/crates/msrv/pacwrap/0.8.0?style=for-the-badge&color=fba759)
<img align="left" src="./assets/logo.svg">
A package management front-end which utilises libalpm to facilitate the creation of unprivileged, userspace containers with parallelised, filesystem-agnostic deduplication. These containers are constructed via bubblewrap to execute package transactions and launch applications.
@ -30,6 +35,12 @@ And finally, to install ```neovim``` inside of a fresh, aggregated container cal
$ pacwrap -Syucat editor --dep=base neovim
```
To update these containers just created in aggregate:
```
$ pacwrap -Syu
```
More advanced examples along with further documentation of configuration can be found further
elaborated upon **[here](./docs/)**.
@ -45,7 +56,7 @@ If a feature you see here is not completed, feel free to submit a PR; or submit
| Transaction Agent | Transact within a sandboxed runtime environment | ✅ |
| Transaction CLI | Functional | ✅ |
| Global Configuration | Functional | ✅ |
| Dependency Resolution | Functional, but too liberal to compensate for a lack of conflict resolution | ⚠ |
| Package Dependency Resolution | Utilizes a recursive first-depth search algorithm; resilient to cycling | ✅ |
| Foreign Database Resolution | Populates foreign package database in aggregate containers | ✅ |
| Foreign Database Resolution (Lazy) | Not yet implemented | ❌ |
| Conflict Resolution | Not yet implemented | ❌ |
@ -78,7 +89,7 @@ An online version of the user manual is viewable **[here](./docs/manual.md)**.
A minimum version of Rust 1.72 is required to build with the following libraries fulfilled by your distribution:
```
libalpm, libseccomp, libzstd
libalpm>=14, libseccomp, libzstd
```
## Packaging requirements

13
docs/dbus/README.md Normal file
View file

@ -0,0 +1,13 @@
# Modules
This directory contains brief documentation along with examples for each permission module.
### Table of Contents
- [Devices](./dev.md)
- [Display Servers](./display.md)
- [Environment Variables](./env.md)
- [Graphics Devices](./gpu.md)
- [Networking](./gpu.md)
- [Pipewire](./pipewire.md)
- [PulseAudio](./pulseaudio.md)

View file

@ -0,0 +1,10 @@
# Filesystem Modules
This directory contains brief documentation along with examples for each filesystem module.
### Table of Contents
- [Home](./home.md)
- [Root](./root.md)
- [Mount to root](./to_home.md)
- [Mount to home](./to_root.md)

19
docs/filesystems/home.md Normal file
View file

@ -0,0 +1,19 @@
# Mount home
Mount the container's home directory into the container.
## Example
```
filesystems:
- mount: home
```
## Description
Provides the binding for the container's home directory. By default, this module
will mount `~/.local/share/pacwrap/home/[container_name]` to `$HOME` inside the
container at `/home/[container_name]`. Please refer to the [**to_home**](./to_home.md)
module for more advanced options.
This module plays an important role in initializing the container's runtime environment.

19
docs/filesystems/root.md Normal file
View file

@ -0,0 +1,19 @@
# Mount root
Mount the container's home directory into the container.
## Example
```
filesystems:
- mount: root
```
## Description
Provides the binding for the container's root filesystem. By default, this module
will bind the minimum required at `~/.local/share/pacwrap/root/[container_name]`
to allow for a functional userspace inside of the container. Please refer to the
[**to_root**](./to_root.md) module for more advanced options.
This module plays an important role in initializing the container's runtime environment.

View file

@ -0,0 +1,27 @@
# Mount to home
Mount filesystem volumes to the container's root directory.
## Example
Mount `~/.config/fontconfig/ to the container's `$HOME` directory as read-only; then mount the
`~/Downloads` to the container's `$HOME` directory with read-write permissions.
```
filesystems:
- mount: to_home
volumes:
- path: .config/fontconfig
- permission: rw
path: Downloads
```
## Description
Mount filesystem volumes from the runtime user's `$HOME` into the container, in the container's `$HOME`
directory, unless the destination is otherwise specified with `dest` variable. Pacwrap will terminate
with an error condition if the file or directory is not found; or the user is otherwise not sufficiently
privileged.
Specify read/write permissions with a string using the `permission` variable. Valid combinations are
abbreviated as following: `ro` for read-only, and `rw` for read-write permissions.

View file

@ -0,0 +1,28 @@
# Mount to root
Mount filesystem volumes to the container's root directory.
## Example
Mount `/usr/share/icons/`, and `/etc/fonts/`, as read-only to the container; then mount
`/media/Storage/Games/Steam` to `/mnt/SteamLibrary`/ in the container with read-write permissions.
```
filesystems:
- mount: to_root
volumes:
- path: /usr/share/fonts
- path: /etc/fonts
- permission: rw
path: /media/Storage/Games/Steam
dest: /mnt/SteamLibrary
```
## Description
Mount filesystem volumes from the host into the container, at the destination mirroring the host, unless
the destination is otherwise specified with `dest` variable. Pacwrap will terminate with an error condition
if the file or directory is not found; or the user is otherwise not sufficiently privileged.
Specify read/write permissions with a string using the `permission` variable. Valid value combinations are
abbreviated as following: `ro` for read-only, and `rw` for read-write permissions.

View file

@ -0,0 +1,13 @@
# Modules
This directory contains brief documentation along with examples for each permission module.
### Table of Contents
- [Devices](./dev.md)
- [Display Servers](./display.md)
- [Environment Variables](./env.md)
- [Graphics Devices](./gpu.md)
- [Networking](./gpu.md)
- [Pipewire](./pipewire.md)
- [PulseAudio](./pulseaudio.md)

22
docs/modules/dev.md Normal file
View file

@ -0,0 +1,22 @@
# Device Module
Permiss access to devices from the `/dev/` sysfs.
## Example
Permiss access to the /dev/input device in the container environment.
```
- module: dev
devices:
- input
```
## Description
Mount a list of devices as specified in the `devices` array. Requires the user
have unprivileged access to the device in question. Otherwise this module will
terminate pacwrap with an error condition.
In most cases, the automatic permissions should be sufficient with the exception
of some USB input devices for particular use cases.

14
docs/modules/display.md Normal file
View file

@ -0,0 +1,14 @@
# Display Module
Provide access to display server sockets.
## Example
```
permissions:
- module: display
```
## Description
Use this module to automatically detect and bind available display server sockets to the container environment.
If no sockets are available, this module will terminate pacwrap with an error condition.

25
docs/modules/env.md Normal file
View file

@ -0,0 +1,25 @@
# Environment Variables Module
Set environment variables in the container environment.
## Example
Passthrough the environment variable `LANG`, and set the environment variable `QT_X11_NO_MITSHM`.
```
permissions:
- module: env
variables:
- var: LANG
- var: QT_X11_NO_MITSHM
set: '1'
```
## Description
Use this module to passthrough or define environment variables in the container environment.
When an environment variable is not available in the host environment, this module will
provide warning and set a blank value in the target container.
By default pacwrap, will not passthrough environment variables from the host, unless specified
otherwise by the user with the elidation of the `set` parameter when definining a `var`.

14
docs/modules/gpu.md Normal file
View file

@ -0,0 +1,14 @@
# GPU Module
Provide available GPUs to the container.
## Example
```
permissions:
- module: gpu
```
## Description
Use this module to automatically detect and bind available GPUs to the container environment.

14
docs/modules/net.md Normal file
View file

@ -0,0 +1,14 @@
# Networking Module
Provide host networking to the container.
## Example
```
permissions:
- module: net
```
## Description
Use this module to bind all available host networking to the container environment.

14
docs/modules/pipewire.md Normal file
View file

@ -0,0 +1,14 @@
# Pipewire Module
Avail pipewire audio devices to the container.
## Example
```
permissions:
- module: pipewire
```
## Description
Use this module to automatically detect and bind available pipewire sockets to the container environment.

View file

@ -0,0 +1,14 @@
# PulseAudio Module
Avail PulseAudio devices to the container.
## Example
```
permissions:
- module: pulseaudio
```
## Description
Use this module to automatically detect and bind available PulseAudio sockets to the container environment.

View file

@ -153,11 +153,10 @@ impl AlpmConfigData {
pub fn instantiate_alpm_agent(config: &Global, remotes: &AlpmConfigData) -> Alpm {
let mut handle = Alpm::new("/mnt/fs", "/mnt/fs/var/lib/pacman/").unwrap();
let hook_dirs = vec!["/mnt/fs/usr/share/libalpm/hooks/", "/mnt/fs/etc/pacman.d/hooks/"];
handle.set_logfile("/mnt/share/pacwrap.log").unwrap();
handle
.set_hookdirs(vec!["/mnt/fs/usr/share/libalpm/hooks/", "/mnt/fs/etc/pacman.d/hooks/"].iter())
.unwrap();
handle.set_hookdirs(hook_dirs.iter()).unwrap();
handle.set_cachedirs(vec!["/mnt/share/cache"].iter()).unwrap();
handle.set_gpgdir("/mnt/share/gnupg").unwrap();
handle.set_logfile("/mnt/share/pacwrap.log").unwrap();
@ -256,13 +255,15 @@ fn synchronize_database(cache: &ContainerCache, force: bool, lock: &Lock) -> Res
}
Alpm::release(handle).unwrap();
lock.assert()?;
for i in cache.registered().iter() {
let ins: &ContainerHandle = cache.get_instance(i).unwrap();
let ins: &ContainerHandle = cache.get_instance(i)?;
for repo in PACMAN_CONF.repos.iter() {
let src = &format!("{}/pacman/sync/{}.db", *DATA_DIR, repo.name);
let dest = &format!("{}/var/lib/pacman/sync/{}.db", ins.vars().root(), repo.name);
if let Err(error) = create_hard_link(src, dest).prepend(|| format!("Failed to hardlink db '{}'", dest)) {
error.warn();
}

View file

@ -1,5 +1,10 @@
# pacwrap
[![pacwrap](https://shields.io/aur/version/pacwrap?style=for-the-badge&color=599ffb&archlinux&label=pacwrap)](https://aur.archlinux.org/packages/pacwrap/)
[![pacwrap-git](https://shields.io/aur/version/pacwrap-git?style=for-the-badge&color=599ffb&logo=archlinux&label=pacwrap-git)](https://aur.archlinux.org/packages/pacwrap-git/)
[![License](https://shields.io/crates/l/pacwrap/0.8.0?style=for-the-badge&color=6dfb59)](https://spdx.org/licenses/GPL-3.0-only.html)
![MSRV](https://shields.io/crates/msrv/pacwrap/0.8.0?style=for-the-badge&color=fba759)
<img align="left" src="../assets/logo.svg">
A package management front-end which utilises libalpm to facilitate the creation of unprivileged, userspace containers with parallelised, filesystem-agnostic deduplication. These containers are constructed via bubblewrap to execute package transactions and launch applications.
@ -30,6 +35,12 @@ And finally, to install ```neovim``` inside of a fresh, aggregated container cal
$ pacwrap -Syucat editor --dep=base neovim
```
To update these containers just created in aggregate:
```
$ pacwrap -Syu
```
More advanced examples along with further documentation of configuration can be found further
elaborated upon **[here](../docs/)**.
@ -45,7 +56,7 @@ If a feature you see here is not completed, feel free to submit a PR; or submit
| Transaction Agent | Transact within a sandboxed runtime environment | ✅ |
| Transaction CLI | Functional | ✅ |
| Global Configuration | Functional | ✅ |
| Dependency Resolution | Functional, but too liberal to compensate for a lack of conflict resolution | ⚠ |
| Package Dependency Resolution | Utilizes a recursive first-depth search algorithm; resilient to cycling | ✅ |
| Foreign Database Resolution | Populates foreign package database in aggregate containers | ✅ |
| Foreign Database Resolution (Lazy) | Not yet implemented | ❌ |
| Conflict Resolution | Not yet implemented | ❌ |
@ -78,7 +89,7 @@ An online version of the user manual is viewable **[here](../docs/manual.md)**.
A minimum version of Rust 1.72 is required to build with the following libraries fulfilled by your distribution:
```
libalpm, libseccomp, libzstd
libalpm>=14, libseccomp, libzstd
```
## Packaging requirements

View file

@ -58,6 +58,7 @@ pub fn compose(args: &mut Arguments) -> Result<()> {
fn delete_containers<'a>(
cache: &'a ContainerCache<'a>,
lock: &'a Lock,
logger: &mut Logger,
delete: &Vec<&str>,
flags: &TransactionFlags,
@ -67,9 +68,9 @@ fn delete_containers<'a>(
if flags.contains(TransactionFlags::NO_CONFIRM) {
println!("{} {}{}...{}", *BAR_GREEN, *BOLD, &message, *RESET);
delete_roots(cache, logger, delete, force)?;
delete_roots(cache, lock, logger, delete, force)?;
} else if let Ok(_) = prompt_targets(&delete, &message, false) {
delete_roots(cache, logger, delete, force)?;
delete_roots(cache, lock, logger, delete, force)?;
}
Ok(())
@ -103,10 +104,12 @@ fn compose_handles<'a>(
fn instantiate<'a>(
composed: HashMap<&'a str, ContainerHandle<'a>>,
mut cache: ContainerCache<'a>,
lock: &'a Lock,
logger: &mut Logger,
) -> Result<ContainerCache<'a>> {
lock.assert()?;
println!("{} {}Instantiating container{}...{}", *BAR_GREEN, *BOLD, if composed.len() > 1 { "s" } else { "" }, *RESET);
for (instance, handle) in composed {
instantiate_container(&handle)?;
@ -220,10 +223,10 @@ fn engage_aggregator<'a>(args: &mut Arguments, lock: &'a Lock) -> Result<()> {
}
if delete.len() > 0 {
delete_containers(&cache, &mut logger, &delete, &flags, force)?;
delete_containers(&cache, lock, &mut logger, &delete, &flags, force)?;
}
cache = instantiate(compose_handles(&cache, compose)?, cache, &mut logger)?;
cache = instantiate(compose_handles(&cache, compose)?, cache, lock, &mut logger)?;
acquire_targets(&cache, &mut targets, &mut queue)?;
Ok(TransactionAggregator::new(&cache, &mut logger, TransactionType::Upgrade(true, true, false))
.assert_lock(lock)?

306
pacwrap/src/help.rs Normal file
View file

@ -0,0 +1,306 @@
/*
* pacwrap
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use indexmap::IndexSet;
use lazy_static::lazy_static;
use std::fmt::{Display, Formatter, Result as FmtResult};
use pacwrap_core::{
err,
impl_error,
utils::{arguments::Operand, is_color_terminal, Arguments},
Error,
ErrorTrait,
};
mod manual;
mod config;
pub mod version;
pub use version::print_version;
lazy_static! {
static ref HELP_ALL: Vec<HelpTopic> = [
HelpTopic::Execute,
HelpTopic::Sync,
HelpTopic::Remove,
HelpTopic::Compose,
HelpTopic::Query,
HelpTopic::Process,
HelpTopic::Utils,
HelpTopic::List,
HelpTopic::Help,
HelpTopic::Env,
HelpTopic::Version,
HelpTopic::Copyright
]
.into();
}
#[derive(Debug)]
enum ErrorKind {
InvalidTopic(String),
}
impl_error!(ErrorKind);
impl Display for ErrorKind {
fn fmt(&self, fmter: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
Self::InvalidTopic(err) => write!(fmter, "Topic '{}' is not available.", err),
}?;
write!(fmter, "\nTry 'pacwrap -h' for more information on valid operational parameters.")
}
}
pub fn help(mut args: &mut Arguments) -> Result<(), Error> {
let help = ascertain_help(&mut args)?;
let mut buffer = String::new();
for topic in help.0 {
topic.write(&mut buffer, help.1).unwrap();
}
if let HelpLayout::Console = help.1 {
print!("\x1b[?7l{}\x1b[?7h", buffer)
} else {
print!("{}", buffer)
}
Ok(())
}
fn ascertain_help<'a>(args: &'a mut Arguments) -> Result<(IndexSet<&'a HelpTopic>, &'a HelpLayout), Error> {
let mut layout = match is_color_terminal() {
true => &HelpLayout::Console,
false => &HelpLayout::Dumb,
};
let mut topic: Vec<&HelpTopic> = vec![&HelpTopic::Default];
let mut more = false;
while let Some(arg) = args.next() {
match arg {
Operand::Long("format") | Operand::Long("help") | Operand::Short('f') | Operand::Short('h') => continue,
Operand::Short('m') | Operand::Long("more") => more = true,
Operand::ShortPos('f', "man") | Operand::LongPos("format", "man") => layout = &HelpLayout::Man,
Operand::ShortPos('f', "ansi") | Operand::LongPos("format", "ansi") => layout = &HelpLayout::Console,
Operand::ShortPos('f', "dumb") | Operand::LongPos("format", "dumb") => layout = &HelpLayout::Dumb,
Operand::ShortPos('f', "markdown") | Operand::LongPos("format", "markdown") => layout = &HelpLayout::Markdown,
Operand::ShortPos('h', "all")
| Operand::LongPos("help", "all")
| Operand::Short('a')
| Operand::Long("all")
| Operand::Value("all") => topic.extend(HELP_ALL.iter()),
Operand::ShortPos('h', value) | Operand::LongPos("help", value) | Operand::Value(value) =>
topic.push(HelpTopic::from(value)?),
_ => args.invalid_operand()?,
}
}
let len = topic.len();
let start = if more || len == 1 || len > 7 { 0 } else { 1 };
args.set_index(1);
Ok((topic.drain(start ..).collect(), layout))
}
#[derive(Eq, PartialEq, Hash)]
enum HelpTopic {
Sync,
Remove,
Compose,
Execute,
Default,
Query,
Utils,
Process,
List,
Help,
Env,
Copyright,
Version,
PacwrapYml,
}
impl HelpTopic {
fn from(str: &str) -> Result<&Self, Error> {
Ok(match str {
"E" | "exec" | "run" => &HelpTopic::Execute,
"S" | "sync" | "init" => &HelpTopic::Sync,
"P" | "process" | "ps" => &HelpTopic::Process,
"L" | "list" | "ls" => &HelpTopic::List,
"U" | "utils" => &HelpTopic::Utils,
"R" | "remove" => &HelpTopic::Remove,
"C" | "compose" => &HelpTopic::Compose,
"Q" | "query" => &HelpTopic::Query,
"V" | "version" => &HelpTopic::Version,
"h" | "help" => &HelpTopic::Help,
"env" | "environment" => &HelpTopic::Env,
"copyright" => &HelpTopic::Copyright,
"synopsis" => &HelpTopic::Default,
"pacwrap.yml" => &HelpTopic::PacwrapYml,
_ => err!(ErrorKind::InvalidTopic(str.into()))?,
})
}
fn write(&self, buf: &mut String, layout: &HelpLayout) -> FmtResult {
match self {
Self::Default => manual::default(buf, layout),
Self::Sync => manual::sync(buf, layout),
Self::Remove => manual::remove(buf, layout),
Self::Execute => manual::execute(buf, layout),
Self::Process => manual::process(buf, layout),
Self::Version => manual::version(buf, layout),
Self::Env => manual::environment(buf, layout),
Self::Compose => manual::compose(buf, layout),
Self::Utils => manual::utils(buf, layout),
Self::List => manual::list(buf, layout),
Self::Help => manual::meta(buf, layout),
Self::Query => manual::query(buf, layout),
Self::Copyright => manual::copyright(buf, layout),
Self::PacwrapYml => config::default(buf, layout),
}
}
}
enum HelpLayout {
Man,
Dumb,
Markdown,
Console,
}
impl HelpLayout {
fn head(&self) -> &str {
match self {
Self::Console => "",
Self::Markdown => "## ",
Self::Man => ".SH\n",
Self::Dumb => "",
}
}
fn sub_bold(&self) -> &str {
match self {
Self::Console => " ",
Self::Markdown => "#### **",
Self::Man => ".TP\n\\fB",
Self::Dumb => " ",
}
}
fn sub(&self) -> &str {
match self {
Self::Markdown => "#### ",
Self::Man => ".TP\n",
Self::Dumb | Self::Console => " ",
}
}
fn sub_section(&self) -> &str {
match self {
Self::Console => " ",
Self::Markdown => "### **",
Self::Man => ".SS\n",
Self::Dumb => " ",
}
}
fn sub_paragraph(&self) -> &str {
match self {
Self::Console | Self::Dumb => " ",
Self::Man => ".PP\n",
Self::Markdown => "",
}
}
fn tab(&self) -> &str {
match self {
Self::Console | Self::Dumb => " ",
Self::Markdown | Self::Man => "",
}
}
#[allow(dead_code)]
fn underline(&self) -> &str {
match self {
Self::Console => "",
Self::Man => "\n.I",
Self::Markdown => "<ins>",
Self::Dumb => "",
}
}
#[allow(dead_code)]
fn reset_underline(&self) -> &str {
match self {
Self::Console => "",
Self::Man => "\n.I",
Self::Markdown => "</ins>",
Self::Dumb => "",
}
}
fn reset(&self) -> &str {
match self {
Self::Console => "",
Self::Man => "\\fR",
Self::Markdown | Self::Dumb => "",
}
}
fn reset_bold(&self) -> &str {
match self {
Self::Console => "",
Self::Man => "\\fR",
Self::Markdown => "**",
Self::Dumb => "",
}
}
fn bold(&self) -> &str {
match self {
Self::Console => "",
Self::Man => "\\fB",
Self::Markdown => "**",
Self::Dumb => "",
}
}
fn code(&self) -> &str {
match self {
Self::Console | Self::Dumb | Self::Man => "",
Self::Markdown => "```",
}
}
}
fn version_string() -> String {
let version = env!("CARGO_PKG_VERSION");
let release = env!("PACWRAP_BUILD");
let head = env!("PACWRAP_BUILDHEAD");
let date = env!("PACWRAP_BUILDSTAMP");
if head.is_empty() {
format!("{version} ({date})")
} else {
format!("{version}-{head}-{release} ({date})")
}
}

160
pacwrap/src/help/config.rs Normal file
View file

@ -0,0 +1,160 @@
/*
* pacwrap
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::fmt::{Result as FmtResult, Write};
use crate::help::{version_string, HelpLayout};
pub fn default(buf: &mut String, layout: &HelpLayout) -> FmtResult {
let head = layout.head();
let tab = layout.tab();
let bold = layout.bold();
let sub_bold = layout.sub_bold();
let sub_para = layout.sub_paragraph();
let reset = layout.reset();
let reset_bold = layout.reset_bold();
let code = layout.code();
let name = env!("CARGO_PKG_NAME");
let date = env!("PACWRAP_BUILDSTAMP");
match layout {
HelpLayout::Man => writeln!(
buf,
".nh\n.TH {name}.yml 2 \"{date}\" \"{name} version_string_placeholder\" \"Pacwrap Configuration Directives\"\n"
)?,
HelpLayout::Markdown => writeln!(
buf,
"# Pacwrap Configuration Directives
This document was generated by the {name} binary with version {} of the program.\n",
version_string(),
)?,
_ => (),
}
writeln!(
buf,
"{head}NAME{reset}
{sub_bold}pacwrap.yml{reset_bold} - pacwrap configuration file.
{sub_bold}repositories.conf{reset_bold} - pacman repository file.
{head}SYNOPSIS{reset}
{sub_bold}~/.config/pacwrap/pacwrap.yml{reset_bold}
{sub_bold}~/.config/pacwrap/repositories.conf{reset_bold}
{head}DESCRIPTION{reset}
{tab}Pacwrap upon invocation will attempt to deserialise {bold}pacwrap.yml{reset_bold}(2) at the location specified herein
{tab}compliant with the XDG Directory Specification. Upon activation of {bold}libalpm{reset_bold}(3), the repositories.conf
{tab}file will be deserialised by the pacman_conf crate in order to faciliate the population of package
{tab}repositories and mirrors.
{sub_para}Repository configuration is parsed using {bold}pacman.conf{reset_bold}(5)'s ini format. All other options related the
{tab}{bold}libalpm{reset_bold}(3) as defined by {bold}pacman.conf{reset_bold}(5) therein are otherwise ignored. At present by default,
{tab}the repository configuration, as defined, references the mirrorlist defined by your system.
{tab}This ensures as smooth of an out-of-box experience as humanly possible.
{tab}Optionally you may specify environment variables, as specified in {bold}pacwrap{reset_bold}(1), to override
{tab}these locations. These options are designated for experienced users only.
{head}EXAMPLE{reset}
{code}
{tab}config:
{tab} logging: Basic
{tab} summary: Table
{tab} progress:
{tab} transact: CondensedForeign
{tab} download: CondensedForeign
{tab}alpm:
{tab} ignore_pkg:
{tab} - nvidia-utils
{tab} - lib32-nvidia-utils
{code}
{head}SECTIONS{reset}
{sub_bold}config:{reset_bold}
{sub_para}{tab}Configuration pertaining to {bold}pacwrap{reset_bold}(1) are to be declared within this section.
{sub_bold}alpm:{reset_bold}
{sub_para}{tab}Configuration pertaining to {bold}libalpm{reset_bold}(3) are to be declared within this section.
{head}CONFIG{reset}
{sub_bold}logging{reset_bold}: Basic
{tab}{tab}Logging verbosity specified here. Available options are {bold}Basic{reset_bold}, {bold}Verbose{bold}, and {bold}None{reset_bold}.
{sub_bold}summary{reset_bold}: Basic
{tab}{tab}Transaction summary type. Available options are {bold}Basic{reset_bold}, {bold}BasicForeign{reset_bold}, {bold}Table{reset_bold}, and {bold}TableForeign{reset_bold}.
{tab}{tab}Each option suffixed with the {bold}Foreign{reset_bold} juxtaposition, will take effect only during the
{tab}{tab}juxtaposed transaction type with otherwise the opposite effect.
{sub_bold}progress:{reset_bold}
{tab}{tab}Progress types are declared within this subsection.
{head}PROGRESS{reset}
{sub_bold}transact{reset_bold}: CondensedForeign
{tab}{tab}Progress type for transaction progress is specified with this option. Available values are
{tab}{tab}{bold}Basic{reset_bold}, {bold}Condensed{bold}, {bold}CondensedForeign{reset_bold}, {bold}CondensedLocal{reset_bold}, and {bold}Verbose{reset_bold}.
{tab}{tab}Each option suffixed with the {bold}Foreign{reset_bold} or {bold}Local{reset_bold} juxtaposition, will take effect only during
{tab}{tab}the juxtaposed transaction type with otherwise the opposite effect.
{sub_bold}download{reset_bold}: Verbose
{tab}{tab}Download type for download progress is specified with this option. Available values are
{tab}{tab}{bold}Basic{reset_bold}, {bold}Condensed{bold}, {bold}CondensedForeign{reset_bold}, {bold}CondensedLocal{reset_bold}, and {bold}Verbose{reset_bold}.
{tab}{tab}Each option suffixed with the {bold}Foreign{reset_bold} or {bold}Local{reset_bold} juxtaposition, will take effect only during
{tab}{tab}the juxtaposed transaction type with otherwise the opposite effect.
{head}ALPM{reset}
{sub_bold}ignore_pkg:{reset_bold}
{tab}{tab}Ignored package(s) are declared herein with a string array.
{sub_bold}hold_pkg:{reset_bold}
{tab}{tab}Held package(s) are declared herein with a string array.
{sub_bold}sig_level{reset_bold}: Required DatabaseOptional
{tab}{tab}Default global signature level - see {bold}pacman.conf{reset_bold}(5) for valid options. Options are declared
{tab}{tab}as a singular string value.
{sub_bold}sig_level_local{reset_bold}: Optional
{tab}{tab}Default local signature level - see {bold}pacman.conf{reset_bold}(5) for valid options. Options are declared
{tab}{tab}as a single string value.
{sub_bold}check_space{reset_bold}: true
{tab}{tab}Instructs {bold}libalpm{reset_bold}(3), where applicable, to check if there's available space on disk in order
{tab}{tab}to facilitate a transaction. Value is declared with a {bold}bool{reset_bold}.
{sub_bold}download_timeout{reset_bold}: true
{tab}{tab}Instructs {bold}libalpm{reset_bold}(3) to timeout downloads from unsatisfactory mirrors. Value is declared with
{tab}{tab}a {bold}bool{reset_bold}.
{sub_bold}parallel_downloads{reset_bold}: 1
{tab}{tab}Instructs {bold}libalpm{reset_bold}(3) to parallelise the download queue with a maximum queue amount. Specify an
{tab}{tab}{bold}integer{reset_bold} to declare a maximum value.
{head}SEE ALSO{reset}
{tab}{tab}{bold}pacman.conf{reset_bold}(5), {bold}libalpm{reset_bold}(3)
{head}COPYRIGHT{reset}
{tab}Copyright (C) 2023-2024 Xavier R.M.
{sub_para}This program may be freely redistributed under the
{tab}terms of the GNU General Public License v3 only.\n"
)
}

641
pacwrap/src/help/manual.rs Normal file
View file

@ -0,0 +1,641 @@
/*
* pacwrap
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::fmt::{Result as FmtResult, Write};
use crate::help::{version_string, HelpLayout};
fn header(buf: &mut String, layout: &HelpLayout) -> FmtResult {
let name = env!("CARGO_PKG_NAME");
let date = env!("PACWRAP_BUILDSTAMP");
match layout {
HelpLayout::Man => writeln!(buf, ".nh\n.TH {name} 1 \"{date}\" \"{name} version_string_placeholder\" \"User Manual\"\n"),
HelpLayout::Markdown => writeln!(
buf,
"# Pacwrap User Manual
This document was generated by the {name} binary with version {} of the program.\n",
version_string()
),
_ => Ok(()),
}
}
pub fn default(buf: &mut String, layout: &HelpLayout) -> FmtResult {
let head = layout.head();
let tab = layout.tab();
let sub_para = layout.sub_paragraph();
let sub_bold = layout.sub_bold();
let bold = layout.bold();
let reset = layout.reset();
let reset_bold = layout.reset_bold();
header(buf, layout)?;
writeln!(
buf,
"{head}NAME{reset}
{tab}pacwrap
{head}SYNOPSIS{reset}
{tab}pacwrap [{bold}OPERATION{reset_bold} | {bold}COMMAND MODULE{reset_bold}] [{bold}ARGUMENTS{reset_bold}] [{bold}TARGETS{reset_bold}]
{head}DESCRIPTION{reset}
{sub_para}A package management front-end which utilises libalpm to facilitate the creation of unprivileged,
{tab}namespace containers with parallelised, filesystem-agnostic deduplication. These containers
{tab}are constructed with bubblewrap to execute package transactions and launch applications.
{sub_para}This application is designed to allow for the creation and execution of secure, replicable
{tab}containerised environments for general-purpose use. CLI and GUI applications are all supported.
{tab}Once a container environment is configured, it can be re-established or replicated on any system.
{head}OPERATIONS{reset}
{sub_bold}-S, --sync{reset_bold}
{tab}{tab}Synchronize package databases and update packages in target containers.
{sub_bold}-R, --remove{reset_bold}
{tab}{tab}Remove packages from target containers.
{sub_bold}-Q, --query{reset_bold}
{tab}{tab}Query package information from target container.
{sub_bold}-C, --compose{reset_bold}
{tab}{tab}Compose a container from configuration.
{sub_bold}-P, --process{reset_bold}
{tab}{tab}Manage and show status of running container processes.
{sub_bold}-E, --execute{reset_bold}
{tab}{tab}Executes application in target container using bubblewrap.
{sub_bold}-L, --list{reset_bold}
{tab}{tab}List of available containers managed by pacwrap.
{sub_bold}-U, --utils{reset_bold}
{tab}{tab}Invoke miscellaneous utilities to manage containers.
{sub_bold}-h, --help=<MODULE>{reset_bold}
{tab}{tab}Invoke a printout of this manual to {bold}STDOUT{reset_bold}.
{sub_bold}-V, --version{reset_bold}
{tab}{tab}Display version and copyright information in {bold}STDOUT{reset_bold}.\n"
)
}
pub fn execute(buf: &mut String, layout: &HelpLayout) -> FmtResult {
let head = layout.head();
let sub_bold = layout.sub_bold();
let tab = layout.tab();
let reset = layout.reset();
let reset_bold = layout.reset_bold();
writeln!(
buf,
"{head}EXECUTE{reset}
{sub_bold}-r, --root{reset_bold}
{tab}{tab}Execute operation with fakeroot and fakechroot. Facilitates a command with faked privileges.
{sub_bold}-s, --shell{reset_bold}
{tab}{tab}Invoke a bash shell\n"
)
}
pub fn meta(buf: &mut String, layout: &HelpLayout) -> FmtResult {
let head = layout.head();
let bold = layout.bold();
let sub_bold = layout.sub_bold();
let reset = layout.reset();
let reset_bold = layout.reset_bold();
let tab = layout.tab();
writeln!(
buf,
"{head}HELP{reset}
{sub_bold}-m, --more{reset_bold}
{tab}{tab}When specifying a topic to display, show the default topic in addition to specified options.
{sub_bold}-f, --format=FORMAT{reset_bold}
{tab}{tab}Change output format of help in {bold}STDOUT{reset_bold}. Format options include: 'ansi', 'dumb', 'markdown', and 'man'.
{tab}{tab}This option is for the express purposes of generating documentation at build time, and has little utility
{tab}{tab}outside the context of package maintenance. 'man' option produces troff-formatted documents for man pages.
{sub_bold}-a, --all, --help=all{reset_bold}
{tab}{tab}Display all help topics.\n"
)
}
pub fn sync(buf: &mut String, layout: &HelpLayout) -> FmtResult {
let head = layout.head();
let bold = layout.bold();
let tab = layout.tab();
let reset = layout.reset();
let reset_bold = layout.reset_bold();
let sub = layout.sub();
let sub_bold = layout.sub_bold();
let sub_sect = layout.sub_section();
let sub_para = layout.sub_paragraph();
writeln!(
buf,
"{head}SYNCHRONIZATION{reset}
{sub_para}Provides the facilities required to be able to synchronize and create containers in aggregate.
{sub_bold}-y, --refresh{reset_bold}
{tab}{tab}Synchronize remote package databases. Specify up to 2 times to force a refresh.
{sub_bold}-u, --upgrade{reset_bold}
{tab}{tab}Execute aggregate upgrade routine on all or specified containers. Use {bold}-t, --target=<TARGET>{reset_bold} followed by
{tab}{tab}a list of packages to specify package targets. Packages applicable to a target {bold}must{reset_bold} only be specified
{tab}{tab}after the target operand.
{sub_bold}-f, --filesystem{reset_bold}
{tab}{tab}Force execution of filesystem synchronization target on all or specified containers. In combination
{tab}{tab}with {bold}-o/--target-only{reset_bold}, in addition to no other specified targets, filesystem slices will be synchronized
{tab}{tab}without package synhcronization on on all applicable containers. This operation is useful for propagation
{tab}{tab}of manual filesystem changes to all aggregate containers.
{sub_bold}-c, --create{reset_bold}
{tab}{tab}Create a container with the first specified target. A container type argument is also required.
{sub_bold}-b, --base{reset_bold}
{tab}{tab}Base container type. Specify alongside {bold}-c, --create{reset_bold} to assign this container type during creation.
{tab}{tab}This container type is used as the base layer for all downstream containers. Only one base container
{tab}{tab}dependency per slice or aggregate is supported. Filesystem and package deduplication via slices and
{tab}{tab}aggregate containers are recommended, but optional. This container type is not dependant.
{sub_bold}-s, --slice{reset_bold}
{tab}{tab}Slice container type. Specify alongside {bold}-c, --create{reset_bold} to assign this container type during creation.
{tab}{tab}Requires a base dependency, and optionally one or more sliced dependencies, to ascertain foreign
{tab}{tab}packages and influence ordering of downstream synchronization target(s). Container slicing provides
{tab}{tab}the ability to install packages in a lightweight, sliced filesytem, which aid in the deduplication
{tab}{tab}of common downstream package and filesystem dependencies.
{tab}{tab}Useful for graphics drivers, graphical toolkits, fonts, etc.; these are not meant for applications.
{sub_bold}-a, --aggegrate{reset_bold}
{tab}{tab}Aggregate container type. Specify alongside {bold}-c, --create{reset_bold} to this assign container type during creation.
{tab}{tab}
{tab}{tab}Requires a base dependency, and optionally one or more sliced dependencies, in order to acertain foreign
{tab}{tab}packages and amalgamate the target. These containers are ideal for installing software with the aid of
{tab}{tab}filesystem and package deduplication.
{tab}{tab}
{tab}{tab}Useful for all general purpose applications, browsers, e-mail clients, or even terminal user interface
{tab}{tab}applications such as IRC clients. It is recommended to base your containers on aggregate type containers.
{sub_bold}-t, --target=TARGET{reset_bold}
{tab}{tab}Specify a target container for the specified operation.
{sub_bold}-d, --dep=DEPEND{reset_bold}
{tab}{tab}Specify a dependent container for the specified operation.
{sub_bold}-p, --preview{reset_bold}
{tab}{tab}Perform a dryrun operation on existing containers to preview changes applicable or otherwise specified.
{tab}{tab}Only applicable to
{sub_bold}-o, --target-only{reset_bold}
{tab}{tab}Apply specified operation on the specified target(s) only.
{sub_bold}--force-foreign{reset_bold}
{tab}{tab}Force synchronization of foreign packages on resident container. Useful for when installing
{tab}{tab}a new package in an aggregate container without all the prerequisite foreign dependencies
{tab}{tab}synchronized to this container's package database.
{sub_bold}--dbonly{reset_bold}
{tab}{tab}Transact on resident containers with a database-only transaction.
{sub_bold}--noconfirm{reset_bold}
{tab}{tab}Override confirmation prompts and confirm all operations.
{sub_sect}EXAMPLES{reset_bold}
{sub}`$ pacwrap -Syucbt base`
{tab}{tab}Create a base container named base with no additional packages
{sub}`$ pacwrap -Syucbt firefox firefox --dep=base,common,nvidia`
{tab}{tab}Create aggregate container named firefox with firefox installed.
{sub}`$ pacwrap -Syut electron element-desktop -t mozilla firefox thunderbird`
{tab}{tab}Synchronize package databases and upgrade all containers, as well as install element-desktop
{tab}{tab}in the target electron, and install firefox and thunderbird in the target mozilla.
{sub}`$ pacwrap -Syucst common gtk3 qt6-base --dep=base -cst nvidia nvidia-utils --dep=base,common`
{tab}{tab}Create two sliced containers, one named common with the packages gtk3, qt6-base, and another
{tab}{tab}named nvidia with the package nvidia-utils.
{sub}`$ pacwrap -Sof`
{tab}{tab}Synchronize filesystem state of all associated containers present in the data directory.\n")
}
pub fn remove(buf: &mut String, layout: &HelpLayout) -> FmtResult {
let head = layout.head();
let tab = layout.tab();
let sub = layout.sub();
let bold = layout.bold();
let sub_sect = layout.sub_section();
let sub_bold = layout.sub_bold();
let sub_para = layout.sub_paragraph();
let reset = layout.reset();
let reset_bold = layout.reset_bold();
writeln!(
buf,
"{head}REMOVE{reset}
{sub_para}Remove packages from specified containers.
{sub_bold}-s, --recursive{reset_bold}
{tab}{tab}Recursively remove all target packages with the associated target container. This does
{tab}{tab}not apply to packages upstream of a downstream container.
{sub_bold}-c, --cascade{reset_bold}
{tab}{tab}Remove all target packages with the associated target container, including all their
{tab}{tab}associated dependencies, provided they are not required by other packages, and are not
{tab}{tab}marked as being upstream of the target container.
{sub_bold}-t, --target=TARGET{reset_bold}
{tab}{tab}Specify a target container for the specified operation. At least one container target is
{tab}{tab}is required for package removal operations.
{sub_bold}--force-foreign{reset_bold}
{tab}{tab}Force the removal of foreign packages on target container. Useful for cleaning up
{tab}{tab}the package database of foreign, upstream dependencies synchronized to the target
{tab}{tab}container's package database.
{sub_bold}-m, --delete{reset_bold}
{tab}{tab}Delete root filesystem(s) of specified targets. Shortcout to {bold}-Ur{reset_bold}.
{sub_bold}-p, --preview{reset_bold}
{tab}{tab}Preview operation and perform no transaction.
{sub_bold}--dbonly{reset_bold}
{tab}{tab}Transact on resident containers with a database-only transaction.
{sub_bold}--noconfirm{reset_bold}
{tab}{tab}Override confirmation prompts and confirm all operations.
{sub_sect}EXAMPLES{reset_bold}
{sub}`$ pacwrap -Rt firefox firefox`
{tab}{tab}Remove the target package firefox from target container firefox.
{sub}`$ pacwrap rm firefox`
{tab}{tab}Delete the root filesystem for the firefox container.
\n"
)
}
pub fn compose(buf: &mut String, layout: &HelpLayout) -> FmtResult {
let head = layout.head();
let tab = layout.tab();
let bold = layout.bold();
let sub_bold = layout.sub_bold();
let reset = layout.reset();
let reset_bold = layout.reset_bold();
let sub = layout.sub();
let sub_sect = layout.sub_section();
writeln!(
buf,
"{head}COMPOSE{reset}
{tab}Compose containers from container configuration files. This functionality provides a way
{tab}to deterministically compose containers from an established configuration.
{sub_bold}<FILE_PATH>{reset_bold}
{tab}{tab}Compose a container from the specified configuration file on disk. Unless a target is
{tab}{tab}otherwise specified, the container will be initialized with a name derived from the
{tab}{tab}filename provided.
{sub_bold}-r, --reinitialize{reset_bold}
{tab}{tab}Compose an available, existing container for composition. The pre-existing container root
{tab}{tab}will be deleted and the container will be composited from the configuration data enumerated.
{tab}{tab}Reinitialize container
{sub_bold}-t, --target=TARGET{reset_bold}
{tab}{tab}Specify a target container for the specified operation.
{sub_bold}-f, --force{reset_bold}
{tab}{tab}Disable sanity checks and force removal of container filesystem(s).
{sub_bold}--reinitialize-all{reset_bold}
{tab}{tab}Queues all available, existing containers for composition. All pre-existing container roots
{tab}{tab}will be deleted and composited from the available configuration data enumerated.
{sub_bold}--from-config{reset_bold}
{tab}{tab}Instruct pacwrap to populate configuration data from uninitialized containers. Under normal
{tab}{tab}circumstances, configuration data will only be populated from containers with configuration
{tab}{tab}data and an