docs: update README for new architecture

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 05:08:32 +02:00
parent 6e9f9c9e30
commit bc420114bf

243
README.md
View File

@@ -1,15 +1,6 @@
# flow # flow
`flow` is a CLI for managing development instances, containers, dotfiles, and host bootstrap. `flow` is a CLI for managing development environments: dotfiles, packages, containers, remote targets, and system bootstrap.
## What is implemented
- Instance access via `flow enter`
- Container lifecycle under `flow dev`
- Dotfiles repo management (`flow dotfiles`)
- Bootstrap provisioning (`flow bootstrap`)
- Package installs from unified manifest definitions (`flow package`)
- Project sync checks (`flow sync`)
## Installation ## Installation
@@ -20,88 +11,111 @@ make install-local
This installs `flow` to `~/.local/bin/flow`. This installs `flow` to `~/.local/bin/flow`.
## Core behavior ## Architecture
### Security model Four-layer design: **core** (runtime, config, errors) -> **domain** (pure functions, frozen dataclasses) -> **services** (I/O orchestration) -> **commands** (thin CLI adapters).
- `flow` must run as a regular user (root/sudo invocation is rejected). - Domain layer is pure: no I/O, no side effects, fully testable
- At startup, `flow` refreshes sudo credentials once (`sudo -v`) for privileged steps. - Services perform all I/O and delegate logic to domain functions
- Package `post-install` hooks run without sudo by default. - Commands are trivial dispatchers from argparse to services
- A package hook can use sudo only when `allow_sudo: true` is explicitly set.
### Config location and merge rules ## Commands
`flow` loads all YAML files from: ```bash
# Dotfiles
flow dotfiles link [--profile NAME] [--dry-run] [--skip PKG...]
flow dotfiles unlink [PACKAGES...] [--dry-run]
flow dotfiles status
flow dotfiles sync
1. `~/.local/share/flow/dotfiles/_shared/flow/.config/flow/` (self-hosted, if present) # Packages
2. `~/.config/flow/` (local fallback) flow packages install [NAMES...] [--profile NAME] [--dry-run]
flow packages remove NAMES... [--dry-run]
flow packages list
Files are read alphabetically (`*.yaml` and `*.yml`) and merged at top level. # Bootstrap
If the same top-level key appears in multiple files, the later filename wins. flow setup run PROFILE [--dry-run]
flow setup show PROFILE
flow setup list
`repository.pull-before-edit` controls whether `flow dotfiles edit` runs `git pull --rebase` first (default: `true`). # Remote targets
When pull brings new changes, flow shows an info message and waits for Enter before opening the editor. flow remote enter NAMESPACE@PLATFORM [--dry-run]
flow remote list
### Dotfiles layout (flat with reserved dirs) # Dev containers
flow dev create IMAGE [--namespace NS] [--dry-run]
flow dev enter NAME [--shell PATH]
flow dev stop NAME
flow dev remove NAME
flow dev list
Inside your dotfiles repo root: # Projects
flow projects check [--fetch]
```text # Other
_shared/ flow --version
flow/ flow --quiet
.config/flow/ flow completion
config.yaml
packages.yaml
profiles.yaml
dnsmasq/
.user_hosts
_root/
opt/homebrew/etc/dnsmasq.conf
git/
.gitconfig
linux-auto/
nvim/
.config/nvim/init.lua
``` ```
- `_shared/`: linked for all profiles ## Configuration
- `_root/` is a marker inside a package for absolute paths (via sudo), e.g. `dnsmasq/_root/etc/hostname -> /etc/hostname`
- every other directory at this level is a profile name
- any target conflict fails (including `_shared` vs profile)
### External module packages Config is loaded from `~/.config/flow/config.yaml` and merged with self-hosted config from the dotfiles repo at `~/.local/share/flow/dotfiles/_shared/flow/.config/flow/`.
Any directory inside a package can be backed by an external git repository using `_module.yaml`: ```yaml
repository:
url: git@github.com:you/dotfiles.git
branch: main
```text paths:
_shared/ projects: ~/projects
defaults:
container-registry: registry.example.com
tmux-session: main
targets:
personal@orb: personal.orb
work@ec2:
host: work.ec2.internal
identity: ~/.ssh/id_work
```
## Dotfiles layout
```
_shared/ # Linked for all profiles
zsh/
.zshrc
nvim/ nvim/
.config/nvim/ .config/nvim/
_module.yaml _module.yaml # External git module
.local/bin/
nvim-wrapper
dns/
_root/ # Absolute path marker (needs sudo)
etc/hosts
linux-work/ # Profile-specific layer
i3/
.config/i3/config
``` ```
### External modules
A `_module.yaml` inside a package mounts an external git repo at that location:
```yaml ```yaml
source: github:org/nvim-config source: github:org/nvim-config
ref: ref:
branch: main branch: main
``` ```
- Flow mounts the module repo root at the directory containing `_module.yaml` (e.g. the example mounts into `~/.config/nvim/`). Module files are linked from the clone cache, while local files outside the mount path are linked from the dotfiles repo.
- Local files under that directory are ignored (shown only in `--verbose`).
- Only one `_module.yaml` per package is supported.
- Modules are refreshed on `flow dotfiles init` and `flow dotfiles sync` (not on `link`).
## Manifest model ## Manifest
Top-level keys: Packages and profiles are defined in YAML files under the flow config directory.
- `profiles`
- `packages`
- optional global settings like `repository`, `paths`, `defaults`, `targets`
`environments` is not supported.
### Packages (unified)
```yaml ```yaml
packages: packages:
@@ -109,103 +123,40 @@ packages:
type: pkg type: pkg
sources: sources:
apt: fd-find apt: fd-find
dnf: fd-find
brew: fd brew: fd
- name: wezterm
type: cask
sources:
brew: wezterm
- name: neovim - name: neovim
type: binary type: binary
source: github:neovim/neovim source: github:neovim/neovim
version: "0.10.4" version: "0.10.4"
asset-pattern: "nvim-{{os}}-{{arch}}.tar.gz"
platform-map: platform-map:
linux-x64: { os: linux, arch: x64 } linux-x64: nvim-linux-x86_64.tar.gz
linux-arm64: { os: linux, arch: arm64 }
darwin-arm64: { os: macos, arch: arm64 }
extract-dir: "nvim-{{os}}64"
install:
bin: [bin/nvim]
share: [share/nvim]
man: [share/man/man1/nvim.1]
lib: [lib/libnvim.so]
```
### Profile package syntaxes
All are supported in one profile list:
```yaml
profiles: profiles:
macos-dev: linux-work:
os: macos os: linux
hostname: dev-box
locale: en_US.UTF-8
shell: zsh
packages: packages:
- git - fd
- cask/wezterm
- binary/neovim - binary/neovim
- name: docker ssh-keys:
allow_sudo: true - path: ~/.ssh/id_ed25519
post-install: | type: ed25519
sudo groupadd docker || true runcmd:
sudo usermod -aG docker $USER - echo "setup complete"
``` ```
### Templates ## Security
- `{{ env.VAR_NAME }}` - `flow` must run as a regular user (root invocation is rejected)
- `{{ version }}` - `_root/` files require sudo for linking
- `{{ os }}` - Package post-install hooks run without sudo by default
- `{{ arch }}`
### Bootstrap profile features
- `os` is required (`linux` or `macos`)
- `package-manager` optional (auto-detected if omitted)
- default locale is `en_US.UTF-8`
- shell auto-install + `chsh` when `shell:` is declared and missing
- `requires` validation for required env vars
- `ssh-keygen` definitions
- `runcmd` (runs after package installation)
- automatic config linking (`_shared` + profile, including package-local `_root` markers)
- `post-link` hook (runs after symlink phase)
- config skip patterns:
- package names (e.g. `nvim`)
- `_shared`
- `_profile`
- `_root`
## Command overview
```bash
flow enter personal@orb
flow dev create api -i tm0/node -p ~/projects/api
flow dotfiles init --repo git@github.com:you/dotfiles.git
flow dotfiles link --profile linux-auto
flow dotfiles undo
flow dotfiles status
flow dotfiles modules list
flow dotfiles modules sync
flow bootstrap list
flow bootstrap show linux-auto
flow bootstrap run --profile linux-auto --var USER_EMAIL=you@example.com
flow package install neovim
flow package list --all
flow sync check
flow completion install-zsh
```
## Development ## Development
```bash ```bash
python3 -m venv .venv make deps
.venv/bin/pip install -e ".[dev]" .venv/bin/python -m pytest tests/ -v
python3 -m pytest
FLOW_RUN_E2E_CONTAINER=1 .venv/bin/pytest -q tests/test_dotfiles_e2e_container.py
``` ```