chore: add git remote topology module + docs

modules/git/remotes.nix declares upstream/fork/extras schema. extras is
a typed submodule list so order is preserved and future fields
(pushUrl, mirror) can extend without breaking callers. docs/remotes.md
walks the three canonical topologies (upstream-only / github-fork /
multi-remote with private host).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-24 22:36:33 +02:00
commit 6adc82e8f3
2 changed files with 194 additions and 0 deletions

114
modules/git/remotes.nix Normal file
View file

@ -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:<you>/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:<you>/lnbits.git";
# extras = [
# { name = "internal"; url = "git@<your-forgejo>:<org>/lnbits.git"; }
# { name = "mirror"; url = "git@codeberg.org:<you>/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 `<name>` in `git remote add <name> <url>`).";
example = "internal";
};
url = mkOption {
type = types.str;
description = "Git remote URL (ssh or https).";
example = "git@codeberg.org:<you>/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:<you>/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 <name> <url>`
on bootstrap.
'';
example = lib.literalExpression ''
[
{ name = "internal"; url = "git@<your-forgejo>:<org>/lnbits.git"; }
{ name = "mirror"; url = "git@codeberg.org:<you>/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.
}