Skip to content

Commit 659a367

Browse files
author
Derek
committed
feat: dual GPG key for git-core PPA, Claude Code updates
git.yml: Import both PPA signing keys (RSA-1024 legacy + newer key) into binary keyring. Single code path for all Ubuntu releases - no hardcoded release names. Fixes PPA failure on Ubuntu 25.04+ which rejects weak RSA-1024 signatures. claude.yml: Updated Claude Code installation and configuration. verify.yml: Additional verification improvements. shell_config.yml: Shell configuration updates.
1 parent 09ebf55 commit 659a367

5 files changed

Lines changed: 198 additions & 60 deletions

File tree

ansible/roles/developer/tasks/git.yml

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
when: ansible_facts['distribution'] == 'Fedora'
1616

1717
# ============================================================================
18-
# UBUNTU - Latest Git from PPA (LTS only) or distro repos (non-LTS)
18+
# UBUNTU - Latest Git from PPA
1919
# ============================================================================
20-
# The git-core PPA only publishes for LTS releases and uses a weak RSA-1024
21-
# signing key that non-LTS releases (25.04+) reject. Non-LTS Ubuntu ships
22-
# with a recent enough git so the PPA is unnecessary.
20+
# The git-core PPA signs with two keys:
21+
# E1DD270288B4E6030699E45FA1715D88E1DF1F24 (RSA-1024, legacy)
22+
# E363C90F8F1B6217 (newer key)
23+
# Ubuntu 25.04+ rejects the RSA-1024 key. We import both keys into a binary
24+
# keyring so apt can verify against whichever key the PPA uses.
2325

2426
# Clean up old PPA configuration if exists (migration from legacy method)
2527
- name: Remove old git-core PPA configuration
@@ -32,18 +34,28 @@
3234
- ppa_git_core_ppa.list
3335
when: ansible_facts['distribution'] == 'Ubuntu'
3436

35-
# LTS releases: use git-core PPA for latest git
36-
- name: Install latest Git via PPA (Ubuntu LTS)
37+
- name: Remove old git-core PPA ASC key (replaced by binary keyring)
38+
ansible.builtin.file:
39+
path: /etc/apt/keyrings/git-core-ppa.asc
40+
state: absent
41+
when: ansible_facts['distribution'] == 'Ubuntu'
42+
43+
- name: Install latest Git via PPA (Ubuntu)
3744
block:
38-
- name: Import git-core PPA GPG key
39-
ansible.builtin.get_url:
40-
url: https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xE1DD270288B4E6030699E45FA1715D88E1DF1F24
41-
dest: /etc/apt/keyrings/git-core-ppa.asc
42-
mode: '0644'
45+
- name: Import git-core PPA GPG keys (both legacy and current)
46+
ansible.builtin.shell:
47+
cmd: |
48+
gpg --no-default-keyring --keyring /tmp/git-core-ppa.gpg \
49+
--keyserver keyserver.ubuntu.com \
50+
--recv-keys E1DD270288B4E6030699E45FA1715D88E1DF1F24 E363C90F8F1B6217
51+
gpg --no-default-keyring --keyring /tmp/git-core-ppa.gpg \
52+
--export --output /etc/apt/keyrings/git-core-ppa.gpg
53+
rm -f /tmp/git-core-ppa.gpg /tmp/git-core-ppa.gpg~
54+
creates: /etc/apt/keyrings/git-core-ppa.gpg
4355

4456
- name: Add git-core PPA repository
4557
ansible.builtin.apt_repository:
46-
repo: "deb [signed-by=/etc/apt/keyrings/git-core-ppa.asc] https://ppa.launchpadcontent.net/git-core/ppa/ubuntu {{ ansible_facts['distribution_release'] }} main"
58+
repo: "deb [signed-by=/etc/apt/keyrings/git-core-ppa.gpg] https://ppa.launchpadcontent.net/git-core/ppa/ubuntu {{ ansible_facts['distribution_release'] }} main"
4759
filename: git-core-ppa
4860
state: present
4961

@@ -56,22 +68,7 @@
5668
state: latest
5769
update_cache: true
5870

