feat: add source control identity management
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
homeModules = config.flake.modules.homeManager;
|
||||
serviceHostNames = {
|
||||
github = "github.com";
|
||||
gitlab = "gitlab.com";
|
||||
};
|
||||
in
|
||||
{
|
||||
flake.modules.homeManager.source-control =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
host = config.meta.host;
|
||||
user = config.meta.user;
|
||||
sourceControl = user.sourceControl;
|
||||
hostSourceControlUsers = host.sourceControl.users;
|
||||
hostUserSourceControl =
|
||||
if lib.hasAttr user.name hostSourceControlUsers then hostSourceControlUsers.${user.name} else { };
|
||||
profileNames = builtins.attrNames sourceControl.profiles;
|
||||
|
||||
parsedProfiles = map (
|
||||
name:
|
||||
let
|
||||
matches = builtins.match "(github|gitlab)-(personal|work)" name;
|
||||
in
|
||||
{
|
||||
inherit matches name;
|
||||
isValid = matches != null;
|
||||
scope = if matches == null then null else builtins.elemAt matches 1;
|
||||
service = if matches == null then null else builtins.elemAt matches 0;
|
||||
}
|
||||
) profileNames;
|
||||
|
||||
validProfiles = builtins.filter (profile: profile.isValid) parsedProfiles;
|
||||
invalidProfileNames = map (profile: profile.name) (
|
||||
builtins.filter (profile: !profile.isValid) parsedProfiles
|
||||
);
|
||||
|
||||
emailNamesForScope = {
|
||||
personal = [
|
||||
"personal"
|
||||
"main"
|
||||
];
|
||||
work = [ "work" ];
|
||||
};
|
||||
|
||||
scopeEmails =
|
||||
scope:
|
||||
map (name: user.emails.${name}) (
|
||||
builtins.filter (name: lib.hasAttr name user.emails) emailNamesForScope.${scope}
|
||||
);
|
||||
|
||||
emailForScope =
|
||||
scope:
|
||||
let
|
||||
emails = scopeEmails scope;
|
||||
in
|
||||
if builtins.length emails == 1 then (builtins.head emails).address else null;
|
||||
|
||||
scopeConfig =
|
||||
scope: if lib.hasAttr scope hostUserSourceControl then hostUserSourceControl.${scope} else null;
|
||||
|
||||
privateKeyPathForScope =
|
||||
scope:
|
||||
let
|
||||
keyConfig = scopeConfig scope;
|
||||
in
|
||||
if keyConfig == null || keyConfig.privateKeyPath == null then
|
||||
"~/.ssh/id_${scope}"
|
||||
else
|
||||
keyConfig.privateKeyPath;
|
||||
|
||||
scopePublicKey =
|
||||
scope:
|
||||
let
|
||||
keyConfig = scopeConfig scope;
|
||||
in
|
||||
if keyConfig == null then null else keyConfig.publicKey;
|
||||
|
||||
scopesInUse = lib.unique (
|
||||
[
|
||||
"personal"
|
||||
sourceControl.projectScope
|
||||
]
|
||||
++ map (profile: profile.scope) validProfiles
|
||||
);
|
||||
|
||||
missingKeyScopes = builtins.filter (scope: scopePublicKey scope == null) scopesInUse;
|
||||
invalidEmailScopes = builtins.filter (scope: emailForScope scope == null) scopesInUse;
|
||||
allowedSignersLines = map (scope: "${emailForScope scope} ${scopePublicKey scope}") (
|
||||
builtins.filter (scope: emailForScope scope != null && scopePublicKey scope != null) scopesInUse
|
||||
);
|
||||
|
||||
gitConfigForScope = scope: {
|
||||
gpg.ssh.allowedSignersFile = "${config.xdg.configHome}/git/allowed_signers";
|
||||
user = {
|
||||
name = user.realName;
|
||||
email = emailForScope scope;
|
||||
signingKey = "${privateKeyPathForScope scope}.pub";
|
||||
};
|
||||
};
|
||||
|
||||
gitRoots = [
|
||||
{
|
||||
root = user.nixosConfigurationPath;
|
||||
scope = "personal";
|
||||
}
|
||||
{
|
||||
root = config.xdg.userDirs.projects;
|
||||
scope = sourceControl.projectScope;
|
||||
}
|
||||
];
|
||||
in
|
||||
{
|
||||
imports = [ homeModules.git ];
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = invalidProfileNames == [ ];
|
||||
message = "Invalid source control profiles for `${user.name}`: ${lib.concatStringsSep ", " invalidProfileNames}. Expected `<service>-<scope>` using github/gitlab and personal/work.";
|
||||
}
|
||||
{
|
||||
assertion = missingKeyScopes == [ ];
|
||||
message = "Missing source control keys for `${user.name}` scopes: ${lib.concatStringsSep ", " missingKeyScopes}.";
|
||||
}
|
||||
{
|
||||
assertion = invalidEmailScopes == [ ];
|
||||
message = "Expected exactly one email selected by name for `${user.name}` scopes: ${lib.concatStringsSep ", " invalidEmailScopes}. Personal uses `personal` or `main`; work uses `work`.";
|
||||
}
|
||||
];
|
||||
|
||||
xdg.configFile."git/allowed_signers".text = lib.concatStringsSep "\n" (
|
||||
allowedSignersLines ++ [ "" ]
|
||||
);
|
||||
|
||||
programs.git.includes = map (gitRoot: {
|
||||
condition = "gitdir:${gitRoot.root}/";
|
||||
contents = gitConfigForScope gitRoot.scope;
|
||||
}) gitRoots;
|
||||
|
||||
programs.ssh = {
|
||||
enable = true;
|
||||
matchBlocks = lib.listToAttrs (
|
||||
map (
|
||||
profile:
|
||||
lib.nameValuePair profile.name {
|
||||
hostname = serviceHostNames.${profile.service};
|
||||
identitiesOnly = true;
|
||||
identityFile = privateKeyPathForScope profile.scope;
|
||||
user = "git";
|
||||
}
|
||||
) validProfiles
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user