tenseleyflow/shithub / 2f1f48e

Browse files

S38: docs — user guides (quickstart/ssh/https/PATs/account/2FA/issues/PRs/branch-protection/notifs/webhooks/search/orgs/markdown)

Authored by espadonne
SHA
2f1f48e966085953e9e2541301a393b66637fc05
Parents
88222cc
Tree
892e33b

14 changed files

StatusFile+-
A docs/public/user/2fa.md 67 0
A docs/public/user/account.md 75 0
A docs/public/user/branch-protection.md 65 0
A docs/public/user/https.md 70 0
A docs/public/user/issues.md 67 0
A docs/public/user/markdown.md 112 0
A docs/public/user/notifications.md 70 0
A docs/public/user/orgs.md 72 0
A docs/public/user/personal-access-tokens.md 71 0
A docs/public/user/pull-requests.md 78 0
A docs/public/user/quickstart.md 69 0
A docs/public/user/search.md 59 0
A docs/public/user/ssh.md 67 0
A docs/public/user/webhooks.md 118 0
docs/public/user/2fa.mdadded
@@ -0,0 +1,67 @@
1
+# Two-factor authentication
2
+
3
+Two-factor authentication (2FA) requires a second proof — beyond
4
+your password — before you can sign in. shithub supports
5
+**TOTP** (Time-based One-Time Password): six-digit codes from an
6
+authenticator app that change every 30 seconds.
7
+
8
+Strongly recommended. Account takeover is the most common bad
9
+outcome on a forge; a stolen password by itself can't sign in if
10
+2FA is on.
11
+
12
+## Setting up TOTP
13
+
14
+1. Settings → Account security → Two-factor authentication →
15
+   "Enable TOTP".
16
+2. Open your authenticator (Google Authenticator, 1Password,
17
+   Authy, etc.) and scan the QR code. The text-form secret is
18
+   shown beneath the QR if your app needs it.
19
+3. Enter the six-digit code from the app to confirm enrollment.
20
+4. **Save the recovery codes** that appear. There are 10. Each is
21
+   single-use. Store them somewhere your authenticator-device
22
+   loss won't take with it (password manager, paper safe).
23
+
24
+You're now enrolled. The next sign-in will ask for the code after
25
+the password.
26
+
27
+## Recovery codes
28
+
29
+If you lose your authenticator (phone died, factory reset, etc.),
30
+recovery codes are your way back in.
31
+
32
+- Each code works **once**.
33
+- Used codes are crossed off — you can see which are spent.
34
+- When you have ≤2 unused codes left, the UI nudges you to
35
+  regenerate. Regenerating invalidates all previous codes.
36
+
37
+If you exhaust all 10 codes, the only path back in is operator
38
+intervention — they cannot give you your codes back, but they can
39
+disable 2FA on the account after verifying your identity through
40
+a side channel.
41
+
42
+## Disabling 2FA
43
+
44
+Settings → Account security → "Disable TOTP". Requires entering
45
+the current TOTP code.
46
+
47
+This is recorded in your audit log. If you didn't disable 2FA,
48
+treat that audit row as evidence of compromise and rotate
49
+everything (password, all PATs, all SSH keys).
50
+
51
+## Sign-in flow with 2FA
52
+
53
+1. Username + password.
54
+2. If correct, you're prompted for the six-digit code (or "use a
55
+   recovery code").
56
+3. Code accepted → signed in.
57
+
58
+## What 2FA does and doesn't protect
59
+
60
+- **Protects against** stolen passwords (phishing, leaked-DB
61
+  reuse, shoulder-surfing).
62
+- **Does not protect against** stolen sessions — once you're
63
+  signed in, the session cookie is the access. Use "Sign out
64
+  everywhere" if a device is lost.
65
+- **Does not protect** PAT-based git or API access — PATs are
66
+  separate credentials; rotate them on the same cadence as
67
+  passwords.
docs/public/user/account.mdadded
@@ -0,0 +1,75 @@
1
+# Account settings
2
+
3
+Your settings are split into a small number of pages reachable
4
+from the avatar menu.
5
+
6
+## Profile
7
+
8
+Display name, bio, location, website, company, pronouns. Visible
9
+on your profile page (`/<username>`). Markdown is **not** rendered
10
+in the bio — it's plain text with hyperlinks auto-linked.
11
+
12
+## Emails
13
+
14
+You can have multiple email addresses on one account. One is
15
+**primary** (used for notifications and as the default committer
16
+match), the others are secondary (still match commits authored
17
+with that email).
18
+
19
+Each email is independently verified — until you click the link
20
+sent to it, that address can't be used as primary.
21
+
22
+## Password
23
+
24
+Change with the current password. Successful change invalidates
25
+**every** session except the current one (the session-epoch
26
+mechanism). Other devices will be signed out.
27
+
28
+## Two-factor authentication
29
+
30
+See [TOTP & recovery codes](./2fa.md). Strongly recommended.
31
+
32
+## Sessions
33
+
34
+Lists every active session with device + last-used time. "Sign
35
+out everywhere" bumps your session epoch — every other session is
36
+killed instantly. Use this if you suspect an unauthorized sign-in.
37
+
38
+## SSH and GPG keys
39
+
40
+- **SSH keys** authenticate `git@shithub.example:...` operations.
41
+- **GPG keys** verify signed commits — when a commit's signature
42
+  matches a registered GPG key, the commit shows a "Verified"
43
+  badge in history.
44
+
45
+Each key shows last-used timestamp; rotate when devices are lost.
46
+
47
+## Personal access tokens
48
+
49
+See [PATs](./personal-access-tokens.md).
50
+
51
+## Notifications
52
+
53
+See [Notifications](./notifications.md).
54
+
55
+## Audit log
56
+
57
+Settings → Audit log lists security-relevant actions on your
58
+account: sign-ins, password changes, 2FA toggles, key add/remove,
59
+PAT create/revoke, suspension events. Each row carries the IP and
60
+user-agent at the time. Export as JSON if you need to share with
61
+an operator during incident response.
62
+
63
+## Delete account
64
+
65
+The bottom of Settings → Account has a delete button. It requires
66
+typing your username for confirmation. Deletion:
67
+
68
+- Marks the account for soft-delete with a 30-day grace.
69
+- Hides your repos, issues, comments, and PRs immediately.
70
+- Frees the username after grace; until then, the username is
71
+  reserved.
72
+
73
+Within the grace period, signing in restores the account fully.
74
+After grace, the deletion is final; comments and issues you
75
+authored are reattributed to a `ghost` user.
docs/public/user/branch-protection.mdadded
@@ -0,0 +1,65 @@
1
+# Branch protection & reviews
2
+
3
+Branch protection lets a repo admin guard specific branches —
4
+typically `main` — from direct pushes, force-pushes, deletion,
5
+and merges that don't meet the team's review/CI bar.
6
+
7
+Configured at Repository → Settings → Branches.
8
+
9
+## Per-rule controls
10
+
11
+A rule applies to one or more branches by name pattern (`main`,
12
+`release/*`, etc.). Each rule can independently:
13
+
14
+- **Require pull request before merging** — direct pushes to the
15
+  branch are rejected. The only way in is a merged PR.
16
+- **Require N approvals** — a PR can't merge without that many
17
+  approvals from users with write access.
18
+- **Dismiss stale approvals on new commits** — when the PR head
19
+  moves, prior approvals are wiped.
20
+- **Require approval from someone other than the last pusher** —
21
+  the author of the most recent push to the PR can't be one of
22
+  the approvers.
23
+- **Require conversation resolution** — every line comment must
24
+  be marked "Resolved" before merge.
25
+- **Require status checks** — list of CI check names. The PR's
26
+  head commit must report success on each before merge enables.
27
+- **Require status checks to be up-to-date** — head must include
28
+  the latest base commit before checks count.
29
+- **Restrict who can push** (for non-PR pushes if PRs aren't
30
+  required) — list of users/teams.
31
+- **Disallow force-pushes**.
32
+- **Disallow deletions**.
33
+- **Lock branch** — read-only; no merges either. Used for
34
+  archived release lines.
35
+
36
+## How reviews count
37
+
38
+- Approvals from users with **read-only** access on the repo
39
+  don't count.
40
+- An approval is dismissed if the reviewer is removed from the
41
+  repo.
42
+- "Request changes" blocks merge until the same reviewer
43
+  re-reviews (Approve or Comment) — a different reviewer's
44
+  Approve does not lift the block.
45
+
46
+## Status checks
47
+
48
+Status checks come from external CI runners (or, in the future,
49
+shithub's own runner). A check has:
50
+
51
+- **Context** — the name (e.g., `ci/lint`, `ci/test`).
52
+- **State** — `pending`, `success`, `failure`, `error`.
53
+- **Description** — short human text.
54
+- **Target URL** — link to the run details.
55
+
56
+The first time a context name appears on the repo, you can add
57
+it to the required-checks list. The PR's merge gate evaluates
58
+the **head commit's most recent state per context**.
59
+
60
+## Bypass
61
+
62
+A repo admin can bypass branch protection for a single push
63
+(per-action; not a permanent setting) — useful for emergencies.
64
+The bypass is recorded in the audit log with the admin's id and
65
+the affected commits.
docs/public/user/https.mdadded
@@ -0,0 +1,70 @@
1
+# Cloning over HTTPS with a PAT
2
+
3
+For git over HTTPS, you authenticate with a **personal access
4
+token** (PAT), not your account password. This matches GitHub's
5
+behavior since 2021 and reflects the same security thinking:
6
+account passwords are more sensitive than scoped, revocable
7
+tokens.
8
+
9
+## 1. Create a PAT
10
+
11
+Settings → Developer settings → Personal access tokens → "New
12
+token".
13
+
14
+- **Note** — name the token after where you'll use it ("laptop",
15
+  "ci-runner-1"). Future-you will thank present-you.
16
+- **Expiration** — pick the shortest interval that's tolerable.
17
+  Tokens you forget about are tokens an attacker eventually finds.
18
+- **Scopes** — for git push/pull from a workstation, pick `repo`
19
+  (read+write). For read-only mirroring, `repo:read` is enough.
20
+
21
+When you submit, the token is shown **once**. Copy it immediately
22
+into your password manager — we never display it again.
23
+
24
+## 2. Clone
25
+
26
+```sh
27
+git clone https://shithub.example/<owner>/<repo>.git
28
+```
29
+
30
+When git asks for credentials:
31
+
32
+- **Username:** your shithub username.
33
+- **Password:** the PAT.
34
+
35
+## 3. Cache credentials
36
+
37
+Typing the PAT every push gets old. Use a credential helper:
38
+
39
+- **macOS:** `git config --global credential.helper osxkeychain`
40
+- **Windows:** `git config --global credential.helper manager`
41
+- **Linux (GNOME):** `git config --global credential.helper
42
+  /usr/share/doc/git/contrib/credential/libsecret/...`
43
+- **Anywhere, in a pinch:** `git config --global credential.helper
44
+  cache` (in-memory, default 15-minute TTL).
45
+
46
+The helper stores `(url, username, password)`; the next push to
47
+the same host reuses it.
48
+
49
+## 4. Use a CI runner
50
+
51
+In CI, set the username/password as secrets and inject them via
52
+the URL or `~/.netrc`. Use a token with the narrowest scope the
53
+job needs and a short expiration.
54
+
55
+```sh
56
+git clone https://x-access-token:${SHITHUB_PAT}@shithub.example/owner/repo.git
57
+```
58
+
59
+Because the token is in the URL, make sure your CI doesn't echo
60
+the URL into logs.
61
+
62
+## When pushes fail
63
+
64
+| Symptom                                              | Likely cause                              |
65
+|------------------------------------------------------|-------------------------------------------|
66
+| `403 Forbidden` on push                              | Token lacks `repo` write scope.           |
67
+| `401 Unauthorized` immediately                       | Wrong username, expired token, or the token was revoked. |
68
+| `protected branch hook declined`                     | Branch protection requires PR + reviews — push to a feature branch instead. |
69
+| `pre-receive hook declined: repo over quota`        | Repo size cap hit; see your operator.     |
70
+| `error: failed to push some refs … updates were rejected` | Standard git non-fast-forward — pull/rebase first. |
docs/public/user/issues.mdadded
@@ -0,0 +1,67 @@
1
+# Issues
2
+
3
+Issues track bugs, ideas, and conversations against a repository.
4
+Anyone with read access to a repo can see its issues; opening and
5
+commenting depends on the repo's settings (open to all logged-in
6
+users by default).
7
+
8
+## Opening an issue
9
+
10
+Repo → Issues → "New issue". Required:
11
+
12
+- **Title** — one line.
13
+- **Body** — markdown ([reference](./markdown.md)). Drag-and-drop
14
+  attachments upload to the repo's blob store.
15
+
16
+Optional:
17
+
18
+- **Labels** — colored tags maintainers use for triage.
19
+- **Assignees** — who's expected to handle it.
20
+- **Milestone** — group issues toward a release/target.
21
+
22
+## References
23
+
24
+shithub auto-links these in issue + comment bodies:
25
+
26
+- `#123` — issue or PR in this repo.
27
+- `owner/repo#123` — issue or PR in another repo.
28
+- `@username` — user mention; they get a notification.
29
+- `@org/team` — team mention; every member is notified.
30
+- Commit SHAs (full or 7+ chars) — link to the commit page.
31
+
32
+## Closing
33
+
34
+Three ways an issue closes:
35
+
36
+- Manually, via the "Close" button.
37
+- Via a referenced commit/PR — `Fixes #123` or `Closes #123` in a
38
+  merged PR's title or body auto-closes the issue.
39
+- Via API.
40
+
41
+Closed issues stay visible; you can reopen them.
42
+
43
+## Comments + reactions
44
+
45
+Each comment supports the same markdown as the body. Reactions
46
+(👍 👎 😄 🎉 😕 ❤️ 🚀 👀) live on every issue/comment as a way to
47
+signal agreement without adding "+1" noise.
48
+
49
+## Locking
50
+
51
+Maintainers can lock a conversation. Locked issues accept no new
52
+comments or reactions; existing content stays.
53
+
54
+## Filters and search
55
+
56
+The Issues list supports filters:
57
+
58
+- `is:open` / `is:closed`
59
+- `author:<user>`
60
+- `assignee:<user>`
61
+- `label:"good first issue"`
62
+- `milestone:"v1.0"`
63
+- `mentions:<user>`
64
+- `commenter:<user>`
65
+- Sort: newest, oldest, most commented, recently updated.
66
+
67
+Free-text after the filter narrows by title + body.
docs/public/user/markdown.mdadded
@@ -0,0 +1,112 @@
1
+# Markdown reference
2
+
3
+shithub renders user-authored markdown — issue bodies, comments,
4
+PR descriptions, READMEs — through a CommonMark + GFM parser
5
+with a UGC-safe sanitizer. The set we support is close to
6
+GitHub's, with a few deliberate omissions.
7
+
8
+## Basics
9
+
10
+```
11
+# Heading 1
12
+## Heading 2
13
+### Heading 3
14
+
15
+**bold**, *italic*, ~~strikethrough~~, `inline code`.
16
+
17
+> A blockquote.
18
+
19
+- bullet
20
+- list
21
+
22
+1. ordered
23
+2. list
24
+
25
+[link text](https://example.com)
26
+
27
+![alt text](https://example.com/image.png)
28
+
29
+---  (horizontal rule)
30
+```
31
+
32
+## Code
33
+
34
+Three-backtick fenced blocks with an optional language tag. The
35
+language drives syntax highlighting (chroma).
36
+
37
+````
38
+```go
39
+func main() {
40
+    fmt.Println("hello")
41
+}
42
+```
43
+````
44
+
45
+## Tables
46
+
47
+GFM-style:
48
+
49
+```
50
+| Column A | Column B |
51
+|----------|----------|
52
+| cell 1   | cell 2   |
53
+| cell 3   | cell 4   |
54
+```
55
+
56
+Alignment with `:`:
57
+
58
+```
59
+| Left | Center | Right |
60
+|:-----|:------:|------:|
61
+```
62
+
63
+## Task lists
64
+
65
+```
66
+- [x] done
67
+- [ ] todo
68
+```
69
+
70
+Checkboxes are clickable in issues and PRs you can edit.
71
+
72
+## References
73
+
74
+shithub auto-links these in any user-authored markdown:
75
+
76
+- `#123` — issue or PR in the current repo.
77
+- `owner/repo#123` — issue/PR in another repo.
78
+- `@username` — user mention; notifies them.
79
+- `@org/team` — team mention.
80
+- A SHA (full or 7+ chars) — commit link.
81
+
82
+## Emoji shortcodes
83
+
84
+`:rocket:` → 🚀, `:eyes:` → 👀, etc. The set matches GitHub's
85
+shortcode list.
86
+
87
+## What we don't support
88
+
89
+- **Inline HTML** — sanitized away. Use markdown alternatives.
90
+- **`<style>` / `<script>`** — sanitized away.
91
+- **Custom HTML attributes** (`onclick`, `style`, `id` on arbitrary
92
+  elements) — stripped.
93
+- **Footnotes** — not yet (planned).
94
+- **MathJax / KaTeX** — not yet (planned).
95
+- **Mermaid diagrams** — not yet (post-MVP).
96
+- **Image attributes** like `<img width="200">` — strip-and-render
97
+  without the attribute. Use markdown image syntax with a small
98
+  thumbnail upstream.
99
+
100
+## Why a sanitizer?
101
+
102
+User-authored markdown is the largest XSS surface on a forge.
103
+shithub renders through a single helper (`internal/markdown`)
104
+that runs every input through a `bluemonday` UGC policy after
105
+parsing. Anything outside the supported set is silently dropped,
106
+not preserved as escaped text.
107
+
108
+## Previewing
109
+
110
+Issue and PR forms have a "Preview" tab that renders the markdown
111
+through the exact same pipeline we use for the saved version.
112
+What you see in preview is what you'll see after submit.
docs/public/user/notifications.mdadded
@@ -0,0 +1,70 @@
1
+# Notifications
2
+
3
+shithub notifies you when something happens that needs your
4
+attention: an issue is assigned, a review is requested, a comment
5
+mentions you, a watched repo has new activity.
6
+
7
+Two delivery channels:
8
+
9
+- **In-app inbox** at `/notifications`.
10
+- **Email** to your primary email.
11
+
12
+Both are driven by the same routing rules.
13
+
14
+## Watch levels
15
+
16
+For each repo, your subscription level is one of:
17
+
18
+- **Ignore** — never notify, even on direct mentions.
19
+- **Participating only** (default for repos you've never touched)
20
+  — notify only when something concerns you directly: you're
21
+  assigned, mentioned, your comment was replied to, your PR has
22
+  a review.
23
+- **All activity** — every issue, PR, comment, push gets a
24
+  notification.
25
+- **Custom** — pick which event categories you want.
26
+
27
+Set the level from the repo header's "Watch" dropdown.
28
+
29
+## Auto-subscribe
30
+
31
+You're auto-subscribed to:
32
+
33
+- Issues + PRs you opened.
34
+- Issues + PRs you commented on (until you unsubscribe via the
35
+  "Unsubscribe" button on the thread).
36
+- Issues + PRs you're assigned to or your review is requested on.
37
+
38
+Auto-subscribe lives at the **thread** level, not the repo level —
39
+unsubscribing from one issue doesn't change anything else.
40
+
41
+## Reading the inbox
42
+
43
+The `/notifications` page lists unread + pinned threads.
44
+- **Mark as read** — collapse and remove from the unread list.
45
+- **Mark as done** — archive; comes back if there's new activity.
46
+- **Mute thread** — never notify on this thread again, even on
47
+  new mentions.
48
+
49
+Filters: by repo, by reason (mention, review-requested, assigned,
50
+etc.), and by read/unread.
51
+
52
+## Email behavior
53
+
54
+- Each notification is one email; we don't bundle (yet).
55
+- Subject: `[<repo>] <issue title> (#<n>)`.
56
+- Reply-by-email is **not supported** — replies bounce. We don't
57
+  want to be in the email-thread-state-management business.
58
+- Each email has a one-click **HMAC-signed unsubscribe link**
59
+  that moves the thread to muted without a sign-in step.
60
+
61
+## Stop emails entirely
62
+
63
+Settings → Notifications → "Email delivery: never". The in-app
64
+inbox keeps working.
65
+
66
+## How frequently we email
67
+
68
+There's no digest mode (yet). Emails go out as the events happen.
69
+If that's too much, switch the repo to "Participating only" or
70
+mute specific threads.
docs/public/user/orgs.mdadded
@@ -0,0 +1,72 @@
1
+# Organizations & teams
2
+
3
+Organizations group people and repos under a shared namespace.
4
+A repo at `acme/widget` is owned by the `acme` org, and access
5
+is managed through teams + direct collaborators.
6
+
7
+## Creating an org
8
+
9
+Account menu → "New organization". You'll need:
10
+
11
+- A name — same rules as usernames; cannot collide with an
12
+  existing user or org.
13
+- A primary email (for billing/notifications; org settings only).
14
+
15
+The creator is the first owner.
16
+
17
+## Roles
18
+
19
+- **Owner** — full admin: settings, billing, member management,
20
+  any repo. There must always be at least one owner.
21
+- **Member** — appears in the org's people list; sees public + the
22
+  private repos a team grants them.
23
+
24
+A user can be both an owner and a team member.
25
+
26
+## Inviting members
27
+
28
+Org → People → "Invite member". Type an existing username or
29
+email. The invitee gets a notification + email; they accept on a
30
+landing page and become a member immediately.
31
+
32
+Invitations expire after 7 days; resend or revoke from the same
33
+page.
34
+
35
+## Removing members
36
+
37
+Owner → click a member → "Remove". The user loses team
38
+memberships and any direct grants the org had given them. If they
39
+authored issues/PRs, those stay; if they had pending invitations,
40
+those are cancelled.
41
+
42
+## Teams
43
+
44
+Teams are how you grant repo access at scale.
45
+
46
+- **Create:** Org → Teams → "New team".
47
+- **Members:** add org members; non-members must be invited to
48
+  the org first.
49
+- **Repo access:** add repos with one of `read`, `triage`, `write`,
50
+  `maintain`, `admin`. Multiple teams can grant on the same repo;
51
+  the user's effective permission is the **maximum**.
52
+- **One-level nesting:** a team can have a parent team. Members
53
+  of a parent team are also considered members of every child
54
+  team for permission purposes.
55
+
56
+## Mentioning a team
57
+
58
+`@acme/security` in any markdown body notifies every member of
59
+that team. Useful for review requests and triage.
60
+
61
+## Org-owned repos
62
+
63
+Indistinguishable from user-owned repos in most ways; the
64
+difference is that access is governed by team grants instead of
65
+the owner being a single user.
66
+
67
+## Audit
68
+
69
+Org → Settings → Audit log surfaces org-level events: member
70
+add/remove, team create/delete, repo transferred in/out, role
71
+changes. Same per-row IP + user-agent capture as the personal
72
+audit log.
docs/public/user/personal-access-tokens.mdadded
@@ -0,0 +1,71 @@
1
+# Personal access tokens
2
+
3
+Personal access tokens (PATs) are scoped, expirable credentials
4
+you create from your account settings. They're how you
5
+authenticate to the API and to git over HTTPS.
6
+
7
+PATs are **not** passwords:
8
+
9
+- They have an expiration.
10
+- They have scopes — a token cannot do what its scopes don't grant.
11
+- They are listed in your settings with a "last used" timestamp.
12
+- They can be revoked individually without changing your password.
13
+
14
+## Scopes
15
+
16
+Pick the smallest set the consumer needs.
17
+
18
+| Scope          | What it allows                                                       |
19
+|----------------|----------------------------------------------------------------------|
20
+| `repo:read`    | Read repos you can already see (public + your private + collabs).    |
21
+| `repo`         | Above + push, manage settings on repos you own/admin.                |
22
+| `user:read`    | Read your profile + email.                                           |
23
+| `user`         | Above + edit profile, emails.                                        |
24
+| `notifications`| Read + mark-read your notification inbox.                            |
25
+| `webhooks`     | Manage webhooks on repos you own/admin.                              |
26
+| `admin:org`    | Org management (membership, teams) for orgs you admin.               |
27
+| `gist`         | Reserved for future Gists feature; non-functional today.             |
28
+
29
+Scopes only **grant**; they never elevate. A `repo` scope on your
30
+PAT cannot push to a repo you don't have write access to.
31
+
32
+## Creating a PAT
33
+
34
+Settings → Developer settings → Personal access tokens → "New
35
+token".
36
+
37
+- **Note** — what is this token for? "ci-runner staging" beats
38
+  "test1".
39
+- **Expiration** — pick the smallest tolerable; 90 days is a
40
+  reasonable default. "Never" is available but discouraged.
41
+- **Scopes** — check only what you need.
42
+
43
+The token is displayed **once**. Copy it now; we cannot show it
44
+to you again. If you lose it, revoke and re-create.
45
+
46
+## Token format
47
+
48
+Tokens are 40 characters of base32 with a `shp_` prefix:
49
+
50
+```
51
+shp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
52
+```
53
+
54
+The prefix is recognized by GitHub-style secret-scanning tools.
55
+If you accidentally publish a token, secret scanners may notify
56
+you (and us); revoke immediately.
57
+
58
+## Using a PAT
59
+
60
+- **Git over HTTPS:** username = your shithub username, password
61
+  = the PAT. See [HTTPS clone](./https.md).
62
+- **API:** `Authorization: Bearer <token>` or `Authorization:
63
+  token <token>`.
64
+
65
+## Revoking
66
+
67
+Settings → Developer settings → Personal access tokens shows every
68
+PAT on the account. Click "Revoke" — the token stops working
69
+immediately. Anything using it will get `401`.
70
+
71
+If you suspect a token leaked, revoke first and investigate after.
docs/public/user/pull-requests.mdadded
@@ -0,0 +1,78 @@
1
+# Pull requests
2
+
3
+A pull request (PR) proposes merging one branch into another.
4
+The PR is the conversation around the diff — review comments,
5
+status checks, the merge itself.
6
+
7
+## Opening a PR
8
+
9
+1. Push a branch to the repo (or to your fork).
10
+2. Visit the repo and click the "Compare & pull request" prompt
11
+   that appears, or go to Pull requests → "New pull request".
12
+3. Pick **base** (where you want to merge into) and **compare**
13
+   (your branch).
14
+4. Title + body. Markdown supported. Reference issues with
15
+   `Fixes #N` to auto-close on merge.
16
+
17
+The PR shows the file diff, commit list, and any pre-existing
18
+status checks for the head commit.
19
+
20
+## Reviewing
21
+
22
+Open a PR → "Files changed" tab.
23
+
24
+- **Comment on a line:** click the `+` in the gutter.
25
+- **Suggest a change:** in the comment, pick "Suggestion" — your
26
+  inline patch becomes a one-click commit the author can apply.
27
+- **Submit a review:** "Review changes" → choose:
28
+  - **Comment** — observations, no verdict.
29
+  - **Approve** — green checkmark, counts toward the required-
30
+    reviewer threshold (if branch protection is on).
31
+  - **Request changes** — red X, blocks merge until dismissed or
32
+    re-reviewed.
33
+
34
+A reviewer can leave many inline comments and bundle them into
35
+one review submission.
36
+
37
+## Merging
38
+
39
+Three merge methods (the maintainer picks per repo or per PR if
40
+multiple are enabled):
41
+
42
+- **Merge commit** — the classic non-fast-forward merge with a
43
+  commit that has both parents. Preserves all PR commits as-is.
44
+- **Squash and merge** — replaces every PR commit with one
45
+  squashed commit on the base branch. PR title/body becomes the
46
+  commit message.
47
+- **Rebase and merge** — replays the PR's commits onto the base.
48
+  No merge commit; linear history.
49
+
50
+shithub detects merge conflicts up front and prevents merge
51
+until the PR head is updated. Use the "Update branch" button to
52
+merge or rebase the base into your branch (the maintainer picks
53
+which strategy is allowed).
54
+
55
+## Branch protection gates
56
+
57
+If the base branch is protected, the merge button is disabled
58
+until:
59
+
60
+- Required status checks pass (CI integrations report success).
61
+- Required number of approvals collected.
62
+- "Request changes" reviews resolved (dismiss or re-review).
63
+- Conversations resolved (if "Require conversation resolution" is on).
64
+
65
+See [Branch protection & reviews](./branch-protection.md).
66
+
67
+## Draft PRs
68
+
69
+Open as draft to signal "not ready yet, but visible". Drafts
70
+cannot be merged. Convert to "Ready for review" when you want
71
+reviewers.
72
+
73
+## After merge
74
+
75
+- The head branch can be auto-deleted (account-level + per-repo
76
+  setting).
77
+- Linked issues with `Fixes #N` close automatically.
78
+- Watchers + subscribers get notifications.
docs/public/user/quickstart.mdadded
@@ -0,0 +1,69 @@
1
+# Quickstart
2
+
3
+This walks the simplest possible path: sign up, create a repo,
4
+clone it, push a commit. About five minutes.
5
+
6
+## 1. Sign up
7
+
8
+Visit `/signup`. You'll need:
9
+
10
+- A working email address (a verification link is sent there).
11
+- A username — letters, numbers, and dashes; not in our reserved
12
+  list (the form tells you if it is).
13
+- A password — 12 characters minimum and not in the common-password
14
+  list. Argon2id is used to hash; we never store the plaintext.
15
+
16
+After you submit, the verification email arrives within a minute.
17
+Click the link to activate the account. Until you do, you can sign
18
+in but most write actions are gated.
19
+
20
+## 2. Create a repository
21
+
22
+Sign in and click "New repository" (or visit `/new`).
23
+
24
+- **Name** — alphanumerics and dashes; can't collide with your
25
+  existing repos.
26
+- **Visibility** — public (anyone can read) or private (only you
27
+  + collaborators).
28
+- **Initialize** — optionally add a README, a `.gitignore` (pick
29
+  from the templates), and a license. If you initialize, the repo
30
+  has commits immediately and you can clone right away. If you
31
+  don't, the repo is empty and you'll see push instructions.
32
+
33
+## 3. Clone the repo
34
+
35
+The repo page shows a "Code" dropdown with a clone URL. For
36
+HTTPS you'll need a [personal access
37
+token](./personal-access-tokens.md) — your account password does
38
+not work for git operations.
39
+
40
+```sh
41
+git clone https://shithub.example/<your-username>/<repo>.git
42
+cd <repo>
43
+```
44
+
45
+When prompted for credentials, the username is your shithub
46
+username and the password is the PAT.
47
+
48
+## 4. Push a commit
49
+
50
+```sh
51
+echo "hello" >> README.md
52
+git add README.md
53
+git commit -m "say hello"
54
+git push origin main
55
+```
56
+
57
+Refresh the repo page — your commit appears in the history. If
58
+this is your first push, the repo's default branch is set to
59
+whatever you pushed (usually `main`).
60
+
61
+## Where to go next
62
+
63
+- [Set up SSH](./ssh.md) so you don't have to enter a token every
64
+  push.
65
+- [Open your first issue](./issues.md).
66
+- [Open a pull request](./pull-requests.md) — branch, push,
67
+  compare.
68
+- [Configure notifications](./notifications.md) so you don't get
69
+  buried.
docs/public/user/search.mdadded
@@ -0,0 +1,59 @@
1
+# Search
2
+
3
+Top-bar search opens a quick palette: type and you'll see matching
4
+repos, users, issues, and code snippets. Pressing enter takes you
5
+to the full search results page with filters.
6
+
7
+The full results page (`/search`) supports four scopes:
8
+
9
+- **Code** — file content + path matches across repos you can see.
10
+- **Repositories** — repo names, descriptions, topics.
11
+- **Issues + PRs** — across repos you can see.
12
+- **Users** — usernames, display names.
13
+
14
+## Filters and operators
15
+
16
+The search bar supports a small grammar inspired by GitHub's:
17
+
18
+| Operator                  | Effect                                                |
19
+|---------------------------|-------------------------------------------------------|
20
+| `repo:owner/name`         | Restrict to one repo.                                 |
21
+| `org:name`                | Restrict to repos owned by an org.                    |
22
+| `user:name`               | Restrict to a user's repos.                           |
23
+| `path:src/`               | Restrict by file path prefix.                         |
24
+| `extension:go`            | Restrict by file extension.                           |
25
+| `language:go`             | Restrict by detected language.                        |
26
+| `author:name`             | (issues/PRs) author.                                  |
27
+| `commenter:name`          | (issues/PRs) someone commented.                       |
28
+| `is:open` / `is:closed`   | (issues/PRs) state.                                   |
29
+| `is:pr` / `is:issue`      | Narrow to PR or issue.                                |
30
+| `label:"good first issue"`| (issues/PRs) labelled.                                |
31
+| `milestone:"v1.0"`        | (issues/PRs) milestoned.                              |
32
+| `created:2026-01-01..2026-04-01` | Date range (ISO 8601).                         |
33
+| `-foo`                    | Negate.                                               |
34
+
35
+Quoted strings are exact; unquoted strings are tokenized.
36
+
37
+## Visibility rules
38
+
39
+Search only ever returns results you would be allowed to see:
40
+
41
+- Public repos and their content/issues/PRs are searchable
42
+  without sign-in.
43
+- Private repos require you to have read access (collaborator,
44
+  team member, owner).
45
+- Search through someone else's PAT or session never reveals
46
+  results to your own queries — each user's search is scoped to
47
+  their own visibility.
48
+
49
+## Indexing latency
50
+
51
+- New repos are indexed within seconds of the first push.
52
+- Subsequent pushes update the index within a few seconds.
53
+- Issue + PR text is indexed inside the same transaction that
54
+  created the row — searchable immediately.
55
+
56
+If you push but the new content isn't appearing in search after
57
+~30 seconds, the indexer is backed up; try the file directly via
58
+the repo's tree view to confirm the data exists. Operators
59
+monitor index lag; persistent staleness is an alert.
docs/public/user/ssh.mdadded
@@ -0,0 +1,67 @@
1
+# Cloning over SSH
2
+
3
+> **Status:** the SSH transport is planned but not yet shipped.
4
+> Until it lands, use [HTTPS with a PAT](./https.md). The procedure
5
+> below is what the SSH path will look like; the underlying server
6
+> infrastructure (per-key authorization, command-locked sessions)
7
+> already exists in the codebase.
8
+
9
+SSH lets you push and pull without re-entering credentials each
10
+time. shithub authenticates each connection by SSH public key:
11
+the key fingerprint maps to a user, and the session is locked to
12
+the git protocol — you cannot get a shell.
13
+
14
+## 1. Generate an SSH key
15
+
16
+Skip this if you already have a key you're happy with
17
+(`~/.ssh/id_ed25519.pub` typically).
18
+
19
+```sh
20
+ssh-keygen -t ed25519 -C "you@example.com"
21
+```
22
+
23
+Accept the default path. Set a passphrase if you want belt-and-
24
+braces; `ssh-agent` will remember it for the session.
25
+
26
+## 2. Copy the public key
27
+
28
+```sh
29
+cat ~/.ssh/id_ed25519.pub
30
+```
31
+
32
+Copy the entire line, including the `ssh-ed25519` prefix and the
33
+trailing comment.
34
+
35
+## 3. Add the key in shithub
36
+
37
+Settings → SSH and GPG keys → "New SSH key". Paste the key, give
38
+it a label (e.g., "laptop"), save.
39
+
40
+The page shows the fingerprint shithub computed; verify it matches
41
+what `ssh-keygen -l -f ~/.ssh/id_ed25519.pub` prints locally.
42
+
43
+## 4. Test the connection
44
+
45
+```sh
46
+ssh -T git@shithub.example
47
+```
48
+
49
+You'll see a confirmation message. The `-T` disables PTY allocation;
50
+shithub's SSH service refuses TTYs anyway.
51
+
52
+## 5. Clone with SSH
53
+
54
+```sh
55
+git clone git@shithub.example:<owner>/<repo>.git
56
+```
57
+
58
+Subsequent pushes don't prompt — the agent presents the key, the
59
+server matches the fingerprint to your account, and the session
60
+is locked to `git-receive-pack` / `git-upload-pack`.
61
+
62
+## Removing or rotating a key
63
+
64
+Settings → SSH and GPG keys lists every key on the account with
65
+its last-used timestamp. Remove a key the moment a device is lost
66
+or decommissioned — the next `git push` from that device will be
67
+rejected.
docs/public/user/webhooks.mdadded
@@ -0,0 +1,118 @@
1
+# Webhooks
2
+
3
+Webhooks send HTTP POSTs to your URL when something happens in a
4
+repo (push, PR opened, issue commented, etc.). Configured at
5
+Repository → Settings → Webhooks → "Add webhook".
6
+
7
+## Configuration
8
+
9
+- **Payload URL** — the HTTPS endpoint we POST to. HTTP is
10
+  rejected on production instances.
11
+- **Content type** — `application/json` (default) or
12
+  `application/x-www-form-urlencoded`.
13
+- **Secret** — used to HMAC-sign each delivery. We strongly
14
+  recommend setting one. The secret is stored AEAD-encrypted at
15
+  rest; you cannot retrieve it after creation, only replace it.
16
+- **Events** — pick "Just push", "Send everything", or specific
17
+  events.
18
+- **Active** — toggle without deleting.
19
+
20
+## Signature verification
21
+
22
+Each delivery includes:
23
+
24
+- `X-Shithub-Event: <event-name>` — e.g., `push`, `pull_request`.
25
+- `X-Shithub-Delivery: <uuid>` — unique per delivery (idempotent).
26
+- `X-Shithub-Signature-256: sha256=<hex>` — HMAC-SHA256 of the
27
+  raw body using your configured secret.
28
+
29
+**Always verify the signature before trusting the payload.**
30
+Constant-time comparison; never compare with `==`.
31
+
32
+### Go
33
+
34
+```go
35
+func verify(body []byte, sig, secret string) bool {
36
+    sig = strings.TrimPrefix(sig, "sha256=")
37
+    mac := hmac.New(sha256.New, []byte(secret))
38
+    mac.Write(body)
39
+    expected := hex.EncodeToString(mac.Sum(nil))
40
+    return hmac.Equal([]byte(sig), []byte(expected))
41
+}
42
+```
43
+
44
+### Python
45
+
46
+```python
47
+import hmac, hashlib
48
+
49
+def verify(body: bytes, sig: str, secret: str) -> bool:
50
+    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
51
+    sig = sig.removeprefix("sha256=")
52
+    return hmac.compare_digest(sig, expected)
53
+```
54
+
55
+### Node.js
56
+
57
+```js
58
+const crypto = require("crypto");
59
+
60
+function verify(body, sig, secret) {
61
+  const expected = "sha256=" + crypto
62
+    .createHmac("sha256", secret)
63
+    .update(body)
64
+    .digest("hex");
65
+  return crypto.timingSafeEqual(
66
+    Buffer.from(sig),
67
+    Buffer.from(expected),
68
+  );
69
+}
70
+```
71
+
72
+The body must be the **raw request body**, not the parsed JSON.
73
+Frameworks that auto-parse will give you the wrong bytes.
74
+
75
+## Idempotency
76
+
77
+Use `X-Shithub-Delivery` as your idempotency key. We may retry a
78
+delivery if your endpoint returns 5xx or times out, so processing
79
+the same delivery twice should be safe in your system.
80
+
81
+## Retries
82
+
83
+A delivery is retried on:
84
+
85
+- Network error.
86
+- 5xx response.
87
+- Timeout (default 10s).
88
+
89
+Retry schedule: exponential backoff with jitter, up to ~6 retries
90
+over ~24h. After 50 consecutive failures, the webhook **auto-
91
+disables** to stop bombarding a broken endpoint. You'll see a
92
+banner on the webhook config page; flip "Active" back on once
93
+the endpoint is fixed.
94
+
95
+## Inspecting deliveries
96
+
97
+Webhook detail page → "Recent deliveries". Each row shows:
98
+
99
+- Event + delivery ID + timestamp.
100
+- Request headers + (truncated) body we sent.
101
+- Response status + headers + (truncated) body we got back.
102
+- "Redeliver" — re-sends the original payload with the same
103
+  signature.
104
+
105
+Stored bodies are capped at 32 KiB (your endpoint can accept
106
+bigger; we just don't keep more for the inspector).
107
+
108
+## SSRF defense
109
+
110
+shithub validates webhook URLs server-side: hostnames are
111
+resolved, IPs are checked against a block-list (RFC1918, link-
112
+local, loopback, multicast, 169.254.0.0/16, etc.), and the
113
+request is dialed to the resolved IP — no following CNAMEs into
114
+internal address space at delivery time.
115
+
116
+Operators of self-hosted instances can opt in to private
117
+destinations via an `AllowedHosts` list — see
118
+[self-host configuration](../self-host/configuration.md).