59-
when:
60-
- ansible_facts['distribution'] == 'Ubuntu'
61-
- ansible_facts['distribution_release'] in ['noble', 'jammy']
62-
63-
# Non-LTS releases: use distro repos (ships recent git, PPA unavailable)
64-
- name: Install Git and Git LFS (Ubuntu non-LTS)
65-
ansible.builtin.apt:
66-
name:
67-
- git
68-
- git-lfs
69-
- git-filter-repo
70-
state: latest
71-
update_cache: true
72-
when:
73-
- ansible_facts['distribution'] == 'Ubuntu'
74-
- ansible_facts['distribution_release'] not in ['noble', 'jammy']
71+
when: ansible_facts['distribution'] == 'Ubuntu'
7572

7673
# ============================================================================
7774
# macOS - Latest Git from Homebrew

ansible/roles/developer/tasks/shell_config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
export PATH="$HOME/.local/bin:$PATH"
2424
fi
2525
26-
# Add ~/.npm-global/bin for npm global tools (claude, semantic-release, etc.)
26+
# Add ~/.npm-global/bin for npm global tools (semantic-release, etc.)
2727
if [ -d "$HOME/.npm-global/bin" ]; then
2828
export PATH="$HOME/.npm-global/bin:$PATH"
2929
fi

ansible/roles/developer_core/tasks/claude.yml

Lines changed: 112 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,141 @@
11
---
22
# Claude Code CLI installation and managed settings
33
#
4-
# Installs Claude Code CLI and configures system-wide managed settings
5-
# that apply to all users and cannot be overridden.
4+
# Linux: Native binary download with SHA256 verification from manifest
5+
# macOS: Homebrew cask
66
#
7+
# Binary location: ~/.local/bin/claude (installed by 'claude install')
78
# Settings location: /etc/claude-code/managed-settings.json (Linux)
89
# /Library/Application Support/ClaudeCode/managed-settings.json (macOS)
910

10-
- name: Install Claude Code CLI globally for user (Linux)
11+
# ============================================================================
12+
# Distribution URL
13+
# ============================================================================
14+
15+
- name: Set Claude Code distribution URL
16+
ansible.builtin.set_fact:
17+
claude_gcs_bucket: "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases"
18+
19+
# ============================================================================
20+
# Cleanup deprecated npm installation
21+
# ============================================================================
22+
23+
- name: Remove deprecated Claude Code npm package (Linux)
1124
community.general.npm:
1225
name: '@anthropic-ai/claude-code'
1326
global: true
14-
state: present
27+
state: absent
1528
environment:
1629
NPM_CONFIG_PREFIX: "{{ user_home }}/.npm-global"
1730
become: false
1831
become_user: "{{ actual_user }}"
32+
failed_when: false
1933
when: ansible_facts['distribution'] in ['Fedora', 'Ubuntu']
2034

21-
- name: Install Claude Code CLI globally for user (macOS)
35+
- name: Remove deprecated Claude Code npm package (macOS)
2236
community.general.npm:
2337
name: '@anthropic-ai/claude-code'
2438
global: true
25-
state: present
39+
state: absent
2640
environment: "{{ npm_config_env }}"
2741
become: false
42+
failed_when: false
2843
when: ansible_facts['distribution'] == 'MacOSX'
2944

