feat(git): reach GitHub through gh over HTTPS on agent hosts

Home Manager now owns ~/.config/gh/config.yml on agent hosts; activation moves an existing one to
config.yml.home-manager.backup.

Assisted-by: DeepSeek Harness (claude-opus-5-5)
This commit is contained in:
Ryan Yin committed 2026-11-07 23:16:04 +08:00
1 parent a7787198ce
commit ad3923df98
5 files changed
+100 -79

No files matched your search

+87 -78
View File
@@ -1,100 +1,109 @@
{
config,
lib,
pkgs,
myvars,
...
}:
{
# `programs.git` will generate the config file: ~/.config/git/config
# to make git use this config file, `~/.gitconfig` should not exist!
#
# https://git-scm.com/docs/git-config#Documentation/git-config.txt---global
home.activation.backupExistingGitconfig = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
if [[ -e ~/.gitconfig ]]; then
run mv $VERBOSE_ARG ~/.gitconfig ~/.gitconfig.bak
fi
'';
options.modules.git.githubSsh.enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Rewrite https://github.com/ryan4yin remotes to SSH with the owner's key.";
};
# GitHub CLI tool
# https://cli.github.com/manual/
home.packages = [ pkgs.gh ];
config = {
# `programs.git` will generate the config file: ~/.config/git/config
# to make git use this config file, `~/.gitconfig` should not exist!
#
# https://git-scm.com/docs/git-config#Documentation/git-config.txt---global
home.activation.backupExistingGitconfig = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
if [[ -e ~/.gitconfig ]]; then
run mv $VERBOSE_ARG ~/.gitconfig ~/.gitconfig.bak
fi
'';
programs.git = {
enable = true;
lfs.enable = true;
# GitHub CLI tool
# https://cli.github.com/manual/
home.packages = [ pkgs.gh ];
signing.format = "openpgp";
programs.git = {
enable = true;
lfs.enable = true;
includes = [
{
# use different email & name for work:
#
# [user]
# email = "xxx@xxx.com"
# name = "Ryan Yin"
path = "~/work/.gitconfig";
condition = "gitdir:~/work/";
}
];
signing.format = "openpgp";
settings = {
user.email = myvars.useremail;
user.name = myvars.userfullname;
includes = [
{
# use different email & name for work:
#
# [user]
# email = "xxx@xxx.com"
# name = "Ryan Yin"
path = "~/work/.gitconfig";
condition = "gitdir:~/work/";
}
];
init.defaultBranch = "main";
trim.bases = "develop,master,main"; # for git-trim
push.autoSetupRemote = true;
pull.rebase = true;
log.date = "iso"; # use iso format for date
settings = {
user.email = myvars.useremail;
user.name = myvars.userfullname;
# replace https with ssh
url = {
"ssh://git@github.com/ryan4yin" = {
insteadOf = "https://github.com/ryan4yin";
init.defaultBranch = "main";
trim.bases = "develop,master,main"; # for git-trim
push.autoSetupRemote = true;
pull.rebase = true;
log.date = "iso"; # use iso format for date
# replace https with ssh
url = lib.mkIf config.modules.git.githubSsh.enable {
"ssh://git@github.com/ryan4yin" = {
insteadOf = "https://github.com/ryan4yin";
};
};
alias = {
# common aliases
br = "branch";
co = "checkout";
st = "status";
ls = "log --pretty=format:\"%C(yellow)%h%Cred%d\\\\ %Creset%s%Cblue\\\\ [%cn]\" --decorate";
ll = "log --pretty=format:\"%C(yellow)%h%Cred%d\\\\ %Creset%s%Cblue\\\\ [%cn]\" --decorate --numstat";
cm = "commit -m"; # commit via `git cm <message>`
ca = "commit -am"; # commit all changes via `git ca <message>`
dc = "diff --cached";
amend = "commit --amend -m"; # amend commit message via `git amend <message>`
unstage = "reset HEAD --"; # unstage file via `git unstage <file>`
merged = "branch --merged"; # list merged(into HEAD) branches via `git merged`
unmerged = "branch --no-merged"; # list unmerged(into HEAD) branches via `git unmerged`
nonexist = "remote prune origin --dry-run"; # list non-exist(remote) branches via `git nonexist`
# delete merged branches except master & dev & staging
# `!` indicates it's a shell script, not a git subcommand
delmerged = ''! git branch --merged | egrep -v "(^\*|main|master|dev|staging)" | xargs git branch -d'';
# delete non-exist(remote) branches
delnonexist = "remote prune origin";
# aliases for submodule
update = "submodule update --init --recursive";
foreach = "submodule foreach";
};
};
};
alias = {
# common aliases
br = "branch";
co = "checkout";
st = "status";
ls = "log --pretty=format:\"%C(yellow)%h%Cred%d\\\\ %Creset%s%Cblue\\\\ [%cn]\" --decorate";
ll = "log --pretty=format:\"%C(yellow)%h%Cred%d\\\\ %Creset%s%Cblue\\\\ [%cn]\" --decorate --numstat";
cm = "commit -m"; # commit via `git cm <message>`
ca = "commit -am"; # commit all changes via `git ca <message>`
dc = "diff --cached";
amend = "commit --amend -m"; # amend commit message via `git amend <message>`
unstage = "reset HEAD --"; # unstage file via `git unstage <file>`
merged = "branch --merged"; # list merged(into HEAD) branches via `git merged`
unmerged = "branch --no-merged"; # list unmerged(into HEAD) branches via `git unmerged`
nonexist = "remote prune origin --dry-run"; # list non-exist(remote) branches via `git nonexist`
# delete merged branches except master & dev & staging
# `!` indicates it's a shell script, not a git subcommand
delmerged = ''! git branch --merged | egrep -v "(^\*|main|master|dev|staging)" | xargs git branch -d'';
# delete non-exist(remote) branches
delnonexist = "remote prune origin";
# aliases for submodule
update = "submodule update --init --recursive";
foreach = "submodule foreach";
# A syntax-highlighting pager for git, diff, grep, and blame output
programs.delta = {
enable = true;
enableGitIntegration = true;
options = {
diff-so-fancy = true;
line-numbers = true;
true-color = "always";
};
};
};
# A syntax-highlighting pager for git, diff, grep, and blame output
programs.delta = {
enable = true;
enableGitIntegration = true;
options = {
diff-so-fancy = true;
line-numbers = true;
true-color = "always";
};
# Git terminal UI (written in go).
programs.lazygit.enable = true;
};
# Git terminal UI (written in go).
programs.lazygit.enable = true;
}
+4
View File
@@ -13,4 +13,8 @@
# ~/.secrets is synced to the desktops only.
modules.shell.secrets.enable = false;
# Agent hosts use their own GitHub account, logged in with `gh`, over HTTPS.
modules.git.githubSsh.enable = false;
programs.gh.enable = true;
}
+2 -1
View File
@@ -25,7 +25,8 @@ directory.
- **desktop**: Niri session and GUI apps on top of the full TUI stack.
- **agent**: headless, with the full TUI stack from Home Manager (shell, editors, dev toolchains,
cloud CLIs, AI agents with their rules and skills), and nix-ld, rootless Podman and linger from
the system. Computer use (Xvfb + i3, VNC) is part of the profile but off by default.
the system. Computer use (Xvfb + i3, VNC) is part of the profile but off by default. Git reaches
GitHub over HTTPS with the host's own `gh` login, not the owner's SSH key.
- **server**: system services only, no Home Manager.
The `agent` hosts differ only in:
@@ -5,6 +5,8 @@ let
qwenInstalled = true;
devToolsFromHome = true;
nushellSecrets = false;
githubSshRewrite = false;
ghCredentialHelper = true;
nixLd = true;
rootlessPodman = true;
docker = false;
@@ -27,6 +27,11 @@ lib.genAttrs hosts (
devToolsFromHome = hasPackage "kubectl" home.home.packages;
# ~/.secrets is synced to the desktops only; sourcing it would break nushell.
nushellSecrets = home.modules.shell.secrets.enable;
# GitHub goes through the host's own `gh` login over HTTPS, not the owner's SSH key.
githubSshRewrite = home.programs.git.settings ? url;
ghCredentialHelper = lib.any (lib.hasSuffix "/bin/gh auth git-credential") (
home.programs.git.settings.credential."https://github.com".helper or [ ]
);
# system module: modules/nixos/agent.nix
nixLd = system.programs.nix-ld.enable;