Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
918b184b01 | ||
|
|
ba107c1b53 |
@@ -1,8 +1,10 @@
|
|||||||
# granthi-sync v1.2
|
# granthi-sync v1.2
|
||||||
|
|
||||||
The signup → download → link-folders → cloud product spine for the Granthi
|
The signup → download → link-folders → cloud product spine for the Granthi
|
||||||
forge, tested against the BETA forge (granthi-beta.shre.ai). Python 3 stdlib +
|
forge. Developed and live-E2E-tested against the BETA forge
|
||||||
git CLI only — same portability heritage as the estate's `gitea_sync.py` mesh.
|
(granthi-beta.shre.ai); **the deployed service now runs against the PRODUCTION
|
||||||
|
forge** — see "Where this actually runs" below. Python 3 stdlib + git CLI only
|
||||||
|
— same portability heritage as the estate's `gitea_sync.py` mesh.
|
||||||
|
|
||||||
```
|
```
|
||||||
┌──────────────┐ device flow ┌─────────────────┐
|
┌──────────────┐ device flow ┌─────────────────┐
|
||||||
@@ -13,7 +15,8 @@ git CLI only — same portability heritage as the estate's `gitea_sync.py` mesh.
|
|||||||
│ │ POST /v1/link {zitadel_access_token, device_name}
|
│ │ POST /v1/link {zitadel_access_token, device_name}
|
||||||
│ │ ───────────────▶ ┌───────────────────────────────┐
|
│ │ ───────────────▶ ┌───────────────────────────────┐
|
||||||
│ │ ◀─────────────── │ granthi-link :3042 │
|
│ │ ◀─────────────── │ granthi-link :3042 │
|
||||||
│ │ {login, token} │ (granthi VPS, tailnet-only) │
|
│ │ {login, token} │ (granthi VPS; public via │
|
||||||
|
│ │ │ https://granthi-link.shre.ai)│
|
||||||
│ │ │ · userinfo validation │
|
│ │ │ · userinfo validation │
|
||||||
│ │ POST /v1/repos │ · ensure Gitea user (admin) │
|
│ │ POST /v1/repos │ · ensure Gitea user (admin) │
|
||||||
│ │ ───────────────▶ │ · mint scoped user token │
|
│ │ ───────────────▶ │ · mint scoped user token │
|
||||||
@@ -21,11 +24,28 @@ git CLI only — same portability heritage as the estate's `gitea_sync.py` mesh.
|
|||||||
│ │ git push/fetch (user token │ admin API
|
│ │ git push/fetch (user token │ admin API
|
||||||
│ │ via credential helper) ▼
|
│ │ via credential helper) ▼
|
||||||
│ │ ───────────────▶ ┌───────────────────────────────┐
|
│ │ ───────────────▶ ┌───────────────────────────────┐
|
||||||
└──────────────┘ │ BETA forge :3041 │
|
└──────────────┘ │ PROD forge :3040 │
|
||||||
│ granthi-beta.shre.ai │
|
│ granthi.shre.ai │
|
||||||
└───────────────────────────────┘
|
└───────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Where this actually runs
|
||||||
|
|
||||||
|
Verified on the granthi VPS (`[email protected]`) on 2026-08-30, because
|
||||||
|
this file previously described the beta tier long after the deployment moved:
|
||||||
|
|
||||||
|
| | value |
|
||||||
|
|---|---|
|
||||||
|
| service | `/opt/granthi-link/`, `granthi-link.service`, `systemctl is-active` → `active` |
|
||||||
|
| public entry | `https://granthi-link.shre.ai` — `/health` → 200. `/` → 404 is **no root route**, not an outage |
|
||||||
|
| `gitea_base` | `http://127.0.0.1:3040` (container `gitea-central-gitea-1`) |
|
||||||
|
| `public_gitea_base` | `https://granthi.shre.ai` |
|
||||||
|
| rate limiting | `trust_forwarded_for: true`, `trusted_proxies: ["100.107.37.98/32"]`, no `rules` key → falls back to `DEFAULT_RATE_RULES` |
|
||||||
|
| deployed version | `/health` reports **1.1.0** while this repo is **v1.2** — the running service lags `main` |
|
||||||
|
|
||||||
|
So a new computer that follows Quickstart lands on the **production** forge.
|
||||||
|
Beta remains where changes are proven before they reach it.
|
||||||
|
|
||||||
## Quickstart (invited user)
|
## Quickstart (invited user)
|
||||||
|
|
||||||
You need a shre-id account — an operator creates it; there is no open signup
|
You need a shre-id account — an operator creates it; there is no open signup
|
||||||
@@ -194,13 +214,16 @@ So:
|
|||||||
**64 KB** (413 beyond; missing `Content-Length` → 411, invalid → 400).
|
**64 KB** (413 beyond; missing `Content-Length` → 411, invalid → 400).
|
||||||
* `POST /v1/repos {token, name, private}` → creates the user repo with the
|
* `POST /v1/repos {token, name, private}` → creates the user repo with the
|
||||||
USER token; clone/html URLs are rebased onto `public_gitea_base` because the
|
USER token; clone/html URLs are rebased onto `public_gitea_base` because the
|
||||||
container `ROOT_URL` (https://granthi-beta.shre.ai) does not resolve for
|
container `ROOT_URL` does not necessarily resolve for the client that asked
|
||||||
tailnet-only clients.
|
(it was a tailnet-only address on beta; on prod the rebase keeps clone URLs
|
||||||
|
on `https://granthi.shre.ai` rather than the container's own view).
|
||||||
|
|
||||||
Deployment: `/opt/granthi-link/{granthi_link.py,config.json,state.json}` +
|
Deployment: `/opt/granthi-link/{granthi_link.py,config.json,state.json}` +
|
||||||
systemd unit `granthi-link.service`; binds `127.0.0.1:3042` **and**
|
systemd unit `granthi-link.service`; binds `127.0.0.1:3042` **and**
|
||||||
`100.111.127.127:3042` (tailnet). **Not publicly exposed** — see promotion
|
`100.111.127.127:3042` (tailnet), and is **publicly reachable** at
|
||||||
window. The service **refuses to start** (exit 2) unless `config.json` is
|
`https://granthi-link.shre.ai` through the `pulse-granthi-edge` cloudflared
|
||||||
|
tunnel (done 2026-08-23; re-verified 2026-08-30). The service **refuses to
|
||||||
|
start** (exit 2) unless `config.json` is
|
||||||
mode 0600/0400 and owned by the user it runs as — the config carries the
|
mode 0600/0400 and owned by the user it runs as — the config carries the
|
||||||
forge admin password, so permissive perms fail closed, not open.
|
forge admin password, so permissive perms fail closed, not open.
|
||||||
|
|
||||||
@@ -620,10 +643,12 @@ Shape this should take, so the next session does not re-litigate it:
|
|||||||
`https://granthi-link.shre.ai` (second-level, see above), origin stays
|
`https://granthi-link.shre.ai` (second-level, see above), origin stays
|
||||||
tailnet-only, `trust_forwarded_for` on with the tunnel as the only
|
tailnet-only, `trust_forwarded_for` on with the tunnel as the only
|
||||||
trusted proxy.
|
trusted proxy.
|
||||||
2. **Swap forge base URLs** in `/opt/granthi-link/config.json`:
|
2. ~~**Swap forge base URLs** in `/opt/granthi-link/config.json`~~
|
||||||
`gitea_base` → prod forge, `public_gitea_base` →
|
**DONE — verified live 2026-08-30**: the deployed config reads
|
||||||
`https://granthi.shre.ai`; the client default server URL moves to the
|
`gitea_base: http://127.0.0.1:3040` and
|
||||||
public endpoint.
|
`public_gitea_base: https://granthi.shre.ai`, and the client default
|
||||||
|
server is already the public endpoint. Linking a new device therefore
|
||||||
|
creates the account on **prod**.
|
||||||
3. The `granthi-web` OIDC app already lists the prod callback; the device
|
3. The `granthi-web` OIDC app already lists the prod callback; the device
|
||||||
app is host-independent. Rotate the beta admin token/password out of the
|
app is host-independent. Rotate the beta admin token/password out of the
|
||||||
config when pointing at prod (prod forge is READ-ONLY to this estate —
|
config when pointing at prod (prod forge is READ-ONLY to this estate —
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
{
|
{
|
||||||
"gitea_base": "http://127.0.0.1:3041",
|
"_comment_forge": "Values below mirror the LIVE prod deployment (verified 2026-08-30). For the beta tier use gitea_base http://127.0.0.1:3041, public_gitea_base https://granthi-beta.shre.ai, container gitea-beta-gitea-1, creds /opt/gitea-beta/.admin-creds.",
|
||||||
"public_gitea_base": "http://100.111.127.127:3041",
|
"gitea_base": "http://127.0.0.1:3040",
|
||||||
|
"public_gitea_base": "https://granthi.shre.ai",
|
||||||
"zitadel_userinfo": "https://id.shre.ai/oidc/v1/userinfo",
|
"zitadel_userinfo": "https://id.shre.ai/oidc/v1/userinfo",
|
||||||
"admin_token": "MINT-VIA: docker exec -u git gitea-beta-gitea-1 gitea admin user generate-access-token --username nirpa --scopes write:admin,write:user,write:repository --raw",
|
"admin_token": "MINT-VIA: docker exec -u git gitea-central-gitea-1 gitea admin user generate-access-token --username nirpa --scopes write:admin,write:user,write:repository --raw",
|
||||||
"admin_login": "nirpa",
|
"admin_login": "nirpa",
|
||||||
"admin_password": "FROM /opt/gitea-beta/.admin-creds (required: Gitea 1.27 token minting only works via basic auth + Sudo header)",
|
"admin_password": "FROM shre-cred: superadmin/granthi-link/granthi-prod-forge-admin (required: Gitea 1.27 token minting only works via basic auth + Sudo header). NEVER paste this into chat or a shell history.",
|
||||||
"binds": [["127.0.0.1", 3042], ["100.111.127.127", 3042]],
|
"binds": [["127.0.0.1", 3042], ["100.111.127.127", 3042]],
|
||||||
"test_mode": false,
|
"test_mode": false,
|
||||||
"rate_limit": {
|
"rate_limit": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"trust_forwarded_for": false,
|
"trust_forwarded_for": true,
|
||||||
|
"trusted_proxies": ["100.107.37.98/32"],
|
||||||
"rules": {"/v1/link": [5, 3600], "/v1/repos": [60, 3600]}
|
"rules": {"/v1/link": [5, 3600], "/v1/repos": [60, 3600]}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -29,11 +29,9 @@ GIT_ENV = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def run_git(cwd, *args, strip=True):
|
def run_git(cwd, *args):
|
||||||
stdout = subprocess.run(["git", "-C", cwd] + list(args), check=True,
|
return subprocess.run(["git", "-C", cwd] + list(args), check=True,
|
||||||
capture_output=True, text=True,
|
capture_output=True, text=True, env=GIT_ENV).stdout.strip()
|
||||||
env=GIT_ENV).stdout
|
|
||||||
return stdout.strip() if strip else stdout
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -907,13 +905,15 @@ class TestCredentialHelperIsolation(GitScenarioBase):
|
|||||||
def test_install_leaves_exactly_one_helper(self):
|
def test_install_leaves_exactly_one_helper(self):
|
||||||
run_git(self.local, "config", "--add", "credential.helper", "store")
|
run_git(self.local, "config", "--add", "credential.helper", "store")
|
||||||
client.install_credential_helper(self.local)
|
client.install_credential_helper(self.local)
|
||||||
# Inspect the repo-local list so this assertion is deterministic even
|
# --get-all merges system + global + local, so entries inherited from
|
||||||
# when the machine has no inherited helper. Keep the leading newline:
|
# the machine still appear. What matters is that the last two are the
|
||||||
# it represents the empty reset value, not disposable whitespace.
|
# reset and ours: git reads an empty value as "forget every helper
|
||||||
helpers = run_git(self.local, "config", "--local", "--get-all",
|
# inherited so far", so nothing before it can answer.
|
||||||
"credential.helper", strip=False).splitlines()
|
helpers = run_git(self.local, "config", "--get-all",
|
||||||
self.assertEqual(helpers, ["", client.credential_helper_value()])
|
"credential.helper").splitlines()
|
||||||
# The repo-level 'store' this test added is gone, not merely outvoted.
|
self.assertEqual(helpers[-2], "", helpers)
|
||||||
|
self.assertIn("git-credential", helpers[-1])
|
||||||
|
# the repo-level 'store' this test added is gone, not merely outvoted
|
||||||
self.assertNotIn("store", helpers)
|
self.assertNotIn("store", helpers)
|
||||||
|
|
||||||
def test_inherited_helper_cannot_answer_for_the_forge(self):
|
def test_inherited_helper_cannot_answer_for_the_forge(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user