30-
- name: Enable Claude Code auto-update (Linux)
31-
ansible.builtin.command:
32-
cmd: "{{ user_home }}/.npm-global/bin/claude config set auto_update true"
33-
become: false
34-
become_user: "{{ actual_user }}"
35-
failed_when: false
36-
changed_when: false
45+
# ============================================================================
46+
# Linux - Native binary installation with SHA256 verification
47+
# ============================================================================
48+
49+
- name: Install Claude Code native binary (Linux)
50+
block:
51+
- name: Fetch latest Claude Code version
52+
ansible.builtin.uri:
53+
url: "{{ claude_gcs_bucket }}/latest"
54+
return_content: true
55+
register: claude_latest_version
56+
57+
- name: Set Claude Code version fact
58+
ansible.builtin.set_fact:
59+
claude_version: "{{ claude_latest_version.content | trim }}"
60+
61+
- name: Determine Claude Code platform
62+
ansible.builtin.set_fact:
63+
claude_platform: >-
64+
{%- if ansible_facts['architecture'] == 'x86_64' -%}
65+
linux-x64
66+
{%- elif ansible_facts['architecture'] == 'aarch64' -%}
67+
linux-arm64
68+
{%- else -%}
69+
unsupported
70+
{%- endif -%}
71+
72+
- name: Fail if unsupported architecture
73+
ansible.builtin.fail:
74+
msg: "Unsupported architecture for Claude Code: {{ ansible_facts['architecture'] }}"
75+
when: claude_platform == 'unsupported'
76+
77+
- name: Fetch Claude Code manifest for checksum
78+
ansible.builtin.uri:
79+
url: "{{ claude_gcs_bucket }}/{{ claude_version }}/manifest.json"
80+
return_content: true
81+
register: claude_manifest
82+
83+
- name: Set Claude Code checksum fact
84+
ansible.builtin.set_fact:
85+
claude_checksum: "{{ claude_manifest.json.platforms[claude_platform].checksum }}"
86+
87+
- name: Create temporary download directory
88+
ansible.builtin.file:
89+
path: /tmp/claude-install
90+
state: directory
91+
mode: '0755'
92+
93+
- name: Download Claude Code binary with checksum verification
94+
ansible.builtin.get_url:
95+
url: "{{ claude_gcs_bucket }}/{{ claude_version }}/{{ claude_platform }}/claude"
96+
dest: /tmp/claude-install/claude
97+
mode: '0755'
98+
checksum: "sha256:{{ claude_checksum }}"
99+
100+
- name: Create ~/.local/bin directory
101+
ansible.builtin.file:
102+
path: "{{ user_home }}/.local/bin"
103+
state: directory
104+
mode: '0755'
105+
owner: "{{ actual_user }}"
106+
group: "{{ actual_user }}"
107+
108+
- name: Run claude install to set up launcher and shell integration
109+
ansible.builtin.command:
110+
cmd: /tmp/claude-install/claude install
111+
become: true
112+
become_user: "{{ actual_user }}"
113+
environment:
114+
HOME: "{{ user_home }}"
115+
register: claude_install_result
116+
changed_when: claude_install_result.rc == 0
117+
118+
- name: Remove temporary download directory
119+
ansible.builtin.file:
120+
path: /tmp/claude-install
121+
state: absent
122+
123+
- name: Display Claude Code installation info
124+
ansible.builtin.debug:
125+
msg: "Claude Code {{ claude_version }} installed ({{ claude_platform }})"
126+
37127
when: ansible_facts['distribution'] in ['Fedora', 'Ubuntu']
38128

39-
- name: Enable Claude Code auto-update (macOS)
40-
ansible.builtin.command:
41-
cmd: "{{ user_home }}/.npm-global/bin/claude config set auto_update true"
42-
environment:
43-
PATH: "/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:{{ user_home }}/.npm-global/bin:{{ ansible_facts['env'].PATH }}"
129+
# ============================================================================
130+
# macOS - Homebrew cask installation
131+
# ============================================================================
132+
133+
- name: Install Claude Code via Homebrew cask (macOS)
134+
community.general.homebrew_cask:
135+
name: claude-code
136+
state: present
44137
become: false
45-
failed_when: false
46-
changed_when: false
138+
environment: "{{ homebrew_cask_env }}"
47139
when: ansible_facts['distribution'] == 'MacOSX'
48140

49141
# ============================================================================

ansible/roles/developer_core/tasks/verify.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,21 @@
113113
failed_when: verify_netbird.rc != 0
114114
when: ansible_facts['distribution'] == 'MacOSX'
115115

116-
- name: Verify Claude Code CLI
117-
ansible.builtin.command: "{{ user_home }}/.npm-global/bin/claude --version"
116+
- name: Verify Claude Code CLI (Linux)
117+
ansible.builtin.command: "{{ user_home }}/.local/bin/claude --version"
118118
register: verify_claude
119119
changed_when: false
120120
failed_when: verify_claude.rc != 0
121121
become: false
122+
when: ansible_facts['distribution'] in ['Fedora', 'Ubuntu']
123+
124+
- name: Verify Claude Code CLI (macOS)
125+
ansible.builtin.command: claude --version
126+
environment: "{{ homebrew_env }}"
127+
register: verify_claude
128+
changed_when: false
129+
failed_when: verify_claude.rc != 0
130+
when: ansible_facts['distribution'] == 'MacOSX'
122131

123132
- name: Verify Gitleaks (Linux)
124133
ansible.builtin.command: gitleaks version

fedora/install-dfe-developer-core.sh

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# - semantic-release and npm packages
1515
# - JFrog CLI, GitHub CLI, Azure CLI
1616
# - OpenVPN 3 client
17-
# - Claude Code CLI with auto-update
17+
# - Claude Code CLI (native binary)
1818
# - UV and Nox Python tools
1919
# - Slack (Flatpak)
2020
#
@@ -119,13 +119,53 @@ gpgkey=https://packages.microsoft.com/keys/microsoft.asc
119119
EOF
120120
sudo dnf install -y azure-cli
121121

