Skip to content

get2knowio/hangar

Repository files navigation

Hangar

Hangar is a self-hosted, single-operator fleet control plane for your repositories. It aggregates every repo across one or more provider connections (GitHub today, Gitea designed-for) into one dashboard, scores each repo against a declarative best-practice policy, and lets you remediate hygiene drift in place — every content change delivered as a pull request, never a push.

It is provider-agnostic at its core, fail-closed behind a reverse-proxy SSO layer, and built to run as a single Docker Compose stack on a modest homelab host.

Running Hangar

Hangar ships as a prebuilt multi-arch Docker image on GHCR — ghcr.io/get2knowio/hangar — and runs as a single Docker Compose stack. The fastest look, no source checkout:

docker compose -f deploy/docker-compose.example.yml up -d   # then open http://127.0.0.1:8000

That boots a no-auth instance bound to loopback — fine for kicking the tyres. Before exposing it to a network, set an access mode and a secret key. The full run modes (local dev, the Traefik + SSO stack, Postgres) and the complete configuration reference live in CONTRIBUTING.md.


Why Hangar

A maintainer running a portfolio of repos has no standing, fleet-level answer to two recurring questions:

  1. What needs my attention right now? Open PRs piling up, dependency-bot PRs waiting to merge, failing CI on main, security alerts by severity, repos with unreleased commits sitting too long. Today that's N browser tabs and tribal memory.
  2. Is the fleet configured the way I want it to be? Is Dependabot on everywhere? Is the update cooldown set? Is release-please wired up? Is branch protection on main? Is there a LICENSE, a SECURITY.md, CODEOWNERS? These best practices are decided once and then drift silently as repos are created and forgotten.

You can answer (2) for a moment in time with a one-shot script that configures cooldowns and Dependabot across an org. That's the right action but the wrong lifecycle: a one-shot run doesn't persist, doesn't re-check, and doesn't catch the next repo. Hangar turns that one-shot into a standing control plane — continuous visibility, a declarative definition of "good," and a path to fix what's drifted.


How it works

 connect a provider ──▶ poll & interrogate ──▶ score against policy ──▶ remediate (PR-first)
 (GitHub App / token)   (background, cached,    (23-check catalog,       (operator-triggered,
                         ETag-conditional)       pass/fail/unknown)        audit-logged)
  • Reads never hit the provider live. A background poller interrogates each repo with conditional (ETag) requests and stores a normalized snapshot; page loads read the cache and evaluate the catalog. A poll that finds nothing changed costs no API quota.
  • Every connection is scoped and attributed. Repos, findings, and remediations always carry their originating provider connection — two same-named repos across connections never collide.
  • Honest state. A signal Hangar can't determine (missing scope, or a platform that has no equivalent) is reported as unknown — never a fabricated pass or fail.

The core concepts are deliberately platform-neutral so GitHub and Gitea map onto the same vocabulary: a Fleet is the union of watched repos; a Provider connection is one configured credential + scope; a Check is one evaluable rule; a Policy is an ordered set of checks; a Finding is one check evaluated against one repo; a Remediation is the action that resolves it.


What Hangar validates — the check catalog

Hangar evaluates a fleet-wide policy of 23 checks, grouped into five areas. Each check declares a remediation tier — how far Hangar can go toward fixing it — evaluated per connection, since a provider may support auto-correction on one platform and only a deep-link on another:

Tier Badge What Hangar does
Report Report Surfaces the finding and its evidence. No action to take on Hangar's side.
Deep-link Deep-link Sends you straight to the exact settings page to fix it — used when Hangar can't safely synthesize the change.
PR API · PR Opens a fix pull request (adds a config/file). Human-triggered, idempotent, never a push.
API API Applies a scoped settings change via the provider API (e.g. toggling a repo setting).

Write tiers always degrade gracefully: a check whose native tier is PR or API collapses to deep-link, then report, when a connection lacks the capability (a read-only connection, or a platform without that setting).

Supply chain

Check Tier Passes when
Dependabot alerts enabled API Dependabot alerts are turned on for the repo.
Version updates configured API · PR A Dependabot (.github/dependabot.yml) or Renovate (renovate.json, .renovaterc, …) update config is present.
Update cooldown ≥ target API · PR An update cooldown is configured to the target (default 7 days) — Dependabot cooldown or Renovate minimumReleaseAge.
Lockfile present Report A dependency lockfile is committed.
Dependency review enabled Deep-link The dependency-review action is wired into CI.
Actions pinned to SHA Deep-link Workflows pin actions to immutable commit SHAs, not mutable tags.

