Every Raspberry Pi that needs to git clone or git pull from a private
GTMichelli-Dev repo (pi-network-setup, camera-capture-service,
qb-sync-service) authenticates through one GitHub App installed on the
org. No personal access tokens, no SSH keys, no per-Pi GitHub accounts.
foundation, web-print-service, scale-reader-service and this repo are
public and clone with no credentials at all — worth knowing, because it makes
them useless for testing whether any of this works, and because it is exactly
why the bootstrap below can run on a Pi that cannot yet authenticate to
anything.
This repo is the single source of truth for the scripts and this document. It lives on its own so every service repo can link to one copy instead of keeping its own. The service repos — foundation, web-print-service, scale-reader-service — point here from their release notes.
A Pi you can paste into, and nothing else (Raspberry Pi Connect's web shell):
curl -fsSL -o /tmp/gh-auth.sh https://raw.githubusercontent.com/GTMichelli-Dev/pi-git-auth/main/scripts/pi-connect-github-auth.sh
bash /tmp/gh-auth.sh </dev/ttyIt asks for the Installation ID, takes the PEM as a paste, and hands off to the real installer. Everything else on this page is the long way round, for when that does not fit.
| Field | Value |
|---|---|
| Name | michelli-fleet |
| Owned by | @GTMichelli-Dev (org-owned) |
| App ID | 4260960 |
| Client ID | Iv23livFZQOXhMbgSTed (preferred JWT issuer per GitHub) |
| Installation ID | 145563826 |
| Permissions | Contents: Read-only |
| PEM | downloaded once when the App was created (kept off-repo) |
The App has a private key (.pem file). On each Pi:
- The
.pemlives at/etc/michelli/github-app.pem(mode0644— see "Why so loose" below). - A token-mint helper (
/usr/local/bin/michelli-github-app-token) signs a short-lived JWT with the PEM (using the Client ID as theissclaim), swaps it for a 1-hour installation access token, and caches the token to/tmp/michelli-gh-token-$UID. - A git credential helper (
/usr/local/bin/git-credential-michelli) is registered forhttps://github.com/GTMichelli-Dev/*in/etc/gitconfig. Git calls it before every operation that needs auth; it returnsusername=x-access-tokenplus the freshly-minted token.
End result: plain git clone https://github.com/GTMichelli-Dev/<anything>.git
and git pull work silently from any user on the Pi. No PAT, no env vars, no
SSH config, no group memberships to chase.
/etc/michelli/github-app.pem is mode 0644 — readable by any local user on
the box. On a single-user scale-house or kiosk Pi the security benefit of
tightening it is nil: anyone with shell access already has sudo, which can
cat the PEM regardless of group. Group-restricted perms also interact badly
with Pi Connect's web shell, where sessions are sticky and don't reliably pick
up new group membership without a full browser-tab restart — real time lost
per Pi, for nothing.
On a multi-tenant Linux host this would be a downgrade and the group mechanism would be worth having back. The Michelli fleet doesn't have any of those.
- The PEM — the
.pemfile downloaded when the App was created. Keep it on your laptop or in a password manager, and paste its contents into the Pi during the bootstrap below. It is never committed to a repo: this repo is public, so a PEM committed here would be published to the world. - The Installation ID —
145563826for the current GTMichelli-Dev installation (visible in the URLhttps://github.com/organizations/GTMichelli-Dev/settings/installations/145563826). If you install the App on additional repos later, the same Installation ID covers them, as long as the new repos are checked into the existing installation.
The App ID and Client ID are already baked into
scripts/setup-pi-github-app.sh — you
don't pass them.
The Pi Connect web shell mangles multi-line bracketed pastes (the ^[[201~
end marker gets appended to the last line), so this path is structured as
single-line commands plus a nano step for the multi-line PEM. Run each step
in order. Each one is a single line, safe to copy-paste end to end.
For most Pis, Quick start above does all of this in one script; use these steps when you want to see each part happen, or when the paste-driven script has failed and you are working out why.
Step 1 — install deps, create the config dir.
sudo apt-get update -y && sudo apt-get install -y git curl jq openssl && sudo install -d -m 0755 /etc/michelliStep 2 — get the helper scripts. This repo is public, so the Pi can clone
it before it can authenticate to anything — no nano-pasting of helper scripts
needed. Sparse checkout keeps it to the scripts/ folder:
git clone --filter=blob:none --sparse https://github.com/GTMichelli-Dev/pi-git-auth.git ~/pi-git-auth && git -C ~/pi-git-auth sparse-checkout set scriptsStep 3 — paste the PEM. Open nano and paste the whole
-----BEGIN ... -----END ... block, every line. Save with Ctrl+O + Enter,
exit with Ctrl+X:
sudo nano /etc/michelli/github-app.pemThen set its mode:
sudo chmod 0644 /etc/michelli/github-app.pemStep 4 — run the installer. It writes the conf, installs both helpers,
registers the credential helper in /etc/gitconfig, and smoke-tests with a
real token mint. The PEM is already in place from step 3, so no --pem here:
sudo bash ~/pi-git-auth/scripts/setup-pi-github-app.sh --install-id 145563826Step 5 — smoke test:
git ls-remote https://github.com/GTMichelli-Dev/pi-network-setup.git HEADShould print a SHA and HEAD with no prompt. Test against a private repo
— this repo and foundation are public and answer without the credential
helper being involved at all, so they would succeed even on a Pi where this
all failed. If it prompts for a username, see
Troubleshooting.
You can now git clone any GTMichelli-Dev repo on this Pi.
Every release
carries the scripts as a tarball and individually. This is the path when the
Pi has curl but not git, or when you want a pinned version rather than
whatever main says today.
curl -fsSL -o pga.tar.gz https://github.com/GTMichelli-Dev/pi-git-auth/releases/latest/download/pi-git-auth.tar.gz
mkdir -p /tmp/pga && tar -xzf pga.tar.gz -C /tmp/pga
sudo bash /tmp/pga/setup-pi-github-app.sh --install-id 145563826 --pem /path/to/michelli-app.pemTake the tarball, not a single script. setup-pi-github-app.sh installs
michelli-github-app-token.sh and git-credential-michelli.sh from
alongside itself and stops with an error if they are not there, so a lone
setup-pi-github-app.sh cannot do the job. The individual scripts are
published for reading and for replacing one helper on a Pi that is already
set up.
When you have shell and scp access from a workstation that has this repo and the PEM already on disk:
PI=admin@pi-hostname.local
PEM=/path/to/michelli-app.pem
INSTALL_ID=145563826
scp scripts/setup-pi-github-app.sh \
scripts/michelli-github-app-token.sh \
scripts/git-credential-michelli.sh \
"$PEM" "$PI:/tmp/"
ssh "$PI" "sudo bash /tmp/setup-pi-github-app.sh \
--install-id $INSTALL_ID \
--pem /tmp/$(basename "$PEM")"
ssh "$PI" "shred -u /tmp/$(basename "$PEM") && rm -f /tmp/setup-pi-github-app.sh /tmp/michelli-github-app-token.sh /tmp/git-credential-michelli.sh"Same end state as path A, and no nano step — scp doesn't mangle the PEM the way a browser terminal does.
Once this repo is on the Pi:
sudo bash ~/pi-git-auth/scripts/setup-pi-github-app.sh # refresh helpers from repo
sudo bash ~/pi-git-auth/scripts/setup-pi-github-app.sh --pem /tmp/new.pem # rotate PEM
sudo bash ~/pi-git-auth/scripts/setup-pi-github-app.sh --install-id <N> # re-install or repoint installationEach run is idempotent. Re-running with no flags refreshes the helper scripts
(scripts/michelli-github-app-token.sh / scripts/git-credential-michelli.sh)
from the current checkout — useful after a git pull that updates them.
- Token lifetime is 1 hour. The cache file
/tmp/michelli-gh-token-$UIDholds the token until 60s before expiry, thenmichelli-github-app-tokenmints a new one transparently. - Pis need internet to mint tokens. A fully offline Pi can't clone or pull regardless of auth model — same as PATs and deploy keys.
- PEM rotation. GitHub Apps let you generate a new private key without
invalidating the old. Generate a new PEM in the App settings, distribute it
to the fleet (
--pemflag), then revoke the old one. No Installation ID change needed. - Off-boarding a stolen Pi. If you can't recover it, generate a new App PEM, revoke the old, and re-bootstrap the fleet. Any cached token on the lost Pi keeps working for up to its remaining ~1h validity, but no new ones can be minted.
- Audit. Every git operation through the App shows up in the org's audit log under the App's identity — cleaner than a PAT model where every clone looks like a user action.
Already done for GTMichelli-Dev; recorded here for rebuilds. Org Settings
→ Developer settings → GitHub Apps → New GitHub App, with
Contents: Read-only and nothing else, Webhook → Active unchecked, and
installable only on this account. Create it, note the App ID and Client
ID, Generate a private key (that is the .pem — the only secret in this
scheme), then Install App on the org across all repositories. The number
ending the resulting URL is the Installation ID.
Bake the App ID and Client ID into the CLIENT_ID_DEFAULT and
APP_ID_DEFAULT lines near the top of scripts/setup-pi-github-app.sh and
commit, so per-Pi bootstraps only need the Installation ID and the PEM.
git ls-remote prompts for a username — the credential helper isn't
being called, or is failing silently. Run the minter directly to see the
error:
michelli-github-app-tokenCommon failures:
cannot read /etc/michelli/github-app.conf— the conf isn't readable. Checkls -l /etc/michelli/; it should be0644.cannot read /etc/michelli/github-app.pem— same fix, mode0644.token mint failed: {"message": "Bad credentials" ...}— the PEM doesn't match the App ID / Client ID. ConfirmCLIENT_IDin/etc/michelli/github-app.confmatches the App's settings page. A PEM pasted through a browser terminal can also arrive corrupted — verify it withsudo openssl rsa -in /etc/michelli/github-app.pem -noout -check.token mint failed: {"message": "Not Found" ...}— the Installation ID is wrong, or the App isn't installed on the repo. Checkhttps://github.com/organizations/GTMichelli-Dev/settings/installations/145563826.
Cannot find helper scripts alongside ... — you downloaded
setup-pi-github-app.sh on its own. It needs the two helpers beside it; take
pi-git-auth.tar.gz from the release instead. See
Bootstrap path B.
ls-remote succeeds but proves nothing — check you didn't test against
pi-git-auth, foundation, web-print-service, or scale-reader-service.
They are public and answer without any credential helper involved.
/etc/gitconfig permission denied — the system gitconfig was created
with too-tight perms by an earlier bootstrap. Fix:
sudo chmod 0644 /etc/gitconfig| Path | Mode | Purpose |
|---|---|---|
/etc/michelli/ |
0755 | Config directory |
/etc/michelli/github-app.pem |
0644 | App private key (single-user-Pi tradeoff — see "Why so loose") |
/etc/michelli/github-app.conf |
0644 | CLIENT_ID= / APP_ID= / INSTALL_ID= (public IDs only, not secret) |
/usr/local/bin/michelli-github-app-token |
0755 | Token minter — signs the JWT, exchanges it for an installation token, caches it |
/usr/local/bin/git-credential-michelli |
0755 | Git credential helper — calls the minter, formats output for git |
/etc/gitconfig |
0644 | Registers the helper for https://github.com/GTMichelli-Dev/* (system-wide) |
/tmp/michelli-gh-token-$UID |
0600 | Per-user token cache (regenerated as needed; ephemeral) |
| Path | Purpose |
|---|---|
scripts/setup-pi-github-app.sh |
The installer. Needs the two helpers beside it. |
scripts/michelli-github-app-token.sh |
Token minter, installed to /usr/local/bin. |
scripts/git-credential-michelli.sh |
Git credential helper, installed to /usr/local/bin. |
scripts/pi-connect-github-auth.sh |
Paste-driven wrapper for the Pi Connect web shell. |
Tag v* to publish a release carrying all four.