* Add Hugo build artifacts to .gitignore Ignore docs/public/, docs/resources/, and docs/.hugo_build.lock so generated Hugo output is never accidentally committed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add Hugo site infrastructure for Linutil docs Adds the Hugo configuration, theme module (hextra), layout shortcodes, i18n strings, static assets (favicons, nav logo), and archetype template needed to build the documentation site. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add docs site homepage Adds the root _index.md with badges, the quick-start curl command, and an important-note callout about frequent updates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add user guide content pages for Linutil Rewrites all user guide sections from Winutil (Windows) content to accurate Linutil (Linux) documentation: - _index.md: overview, feature summary, quick links - getting-started.md: how to run, install options, TUI navigation, keyboard shortcuts - store/_index.md: application installation guide with category tables - tweaks/_index.md: distro-specific system setup (Arch, Fedora, Debian, Ubuntu, Alpine) - features/_index.md: security, gaming, emulators, and utilities reference - updates/_index.md: how to keep Linutil up to date - automation/_index.md: TOML config file usage and use-case examples Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add FAQ, known issues, and contributing guide pages - faq.md: answers for general usage, running, scripts, contributing, and updates - KnownIssues.md: documents terminal size, rendering, distro support, Nvidia, and Cargo update issues - CONTRIBUTING.md: full contributing guide based on .github/CONTRIBUTING.md, formatted for the docs site Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add developer reference section and architecture docs - dev/_index.md: landing page for the developer reference section - dev/architecture.md: comprehensive architecture doc covering workspace layout, data model (Tab/ListNode/Command), tab_data.toml format, task list flags, preconditions system, script embedding via include_dir!, the script execution pipeline (PTY/vt100/tui-term), TUI layout and focus state machine, config file parsing, and key dependencies Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docgen: write walkthrough to docs/content/userguide/walkthrough.md - Rename generated file from userguide.md to walkthrough.md to better reflect that it is an auto-generated script reference/walkthrough - Update USER_GUIDE path to docs/content/userguide/walkthrough.md so cargo xtask docgen writes to the correct Hugo content location - Update automation guide link to point to ../walkthrough/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: remove unused Winutil screenshot assets All images in docs/assets/images/ were Winutil (Windows) screenshots copied over from the Winutil docs. None are referenced by any content file. Keeping only favicon.png and navlogo.png at the assets root, which are used by hugo.toml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Update CNAME * updated hugo configs * docs: add preview.gif to homepage via Hugo mount Mount .github/preview.gif directly into Hugo's static pipeline using includeFiles so only the gif is exposed (no workflows or other .github files). This means any update to .github/preview.gif is automatically reflected on the site without any manual copying. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * added workflow * Delete docs/assets/navlogo.png can change this to something else * Delete navlogo.png * Update docs.yaml * Update hugo.toml * Update _index.md * fix typo --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
10 KiB
title, weight, toc
| title | weight | toc |
|---|---|---|
| Architecture & Design | 1 | true |
This document describes how Linutil is structured internally — the crate layout, data model, TUI design, script execution pipeline, and the build tooling.
Workspace Layout
Linutil is a Cargo workspace with three crates:
linutil/
├── core/ # linutil_core — backend library
├── tui/ # linutil_tui — binary (the TUI you run)
└── xtask/ # build tooling (cargo xtask docgen)
core/ — linutil_core
The library crate. Responsible for:
- Defining the data model (
Tab,ListNode,Command) - Parsing all
tab_data.tomlfiles and building the menu tree - Embedding all scripts into the binary at compile time using
include_dir! - Extracting embedded scripts to a temp directory at runtime
- Evaluating preconditions to filter out scripts unsupported on the current system
- Parsing the user's TOML config file
tui/ — linutil_tui
The binary crate. Responsible for:
- Setting up the terminal (crossterm alternate screen, raw mode)
- Running the main event loop
- Rendering the entire TUI layout via
ratatui - Handling all keyboard and mouse input
- Launching scripts in a pseudo-terminal (PTY) via
portable-pty - Parsing CLI arguments via
clap
xtask/
Cargo's task runner extension. Run with:
cargo xtask docgen
This reads all tab_data.toml files and generates docs/content/userguide/userguide.md — the auto-generated walkthrough page. Always run this after adding or editing a script entry.
Data Model
The menu is a tree of ListNode items, grouped into named Tabs.
Tab
pub struct Tab {
pub name: String,
pub tree: Tree<Rc<ListNode>>,
}
Each Tab maps to one top-level category. The five built-in tabs are defined in core/tabs/tabs.toml:
directories = [
"applications-setup",
"gaming",
"security",
"system-setup",
"utils"
]
ListNode
pub struct ListNode {
pub name: String,
pub description: String,
pub command: Command,
pub task_list: String,
pub multi_select: bool,
}
Every item in the TUI is a ListNode. A node is either a directory (has children, command = Command::None) or a leaf command (no children, has a runnable command).
Command
pub enum Command {
Raw(String), // inline shell command
LocalFile { // shell script file
executable: String,
args: Vec<String>,
file: PathBuf,
},
None, // directory node
}
Raw— a short command string run directly by the shellLocalFile— a script file whose interpreter is read from the shebang line (e.g.#!/bin/bash)None— marks a category/folder node
Tab Data Format
Each tab is defined by a tab_data.toml file inside core/tabs/<tab-name>/. Example:
name = "Applications Setup"
[[data]]
name = "Communication Apps"
[[data.entries]]
name = "Discord"
description = "Discord is a versatile communication platform..."
script = "communication-apps/discord-setup.sh"
task_list = "I"
[[data.entries]]
name = "Some Inline Command"
description = "Runs a quick command"
command = "echo hello"
task_list = "MP"
Entry Fields
| Field | Required | Description |
|---|---|---|
name |
Yes | Display name shown in the TUI |
description |
No | Shown in the description floating window (d key) |
script |
One of | Path to a shell script (relative to the tab directory) |
command |
One of | Inline shell command string |
entries |
One of | Nested sub-entries (makes this node a directory) |
task_list |
No | One or more flag codes shown next to the item name |
multi_select |
No | Whether this command can be queued in multi-select mode (default: true) |
preconditions |
No | Conditions that must pass for the entry to be shown |
Task List Flags
Flags shown to the right of each command name, defined in state.rs:
| Flag | Meaning |
|---|---|
D |
Disk modifications (privileged) |
FI |
Flatpak installation |
FM |
File modification |
I |
Installation (privileged) |
K |
Kernel modifications (privileged) |
MP |
Package manager actions |
SI |
Full system installation |
SS |
Systemd actions (privileged) |
RP |
Package removal |
Preconditions
Preconditions let a script declare when it should be visible. If any precondition fails, the entry is hidden from the TUI.
[[data.entries]]
name = "Paru AUR Helper"
script = "paru-setup.sh"
[[data.entries.preconditions]]
matches = true
data = { containing_file = "/etc/os-release" }
values = ["Arch Linux", "Manjaro"]
Precondition Types
| Type | Checks |
|---|---|
environment |
Whether an environment variable equals one of the given values |
containing_file |
Whether a file's contents contain all of the given strings |
command_exists |
Whether a command is present on $PATH |
file_exists |
Whether a file path exists on disk |
The matches field inverts the check when false (i.e. "must NOT match").
Script Embedding
All files under core/tabs/ are embedded into the compiled binary at build time using the include_dir! macro:
const TAB_DATA: Dir = include_dir!("$CARGO_MANIFEST_DIR/tabs");
At runtime, get_tabs() extracts the embedded directory to a system temp directory (/tmp/linutil_scripts_XXXX), and all LocalFile commands reference scripts inside that temp dir. The temp directory is cleaned up automatically when TabList is dropped.
This means a single binary contains everything — no external script files needed after build.
Script Execution Pipeline
When a user selects a command and confirms it:
AppState::handle_confirm_command()creates aRunningCommandfrom the list of selectedCommandvaluesRunningCommand::new()allocates a PTY viaportable-pty(NativePtySystem)- The command is spawned inside the PTY as a child process
- A reader thread reads output from the PTY master and writes it into a shared
Arc<Mutex<Vec<u8>>>buffer - An atomic flag (
TERMINAL_UPDATED) is set when new output arrives, triggering a TUI redraw - The PTY output is decoded by a
vt100parser and rendered as aPseudoTerminalwidget (fromtui-term) inside a floating window - The user can scroll up/down to review output, or press
Ctrl-Cto kill the process - When the process exits, the floating window title changes to
SUCCESS(green) orFAILED(red)
Using a real PTY (instead of piped stdio) means scripts that use terminal colors, interactive prompts, or check isatty() work correctly.
TUI Layout
The TUI is rendered by AppState::draw() and divided into these regions:
┌──────────────┬────────────────────────────────┐
│ Logo / │ [ Search bar ] │
│ Version ├────────────────────────────────┤
│ │ │
│ Tab List │ Item List │
│ │ │
│ System Info │ │
├──────────────┴────────────────────────────────┤
│ Keyboard hint bar │
└───────────────────────────────────────────────┘
- Left column: Logo (or version label), tab list, system info panel
- Right column: Search bar (top) + scrollable item list
- Bottom bar: Context-sensitive keyboard shortcut hints
- Floating windows: Overlaid on the item list for running commands, previews, descriptions, and confirmation prompts
Focus State Machine
AppState tracks a Focus enum:
| State | Description |
|---|---|
TabList |
User is navigating the left tab panel |
List |
User is navigating the item list |
Search |
The search bar is active |
FloatingWindow |
A modal is open (preview, description, running command, guide) |
ConfirmationPrompt |
User is being asked to confirm before running a command |
Input is dispatched to the currently focused component.
Config File
Linutil reads an optional TOML config file at startup (--config path). The linutil_core::Config struct deserializes it:
pub struct Config {
auto_execute: Option<Vec<String>>,
skip_confirmation: Option<bool>,
size_bypass: Option<bool>,
}
After parsing, auto_execute command names are looked up by name in the loaded TabList (using Tab::find_command_by_name), and the resulting Vec<Rc<ListNode>> is placed directly into selected_commands to be run immediately on startup.
Adding a New Script
- Create a shell script in
core/tabs/<tab-name>/<category>/your-script.sh - Add an entry to the corresponding
tab_data.toml:
[[data.entries]]
name = "Your Script"
description = "What it does."
script = "<category>/your-script.sh"
task_list = "I"
- Add preconditions if the script is distro-specific
- Run
cargo xtask docgento update the documentation - Run
cargo runto test it locally
Key Dependencies
| Crate | Purpose |
|---|---|
ratatui |
TUI rendering framework |
crossterm |
Cross-platform terminal control (raw mode, events) |
portable-pty |
Pseudo-terminal allocation for running commands |
tui-term |
PTY output rendering widget for ratatui |
vt100-ctt |
VT100 terminal emulator (parses ANSI escape codes) |
ego-tree |
Generic tree structure used for the menu |
include_dir |
Embed entire directory trees into the binary at compile time |
serde + toml |
Deserialize tab_data.toml and config files |
clap |
CLI argument parsing |
tree-sitter-bash |
Bash syntax highlighting in script previews |