irongit

Git hosting and a container registry in one Rust binary (axum + Astro)

Docs: overview and ig CLI reference

huncholanehuncholaneauthored
parent b229bcccommit 9900f162bbd3bb60a09bff4cb6ba2bb2f06d9e7aBrowse files

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 &lt; 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>