Release

Check Tier Passes when
release-please configured API · PR A release-please manifest/config is present.
Conventional commits enforced Deep-link A commitlint config or PR-title-lint workflow enforces conventional commits.
CHANGELOG automated Report A CHANGELOG / automated release notes exist.
Release health / commit age Report The latest release isn't lagging too far behind main.
CI workflow green on default Report Default-branch CI is configured and passing.

Governance

Check Tier Passes when
Branch protection on default Deep-link A protection ruleset guards the default branch.
CODEOWNERS present API · PR A CODEOWNERS file exists.
Default branch = main Report The default branch is main.

Security

Check Tier Passes when
SECURITY.md present API · PR A SECURITY.md policy exists.
Secret scanning + push protection Deep-link Secret scanning and push protection are enabled.
Code scanning (CodeQL) Deep-link A CodeQL / code-scanning workflow is configured.
Org 2FA required Deep-link The owning org enforces two-factor auth.
Workflow permissions least-privilege Deep-link GITHUB_TOKEN isn't left at write-all; a least-privilege permissions block is set.

Project meta

Check Tier Passes when
LICENSE present API · PR A LICENSE file is at the repo root.
README present Report A README exists.
Description & topics set Deep-link The repo has a description and topics.
Issue / PR templates API · PR .github/ISSUE_TEMPLATE (and/or PR template) exists.

Checks are data. The catalog lives in backend/src/hangar/domain/checks/ — adding or changing a rule is a data edit there, never dashboard code. Detection (which repos pass / fail / unknown) is done by the provider adapters from read-only interrogation.


Repo-level overrides — .hangar.json

Not every check applies to every repo, and a deliberate gap shouldn't drag a repo's score forever. A watched repo can carry a .hangar.json at the root of its default branch to tell Hangar how to treat it. Today it supports one thing: ignoring checks the repo intentionally doesn't satisfy.

{
  "version": 1,
  "ignore": [
    { "check": "dependabot_alerts", "reason": "Internal tool, no external deps" },
    "code_scanning"
  ]
}
  • Each ignore entry is either an object { "check": "<id>", "reason": "<optional>" } or a bare "<id>" string. check must be a catalog check id (the id="…" in the catalog, e.g. dependabot_alerts, cooldown, branch_protection); unknown ids are ignored.
  • A suppressed check is shown honestly — it renders as ⊘ Suppressed (with your reason as its evidence), offers no remediation, and is excluded from the score denominator: it neither passes nor fails, so the repo is neither penalized nor credited for it. Repo detail reads e.g. 18/20 scored · 2 suppressed.
  • Reading the file needs the connection's file-read capability; a connection without it simply sees no suppressions (nothing is guessed). Malformed JSON is ignored safely — the repo is still interrogated. Suppressions are picked up on the next sync after you commit the file, and the check is re-scored when you remove it.

This keeps the score meaningful: it reflects the checks you've actually decided should apply to a given repo, per repo, in version control — not a blanket global waiver.


Providers

  • GitHub — the live adapter. Connect in two clicks with the built-in Connect with GitHub flow (Hangar creates your own least-privilege GitHub App — no tokens to paste), or bring your own App / PAT. GitHub Enterprise (GHES and GHEC data-residency) is supported.
  • Gitea — a first-class provider; because Gitea's REST API is GitHub-shaped, the same 23-check catalog, scorecard, and PR-first remediation apply. Signals OSS Gitea has no equivalent for (alerts, secret/code scanning, workflow-permissions, org 2FA) honestly report unknown rather than a fabricated result.

Setup for both — including least-privilege scopes, webhooks, and Enterprise hosts — is in CONTRIBUTING.md.


Security posture

  • Fail-closed by default. Hangar refuses to start unless an access mode is chosen; webhook receivers refuse deliveries when no HMAC secret is set; credential paths refuse to act anonymously or half-configured.
  • Identity is decoupled from your provider credentials. Access is enforced at the homelab edge via forward-auth (Traefik + Authentik reference) or by Hangar acting as an OIDC client against your own IdP. A git provider is never the login.
  • Least-privilege, encrypted at rest. Write scopes are requested only for writable connections; stored credentials (App keys, tokens, webhook secrets) are Fernet-encrypted with HANGAR_SECRET_KEY.
  • Remediation is PR-first. Content changes are always pull requests — Hangar never pushes or force-pushes — and every correction is operator-triggered and written to an audit log.

Details and configuration: CONTRIBUTING.md → Choosing an access mode.


Documentation


License

See repository for license details.

About

Maintain your collection of git repos across providers

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors