diff --git a/docs/remotes.md b/docs/remotes.md new file mode 100644 index 0000000..55cde47 --- /dev/null +++ b/docs/remotes.md @@ -0,0 +1,80 @@ +# Remote topology + +`lnbits-sensei.git.remotes` (declared in `modules/git/remotes.nix`) +abstracts how the local LNbits checkout is wired to its git remotes. +Three patterns cover almost every workflow. + +## 1. Upstream-only + +You read upstream, never push. Good for a read-only dev box, a CI +runner, or initial exploration before you've decided to contribute. + +```nix +lnbits-sensei.git.remotes = { + upstream = "https://github.com/lnbits/lnbits"; + fork = null; + extras = [ ]; +}; +``` + +What you get: + +- `upstream` remote pointing at canonical lnbits/lnbits. +- No `fork` remote — the bootstrap script skips it when `fork` is null. +- No extras. + +## 2. GitHub fork for PRs + +You maintain a personal fork on GitHub and use it as your push target +for PRs landing in upstream. `upstream` stays pull-only. + +```nix +lnbits-sensei.git.remotes = { + upstream = "https://github.com/lnbits/lnbits"; + fork = "git@github.com:/lnbits.git"; + extras = [ ]; +}; +``` + +What you get: + +- `upstream` for fetching releases / rebasing onto main. +- `fork` as the default push target. +- The upstream-PR helper (later pass) knows to push branches to `fork` + and open the compare URL against `upstream`. + +## 3. Multi-remote with a private host + +You also push to a private forgejo / gitea / codeberg mirror — for +internal review, a deployment pipeline, or just an off-GitHub backup. +The upstream + public-fork flow stays intact; the extras layer on top. + +```nix +lnbits-sensei.git.remotes = { + upstream = "https://github.com/lnbits/lnbits"; + fork = "git@github.com:/lnbits.git"; + extras = [ + { name = "internal"; url = "git@:/lnbits.git"; } + { name = "mirror"; url = "git@codeberg.org:/lnbits.git"; } + ]; +}; +``` + +What you get: + +- Same as pattern 2, plus the named extras as additional remotes. +- Each extra becomes a `git remote add ` on bootstrap; + pushing to them is opt-in (never the default push target). + +## Notes + +- `extras` is a list of `{ name; url; }` submodules — order is + preserved, and future fields (`pushUrl`, `mirror`) can be added + without breaking existing configs. +- The module is schema-only. The dev-env bootstrap script (later pass) + is what actually runs `git remote add` against a real checkout. It + is idempotent: re-running after editing `extras` reconciles the + on-disk remotes with the declared set. +- `fork = null` is the right value when you have no GitHub fork — + don't point it at a placeholder URL, the bootstrap script keys on + null to skip the `fork` remote entirely. diff --git a/modules/git/remotes.nix b/modules/git/remotes.nix new file mode 100644 index 0000000..a0e0829 --- /dev/null +++ b/modules/git/remotes.nix @@ -0,0 +1,114 @@ +# lnbits-sensei — git remote topology. +# +# Abstracts how a local LNbits checkout is wired to its remotes. Three +# patterns are supported out of the box (see docs/remotes.md for the +# full prose): +# +# 1. Upstream-only — you read upstream, never push. `fork` = null, +# `extras` = []. +# +# lnbits-sensei.git.remotes = { +# upstream = "https://github.com/lnbits/lnbits"; +# fork = null; +# extras = [ ]; +# }; +# +# 2. GitHub fork for PRs — you maintain a fork on GitHub and send +# PRs upstream. The fork remote is your push target; `upstream` is +# pull-only. +# +# lnbits-sensei.git.remotes = { +# upstream = "https://github.com/lnbits/lnbits"; +# fork = "git@github.com:/lnbits.git"; +# extras = [ ]; +# }; +# +# 3. Multi-remote with private host — you also push to a private +# forgejo/gitea/codeberg for internal review or deployment, while +# keeping the upstream + public-fork flow intact. +# +# lnbits-sensei.git.remotes = { +# upstream = "https://github.com/lnbits/lnbits"; +# fork = "git@github.com:/lnbits.git"; +# extras = [ +# { name = "internal"; url = "git@:/lnbits.git"; } +# { name = "mirror"; url = "git@codeberg.org:/lnbits.git"; } +# ]; +# }; +# +# Modules that materialize remotes on disk (dev-env bootstrap, the +# upstream-PR helper) read this attrset and translate to `git remote +# add` / `git remote set-url` operations idempotently. +{ + config, + lib, + ... +}: + +let + inherit (lib) mkOption types; + + # One entry in `extras`. Kept as a typed submodule rather than an + # `attrsOf str` so the order is preserved (relevant for any UI that + # surfaces remotes in declaration order) and so future fields + # (`pushUrl`, `mirror`, …) can be added without breaking callers. + extraRemoteType = types.submodule { + options = { + name = mkOption { + type = types.str; + description = "Git remote name (the `` in `git remote add `)."; + example = "internal"; + }; + url = mkOption { + type = types.str; + description = "Git remote URL (ssh or https)."; + example = "git@codeberg.org:/lnbits.git"; + }; + }; + }; +in +{ + options.lnbits-sensei.git.remotes = { + upstream = mkOption { + type = types.str; + default = "https://github.com/lnbits/lnbits"; + description = '' + Canonical upstream URL. Read-only in practice — even when you + have push rights, prefer routing changes through `fork` so the + upstream-PR helper does the right thing. + ''; + example = "https://github.com/lnbits/lnbits"; + }; + + fork = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Personal GitHub fork URL used as the push target for upstream + PRs. Null when you don't intend to send PRs upstream; the + skeleton then skips adding a `fork` remote on bootstrap. + ''; + example = "git@github.com:/lnbits.git"; + }; + + extras = mkOption { + type = types.listOf extraRemoteType; + default = [ ]; + description = '' + Additional remotes — private forgejo, internal gitea, codeberg + mirror, etc. Each entry becomes a `git remote add ` + on bootstrap. + ''; + example = lib.literalExpression '' + [ + { name = "internal"; url = "git@:/lnbits.git"; } + { name = "mirror"; url = "git@codeberg.org:/lnbits.git"; } + ] + ''; + }; + }; + + # No config body — this module declares schema only. The dev-env + # bootstrap script (later pass) consumes these values to materialize + # remotes on a real checkout. +}