High-level design and component interactions in trobz_local.
┌─────────────────────────────────────────────────────────────┐
│ User (CLI Interface) │
│ tlc <command> [options] │
└────────────────────┬────────────────────────────────────────┘
│
┌────────────┴────────────┐
│ │
┌────▼──────┐ ┌───────▼────┐
│ Newcomer │ │ Dry-Run │
│ Mode │ │ Preview │
└────┬──────┘ └────┬───────┘
│ │
└────────────┬────────┘
│
┌────────────▼────────────────────┐
│ main.py (CLI Orchestration) │
│ - Command routing │
│ - Task generation │
│ - Progress management │
└────┬──────────────┬─────────────┘
│ │
┌────────▼──┐ ┌──────▼────────┐
│ Config │ │ Installer │
│ (utils) │ │ (installers) │
│ │ │ │
│ - Validate│ │ Strategy 1: │
│ - Parse │ │ Scripts │
│ - Load OS │ │ │
└────┬──────┘ │ Strategy 2: │
│ │ System Pkgs │
│ │ │
│ │ Strategy 3: │
│ │ NPM Packages │
│ │ │
│ │ Strategy 4: │
│ │ UV Tools │
│ └──────┬────────┘
│ │
│ ┌─────────────┴──────────────┐
│ │ │
┌────▼────▼────────────────────┐ ┌──▼──────────┐
│ concurrency.py │ │ exceptions │
│ - ThreadPoolExecutor │ │ │
│ - TaskResult aggregation │ │ Custom │
│ - Progress tracking │ │ exceptions │
└────────┬─────────────────────┘ └─────────────┘
│
┌────────▼──────────────┐
│ Rich Progress Bars │
│ (User Feedback) │
└───────────────────────┘
User runs bootstrap.sh
│
├─ Check sudo privileges
│ └─ Verify user can run sudo without password
│
├─ Detect OS
│ ├─ Debian/Ubuntu: apt/dpkg detection
│ ├─ Fedora: dnf detection
│ ├─ Arch: pacman detection
│ └─ macOS: brew detection
│
├─ Install git (skip if already installed)
│ └─ OS-specific package manager
│
├─ Install gh (GitHub CLI, skip if already installed)
│ ├─ Debian: Add GitHub official APT repository with GPG verification
│ ├─ Fedora/Arch: Package manager
│ └─ macOS: brew
│
├─ Install uv (skip if already installed)
│ └─ curl -LsSf https://astral.sh/uv/install.sh | sh
│
├─ Setup SSH known_hosts for GitHub
│ ├─ Create ~/.ssh directory (700 permissions)
│ └─ Add github.com to known_hosts via ssh-keyscan
│
├─ Install trobz_local (tlc)
│ └─ uv tool install git+https://github.com/trobz/local.py.git
│
└─ Display completion message with next steps
└─ Recommend: tlc install-tools, tlc init, tlc pull-repos, tlc create-venvs
init command
│
└─ Create directory tree at {CODE_ROOT}/ (default: ~/code/, customize with TLC_CODE_DIR)
├─ venvs/
├─ oca/{version}/
├─ odoo/odoo/{version}/
├─ odoo/enterprise/{version}/
└─ trobz/{projects, packages}/
│
└─ Display Rich tree
└─ Exit 0
pull-repos command
│
├─ Load config.toml
├─ Validate config
│
├─ Build task list:
│ for each version:
│ for each repo in config:
│ if --filter && repo not in filter: skip
│ create task: {name, func: _pull_repo, args: {repo, version, path}}
│
├─ Show confirmation (newcomer mode)
│ └─ If --dry-run: show preview, exit 0
│
├─ Execute tasks in parallel (max 4 workers):
│ for each task:
│ if repo exists:
│ git fetch origin {branch}
│ git checkout {branch}
│ git reset --hard origin/{branch}
│ else:
│ git clone --depth=1 --branch={branch} {url} {path}
│ with GitProgress callback
│
├─ Aggregate TaskResults
└─ Report failures, exit 0 or 1
install-tools command
│
├─ Load config.toml
├─ Validate config
│
├─ Build installation preview:
│ - PostgreSQL repo (Debian/Ubuntu only)
│ - Scripts: list URLs
│ - System packages: list packages
│ - NPM packages: list packages
│ - UV tools: list tools
│
├─ Show confirmation (newcomer mode)
│ └─ If --dry-run: show preview, exit 0
│
├─ Execute five-stage installation pipeline in sequence:
│ 1. setup_postgresql_repo() [Debian/Ubuntu only, idempotent]
│ ├─ Check if PGDG repo already configured
│ ├─ If missing, add PGDG APT repository with GPG verification
│ ├─ Download and verify GPG key
│ └─ Update apt sources
│
│ 2. install_scripts()
│ ├─ Create temp directory
│ ├─ For each script:
│ │ ├─ Download via wget or curl
│ │ └─ Execute with /bin/sh
│ └─ Clean up temp directory
│
│ 3. install_system_packages()
│ ├─ Detect OS (Arch, Ubuntu, macOS)
│ ├─ Merge user packages with platform defaults
│ ├─ Run package manager with sudo
│ └─ Return success/failure boolean
│
│ 4. install_npm_packages()
│ ├─ Check if npm exists
│ └─ Parallel: run_tasks() with npm install -g
│
│ 5. install_uv_tools()
│ └─ Parallel: run_tasks() with uv tool install
│
├─ Aggregate all results
└─ Report summary, exit 0 or 1
create-venvs command
│
├─ Load config.toml
├─ Validate config
│
├─ Build task list:
│ for each version:
│ create task: {name: "venv-{version}", func: _create_venvs, ...}
│
├─ Show confirmation (newcomer mode)
│
├─ Execute tasks in parallel (max 4 workers):
│ for each task:
│ uv tool run odoo-venv --preset demo --python 3.12 {version}
│ creates: ~/code/venvs/{version}/
│
├─ Aggregate TaskResults
└─ Report failures, exit 0 or 1
ensure-db-user command
│
├─ Check PostgreSQL availability
│ └─ Run pg_isready on localhost
│ └─ If failed: print error message, exit 1
│
├─ Detect OS (Darwin vs Linux)
│ └─ Set execution method:
│ ├─ macOS (Darwin): Direct psql execution
│ └─ Linux: sudo -n -u postgres psql (requires passwordless sudo)
│
├─ Check if PostgreSQL user "odoo" exists
│ └─ Query system catalog via psql
│
├─ If user missing:
│ ├─ Create user "odoo" with hardcoded dev password
│ ├─ Grant CREATEDB privilege
│ └─ Validate user creation via psql query
│
├─ Test connection with created credentials
│ ├─ Connect as "odoo" user to postgres database
│ ├─ If successful: print ✓ message
│ └─ If failed: print error, exit 3
│
└─ Exit with appropriate code:
├─ 0 = User ready
├─ 1 = PostgreSQL not running
├─ 2 = Sudo auth failed (Linux)
└─ 3 = User creation/connection failed
doctor command
│
├─ Load config.toml
│
└─ Run health checks grouped by category:
│
├─ Configuration
│ └─ check_config()
│ ├─ Verify config.toml exists
│ ├─ Parse and validate TOML syntax
│ └─ Return CheckResult(status: OK|WARN|FAIL, message)
│
├─ Connectivity
│ └─ check_github_ssh()
│ ├─ Test SSH connection to git@github.com
│ └─ Return CheckResult(status: OK|WARN|FAIL)
│
├─ Tools
│ └─ check_tool_versions()
│ ├─ _check_uv_tools() - query uv tool list
│ ├─ _check_npm_packages() - query npm list -g --json
│ └─ Return list[CheckResult] per tool
│
└─ Virtual Environments
└─ list_venvs()
├─ For each configured version in config
├─ Check venvs/{version}/bin/python exists
├─ Run python --version to verify functionality
└─ Return list[CheckResult] per version
│
└─ Format results:
├─ Create Rich table per group (Configuration, Connectivity, Tools, Venvs)
├─ Map CheckStatus → icon (OK=green, WARN=yellow, FAIL=red)
├─ Display summary counts (passed, warnings, failures)
└─ Exit 1 if any FAIL, else exit 0
{CODE_ROOT}/config.toml (default: ~/code/config.toml)
│
▼
[Read TOML]
(tomli.loads)
│
▼
[Validate Structure]
(Pydantic ConfigModel)
│
├─ ConfigModel
│ ├─ versions: list[str]
│ ├─ tools: ToolsConfig
│ └─ repos: RepoConfig
│
├─ ToolsConfig
│ ├─ uv: list[str]
│ ├─ npm: list[str]
│ ├─ script: list[ScriptItem]
│ └─ system_packages: list[str]
│
└─ RepoConfig
├─ odoo: list[str]
└─ oca: list[str]
│
▼
[Return Validated Dict]
(get_config() in utils.py)
│
▼
[Main Commands Use Dict]
(Generate tasks, installers)
Task Dict: {name, func, args}
│
▼
[ThreadPoolExecutor]
(concurrency.run_tasks, max_workers=4)
│
├─ Create Future for each task
├─ Provide Progress callback
└─ Add to Rich progress bar
│
▼
[Task Execution with Progress]
func(progress: Progress, task_id: TaskID, **args)
│
├─ Success: TaskResult(name, success=True)
│
└─ Exception: TaskResult(name, success=False, error=...)
│
▼
[Results Aggregation]
(list[TaskResult])
│
└─ Display summary with Rich
install_tools request
│
├─ PostgreSQL Repository (Debian/Ubuntu only)
│ ├─ Check if PGDG repo already configured
│ ├─ If missing, add PGDG APT repository
│ ├─ Download and verify GPG key
│ └─ Update apt sources (idempotent)
│
├─ Scripts
│ ├─ Create temp directory
│ ├─ For each URL:
│ │ ├─ _get_download_command(url)
│ │ │ ├─ Try wget (preferred)
│ │ │ └─ Fall back to curl
│ │ ├─ Execute download command
│ │ └─ _execute_script(path)
│ │ └─ Run with /bin/sh (safe)
│ └─ Auto-cleanup temp directory
│
├─ System Packages (runs after PostgreSQL repo on Debian/Ubuntu)
│ ├─ _get_package_manager_config(os, distro)
│ │ ├─ Arch → pacman -S --noconfirm --needed
│ │ ├─ Ubuntu → apt-get install -y
│ │ └─ macOS → brew install
│ ├─ Merge user packages with defaults
│ └─ _run_package_install(cmd, packages)
│ └─ Execute with sudo
│
├─ NPM Packages
│ ├─ Check: which npm
│ ├─ For each package:
│ │ └─ npm install -g {package}
│ └─ Parallel via run_tasks()
│
└─ UV Tools
├─ For each tool:
│ └─ uv tool install -- {tool}
└─ Parallel via run_tasks()
[Error Detection Point]
│
├─ Configuration Error
│ └─ Pydantic ValidationError
│ ├─ Print field-level errors
│ ├─ Show example config
│ └─ Exit 1
│
├─ Prerequisite Missing
│ └─ ExecutableNotFoundError(executable)
│ ├─ Record in TaskResult
│ ├─ Continue other tasks
│ └─ Fail at end if any error
│
├─ Task Exception
│ └─ Custom exception
│ ├─ Catch and record
│ ├─ Continue in parallel
│ └─ Show ✗ Error in progress
│
└─ User Interrupt
└─ KeyboardInterrupt
├─ Cancel running futures
├─ Cleanup resources
└─ Exit gracefully
- Type: ThreadPoolExecutor
- Max Workers: 4 (configurable)
- Rationale: I/O-bound tasks (downloads, git, package installation)
- Not suitable for: CPU-intensive tasks
Worker 1: Git clone repo-1
Worker 2: Git clone repo-2
Worker 3: Git fetch repo-3
Worker 4: Git checkout repo-4
If Worker 1 fails:
- TaskResult(name="repo-1", success=False, error=...)
- Workers 2, 3, 4 continue
- Final exit code: 1 (any failure)
Rich Progress Bar (one per task)
├─ Task: "repo-1"
│ ├─ Status: [████████░░░░░░░░░░░░] 45%
│ └─ Message: "Fetching objects..."
│
├─ Task: "repo-2"
│ ├─ Status: [██████████████████░░░] 95%
│ └─ Message: "Writing objects..."
│
└─ Task: "repo-3"
├─ Status: [██████████████████████] 100% ✓
└─ Message: "Complete"
[get_os_info()]
│
├─ Darwin (macOS)
│ └─ system: "Darwin"
│ distro: "macos"
│
└─ Linux
├─ Try freedesktop_os_release()
│ ├─ Arch Linux → distro: "arch"
│ ├─ Ubuntu → distro: "ubuntu"
│ └─ Other → distro: "linux"
│
└─ Fallback → system: "unknown"
[get_package_manager_config(system, distro)]
│
├─ macOS → brew
├─ Arch → pacman
├─ Ubuntu → apt-get
└─ Fallback error
@dataclass
class CheckResult:
name: str # Check identifier ("Config file", "GitHub SSH", etc.)
status: CheckStatus # Enum: OK | WARN | FAIL
message: str # Status message ("Valid — 3 version(s) defined")
detail: str = "" # Additional error details (exception trace, etc.)class CheckStatus(Enum):
OK = "OK" # Check passed
WARN = "WARN" # Check passed with warnings (missing optional tool)
FAIL = "FAIL" # Check failed (invalid config, auth error)@dataclass
class TaskResult:
name: str # Task identifier ("repo-1", "tool-x", etc.)
success: bool # True if completed, False if exception
message: str | None = None # Status message ("Completed", error summary)
error: Exception | None = None # Exception object if failed{
"name": "string", # Identifier for display
"func": callable, # Function to execute
"args": { # Keyword arguments (optional)
"repo": "repo_name",
"version": "16.0",
"path": "/home/user/code/oca/repo_name"
}
}ConfigModel(
versions: list[str],
tools: ToolsConfig | None,
repos: RepoConfig | None
)
ToolsConfig(
uv: list[str] | None,
npm: list[str] | None,
script: list[ScriptItem] | None,
system_packages: list[str] | None
)
ScriptItem(
url: str, # HTTPS enforced
name: str | None # Optional display name
)
RepoConfig(
odoo: list[str] | None,
oca: list[str] | None
)User (trusted)
│
▼
{CODE_ROOT}/config.toml (default: ~/code/config.toml, semi-trusted, validated)
│
├─ Pydantic validation
├─ Regex pattern validation
└─ HTTPS enforcement for URLs
│
▼
External Resources (untrusted)
│
├─ Git repositories
├─ Shell scripts
├─ NPM packages
└─ UV tools
Script Execution
│
├─ Subprocess safety: no shell=True
├─ Full paths: shutil.which() instead of PATH
├─ Argument lists: never string concatenation
└─ Temp directory: auto-cleanup
- Define function in
main.pywith@app.command()decorator - Follow signature:
func(ctx: typer.Context, ...)with typer.Option() for additional parameters - Use
confirm_step()for newcomer mode - Call installers or utilities as needed
- Return
typer.Exit(code=0)on success ortyper.Exit(code=1)on failure
- Create function:
install_xxx(items: list[str], dry_run: bool) -> list[TaskResult] - Use
run_tasks()for parallelization withmax_workersparameter - Add to
_run_installers()in main.py
- Update Pydantic models in
utils.py - Add validators if needed (regex patterns, custom logic)
- Document in
docs/project-overview-pdr.md
run_tasks()acceptsmax_workersparameter (default: 4)- Can be customized per operation type if needed
- Example:
run_tasks(tasks, max_workers=8)for more parallelization
- Semantic Versioning: "16.0", "17.0", "18.0" (standard Odoo releases)
- Master Branch: "master" for development/cutting-edge versions
- Pattern:
^(?:\d+\.\d+|master)$enforced via Pydantic validation - Branch Mapping: Version strings map to git branch names during pull-repos
- init: < 1 second (local filesystem only)
- pull-repos (1 repo): 10-30 seconds (network dependent)
- pull-repos (5 repos parallel): 15-40 seconds (vs 50-150 sequential)
- install-tools (all types): 5-15 minutes (highly variable, depends on tools)
- create-venvs (4 versions parallel): 10-20 minutes (vs 40-80 sequential)
- 4 concurrent git clones ≈ 3x faster than sequential
- 4 concurrent venv creations ≈ 3-3.5x faster than sequential
- System package install remains sequential (sudo, package manager serialization)
uv tool install git+ssh://git@github.com:trobz/local.py.git- Installs to user's Python tool directory
- Creates
tlcentry point - Self-contained, no global state
# User creates once:
~/code/config.toml # or {TLC_CODE_DIR}/config.toml- Personal environment definition
- Not committed to any repository
- Can vary per developer
- Config location follows the code root directory
tlc <command> # Works from anywhere- Uses
TLC_CODE_DIRenv var if set, otherwise$HOME/code/as root - No elevated privileges needed except for package installation (sudo)
- All changes isolated to user's home directory
- Customize with:
export TLC_CODE_DIR=/custom/path