Order Ledger Google OAuth¶
Create the Gmail OAuth client for
https://orders.infra.realemail.app and
put that client into services/orders/.env.sops.yaml.
The app requests one scope, https://www.googleapis.com/auth/gmail.readonly.
It does not send, modify, or delete mail. Leave the Google app in Testing.
Do not publish it. A testing app can only be used by the addresses you add as
test users, which is the right boundary for a single mailbox.
17TRACK and Telegram live in the same SOPS file. This procedure adds the
Google client without replacing those keys. Ansible refuses to render the
file while any of the four values is still REPLACE_ME_VIA_SOPS, so fill
Telegram before you encrypt if you have those values now.
1. Pick or create a Google Cloud project¶
- Open Google Cloud console.
- In the project picker, create a project named
order-ledger, or select one you already use for this app. Stay in that project for every step below.
2. Enable the Gmail API¶
- Open Gmail API.
- Confirm the project name at the top is
order-ledger. - Click Enable. If the button already says Manage, the API is on.
3. Register the OAuth app¶
- Open Google Auth Platform overview.
- If you see Get started, finish it:
- App name:
Order Ledger - User support email: the Gmail address this app will read
- Audience: External when that mailbox is a personal
@gmail.comaccount. Choose Internal only when the mailbox is in a Google Workspace org you administer. - Contact email: the same address
- Agree and continue
- Open Audience.
- Publishing status must stay Testing.
- Under Test users, click Add users and add the exact Gmail address whose mail will be read. Save.
- Open Data Access.
- Click Add or remove scopes.
- Find
https://www.googleapis.com/auth/gmail.readonly(Gmail API, "View your email messages and settings"). - Select only that scope. Update, then Save.
If the console still shows the older APIs & Services > OAuth consent
screen instead of Google Auth Platform, use that page for the same facts:
External, Testing, your address as a test user, and the gmail.readonly
scope.
4. Create the web client¶
- Open Clients.
- Click Create client.
- Application type: Web application. Do not choose Desktop, Android, or Chrome.
- Name:
Order Ledger. - Leave Authorized JavaScript origins empty.
- Under Authorized redirect URIs, click Add URI and enter this value with no trailing slash:
https://orders.infra.realemail.app/oauth/callback
- Click Create.
Optional, only if you still run the laptop copy: add a second redirect URI
http://127.0.0.1:8766/oauth/callback. The homelab containers send the
https://orders... URI. Google rejects the login when the URI in the
request is not on this list character for character.
- On the client you just created, download the JSON. The file name looks
like
client_secret_....json. - Open it in an editor and check the first key. It must be
"web". A file whose first key is"installed"is the wrong client type. Delete that client and create a Web application again.
Keep that JSON file outside the git repo. Do not commit it, paste it into chat, or attach it to a ticket.
5. Put the client into the SOPS file¶
From the homelab repo root. CLIENT_JSON is the path to the file you
downloaded.
umask 077
cd /path/to/homelab
test -f services/orders/.env.sops.yaml || cp services/orders/.env.sops.yaml.example services/orders/.env.sops.yaml
If services/orders/.env.sops.yaml is already encrypted (sops and ENC[
appear at the top), decrypt a working copy first. Skip this when the file
is still plain YAML from the example:
tmp="$(mktemp)"
sops --decrypt --output-type yaml services/orders/.env.sops.yaml >"$tmp"
cp "$tmp" services/orders/.env.sops.yaml
rm -f "$tmp"
Set SEVENTEEN_TRACK_TOKEN, TELEGRAM_BOT_TOKEN, and
TELEGRAM_ALLOWED_CHAT_ID in that plain file if they are not already real
values. Then merge the downloaded client. This prints only the client id,
not the secret:
uv run python - "$CLIENT_JSON" services/orders/.env.sops.yaml <<'PY'
import json
import sys
from pathlib import Path
import yaml
client_path, dest_path = sys.argv[1:]
raw = Path(dest_path).read_text(encoding="utf-8")
if "ENC[" in raw:
raise SystemExit("destination is still encrypted; decrypt it before merging")
client = json.loads(Path(client_path).read_text(encoding="utf-8"))
web = client.get("web") or client.get("installed") or {}
if not isinstance(web, dict) or not web.get("client_id") or not web.get("client_secret"):
raise SystemExit("JSON needs a web.client_id and web.client_secret")
if "installed" in client and "web" not in client:
raise SystemExit("this is a desktop client; create a Web application instead")
data = yaml.safe_load(raw)
data["GOOGLE_CLIENT_JSON"] = client
Path(dest_path).write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
print("merged Google client", web["client_id"])
PY
Encrypt in place and remove the downloaded JSON from the repo tree if it was copied there:
Before you commit, git diff services/orders/.env.sops.yaml must show
ciphertext only. If you can read client_secret in the diff, stop and
encrypt again. Do not push the plain file.
Commit that on a branch and open a pull request. The encrypted file is
safe to merge. The age recipients in .sops.yaml already cover this path.
6. Render and connect¶
On infra-services, after that pull request is on main:
sudo env ANSIBLE_CONFIG=infra/ansible/ansible.cfg \
ansible-playbook -i infra/ansible/inventory/generated.yml \
infra/ansible/playbooks/site.yml --limit infra-services \
--tags orders,orders-secrets \
-e ansible_connection=local
Run that from /opt/homelab. ANSIBLE_CONFIG loads roles_path, and
ansible_connection=local applies the play on this host. Without those,
sudo looks for roles beside the playbook and then tries to SSH to
root@192.168.6.17.
That writes /etc/homelab/orders/google_client.json as mode 0400 owned
by uid 10001, and services/orders/compose.env for the API tokens. Then
run the Komodo procedure deploy-orders if the containers are not already
up.
In a browser on the LAN or Tailscale:
- Open https://orders.infra.realemail.app and sign in through Authentik.
- Click Connect Gmail.
- Choose the test-user address from step 3.
- Google will say the app is not verified. Click Advanced, then Go to Order Ledger (unsafe). That warning is expected while the app stays in Testing.
- Allow the read-only Gmail scope. You should land back on the dashboard with Gmail connected.
- Click Sync Gmail. The first four-month scan can take a few minutes.
When Google refuses the login¶
| What you see | What to change |
|---|---|
redirect_uri_mismatch |
The console URI is not exactly https://orders.infra.realemail.app/oauth/callback. No trailing slash, no http. |
access_denied or the account cannot continue |
That Google account is not on the Testing user list, or the app was published and then blocked. |
| App is blocked / verification required | The consent screen was published. Set it back to Testing. |
| Connected, then "no refresh token" | Open Google account permissions, remove Order Ledger, and click Connect Gmail again. |
| Dashboard never reaches Google | Authentik did not let you in. /oauth/start is behind Authentik on purpose. The callback path is the one that skips it. |