Batch-update multiple git repositories in parallel
Project description
nostos
__
____ ____ _____/ /_____ _____
/ __ \/ __ \/ ___/ __/ __ \/ ___/
/ / / / /_/ (__ ) /_/ /_/ (__ )
/_/ /_/\____/____/\__/\____/____/
the homecoming - a fleet of git repositories
nostos is a Python CLI for batch-updating and curating fleets of git repositories in parallel. Built for developers and platform teams who maintain dozens - or hundreds - of cloned repositories and need a reliable, auditable, scriptable way to keep them in sync.
Overview
nostos is three tools in one:
- A batch-pull engine that walks a directory tree (and/or a curated index), discovers every git repository it can reach, and updates them concurrently. Dirty trees, detached HEADs, and missing upstreams are reported and skipped, never overwritten.
- A metadata index (SQLite) recording identity, provenance, tags, notes, and triage status for every repository in your fleet.
- An upstream probe layer that queries GitHub, GitLab, and Gitea - hosted and self-hosted - for health signals (archived, stars, last push, latest release, license), with strict fail-closed opsec.
Use cases:
- Platform / DevEx teams keeping shared tool repositories fresh on developer workstations.
- Build boxes or mirror hosts maintaining read-only clones of upstream projects.
- Onboarding automation that bootstraps and refreshes a curated set of team repositories.
- Release engineers reconciling many long-lived checkouts before a coordinated change.
Quick start
pipx install nostos # or: pip install nostos
nostos completion install # enable shell tab-completion
nostos --dry-run # preview updates under cwd
nostos ~/projects --workers 16 # pull in parallel
nostos add https://github.com/org/tool.git --tag recon
nostos list --tag recon --untouched-over 90
nostos triage # classify new intake
No third-party runtime dependencies beyond argcomplete (required for tab-completion, bundled with every install).
Installation
Requirements
| Component | Minimum | Recommended |
|---|---|---|
| Python | 3.10 | 3.12+ |
| Git | 2.25 | 2.45.1+ (nostos warns at startup on versions with CVE-2024-32002 / 32004 / 32465) |
| OS | Linux / macOS / Windows | - |
Install
# Isolated (recommended)
pipx install nostos
# System-wide
pip install nostos
# From source
git clone https://github.com/prodrom3/nostos.git
cd nostos && pip install .
Verify with nostos --version and nostos --help.
Shell tab-completion
nostos completion install # auto-detects your shell
exec $SHELL # reload
nostos <TAB><TAB> # lists all verbs
install is idempotent and writes a managed block into ~/.bashrc, ~/.zshrc, or ~/.config/fish/conf.d/nostos.fish. Remove with nostos completion uninstall. Native Windows shells (PowerShell / cmd) are not supported by argcomplete upstream; use Git Bash or WSL.
Usage
nostos is verb-first: nostos <verb> [args]. An invocation without a verb is an implicit pull, so old scripts and cron jobs keep working.
| Verb | Purpose |
|---|---|
pull (default) |
Batch-update discovered repositories. |
add |
Ingest a local path or remote URL into the metadata index. |
list |
Filter and print the repo fleet. |
show |
Print full metadata for one repo. |
tag |
Add or remove tags on a repo. |
note |
Append a timestamped note. |
triage |
Walk newly-added repos interactively and classify them. |
refresh |
Fetch upstream metadata. Opsec-gated. See docs/upstream-probes.md. |
digest |
Weekly changeset report (zero network). |
dashboard |
Render a static HTML fleet health report. |
vault |
Obsidian vault bridge (export / sync). See docs/vault.md. |
export / import |
Portable JSON bundles for cross-machine / backup. See docs/bundle-schema.md. |
update |
Self-update. Auto-detects source / pipx / pip install. |
doctor |
Index integrity check; --fix auto-remediates safe issues. |
attack |
MITRE ATT&CK technique lookup + tagging helper. |
completion |
Shell tab-completion setup. |
rm |
Remove a repo from the index (optionally --purge the clone). |
Every verb has --help. Every verb that lists or returns results supports --json.
Key flags - nostos pull
| Flag | Default | Description |
|---|---|---|
path |
cwd | Root directory to scan. |
--from-index |
off | Pull every repo registered in the metadata index. |
--dry-run |
off | List discovered repos without pulling. |
--fetch-only |
off | Fetch from remotes; do not merge or rebase. |
--tags |
off | Also fetch all git tags. |
--rebase |
off | Use git pull --rebase. |
--depth N |
5 | Directory-scan depth limit. |
--workers N |
8 | Concurrent worker threads. |
--timeout N |
120 | Seconds before a git operation is killed. |
--exclude PATTERN... |
- | Glob patterns to skip repos by directory name. |
--json |
off | Machine-readable output. |
-q, --quiet |
off | Suppress progress; print only the summary. |
Every pulled repo is automatically registered in the metadata index with an updated last_touched_at.
Examples
# Daily batch pull
nostos ~/projects
# CI-friendly: quiet, JSON, fetch-only
nostos --fetch-only --quiet --json | jq '.counts'
# Ingest + triage
nostos add https://github.com/org/tool.git --tag recon --source "blog:orange.tw"
nostos triage
# Find C2 tools you haven't touched in 90 days
nostos list --tag c2 --untouched-over 90
# Portable backup
nostos export --out fleet.json
nostos import fleet.json --clone-dir ~/repos # on another machine
Configuration
Optional INI file at ~/.nostosrc. CLI flags always override file values.
[defaults]
depth = 5
workers = 8
timeout = 120
max_log_files = 20
rebase = false
clone_dir = /home/user/repos
[exclude]
patterns = archived-*, .backup-*, vendor-*
Environment variables
| Variable | Effect |
|---|---|
NO_COLOR |
Disables ANSI color when set to any non-empty value. |
NOSTOS_SHELL |
Overrides $SHELL when detecting the target shell for nostos completion. Accepts bash / zsh / fish. |
GITHUB_TOKEN, etc. |
Referenced via token_env = "..." in ~/.config/nostos/auth.toml for upstream probes. |
XDG_CONFIG_HOME |
Relocates ~/.config/nostos/ (default ~/.config). |
XDG_DATA_HOME |
Relocates ~/.local/share/nostos/ - the index DB and the logs/ subdirectory (default ~/.local/share). |
APPDATA / LOCALAPPDATA |
Windows fallbacks when the XDG variables are unset: config lives under %APPDATA%\nostos\, data under %LOCALAPPDATA%\nostos\. |
Precedence, highest to lowest: CLI flags -> ~/.nostosrc -> built-in defaults.
Core concepts
Metadata index
SQLite at $XDG_DATA_HOME/nostos/index.db (default ~/.local/share/nostos/index.db, 0600 perms, WAL mode, secure_delete=ON). One row per repository plus tags, timestamped notes, provenance, and triage status. Every verb reads from and writes to this file; the batch pull auto-registers every repo it touches.
| Column | Description |
|---|---|
path |
Absolute, realpath-normalised. Unique. |
remote_url |
origin remote (HTTPS credentials stripped). |
source |
Free-text provenance ("blog:...", "auto-discovered", "legacy-watchlist"). |
status |
new, reviewed, in-use, dropped, flagged. |
quiet |
Opsec flag: never probe upstream for this repo. |
added_at / last_touched_at |
ISO-8601 UTC timestamps. |
| tags / notes | Many-to-many tags; append-only timestamped notes. |
For at-rest confidentiality, place $XDG_DATA_HOME/nostos/ on a disk-layer encrypted volume (LUKS / FileVault / BitLocker). nostos ships no built-in DB encryption by design.
Upstream probes
nostos refresh populates cached upstream health (archived, stars, last push, release, license) for each repo, gated by ~/.config/nostos/auth.toml. Unconfigured hosts are never contacted. Fail-closed invariants are documented in docs/upstream-probes.md.
Portable bundles
nostos export / nostos import serialise the metadata index as a schema-versioned JSON bundle. The import path resolves each entry against the local filesystem (direct match, $HOME-relative match, --remap) and clones repos that carry a remote_url but do not exist locally. Full schema and algorithm in docs/bundle-schema.md.
Obsidian vault
nostos vault export turns the index into one markdown file per repo with YAML frontmatter. nostos vault sync reconciles operator edits to status / tags back into the DB. Details and Dataview queries in docs/vault.md.
Output
Human-readable
[1/9] updated: /home/user/projects/repo-a
[2/9] up-to-date: /home/user/projects/repo-d
[3/9] skipped: /home/user/projects/repo-e
--- Summary ---
Updated (3): ...
Skipped (1): /home/user/projects/repo-e - dirty working tree
Total: 9 | Updated: 3 | Up-to-date: 5 | Skipped: 1 | Failed: 0
JSON
nostos --json | jq '.counts'
Progress lines go to stderr; --json output on stdout stays clean for pipes.
CI / automation
| Exit code | Meaning |
|---|---|
0 |
All discovered repos updated or already up-to-date. |
1 |
At least one repo failed to update. |
Skipped repositories (dirty, detached, no upstream) do not fail the run - they are surfaced in the summary for review.
# GitHub Actions
- name: Refresh vendored clones
run: |
nostos ./vendor --quiet --json > /tmp/nostos.json
jq '.counts' /tmp/nostos.json
# crontab
*/30 * * * * /usr/local/bin/nostos ~/projects --quiet --fetch-only
Logging
Each run writes a timestamped log file to $XDG_DATA_HOME/nostos/logs/ (default ~/.local/share/nostos/logs/ on Linux / macOS, %LOCALAPPDATA%\nostos\logs\ on Windows) alongside the metadata index - so logs survive pipx reinstall and are always findable regardless of install method. Example filename: 2026-04-17_14-30-00.log. Rotated to the most recent 20 (configurable via max_log_files). Files are 0600; the logs/ directory is 0700 on Unix. HTTPS credentials of the form https://user:token@host/ are sanitized to https://***@host/ before being written.
Security
nostos treats git operations on untrusted working directories as an attack surface, and the metadata index as an intelligence artifact. Defense-in-depth applies at both layers.
| Control | Description |
|---|---|
| Git version check | Startup warning on git < 2.45.1 (CVE-2024-32002 / 32004 / 32465). |
| Safe remote clone | add <url> clones with --no-checkout and disables hooks via GIT_CONFIG_*. |
| Credential redaction | HTTPS credentials stripped from all logs and from remote_url values in the index. |
| File permissions | Logs and index DB 0600; config and data dirs 0700 (Unix). |
| Ownership checks | ~/.nostosrc, auth.toml, and legacy watchlist rejected if not owned by the invoking user or world-writable. |
| Repository ownership | Repos not owned by the current user are skipped on Unix. |
| Symlink protection | The logs/ directory is rejected if it is a symlink. |
| No shell injection | Every subprocess call uses list arguments; shell=True is never used. |
| Index hardening | SQLite journal_mode=WAL, secure_delete=ON, foreign_keys=ON; deleted rows overwritten on disk. |
| Probe fail-closed | Upstream probes only contact hosts listed in auth.toml; --offline hard-disables the network layer. |
| Per-repo quiet flag | add --quiet-upstream makes a repo ineligible for upstream probes; the probe layer never queries or logs these repos. |
| Token hygiene | Tokens sourced from env vars by default, sent as Authorization: Bearer, redacted from every log and error path. |
Report security issues privately via a GitHub security advisory. See SECURITY.md for the full disclosure process.
Compatibility
| OS | Python 3.10 | 3.11 | 3.12 | 3.13 |
|---|---|---|---|---|
| Ubuntu (latest) | ✓ | ✓ | ✓ | ✓ |
| macOS (latest) | ✓ | ✓ | ✓ | ✓ |
| Windows (latest) | ✓ | ✓ | ✓ | ✓ |
CI exercises every cell of this matrix on every push and pull request.
Architecture
See docs/architecture.md for the module layout, dependency graph, and end-to-end flows (pull, add -> triage, import).
Versioning & support
nostos follows Semantic Versioning 2.0. Breaking changes appear only in new major versions and are called out in CHANGELOG.md and the corresponding GitHub release notes.
- Stable: CLI flags, exit codes, JSON output schema, bundle schema (reader accepts all versions in
READABLE_SCHEMAS). - Internal: the
core/Python API is not a supported public API; import at your own risk.
Current version: see VERSION and nostos --version.
Response expectations (best-effort, non-commercial): see MAINTAINERS.md.
Project documents
| File | Purpose |
|---|---|
| CHANGELOG.md | Per-release change log (mirrors GitHub releases). |
| CONTRIBUTING.md | Dev setup, test / lint / mypy workflow, PR style. |
| MAINTAINERS.md | Primary maintainer, escalation path, release authority. |
| SECURITY.md | Private disclosure process. |
| LICENSE | MIT. |
Deep-dive docs under docs/:
| File | Topic |
|---|---|
| docs/architecture.md | Module layout, dependency graph, run / intake / import flows. |
| docs/upstream-probes.md | Upstream probe auth, commands, opsec invariants. |
| docs/bundle-schema.md | Portable bundle format v2 and import resolution algorithm. |
| docs/vault.md | Obsidian vault bridge and narrow two-way sync. |
License
Released under the MIT License. Authored by prodrom3; maintained by the radamic organization.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nostos-1.3.0.tar.gz.
File metadata
- Download URL: nostos-1.3.0.tar.gz
- Upload date:
- Size: 118.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
71c749a7dcf85671d3dc7bfe5e9ab86c549655e419936bf2d6b2fe7facb2fcbb
|
|
| MD5 |
f67ec9d7eabbd3a82a0666a23af8e743
|
|
| BLAKE2b-256 |
9e0fd36db713ab6795a5e407b871ec03a81fc2cf0f413fd4615070093f67557e
|
Provenance
The following attestation bundles were made for nostos-1.3.0.tar.gz:
Publisher:
publish.yml on prodrom3/nostos
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nostos-1.3.0.tar.gz -
Subject digest:
71c749a7dcf85671d3dc7bfe5e9ab86c549655e419936bf2d6b2fe7facb2fcbb - Sigstore transparency entry: 1326158717
- Sigstore integration time:
-
Permalink:
prodrom3/nostos@8cd363e1ca0d38f13b9631f3619972948a566b3a -
Branch / Tag:
refs/tags/v1.3.0 - Owner: https://github.com/prodrom3
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8cd363e1ca0d38f13b9631f3619972948a566b3a -
Trigger Event:
push
-
Statement type:
File details
Details for the file nostos-1.3.0-py3-none-any.whl.
File metadata
- Download URL: nostos-1.3.0-py3-none-any.whl
- Upload date:
- Size: 93.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
895a69ec45671e31d9792620149a091590409eda40fc20a5bdc30055c8b80f09
|
|
| MD5 |
09037eb5b33756b09299198896e71205
|
|
| BLAKE2b-256 |
6099fb9adefe1eff94e494ed0c23f8479c047449d4543c39af2b3b160736aa09
|
Provenance
The following attestation bundles were made for nostos-1.3.0-py3-none-any.whl:
Publisher:
publish.yml on prodrom3/nostos
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nostos-1.3.0-py3-none-any.whl -
Subject digest:
895a69ec45671e31d9792620149a091590409eda40fc20a5bdc30055c8b80f09 - Sigstore transparency entry: 1326158886
- Sigstore integration time:
-
Permalink:
prodrom3/nostos@8cd363e1ca0d38f13b9631f3619972948a566b3a -
Branch / Tag:
refs/tags/v1.3.0 - Owner: https://github.com/prodrom3
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8cd363e1ca0d38f13b9631f3619972948a566b3a -
Trigger Event:
push
-
Statement type: