Git hosting and a container registry in one Rust binary (axum + Astro)
Docs: overview and ig CLI reference
2 files changed, +284 -0
+193-0frontend/src/pages/docs/cli.astro
| @@ -0,0 +1,193 @@ | ||
| 1 | +--- | |
| 2 | +import Layout from "../../layouts/Layout.astro"; | |
| 3 | + | |
| 4 | +type Command = [usage: string, description: string]; | |
| 5 | +type Group = { id: string; title: string; intro?: string; commands: Command[] }; | |
| 6 | + | |
| 7 | +const groups: Group[] = [ | |
| 8 | + { | |
| 9 | + id: "auth", | |
| 10 | + title: "Signing in", | |
| 11 | + intro: "ig keeps one token per server in ~/.config/irongit/config.toml (mode 0600).", | |
| 12 | + commands: [ | |
| 13 | + ["ig login --host URL", "Sign in through the browser with a one-time code, save the token, and set up the git and docker credential helpers."], | |
| 14 | + ["ig login --with-token", "Read an existing personal access token from stdin instead (for CI and servers without a browser)."], | |
| 15 | + ["ig login --skip-setup", "Sign in without touching ~/.gitconfig or ~/.docker/config.json."], | |
| 16 | + ["ig logout", "Forget the token for the current server and revoke it there."], | |
| 17 | + ["ig auth status", "Which servers you are signed in to, as whom, with which scopes, and whether the helpers are set up."], | |
| 18 | + ["ig auth token", "Print the current token, e.g. for curl."], | |
| 19 | + ["ig auth setup-git", "Make git ask ig for credentials for this server (HTTPS clone, fetch, push)."], | |
| 20 | + ["ig auth setup-docker", "Point docker at docker-credential-ig for this server's registry."], | |
| 21 | + ], | |
| 22 | + }, | |
| 23 | + { | |
| 24 | + id: "repo", | |
| 25 | + title: "Repositories", | |
| 26 | + intro: "Wherever a repository is expected you can write owner/name, or just name for your own.", | |
| 27 | + commands: [ | |
| 28 | + ["ig repo create NAME [--org ORG] [--public] [-d TEXT]", "Create a repository. Private unless you pass --public."], | |
| 29 | + ["ig repo list [OWNER]", "Repositories of a user or organization that you can see (default: yours)."], | |
| 30 | + ["ig repo view REPO", "Clone URLs, default branch, size, last push and your access level."], | |
| 31 | + ["ig repo clone REPO [DIR] [--ssh]", "Clone over HTTPS with ig's credentials, or over SSH with your key."], | |
| 32 | + ["ig repo visibility REPO public|private", "Change who can see and clone the code. Images keep their own setting."], | |
| 33 | + ["ig repo edit REPO [-d TEXT] [--default-branch B] [--archive|--unarchive]", "Change the description or default branch, or make the repository read-only."], | |
| 34 | + ["ig repo delete REPO --yes", "Delete the repository and its history."], | |
| 35 | + ["ig repo collab list REPO", "People with access besides the owner."], | |
| 36 | + ["ig repo collab add REPO USER [--permission read|write|admin]", "Give someone access (default write)."], | |
| 37 | + ["ig repo collab remove REPO USER", "Take access away. Collaborators can also remove themselves."], | |
| 38 | + ], | |
| 39 | + }, | |
| 40 | + { | |
| 41 | + id: "image", | |
| 42 | + title: "Container images", | |
| 43 | + intro: "Images are named owner/name or owner/path/name. A registry host prefix and :tag are accepted and ignored where they don't apply.", | |
| 44 | + commands: [ | |
| 45 | + ["ig image list [OWNER]", "Images of a user or organization (default: yours) with tag and pull counts."], | |
| 46 | + ["ig image tags IMAGE", "Tags with digests and sizes."], | |
| 47 | + ["ig image visibility IMAGE public|private", "Allow anonymous pulls, or require a token. Independent of any linked repository."], | |
| 48 | + ["ig image delete IMAGE:TAG --yes", "Delete one tag."], | |
| 49 | + ["ig image delete IMAGE --yes", "Delete the image and every tag. Unused layers are cleaned up afterwards."], | |
| 50 | + ], | |
| 51 | + }, | |
| 52 | + { | |
| 53 | + id: "ssh", | |
| 54 | + title: "SSH keys", | |
| 55 | + commands: [ | |
| 56 | + ["ig ssh-key add [FILE] [--title T]", "Upload a public key (default ~/.ssh/id_ed25519.pub, then id_ecdsa.pub, id_rsa.pub)."], | |
| 57 | + ["ig ssh-key list", "Your keys with fingerprints and when each was last used."], | |
| 58 | + ["ig ssh-key remove ID", "Delete a key."], | |
| 59 | + ], | |
| 60 | + }, | |
| 61 | + { | |
| 62 | + id: "token", | |
| 63 | + title: "Access tokens", | |
| 64 | + intro: "Scopes: repo (git and repositories), packages (images), user (profile, keys, tokens, organizations), admin (site admins only). A token can only create tokens with scopes it has itself.", | |
| 65 | + commands: [ | |
| 66 | + ["ig token create NAME [--scopes repo,packages] [--expires-days N]", "Create a token. The secret is printed once."], | |
| 67 | + ["ig token list", "Your tokens, their scopes and last use."], | |
| 68 | + ["ig token revoke ID", "Revoke a token immediately."], | |
| 69 | + ], | |
| 70 | + }, | |
| 71 | + { | |
| 72 | + id: "org", | |
| 73 | + title: "Organizations", | |
| 74 | + intro: "Owners manage members and everything the organization owns; members can create and push to its repositories and images.", | |
| 75 | + commands: [ | |
| 76 | + ["ig org create NAME [--display-name TEXT]", "Create an organization with you as its owner."], | |
| 77 | + ["ig org list", "Organizations you belong to and your role."], | |
| 78 | + ["ig org members ORG", "Who is in it."], | |
| 79 | + ["ig org add ORG USER [--role member|owner]", "Add someone or change their role."], | |
| 80 | + ["ig org remove ORG USER", "Remove someone. You can always leave; the last owner cannot."], | |
| 81 | + ], | |
| 82 | + }, | |
| 83 | + { | |
| 84 | + id: "other", | |
| 85 | + title: "Everything else", | |
| 86 | + commands: [ | |
| 87 | + ["ig api METHOD PATH [-d JSON|-]", "Call the JSON API directly, e.g. ig api GET user or ig api PATCH repos/you/app -d '{\"description\":\"x\"}'."], | |
| 88 | + ["ig upgrade [--check] [--force]", "Replace ig with the newest release from the server, verified by SHA-256."], | |
| 89 | + ["ig --json ...", "Machine-readable output for list and view commands."], | |
| 90 | + ["ig --version", "The installed version."], | |
| 91 | + ], | |
| 92 | + }, | |
| 93 | +]; | |
| 94 | + | |
| 95 | +const env: Command[] = [ | |
| 96 | + ["IG_HOST", "Server to use instead of the saved default (same as --host)."], | |
| 97 | + ["IG_TOKEN", "Token to use instead of the saved one, handy in CI."], | |
| 98 | + ["IG_CONFIG_DIR", "Directory holding config.toml (default ~/.config/irongit)."], | |
| 99 | + ["IG_INSTALL_DIR", "Where install.sh puts ig (default ~/.local/bin)."], | |
| 100 | + ["NO_COLOR", "Disable colored output."], | |
| 101 | +]; | |
| 102 | +--- | |
| 103 | + | |
| 104 | +<Layout title="ig CLI · irongit" description="Install and use ig, the irongit command line tool: login, repositories, images, SSH keys, tokens and organizations."> | |
| 105 | + <div class="grid gap-8 md:grid-cols-[170px_1fr]"> | |
| 106 | + <nav class="hidden text-[13px] md:block"> | |
| 107 | + <div class="sticky top-4 space-y-0.5"> | |
| 108 | + <a href="/docs" class="block py-0.5 text-ink-dim">Docs</a> | |
| 109 | + <a href="#install" class="block py-0.5 text-ink-dim">Install</a> | |
| 110 | + <a href="#login" class="block py-0.5 text-ink-dim">First login</a> | |
| 111 | + {groups.map((g) => <a href={`#${g.id}`} class="block py-0.5 text-ink-dim">{g.title}</a>)} | |
| 112 | + <a href="#env" class="block py-0.5 text-ink-dim">Environment</a> | |
| 113 | + </div> | |
| 114 | + </nav> | |
| 115 | + | |
| 116 | + <div class="min-w-0"> | |
| 117 | + <p class="font-mono text-xs tracking-widest text-ember uppercase">Command line</p> | |
| 118 | + <h1 class="mt-2 text-2xl font-semibold">ig</h1> | |
| 119 | + <p class="mt-2 max-w-prose text-ink-dim"> | |
| 120 | + One static binary for x86_64 Linux. It signs you in, creates and manages repositories and images, and acts as the | |
| 121 | + credential helper for both git and docker, so you never paste a token into either. | |
| 122 | + </p> | |
| 123 | + | |
| 124 | + <h2 id="install" class="mt-8 text-[15px] font-semibold">Install</h2> | |
| 125 | + <div class="box mt-2 overflow-hidden"> | |
| 126 | + <pre class="overflow-x-auto bg-surface-sunken p-3 font-mono text-[12.5px] leading-6">curl -fsSL <span class="host-url">https://this-site</span>/install.sh | sh</pre> | |
| 127 | + </div> | |
| 128 | + <p class="mt-2 text-[13px] text-ink-dim"> | |
| 129 | + The script downloads the newest release, checks its SHA-256, installs it to <code>~/.local/bin/ig</code> and adds the | |
| 130 | + {" "}<code>docker-credential-ig</code> link docker needs. Prefer to do it by hand? Download | |
| 131 | + {" "}<a href="/download/ig" data-track="docs_cli_download_clicked"><code>/download/ig</code></a>, make it executable and put it on | |
| 132 | + your PATH. Later, <code>ig upgrade</code> keeps it current. | |
| 133 | + </p> | |
| 134 | + | |
| 135 | + <h2 id="login" class="mt-8 text-[15px] font-semibold">First login</h2> | |
| 136 | + <div class="box mt-2 overflow-hidden"> | |
| 137 | + <pre class="overflow-x-auto bg-surface-sunken p-3 font-mono text-[12.5px] leading-6"><span class="text-ink-faint">$</span> ig login --host <span class="host-url">https://this-site</span> | |
| 138 | +First copy your one-time code: BCDF-GHJK | |
| 139 | +Then open <span class="host-url">https://this-site</span>/login/device?code=BCDF-GHJK and approve it. | |
| 140 | +Waiting for approval... | |
| 141 | +✓ Logged in to <span class="host-url">https://this-site</span> as you | |
| 142 | +✓ git uses ig for <span class="host-url">https://this-site</span> credentials | |
| 143 | +✓ docker uses ig for <span class="host">this-site</span></pre> | |
| 144 | + </div> | |
| 145 | + <p class="mt-2 text-[13px] text-ink-dim"> | |
| 146 | + The browser page shows which machine is asking and what the token can do. After you approve, <code>git clone</code>, | |
| 147 | + {" "}<code>git push</code>, <code>docker push</code> and <code>docker pull</code> against this server just work. On a machine without a | |
| 148 | + browser, create a token in Settings and run <code>ig login --with-token < token.txt</code>. | |
| 149 | + </p> | |
| 150 | + | |
| 151 | + { | |
| 152 | + groups.map((g) => ( | |
| 153 | + <section> | |
| 154 | + <h2 id={g.id} class="mt-8 text-[15px] font-semibold"> | |
| 155 | + {g.title} | |
| 156 | + </h2> | |
| 157 | + {g.intro && <p class="mt-1 text-[13px] text-ink-dim">{g.intro}</p>} | |
| 158 | + <div class="box mt-2 divide-y divide-edge"> | |
| 159 | + {g.commands.map(([usage, description]) => ( | |
| 160 | + <div class="grid gap-1 px-3 py-2 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] md:gap-4"> | |
| 161 | + <code class="text-[12.5px] break-words text-ink">{usage}</code> | |
| 162 | + <span class="text-[13px] text-ink-dim">{description}</span> | |
| 163 | + </div> | |
| 164 | + ))} | |
| 165 | + </div> | |
| 166 | + </section> | |
| 167 | + )) | |
| 168 | + } | |
| 169 | + | |
| 170 | + <h2 id="env" class="mt-8 text-[15px] font-semibold">Environment</h2> | |
| 171 | + <div class="box mt-2 divide-y divide-edge"> | |
| 172 | + { | |
| 173 | + env.map(([name, description]) => ( | |
| 174 | + <div class="grid gap-1 px-3 py-2 md:grid-cols-[180px_1fr] md:gap-4"> | |
| 175 | + <code class="text-[12.5px] text-ink">{name}</code> | |
| 176 | + <span class="text-[13px] text-ink-dim">{description}</span> | |
| 177 | + </div> | |
| 178 | + )) | |
| 179 | + } | |
| 180 | + </div> | |
| 181 | + <p class="mt-4 text-[13px] text-ink-dim"> | |
| 182 | + Every command exits non-zero on failure and prints the server's reason, so ig is safe to use in scripts. Add | |
| 183 | + {" "}<code>--json</code> to get output you can pipe to <code>jq</code>. | |
| 184 | + </p> | |
| 185 | + </div> | |
| 186 | + </div> | |
| 187 | +</Layout> | |
| 188 | + | |
| 189 | +<script> | |
| 190 | + const origin = `${location.protocol}//${location.host}`; | |
| 191 | + document.querySelectorAll(".host-url").forEach((el) => (el.textContent = origin)); | |
| 192 | + document.querySelectorAll(".host").forEach((el) => (el.textContent = location.host)); | |
| 193 | +</script> |
+91-0frontend/src/pages/docs/index.astro
| @@ -0,0 +1,91 @@ | ||
| 1 | +--- | |
| 2 | +import Layout from "../../layouts/Layout.astro"; | |
| 3 | + | |
| 4 | +const sections = [ | |
| 5 | + { | |
| 6 | + href: "/docs/cli", | |
| 7 | + title: "The ig command line tool", | |
| 8 | + body: "Install ig, sign in from the terminal, and manage repositories, images, SSH keys, tokens and organizations.", | |
| 9 | + }, | |
| 10 | + { | |
| 11 | + href: "/docs/git", | |
| 12 | + title: "Git: HTTPS, SSH and LFS", | |
| 13 | + body: "Clone URLs, SSH keys, file size limits, storage quotas and storing large files with Git LFS.", | |
| 14 | + }, | |
| 15 | + { | |
| 16 | + href: "/docs/registry", | |
| 17 | + title: "Container registry", | |
| 18 | + body: "Push and pull Docker images, make images public or private independently of code, and clean up tags.", | |
| 19 | + }, | |
| 20 | +]; | |
| 21 | +--- | |
| 22 | + | |
| 23 | +<Layout title="Docs · irongit" description="How to use irongit: the ig CLI, git over HTTPS and SSH, Git LFS and the container registry."> | |
| 24 | + <div class="grid gap-8 md:grid-cols-[1fr_300px]"> | |
| 25 | + <div> | |
| 26 | + <p class="font-mono text-xs tracking-widest text-ember uppercase">Documentation</p> | |
| 27 | + <h1 class="mt-2 text-2xl font-semibold">Using irongit</h1> | |
| 28 | + <p class="mt-2 max-w-prose text-ink-dim"> | |
| 29 | + irongit hosts git repositories and container images. Everything in the web UI is also available from the | |
| 30 | + {" "}<a href="/docs/cli"><code>ig</code></a> command line tool. | |
| 31 | + </p> | |
| 32 | + | |
| 33 | + <h2 class="mt-8 text-[15px] font-semibold">Quick start</h2> | |
| 34 | + <div class="box mt-2 overflow-hidden"> | |
| 35 | + <div class="box-head text-ink-dim">terminal</div> | |
| 36 | + <pre class="overflow-x-auto bg-surface-sunken p-3 font-mono text-[12.5px] leading-6"><span class="text-ink-faint"># 1. install ig (x86_64 Linux)</span> | |
| 37 | +curl -fsSL <span class="host-url">https://this-site</span>/install.sh | sh | |
| 38 | + | |
| 39 | +<span class="text-ink-faint"># 2. sign in; this also sets up git and docker credentials</span> | |
| 40 | +ig login --host <span class="host-url">https://this-site</span> | |
| 41 | + | |
| 42 | +<span class="text-ink-faint"># 3. create a repository and push to it</span> | |
| 43 | +ig repo create hello --private | |
| 44 | +git remote add origin <span class="host-url">https://this-site</span>/you/hello.git | |
| 45 | +git push -u origin main | |
| 46 | + | |
| 47 | +<span class="text-ink-faint"># 4. push an image; its visibility is separate from the repo's</span> | |
| 48 | +docker push <span class="host">this-site</span>/you/hello:1.0 | |
| 49 | +ig image visibility you/hello public</pre> | |
| 50 | + </div> | |
| 51 | + | |
| 52 | + <h2 class="mt-8 text-[15px] font-semibold">Guides</h2> | |
| 53 | + <div class="mt-2 grid gap-px sm:grid-cols-2"> | |
| 54 | + { | |
| 55 | + sections.map((s) => ( | |
| 56 | + <a href={s.href} class="block border border-edge p-3 text-ink no-underline hover:bg-surface-hover hover:no-underline"> | |
| 57 | + <div class="font-semibold text-accent">{s.title}</div> | |
| 58 | + <div class="mt-1 text-[13px] text-ink-dim">{s.body}</div> | |
| 59 | + </a> | |
| 60 | + )) | |
| 61 | + } | |
| 62 | + </div> | |
| 63 | + </div> | |
| 64 | + | |
| 65 | + <aside class="space-y-3 text-[13px]"> | |
| 66 | + <div class="box"> | |
| 67 | + <div class="box-head font-semibold">Credentials</div> | |
| 68 | + <div class="space-y-2 p-3 text-ink-dim"> | |
| 69 | + <p> | |
| 70 | + Git, docker and the API take a <strong class="text-ink">personal access token</strong>, never your password. | |
| 71 | + {" "}<code>ig login</code> creates one for you; create others under Settings or with <code>ig token create</code>. | |
| 72 | + </p> | |
| 73 | + <p>Tokens carry scopes: <code>repo</code>, <code>packages</code>, <code>user</code> and, for admins, <code>admin</code>.</p> | |
| 74 | + </div> | |
| 75 | + </div> | |
| 76 | + <div class="box"> | |
| 77 | + <div class="box-head font-semibold">API</div> | |
| 78 | + <div class="space-y-2 p-3 text-ink-dim"> | |
| 79 | + <p>JSON under <code>/api/v1</code> with <code>Authorization: Bearer igp_...</code>.</p> | |
| 80 | + <p>Try it with <code>ig api GET user</code>.</p> | |
| 81 | + </div> | |
| 82 | + </div> | |
| 83 | + </aside> | |
| 84 | + </div> | |
| 85 | +</Layout> | |
| 86 | + | |
| 87 | +<script> | |
| 88 | + const origin = `${location.protocol}//${location.host}`; | |
| 89 | + document.querySelectorAll(".host-url").forEach((el) => (el.textContent = origin)); | |
| 90 | + document.querySelectorAll(".host").forEach((el) => (el.textContent = location.host)); | |
| 91 | +</script> |