122-
# Claude Code CLI (user-specific installation)
123-
print_info "Installing Claude Code CLI via npm (user-specific)..."
124-
npm install -g @anthropic-ai/claude-code
122+
# Claude Code CLI (native binary - user-specific installation)
123+
print_info "Installing Claude Code CLI (native binary)..."
124+
CLAUDE_GCS_BUCKET="https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases"
125125

126-
# Enable auto-update for Claude Code
127-
print_info "Configuring Claude Code auto-update..."
128-
PATH="$HOME/.npm-global/bin:$PATH" claude config set auto_update true || true
126+
# Remove deprecated npm installation if present
127+
if [ -x "$HOME/.npm-global/bin/claude" ]; then
128+
print_info "Removing deprecated npm Claude Code installation..."
129+
npm uninstall -g @anthropic-ai/claude-code || true
130+
fi
131+
132+
# Determine platform
133+
ARCH=$(uname -m)
134+
case "$ARCH" in
135+
x86_64) CLAUDE_PLATFORM="linux-x64" ;;
136+
aarch64) CLAUDE_PLATFORM="linux-arm64" ;;
137+
*) print_error "Unsupported architecture: $ARCH"; exit 1 ;;
138+
esac
139+
140+
# Fetch latest version
141+
CLAUDE_VERSION=$(curl -fsSL "${CLAUDE_GCS_BUCKET}/latest")
142+
print_info "Claude Code version: $CLAUDE_VERSION"
143+
144+
# Fetch manifest and extract checksum
145+
CLAUDE_CHECKSUM=$(curl -fsSL "${CLAUDE_GCS_BUCKET}/${CLAUDE_VERSION}/manifest.json" \
146+
| python3 -c "import sys,json; print(json.load(sys.stdin)['platforms']['${CLAUDE_PLATFORM}']['checksum'])")
147+
148+
# Download binary to temp directory
149+
mkdir -p /tmp/claude-install
150+
curl -fsSL -o /tmp/claude-install/claude "${CLAUDE_GCS_BUCKET}/${CLAUDE_VERSION}/${CLAUDE_PLATFORM}/claude"
151+
chmod +x /tmp/claude-install/claude
152+
153+
# Verify SHA256 checksum
154+
ACTUAL_CHECKSUM=$(sha256sum /tmp/claude-install/claude | cut -d' ' -f1)
155+
if [ "$ACTUAL_CHECKSUM" != "$CLAUDE_CHECKSUM" ]; then
156+
print_error "Claude Code checksum verification failed!"
157+
print_error " Expected: $CLAUDE_CHECKSUM"
158+
print_error " Actual: $ACTUAL_CHECKSUM"
159+
rm -rf /tmp/claude-install
160+
exit 1
161+
fi
162+
163+
# Install (sets up ~/.local/bin/claude and shell integration)
164+
mkdir -p "$HOME/.local/bin"
165+
/tmp/claude-install/claude install
166+
167+
# Cleanup
168+
rm -rf /tmp/claude-install
129169

130170
# Linear.app CLI tool (system-wide installation)
131171
sudo npm install -g @digitalstories/linear-cli
@@ -171,9 +211,9 @@ az version &>/dev/null && echo " [OK] Azure CLI" || echo " [FAIL] Azure CLI"
171211
openvpn3 version &>/dev/null && echo " [OK] OpenVPN 3" || echo " [FAIL] OpenVPN 3"
172212

173213
echo ""
174-
echo "User-installed Tools (in ~/.npm-global/bin):"
214+
echo "User-installed Tools:"
175215
[ -x "$HOME/.npm-global/bin/semantic-release" ] && echo " [OK] semantic-release" || echo " [FAIL] semantic-release"
176-
[ -x "$HOME/.npm-global/bin/claude --version" ] && echo " [OK] Claude Code CLI" || echo " [FAIL] Claude Code CLI"
216+
[ -x "$HOME/.local/bin/claude" ] && echo " [OK] Claude Code CLI" || echo " [FAIL] Claude Code CLI"
177217

178218
echo ""
179219
echo "Python Tools:"

0 commit comments

Comments
 (0)