From 66917550606533798b207407e82cf7023e9f3067 Mon Sep 17 00:00:00 2001 From: claude Date: Mon, 22 Jun 2026 17:15:40 -0400 Subject: [PATCH] Module 8 polish: backup check, credentials, forge, prices (#38,#39,#47,#52) (#64) Co-authored-by: claude Co-committed-by: claude --- modules/08-remotes-and-hosting/README.md | 68 ++++++++++++++----- .../lab/verify-backup.sh | 8 +++ 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/modules/08-remotes-and-hosting/README.md b/modules/08-remotes-and-hosting/README.md index 5475bd7..ecf873f 100644 --- a/modules/08-remotes-and-hosting/README.md +++ b/modules/08-remotes-and-hosting/README.md @@ -49,9 +49,10 @@ is a full, equal Git repo that happens to live on a server. This is the fact the entire rest of the module rests on, so sit with it: **because a remote is just another copy, the commands you use to talk to it are identical no matter who hosts it.** `git push` -to GitHub is byte-for-byte the same operation as `git push` to a forge you run yourself in a -locked-down rack. The provider is a logistics decision — uptime, price, who can see it, where the -servers sit — not a Git decision. We lean on GitHub as the worked example below *only* because it's +to GitHub is byte-for-byte the same operation as `git push` to a **forge** (a Git hosting platform — +GitHub, GitLab, Gitea, Forgejo, and the like) you run yourself in a locked-down rack. The provider is +a logistics decision — uptime, price, who can see it, where the servers sit — not a Git decision. We +lean on GitHub as the worked example below *only* because it's the one you're most likely to hit first, not because the mechanics change anywhere else. The local-to-remote vocabulary is small: @@ -102,12 +103,36 @@ where to go. Everyone hits at least one of these. Recognizing them by their error text saves an afternoon. -**1. Authentication fails.** You push and get `Authentication failed` or `Permission denied -(publickey)`. The cause is almost always that you tried to use an account password (dead) or haven't -set up a token / SSH key. Fix: for HTTPS, generate a personal access token in the host's settings and -use it as your password when prompted; for SSH, generate a key (`ssh-keygen`) and paste the public -half into the host's SSH-keys settings. This is host-specific UI but the *concept* is identical -everywhere. +**1. Authentication fails.** You push and get `Authentication failed`, `Permission denied +(publickey)`, or a `403`. Two different causes hide behind that wall, and they have different fixes. +The common one is *no usable credential at all* — you tried an account password (dead on every modern +host) or never set up a token / SSH key. The sneakier one is a credential that *exists but lacks the +right scope*: a token authenticates fine and then the push is refused with `403` because the token was +never granted write access to repositories. They look alike but you fix them differently — create a +credential vs. *edit the existing token's scopes* (don't regenerate it). For the no-credential case: +for HTTPS, generate a personal access token in the host's settings and use it as your password when +prompted; for SSH, generate a key (`ssh-keygen`) and paste the public half into the host's SSH-keys +settings. This is host-specific UI but the *concept* is identical everywhere — the callout below walks +the shape of getting one. + +> ### Getting a credential (the shape) +> +> The exact menu names and scope labels drift per host, so treat these as the *shape*, not gospel +> (**Verify-before-publish** the specific UI wording for your forge): +> +> - **Scope is the gotcha — check it first.** In the host's **Settings → developer / access tokens → +> create token**, you must grant the token write access to repositories: usually a scope literally +> named `repo`, or a "read **and write**" toggle on the repositories resource. A token created +> *without* it authenticates and then `403`s on push — it looks like an auth failure, but the fix is +> to **edit the token's scopes**, not to delete and recreate it. +> - **The token is shown once.** Hosts reveal the value a single time at creation. Copy it the moment +> it appears; if you lose it you create a new one rather than recover the old. +> - **Pasting it is invisible, and only happens once.** When Git prompts for your "password," paste +> the token — most terminals show *nothing* as you paste a secret, which is normal, not a failure. +> A **credential helper** (`git config --global credential.helper …`, e.g. `store`, `cache`, or your +> OS keychain) remembers it after the first success so you aren't pasting it on every push. +> - **SSH is the alternative.** A key you've added to the host skips passwords entirely: more setup +> once, no token to scope or cache afterward. **2. The remote isn't empty (non-fast-forward).** You let the host create the repo *with* a README, then push, and get `! [rejected] ... (fetch first)` or `non-fast-forward`. The remote has a commit @@ -163,10 +188,10 @@ the forge; you just use it) and **self-hosted** (you run the forge on your own i |---|---|---|---|---| | **GitHub** | Free; Team ~$4/user; Enterprise ~$21/user | GitHub Actions, built in (Free tier includes a monthly minutes allowance for private repos; unlimited for public) | **Deepest.** Most agents, MCP servers, and AI reviewers target GitHub first | Zero ops — pure SaaS | | **GitLab** (SaaS) | Free (capped users/namespace, small CI allowance); Premium ~$29/user; Ultimate ~$99/user | GitLab CI/CD — among the most mature, deeply integrated pipelines | Strong; first-party AI assistant plus growing agent support | Zero ops as SaaS; also self-hostable (see below) | -| **Bitbucket** (Atlassian) | Free (≤5 users); Standard ~$3/user; Premium ~$6/user | Pipelines, built in (small free monthly build-minute allowance) | Growing; tightest value is deep Jira/Atlassian tie-in | Zero ops as SaaS; Data Center edition self-hostable (enterprise pricing) | +| **Bitbucket** (Atlassian) | Free (≤5 users); Standard ~$3.65/user; Premium ~$7.25/user | Pipelines, built in (small free monthly build-minute allowance) | Growing; tightest value is deep Jira/Atlassian tie-in | Zero ops as SaaS; Data Center edition self-hostable (enterprise pricing) | | **Azure DevOps** | First 5 users free; Basic ~$6/user beyond; pipelines ~$40/parallel job after a free job | Azure Pipelines, built in (one free parallel job + monthly minutes) | Good within the Microsoft ecosystem; Copilot integration | Zero ops as SaaS; Azure DevOps Server self-hostable | | **Codeberg** | Free (FOSS projects only; soft repo/storage caps) | Forgejo Actions (it runs Forgejo) | Via API/MCP; not a first-tier agent target | Zero ops; nonprofit-run, no commercial/closed-source hosting | -| **SourceHut** | Paid to host: ~$5 / $10 / $15 tiers (reduced ~$2); free to *contribute* | builds.sr.ht, built in | Minimal first-class AI tooling; reachable via API | Zero ops as SaaS; fully self-hostable (it's open source) | +| **SourceHut** | Paid to host: ~$5 / $10 / $15 (all tiers buy the *same* service — "pay what's fair"); reduced ~$2 rate / financial aid if the full price is a hardship; free to *contribute* | builds.sr.ht, built in | Minimal first-class AI tooling; reachable via API | Zero ops as SaaS; fully self-hostable (it's open source) | **Self-hostable open-source forges (you run it):** @@ -343,6 +368,12 @@ independent* copy, history and all — not a snapshot. commit count as your local repo — i.e. the offsite copy is complete, not partial. Read its output; the green line is your evidence that the backup is real. + > On the **HTTPS + token** path with a *private* repo, the clone check (c) needs your credential + > helper to have cached the token from your earlier push — otherwise it can't authenticate to clone. + > The script won't hang waiting for a prompt (it disables interactive credential prompts); it just + > reports a `NOTE` that it couldn't clone, and the push checks above still stand. SSH and public + > repos clone with no credential at all. + ### Part C — The everyday loop 7. Edit the README in your *teammate* clone, commit, and push from there: @@ -440,18 +471,23 @@ tables, and update the "as of" date when you do. minutes allowance for private repos. - [ ] **GitLab** tiers — Free (user/namespace caps, CI allowance), Premium, Ultimate per-user/month, and the SaaS-vs-self-managed price split. -- [ ] **Bitbucket** tiers — Free user cap, Standard, Premium per-user/month, and free build-minute - allowance. (Sources disagreed between ~$2–3 and ~$5–6 at build time — re-confirm the exact - figures.) +- [ ] **Bitbucket** tiers — Free user cap, Standard (~$3.65), Premium (~$7.25) per-user/month, and + free build-minute allowance. (Reconciled against Atlassian's own pricing page on 2026-06-22; + stale third-party listings still quote ~$2/$5 — trust Atlassian's page, and re-confirm.) - [ ] **Azure DevOps** — free-user count, Basic per-user/month, and the per-parallel-job pipeline price plus free job/minutes. - [ ] **Codeberg** — that it remains FOSS-only and free, and its current soft repo/storage caps. -- [ ] **SourceHut** — paid-to-host tiers ($5/$10/$15 and reduced rate were *proposed for 2026*; - confirm they're in effect and current). +- [ ] **SourceHut** — paid-to-host tiers ($5/$10/$15): the 2026 prices are now *in effect* for new + accounts (confirmed 2026-06-22), so they're no longer "proposed." Note all tiers buy the same + service ("pay what's fair"), with a reduced rate (~the earlier minimum) and financial aid for + hardship — re-confirm before relying on it. - [ ] **Self-hosted forges** — that Forgejo/Gitea still ship GitHub-Actions-compatible CI, GitLab CE's current minimum resource footprint, and whether OneDev/Gogs CI status has changed. - [ ] **"GitHub integrates first" / AI-ecosystem maturity** — re-assess which forges are first-tier agent and MCP targets; this gap narrows fast. - [ ] **Self-host/hosted spans** — confirm GitLab still offers CE self-host, and Bitbucket/Azure DevOps still offer their self-hostable editions, before describing either as spanning both camps. +- [ ] **Credential/token UI** — the "Getting a credential" callout names menu paths and the + write-scope label (`repo` / "read and write") generically; confirm the current wording and + scope name on the default-example host before publishing. - [ ] Update the comparison's **"as of" date** to the build date. diff --git a/modules/08-remotes-and-hosting/lab/verify-backup.sh b/modules/08-remotes-and-hosting/lab/verify-backup.sh index 64c3735..0da87e6 100644 --- a/modules/08-remotes-and-hosting/lab/verify-backup.sh +++ b/modules/08-remotes-and-hosting/lab/verify-backup.sh @@ -15,6 +15,14 @@ set -u +# Fail fast instead of hanging. The clone and fetch below talk to the remote, +# and on the HTTPS+token path with no cached credential (a common fresh-Linux +# state) Git would stop to prompt for a username/password on the tty and block +# this check forever. Disabling interactive prompts turns that into a clean, +# non-zero failure that drops into the graceful warn branch below. SSH keys, +# public repos, and cached credential helpers are unaffected. +export GIT_TERMINAL_PROMPT=0 + # --- tiny output helpers (fall back to plain text if no color) --------------- if [ -t 1 ]; then GREEN=$'\033[32m'; RED=$'\033[31m'; YELLOW=$'\033[33m'; BOLD=$'\033[1m'; RESET=$'\033[0m'