Copilot Spec – git_identity Role (GitHub + GitLab with 1Password SSH signing)
Goal
Create an Ansible role git_identity that configures Git identity and SSH-based commit signing on daily driver machines.
-
Assume a fresh install but the role must be idempotent.
-
Use existing ansible/roles/op_* roles to pull any sensitive values from 1Password (SSH keys, alt emails, etc.).
-
Separate Git configuration for:
- Repos under
~/GitHub (personal, signed commits, GitHub).
- Repos under
~/GitLab (work or other, possibly different identity/signing policy).
The role should leave other Git behavior untouched.
Role Name and Structure
Create:
ansible/roles/git_identity/meta/argument_specs.yml
ansible/roles/git_identity/defaults/main.yml
ansible/roles/git_identity/tasks/main.yml
- (Optional later:
tasks/github.yml, tasks/gitlab.yml, tasks/validate.yml and import them from main.yml for cleanliness.)
Follow these constraints:
- No inline comments in YAML or tasks.
- Use FQCNs for modules (
ansible.builtin.file, ansible.builtin.command, etc.).
- Make tasks minimal, idempotent, and safe.
Inputs and Variables
Define the following variables in meta/argument_specs.yml and defaults/main.yml:
Required (no defaults):
-
git_identity_user_name
Example: "SRF-Audio"
-
git_identity_user_email_github
Example: "srfaudioproductions@gmail.com"
-
git_identity_user_email_gitlab
Can be same or different; allow separate config.
-
git_identity_github_signing_pubkey_item
A 1Password item reference used by the op_* role to retrieve the SSH public key for GitHub signing.
Example: "vault-name/GitHub SSH Signing Key"
Optional (with sensible defaults):
-
git_identity_github_root (default "{{ ansible_env.HOME }}/GitHub")
-
git_identity_gitlab_root (default "{{ ansible_env.HOME }}/GitLab")
-
git_identity_allowed_signers_path
Default: "{{ ansible_env.HOME }}/.config/git/allowed_signers"
-
git_identity_enable_github_signing (default: true)
-
git_identity_enable_gitlab_signing (default: false)
All sensitive values (SSH keys, extra emails) must be fetched via op_ roles*, not hard-coded.
Dependencies (1Password integration)
Assume existing 1Password roles under ansible/roles/op_*. The role should:
-
Use include_role to call appropriate op_* role(s) to fetch:
git_identity_github_signing_pubkey (SSH public key string).
-
Do not assume exact role names; but in the spec, tell Copilot to:
- Expect an included role to set
git_identity_github_signing_pubkey to a single-line SSH public key string (ssh-ed25519 AAAA...).
- Fail fast with
ansible.builtin.assert if that variable is missing or empty.
Example pattern in tasks (Copilot should fill real role name):
- name: Retrieve GitHub SSH signing public key from 1Password
ansible.builtin.include_role:
name: op_ssh_signing_key
vars:
op_item_ref: "{{ git_identity_github_signing_pubkey_item }}"
- name: Assert GitHub SSH signing key present
ansible.builtin.assert:
that:
- git_identity_github_signing_pubkey | length > 0
The actual op_* role is expected to populate git_identity_github_signing_pubkey.
Behavior – Step by Step
1) Create ~/GitHub and ~/GitLab
Tasks:
Idempotent: re-runs should not change if directories already exist.
2) Create / update top-level Git config (~/.gitconfig)
We want a top-level Git config that includes per-tree configs.
- Path:
{{ ansible_env.HOME }}/.gitconfig
- Use
ansible.builtin.blockinfile or ansible.builtin.lineinfile to ensure these stanzas exist:
[includeIf "gitdir:~/GitHub/"]
path = ~/.gitconfig-github
[includeIf "gitdir:~/GitLab/"]
path = ~/.gitconfig-gitlab
Requirements:
- Do not clobber existing content in
~/.gitconfig.
- Ensure both includeIf blocks are present and not duplicated.
- Prefer
blockinfile keyed with a clear marker (e.g. # GIT_IDENTITY MANAGED BLOCK) to isolate the managed portion.
3) Create ~/.gitconfig-github
- Path:
{{ ansible_env.HOME }}/.gitconfig-github
- Use
ansible.builtin.template or ansible.builtin.copy with content to create the whole file.
- The file should contain at least:
[user]
name = {{ git_identity_user_name }}
email = {{ git_identity_user_email_github }}
signingkey = {{ git_identity_github_signing_pubkey }}
[gpg]
format = ssh
[gpg "ssh"]
allowedSignersFile = {{ git_identity_allowed_signers_path }}
[commit]
gpgsign = {{ 'true' if git_identity_enable_github_signing else 'false' }}
Notes:
signingkey should be the literal SSH public key string, not a path.
- Use an absolute path in
allowedSignersFile (no ~).
- If
git_identity_enable_github_signing is false, gpgsign should be false, but keep the rest so toggling later is trivial.
4) Create ~/.gitconfig-gitlab
- Path:
{{ ansible_env.HOME }}/.gitconfig-gitlab
- Similar approach, but allow different behavior:
[user]
name = {{ git_identity_user_name }}
email = {{ git_identity_user_email_gitlab }}
[commit]
gpgsign = {{ 'true' if git_identity_enable_gitlab_signing else 'false' }}
- For now, no SSH signing by default (
enable_gitlab_signing = false), but structure must support enabling later.
- Do not configure
gpg.format here unless enable_gitlab_signing is true; we want GitLab behavior isolated and opt-in.
5) Create and populate allowed_signers file
- Path:
{{ git_identity_allowed_signers_path }}
- Ensure parent dir:
{{ ansible_env.HOME }}/.config/git exists (ansible.builtin.file).
- File contents should include at least one line:
{{ git_identity_user_email_github }} {{ git_identity_github_signing_pubkey }}
Implementation detail:
- Use
ansible.builtin.lineinfile with regexp based on the email to ensure idempotency and avoid duplicate entries.
- Mode:
"0600"; owner = login user.
6) Ensure Git sees the correct config
After the files are in place:
-
Use ansible.builtin.command (FQCN: ansible.builtin.command) to run:
git config --global --get-regexp '^includeIf\.'
git config --file ~/.gitconfig-github --get user.email
git config --file ~/.gitconfig-github --get user.signingkey
git config --file ~/.gitconfig-gitlab --get user.email
-
Set changed_when: false for these validation commands.
-
Register their outputs for assertions.
Use ansible.builtin.assert to enforce:
- GitHub email in
.gitconfig-github equals git_identity_user_email_github.
- GitLab email in
.gitconfig-gitlab equals git_identity_user_email_gitlab.
includeIf "gitdir:~/GitHub/" and includeIf "gitdir:~/GitLab/" show up in the global config.
7) Smoke-test signing in a temporary GitHub repo
Optionally (controlled by a var like git_identity_run_validation_tests, default true):
-
Create a temporary directory under {{ git_identity_github_root }}/.git_identity_test using ansible.builtin.tempfile or a fixed name plus state: directory.
-
Initialize a repo and make a test commit:
git init .
git config user.name "{{ git_identity_user_name }}"
git config user.email "{{ git_identity_user_email_github }}"
git commit --allow-empty -m "git_identity signing test"
-
Run:
git log --show-signature -1
-
Capture stdout and assert:
- If
git_identity_enable_github_signing is true, output must contain "Good \"git\" signature" or at least "signature" and no error about gpg.ssh.allowedSignersFile.
Implementation details:
- Use
ansible.builtin.command with chdir pointing to the temp repo.
- Use
changed_when: false.
- Use
ansible.builtin.assert to fail loudly if signing is expected but not present.
The test directory can be left in place or cleaned up (cleanup is nicer but optional; if cleaning, be idempotent and only remove the test dir).
Idempotency Requirements
Copilot must:
- Use
ansible.builtin.file with state: directory and appropriate modes for directories.
- Use
ansible.builtin.copy/template in a way that does not churn files unnecessarily.
- Use
ansible.builtin.lineinfile or blockinfile for .gitconfig and allowed_signers to avoid duplicates.
- Ensure validation commands have
changed_when: false.
Multiple runs of the role on the same machine must result in no further changes once everything is in the desired state.
Acceptance Criteria
-
After running git_identity on a fresh daily driver machine:
~/GitHub and ~/GitLab exist with correct ownership and permissions.
~/.gitconfig contains the includeIf blocks for both trees.
~/.gitconfig-github and ~/.gitconfig-gitlab exist with correct user identity and (for GitHub) SSH signing configuration.
{{ git_identity_allowed_signers_path }} exists and has a line for git_identity_user_email_github with the SSH public key from 1Password.
git config --file ~/.gitconfig-github --get user.signingkey returns the SSH public key string from 1Password.
- A test commit in a repo under
~/GitHub shows a valid SSH signature in git log --show-signature when signing is enabled.
-
The role is safe to include in your “daily driver bootstrap” playbook and can be run repeatedly without noisy diffs.
Copilot Spec –
git_identityRole (GitHub + GitLab with 1Password SSH signing)Goal
Create an Ansible role
git_identitythat configures Git identity and SSH-based commit signing on daily driver machines.Assume a fresh install but the role must be idempotent.
Use existing
ansible/roles/op_*roles to pull any sensitive values from 1Password (SSH keys, alt emails, etc.).Separate Git configuration for:
~/GitHub(personal, signed commits, GitHub).~/GitLab(work or other, possibly different identity/signing policy).The role should leave other Git behavior untouched.
Role Name and Structure
Create:
ansible/roles/git_identity/meta/argument_specs.ymlansible/roles/git_identity/defaults/main.ymlansible/roles/git_identity/tasks/main.ymltasks/github.yml,tasks/gitlab.yml,tasks/validate.ymland import them frommain.ymlfor cleanliness.)Follow these constraints:
ansible.builtin.file,ansible.builtin.command, etc.).Inputs and Variables
Define the following variables in
meta/argument_specs.ymlanddefaults/main.yml:Required (no defaults):
git_identity_user_nameExample:
"SRF-Audio"git_identity_user_email_githubExample:
"srfaudioproductions@gmail.com"git_identity_user_email_gitlabCan be same or different; allow separate config.
git_identity_github_signing_pubkey_itemA 1Password item reference used by the
op_*role to retrieve the SSH public key for GitHub signing.Example:
"vault-name/GitHub SSH Signing Key"Optional (with sensible defaults):
git_identity_github_root(default"{{ ansible_env.HOME }}/GitHub")git_identity_gitlab_root(default"{{ ansible_env.HOME }}/GitLab")git_identity_allowed_signers_pathDefault:
"{{ ansible_env.HOME }}/.config/git/allowed_signers"git_identity_enable_github_signing(default:true)git_identity_enable_gitlab_signing(default:false)All sensitive values (SSH keys, extra emails) must be fetched via op_ roles*, not hard-coded.
Dependencies (1Password integration)
Assume existing 1Password roles under
ansible/roles/op_*. The role should:Use
include_roleto call appropriateop_*role(s) to fetch:git_identity_github_signing_pubkey(SSH public key string).Do not assume exact role names; but in the spec, tell Copilot to:
git_identity_github_signing_pubkeyto a single-line SSH public key string (ssh-ed25519 AAAA...).ansible.builtin.assertif that variable is missing or empty.Example pattern in tasks (Copilot should fill real role name):
The actual
op_*role is expected to populategit_identity_github_signing_pubkey.Behavior – Step by Step
1) Create
~/GitHuband~/GitLabTasks:
Use
ansible.builtin.filewith:path: "{{ git_identity_github_root }}",state: directory,mode: "0755",owner/group= login user.git_identity_gitlab_root.Idempotent: re-runs should not change if directories already exist.
2) Create / update top-level Git config (
~/.gitconfig)We want a top-level Git config that includes per-tree configs.
{{ ansible_env.HOME }}/.gitconfigansible.builtin.blockinfileoransible.builtin.lineinfileto ensure these stanzas exist:Requirements:
~/.gitconfig.blockinfilekeyed with a clear marker (e.g.# GIT_IDENTITY MANAGED BLOCK) to isolate the managed portion.3) Create
~/.gitconfig-github{{ ansible_env.HOME }}/.gitconfig-githubansible.builtin.templateoransible.builtin.copywithcontentto create the whole file.Notes:
signingkeyshould be the literal SSH public key string, not a path.allowedSignersFile(no~).git_identity_enable_github_signingisfalse,gpgsignshould befalse, but keep the rest so toggling later is trivial.4) Create
~/.gitconfig-gitlab{{ ansible_env.HOME }}/.gitconfig-gitlabenable_gitlab_signing = false), but structure must support enabling later.gpg.formathere unlessenable_gitlab_signingis true; we want GitLab behavior isolated and opt-in.5) Create and populate
allowed_signersfile{{ git_identity_allowed_signers_path }}{{ ansible_env.HOME }}/.config/gitexists (ansible.builtin.file).Implementation detail:
ansible.builtin.lineinfilewithregexpbased on the email to ensure idempotency and avoid duplicate entries."0600"; owner = login user.6) Ensure Git sees the correct config
After the files are in place:
Use
ansible.builtin.command(FQCN:ansible.builtin.command) to run:git config --global --get-regexp '^includeIf\.'git config --file ~/.gitconfig-github --get user.emailgit config --file ~/.gitconfig-github --get user.signingkeygit config --file ~/.gitconfig-gitlab --get user.emailSet
changed_when: falsefor these validation commands.Register their outputs for assertions.
Use
ansible.builtin.assertto enforce:.gitconfig-githubequalsgit_identity_user_email_github..gitconfig-gitlabequalsgit_identity_user_email_gitlab.includeIf "gitdir:~/GitHub/"andincludeIf "gitdir:~/GitLab/"show up in the global config.7) Smoke-test signing in a temporary GitHub repo
Optionally (controlled by a var like
git_identity_run_validation_tests, defaulttrue):Create a temporary directory under
{{ git_identity_github_root }}/.git_identity_testusingansible.builtin.tempfileor a fixed name plusstate: directory.Initialize a repo and make a test commit:
git init .git config user.name "{{ git_identity_user_name }}"git config user.email "{{ git_identity_user_email_github }}"git commit --allow-empty -m "git_identity signing test"Run:
git log --show-signature -1Capture stdout and assert:
git_identity_enable_github_signingistrue, output must contain"Good \"git\" signature"or at least"signature"and no error aboutgpg.ssh.allowedSignersFile.Implementation details:
ansible.builtin.commandwithchdirpointing to the temp repo.changed_when: false.ansible.builtin.assertto fail loudly if signing is expected but not present.The test directory can be left in place or cleaned up (cleanup is nicer but optional; if cleaning, be idempotent and only remove the test dir).
Idempotency Requirements
Copilot must:
ansible.builtin.filewithstate: directoryand appropriate modes for directories.ansible.builtin.copy/templatein a way that does not churn files unnecessarily.ansible.builtin.lineinfileorblockinfilefor.gitconfigandallowed_signersto avoid duplicates.changed_when: false.Multiple runs of the role on the same machine must result in no further changes once everything is in the desired state.
Acceptance Criteria
After running
git_identityon a fresh daily driver machine:~/GitHuband~/GitLabexist with correct ownership and permissions.~/.gitconfigcontains theincludeIfblocks for both trees.~/.gitconfig-githuband~/.gitconfig-gitlabexist with correct user identity and (for GitHub) SSH signing configuration.{{ git_identity_allowed_signers_path }}exists and has a line forgit_identity_user_email_githubwith the SSH public key from 1Password.git config --file ~/.gitconfig-github --get user.signingkeyreturns the SSH public key string from 1Password.~/GitHubshows a valid SSH signature ingit log --show-signaturewhen signing is enabled.The role is safe to include in your “daily driver bootstrap” playbook and can be run repeatedly without noisy diffs.