Skip to content

Create git_identity role in Ansible #56

@SRF-Audio

Description

@SRF-Audio

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:

  • Use ansible.builtin.file with:

    • path: "{{ git_identity_github_root }}", state: directory, mode: "0755", owner/group = login user.
    • Same for 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.

  • 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):

  1. Create a temporary directory under {{ git_identity_github_root }}/.git_identity_test using ansible.builtin.tempfile or a fixed name plus state: directory.

  2. 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"
  3. Run:

    • git log --show-signature -1
  4. 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

  1. 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.
  2. The role is safe to include in your “daily driver bootstrap” playbook and can be run repeatedly without noisy diffs.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions