From bc420114bfb854a63ef2b0aa725a395a2f91c2b5 Mon Sep 17 00:00:00 2001 From: Tomas Mirchev Date: Mon, 16 Mar 2026 05:08:32 +0200 Subject: [PATCH] docs: update README for new architecture Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 243 ++++++++++++++++++++++-------------------------------- 1 file changed, 97 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index 26206b2..a444885 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,6 @@ # flow -`flow` is a CLI for managing development instances, containers, dotfiles, and host 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`) +`flow` is a CLI for managing development environments: dotfiles, packages, containers, remote targets, and system bootstrap. ## Installation @@ -20,88 +11,111 @@ make install-local 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). -- At startup, `flow` refreshes sudo credentials once (`sudo -v`) for privileged steps. -- Package `post-install` hooks run without sudo by default. -- A package hook can use sudo only when `allow_sudo: true` is explicitly set. +- Domain layer is pure: no I/O, no side effects, fully testable +- Services perform all I/O and delegate logic to domain functions +- Commands are trivial dispatchers from argparse to services -### 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) -2. `~/.config/flow/` (local fallback) +# Packages +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. -If the same top-level key appears in multiple files, the later filename wins. +# Bootstrap +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`). -When pull brings new changes, flow shows an info message and waits for Enter before opening the editor. +# Remote targets +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 -_shared/ - flow/ - .config/flow/ - config.yaml - packages.yaml - profiles.yaml - dnsmasq/ - .user_hosts - _root/ - opt/homebrew/etc/dnsmasq.conf - git/ - .gitconfig -linux-auto/ - nvim/ - .config/nvim/init.lua +# Other +flow --version +flow --quiet +flow completion ``` -- `_shared/`: linked for all profiles -- `_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) +## Configuration -### 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 -_shared/ +paths: + 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/ .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 source: github:org/nvim-config ref: branch: main ``` -- Flow mounts the module repo root at the directory containing `_module.yaml` (e.g. the example mounts into `~/.config/nvim/`). -- 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`). +Module files are linked from the clone cache, while local files outside the mount path are linked from the dotfiles repo. -## Manifest model +## Manifest -Top-level keys: - -- `profiles` -- `packages` -- optional global settings like `repository`, `paths`, `defaults`, `targets` - -`environments` is not supported. - -### Packages (unified) +Packages and profiles are defined in YAML files under the flow config directory. ```yaml packages: @@ -109,103 +123,40 @@ packages: type: pkg sources: apt: fd-find - dnf: fd-find brew: fd - - name: wezterm - type: cask - sources: - brew: wezterm - - name: neovim type: binary source: github:neovim/neovim version: "0.10.4" - asset-pattern: "nvim-{{os}}-{{arch}}.tar.gz" platform-map: - linux-x64: { os: linux, arch: x64 } - 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] -``` + linux-x64: nvim-linux-x86_64.tar.gz -### Profile package syntaxes - -All are supported in one profile list: - -```yaml profiles: - macos-dev: - os: macos + linux-work: + os: linux + hostname: dev-box + locale: en_US.UTF-8 + shell: zsh packages: - - git - - cask/wezterm + - fd - binary/neovim - - name: docker - allow_sudo: true - post-install: | - sudo groupadd docker || true - sudo usermod -aG docker $USER + ssh-keys: + - path: ~/.ssh/id_ed25519 + type: ed25519 + runcmd: + - echo "setup complete" ``` -### Templates +## Security -- `{{ env.VAR_NAME }}` -- `{{ version }}` -- `{{ os }}` -- `{{ 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 -``` +- `flow` must run as a regular user (root invocation is rejected) +- `_root/` files require sudo for linking +- Package post-install hooks run without sudo by default ## Development ```bash -python3 -m venv .venv -.venv/bin/pip install -e ".[dev]" -python3 -m pytest -FLOW_RUN_E2E_CONTAINER=1 .venv/bin/pytest -q tests/test_dotfiles_e2e_container.py +make deps +.venv/bin/python -m pytest tests/ -v ```