35 KiB
Gitea and Docusaurus Delivery
Set up Gitea organizations, repositories, and Docusaurus documentation sites with pnpm and Gitea Actions CI/CD pipelines.
Trigger Conditions
- User asks to create a Gitea organization or repo
- User wants a documentation site (Docusaurus)
- User needs CI/CD for docs on Gitea
- Any project scaffolding on gitea.lego-cloud.eu
- User asks about Gitea Pages / static hosting for docs
- User wants to convert an established Markdown, evidence, registry, or research repository into Docusaurus without losing canonical paths or provenance
Prerequisites
- Gitea API token from Bitwarden Secrets Manager as
HL_V1_GITEA_ACCESS_TOKEN, injected into the Hermes gateway environment at startup - Never use
GITHUB_TOKENforgitea.lego-cloud.eu; if the Gitea variable is absent, restart the gateway rather than copying it into/opt/data/.env - SSH key configured for
ssh://git@gitea.lego-cloud.eu:30009/ - Node.js 18+ available
- pnpm available (via
npx pnpmif not globally installed)
1. Gitea API Operations
Create Organization
: "${HL_V1_GITEA_ACCESS_TOKEN:?restart the Hermes gateway to load the Bitwarden Gitea token}"
curl -s -X POST "https://gitea.lego-cloud.eu/api/v1/orgs" \
-H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "org-name",
"full_name": "Display Name",
"description": "Description",
"visibility": "public"
}'
Create Repository in Organization
curl -s -X POST "https://gitea.lego-cloud.eu/api/v1/orgs/ORG_NAME/repos" \
-H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "repo-name",
"description": "Description",
"private": false,
"auto_init": true,
"default_branch": "main"
}'
Clone via SSH
git clone ssh://git@gitea.lego-cloud.eu:30009/ORG/REPO.git
cd REPO
git config user.email "jarvis.at.skic@gmail.com"
git config user.name "Jarvis"
2. Docusaurus Setup with pnpm
Pitfalls
-
pnpm not globally available: Use
npx pnpmas wrapper — corepack may fail with EACCES on permission-restricted systems. -
create docusaurusfails on non-empty dirs: The.gitfolder counts. Create in a temp dir first, then copy files into the git repo. -
Interactive prompt blocks: Always use
--typescriptflag to skip the language selection prompt. -
pnpm installfails first time: Runnpx pnpm approve-builds @swc/core core-jsto approve post-install scripts, then retry the build. -
Broken link on build —
/blog: Docusaurus throws on build if the navbar links to/blogbut no blog posts exist. Remove the blog nav item until posts exist. -
External links in navbar: Use
href:notto:for external URLs (GitHub, Gitea).to:triggers internal routing and breaks. -
onBrokenMarkdownLinksdeprecation: In Docusaurus v3, move this option tomarkdown.hooks.onBrokenMarkdownLinks— the top-levelsiteConfigversion shows a deprecation warning. -
draw.io files must be co-located:
.drawiofiles imported via!!raw-loader!must be in the samedocs/subtree as the.mdximporting them. Placing them in/static/and importing by path does NOT work. -
C4 Mermaid diagrams need the theme package:
C4Context/C4Container/C4Componentrequire@docusaurus/theme-mermaidto be installed AND listed inthemes:in config ANDmarkdown.mermaid: true. -
Root-only pnpm workspace still needs
packages: Ifpnpm-workspace.yamlexists, includepackages: ['.'](YAML list form is preferred). Otherwise pnpm 9 fails in CI withpackages field missing or emptybefore Docusaurus builds. -
Runner-owned build means runner-owned verification: When the user requires Gitea Actions to install/build, push the minimal change and inspect the actual Actions run rather than installing dependencies locally. Retrieve the failed job log, fix the observed step, and re-trigger until the runner and public URL both verify.
-
Do not rewrite a canonical corpus to fit Docusaurus: For established research/evidence repositories, keep canonical paths and validators intact. Generate ignored MDX-safe copies, add explicit slugs for stable numeric routes, publish byte-identical raw artifacts with checksums, and validate route/artifact counts after the real build. Follow
references/migrating-existing-markdown-corpora.md. -
A custom homepage conflicts with a docs page at
/: Before addingsrc/pages/index.tsx, move the docs intro fromslug: /to a stable route such asslug: /overview. Update relative Markdown links from the new route and keep the docs sidebar pointed at the same document ID. -
Local search should have one theme-owned search instance: Use
@easyops-cn/docusaurus-search-localas a Docusaurus theme and let the navbar render@theme/SearchBar. Do not render a secondSearchBaron the landing page; multiple instances can interfere with lazy index loading and result overlays. A landing-page search control should focus the navbar input or dispatch the configured keyboard shortcut instead. -
Adding packages at a root-only pnpm workspace requires workspace intent: use
pnpm add -w PACKAGEwhenpnpm-workspace.yamldeclarespackages: ['.']; otherwise pnpm rejects the add as an accidental workspace-root mutation. -
An unchanged public page may mean Actions never triggered—not that Actions failed: first compare local
git status,HEAD,origin/main, and the newest Actions runhead_sha. If the implementation is still uncommitted or unpushed and the latest run points to the previous commit, commit/push before debugging the runner. After success, verify a distinctive new content marker with a cache-busting query rather than relying on a possibly cached browser tab. -
Authentication can change the public verification result without breaking publication: if Pages starts returning
302to an OAuth start path, check another hosted site and the ingress/auth rollout before blaming the new Docusaurus build. Treat the configured auth redirect as expected, then verify content through an authorized browser session or the internal Pages backend. Do not change authentication to make a health check pass. -
Verify a protected Pages deployment through the Kubernetes service proxy when appropriate: from an authorized MicroK8s control-plane node,
microk8s kubectl get --raw '/api/v1/namespaces/NAMESPACE/services/http:SERVICE:PORT/proxy/ORG/REPO/ROUTE/?verify=COMMIT'exercises the deployed service without depending on host-side cluster DNS or embedding a raw ClusterIP. DeriveROUTEfrom the built output or generated registry slug—not from an entity ID, system ID, or guessed short path—and preserve the route's trailing slash when the static-site build emits directory indexes. A KubernetesNotFoundon a guessed route is a route-discovery failure, not evidence that publication failed. Check the response body for distinctive markers from changed pages, not merely byte count or HTTP success. This is an operator-side deployment check, not a way to bypass authorization for users. -
Exact text deduplication is unsafe for cross-client registries: generic labels such as “official website” or “institution portal” can describe different resources. Scope generic labels to the client, preserve every source observation, and merge only reviewed aliases for unmistakably identical named systems. Follow
references/generated-evidence-registries.md. -
Large evidence portals should be organized around user tasks, not docs-plugin internals: make entity browsing, derived-record exploration, and evidence search the primary discovery paths; keep methodology and provenance globally accessible but secondary. Link global navigation to registry/index routes rather than arbitrary first dossiers, distinguish entity/system/observation counts, and generate changing overview content from canonical data. Follow
references/research-led-evidence-registry-ia.md. -
Changing only
navbar.titleleaves inconsistent page titles: update the top-level Docusaurustitleas well as the visible navbar label, then verifydocument.titleon the homepage and a docs route. -
Multiple top-level user tasks should not share one giant sidebar: use a dedicated docs-plugin instance and sidebar for each major area. Keep one instance as the default plugin (omit
id) because global search/404 rendering may call default docs hooks; disabling the default docs plugin without replacing it can pass compilation but fail static rendering. Followreferences/multi-section-evidence-portals.md. -
Evidence tables need both readable presentation and source indexing: for dense cross-entity relationships, generate reusable React evidence cards while preserving canonical observations and claim-level links. Add a de-duplicated References index to every generated entity and derived-record page, and document hash-derived IDs as internal rather than public identifiers. Follow
references/multi-section-evidence-portals.md. -
A dedicated section can still have an unusably flat sidebar: for large entity sections, make the first level exactly Overview plus an expandable Registry category; place every ordered entity dossier under Registry. Do not leave hundreds of dossiers beside Overview at the root. When constructing sidebar document IDs from generated filenames, verify Docusaurus's numeric-prefix behavior: it may strip a leading position prefix, but preserve the filename ID when the remaining basename itself begins with a digit. Prefer explicit frontmatter
idvalues for new generators; otherwise derive IDs deliberately and prove the first numeric-leading and ordinary records in a production build. Followreferences/multi-section-evidence-portals.md. -
Source observations should navigate to their generated derived dossiers: when client/entity tables feed a generated system or supplier registry, enrich only the generated source-page copies with a dedicated dossier-link column. Display the internal derived ID as the anchor and link with the registry's canonical slug. Reuse the canonical identity result through a stable observation map such as
(source_file, source_line); do not recompute identity or match by label in the page generator. Fail generation unless every observation is linked, then verify link count, target validity, and unique-target coverage. Followreferences/cross-linking-source-and-derived-dossiers.md. -
Reference shortlists must preserve novelty across turns: track every previously suggested site. When the user asks for additional or “new” options, retain only explicitly selected references and replace all other entries; do not repeat rejected or already-presented choices.
-
Inspect the exact selected reference URL before implementation: similarly named documentation and wiki domains can have materially different entry pages. Extract hierarchy and visual principles from the user-selected URL, then create an original adaptation rather than relying on memory or cloning branded content.
-
A reference site does not override explicit shape constraints: if the chosen design uses rounded cards but the user requires sharp rectangles, preserve the layout and information hierarchy while setting all radius tokens to zero and, when the rule is global, applying
*, *::before, *::after { border-radius: 0 !important; }. Verify the compiled production CSS contains the rule. Seereferences/reference-inspired-documentation-portals.md. -
A green pipeline is not feature acceptance: before implementation, map each interactive requirement to a direct browser-level proof. A build, successful Actions run, deployed marker, visible toggle, sidebar container, or search input proves infrastructure/presence—not behavior. For theme switching, verify
data-theme, persistence, and computed colors; for dedicated sidebars, open every top-menu section and verify only its sidebar items; for local search, type a query, wait for loading to finish, confirm a visible result, and open it. Followreferences/behavioral-acceptance-for-doc-portals.md. -
Local-search query hashing can conflict with static hosting redirects:
hashed: truefetchessearch-index.json?_=HASH. If the static host redirects asset requests with query strings, search can remain indefinitely loading despite a valid index. Usehashed: 'filename', verify the emittedsearch-index-HASH.json, and exercise a real browser query under the configuredbaseUrl. Followreferences/behavioral-acceptance-for-doc-portals.md. -
A named category is not a dedicated sidebar: when top-menu sections require their own sidebars, use separate docs-plugin instances with unique IDs, paths, route bases, sidebar files, and navbar
docSidebaritems carrying bothdocsPluginIdandsidebarId. Keep a default docs instance for overview/global compatibility, then inspect every rendered section sidebar. Followreferences/behavioral-acceptance-for-doc-portals.md. -
Renaming a Pages-backed Gitea repository is also a deployment-path migration: the publication workflow derives its destination from
${{ gitea.repository }}, while DocusaurusbaseUrl,projectName, edit links, search routes, and hard-coded portal links may still carry the old slug. Inventory and update those values, preserve repository history with the Gitea rename API, update the Git remote, and push a commit after the rename so Actions republishes to/gondor-v1-gitea-pages/NEW_ORG/NEW_REPO. Verify the exact post-renamehead_shahas a successful Actions run and read back distinctive content from the renamed repository. If the browser reaches the configured OAuth boundary, treat that as route/auth evidence—not content evidence—and use an authorized session or internal backend for marker verification. Decide explicitly whether the old Pages directory should redirect, remain temporarily, or be removed; a repository rename does not clean the old static directory automatically.
Dedicated homepage and native local search
For documentation sites that need a showcase-style product landing page rather than docs content at /:
- Move the docs intro to
/overview(or another explicit route). - Build the homepage in
src/pages/index.tsxwith<Layout>, semantic sections, and CSS Modules. - Keep changing registry counts generated from canonical data rather than hard-coding them in JSX.
- Add
@easyops-cn/docusaurus-search-localunderthemes, setindexPages: true, and include every docs plugin route indocsRouteBasePath(for example['/', 'clients']). Do not configure Ask AI or any external search provider when the requirement is native/offline search. - Verify the production build emits
build/search-index.json, confirm representative terms from each docs plugin are present, then exercise a real browser query and open a result. - Treat push, successful Gitea Actions, and public Pages checks as part of completion; a local build alone is not a published result.
See references/dedicated-homepage-local-search.md for a known-good route, search, generated-summary, styling, and verification pattern.
Setup Steps
# Step 1: Create in a temp directory (avoids "directory not empty" error)
npx pnpm create docusaurus@latest /tmp/docs-temp classic --package-manager pnpm --typescript
# Step 2: Install deps in temp dir
cd /tmp/docs-temp && npx pnpm install
# Step 3: Approve build scripts (required for @swc/core and core-js)
npx pnpm approve-builds @swc/core core-js
# Step 4: Copy into git repo (preserving .git)
cp -r /tmp/docs-temp/* /path/to/git-repo/
cp /tmp/docs-temp/.gitignore /path/to/git-repo/
# Step 5: Verify build
cd /path/to/git-repo && npx pnpm build
# Step 6: Cleanup
rm -rf /tmp/docs-temp
Multi-Application Documentation Structure
For a docs site covering multiple autonomous applications:
docs/
├── intro/ # Landing page, app portfolio
├── applications/
│ └── app-name/
│ ├── index.md # Overview + constraints
│ ├── overview/
│ │ └── architecture.md # System design, tech stack
│ ├── module-1/
│ │ └── index.md # Module docs
│ └── module-n/
│ └── index.md
├── architecture/ # Platform-wide infra
│ └── overview.md
└── guides/
└── getting-started.md
3. Gitea Actions CI/CD
Workflow file goes in .gitea/workflows/build.yml:
name: Build Documentation
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Cache pnpm
uses: actions/cache@v4
with:
path: $(pnpm store path --silent)
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
- run: pnpm install --frozen-lockfile
- run: pnpm build
- uses: actions/upload-artifact@v4
if: github.ref == 'refs/heads/main'
with:
name: docusaurus-build
path: build/
Note: Gitea Actions uses the same syntax as GitHub Actions. Most actions/* work unchanged. Requires a Gitea runner registered for the org.
4. Key Gitea API Differences from GitHub
| Feature | GitHub | Gitea |
|---|---|---|
| Base URL | api.github.com |
gitea.lego-cloud.eu/api/v1 |
| Auth header | Authorization: token X |
Same |
| Create org | POST /orgs |
Same |
| Create org repo | POST /orgs/O/repos |
Same |
| SSH clone | git@github.com:O/R.git |
ssh://git@gitea.lego-cloud.eu:30009/O/R.git |
| Actions dir | .github/workflows/ |
.gitea/workflows/ |
| Runner | GitHub-hosted | Self-hosted (must register) |
5. LEGO Cloud Pages Publishing
Although Gitea core has no built-in Pages feature, LEGO Cloud provides a confirmed external Pages platform backed by the TrueNAS-hosted Actions runner and Gondor Nginx/NFS.
Docusaurus path configuration
For repository ORG/REPO:
url: 'https://pages.apps.lego-cloud.eu',
baseUrl: '/ORG/REPO/',
organizationName: 'ORG',
projectName: 'REPO',
The live URL is https://pages.apps.lego-cloud.eu/ORG/REPO/. Verify both the page and at least one generated /ORG/REPO/assets/... URL return HTTP 200.
Confirmed runner publication workflow
The runner exposes the persistent Pages dataset inside each job container at /gondor-v1-gitea-pages. Publish Docusaurus build/ contents to the repository-derived directory:
- name: Publish to Pages
if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main'
shell: bash
run: |
set -euo pipefail
ORG_REPO='${{ gitea.repository }}'
case "$ORG_REPO" in */*) ;; *) exit 1 ;; esac
case "$ORG_REPO" in *..*|/*|*/|*//* ) exit 1 ;; esac
test -f build/index.html
pages_root="/gondor-v1-gitea-pages"
mountpoint -q "$pages_root" || {
echo "$pages_root is not a persistent host mount inside the Gitea Actions job container" >&2
exit 1
}
destination="$pages_root/$ORG_REPO"
rm -rf -- "$destination"
mkdir -p "$destination"
cp -a build/. "$destination/"
Use dependency-free cp, because the standard runner image may not include rsync. The mountpoint guard prevents a false-success write into an ephemeral job filesystem.
If pnpm-workspace.yaml exists for a root-only Docusaurus project, it must contain:
packages:
- '.'
Without that field, pnpm 9 fails with packages field missing or empty.
The runner-side bind and authorization are documented in the gitea-repository-operations skill under references/truenas-actions-pages.md.
6. Gitea Core Pages Status
Gitea does NOT have a built-in Pages feature (as of v1.26.2). The GitHub issue (#23521) was a discussion that closed — actual implementation lives in a separate standalone project (gitea/pages-server) that must be deployed alongside Gitea. It is NOT baked into Gitea like GitHub Pages is baked into GitHub.
When the user asks "does Gitea have Pages?", the accurate answer is:
- No native equivalent in Gitea core
gitea/pages-serverexists but requires a separate deployment- Recommended self-hosted alternative: MinIO for static file hosting (S3-compatible, bucket with public policy +
mc cpin CI) or a thin Nginx/Caddy container - MinIO caveat: no clean-URL routing natively → add a reverse proxy or use
trailingSlash: truein Docusaurus config
6. Tailwind CSS + shadcn/ui Integration
Recommended stack — endorsed by Docusaurus maintainer team (used on docusaurus.io itself).
Install
npx pnpm add -D tailwindcss postcss autoprefixer
npx pnpm add server-only clsx tailwind-merge @radix-ui/react-slot @docusaurus/theme-common
tailwind.config.js
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx,mdx}', './docs/**/*.{md,mdx}'],
darkMode: ['class'], // ThemeSynchronizer bridges data-theme → .dark
theme: { extend: { borderRadius: { lg:'0px', md:'0px', sm:'0px' } } },
corePlugins: { preflight: false }, // MUST — prevents fighting Infima CSS
};
PostCSS plugin in docusaurus.config.ts
plugins: [
async function tailwindPlugin() {
return {
name: 'docusaurus-tailwindcss',
configurePostCSS(postcssOptions) {
postcssOptions.plugins.push(require('tailwindcss'));
postcssOptions.plugins.push(require('autoprefixer'));
return postcssOptions;
},
};
},
],
ThemeSynchronizer — bridges Docusaurus ↔ shadcn dark mode
Create src/theme/Layout/index.tsx (swizzle wrap):
import React, { useEffect } from 'react';
import Layout from '@theme-original/Layout';
import { useColorMode } from '@docusaurus/theme-common';
function ThemeSynchronizer(): null {
const { colorMode } = useColorMode();
useEffect(() => {
document.documentElement.classList.toggle('dark', colorMode === 'dark');
}, [colorMode]);
return null;
}
export default function LayoutWrapper(props) {
return <Layout {...props}><ThemeSynchronizer />{props.children}</Layout>;
}
CSS variables for custom.css (add to @layer base)
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root { --radius: 0rem; /* sharp corners */ }
.dark { --radius: 0rem; }
}
Pitfalls
@docusaurus/theme-commonis a transitive dep under pnpm — add it explicitly:npx pnpm add @docusaurus/theme-common- shadcn v2+ requires
server-onlypackage:npx pnpm add server-only - CSS variable namespaces don't conflict:
--background/--ifm-*/--tw-*are all separate - Infima can win specificity battles — use
!prefix (e.g.!text-red-500) for overrides
Sharp corners (user preference for Lego)
Set --radius: 0rem in CSS variables AND border-radius: 0 !important on all Infima selectors:
:root {
--ifm-global-radius: 0;
--ifm-code-border-radius: 0;
--ifm-pre-border-radius: 0;
--ifm-alert-border-radius: 0;
--ifm-button-border-radius: 0;
--ifm-badge-border-radius: 0;
}
.button, code, .prism-code, div[class*='codeBlockContainer'],
.admonition, .pagination-nav__link { border-radius: 0 !important; }
7. Forced Dark Navbar (both light and dark modes)
To lock the navbar to dark regardless of color mode:
.navbar {
background-color: #0a0a0a !important;
border-bottom: 1px solid #1f1f1f !important;
box-shadow: none !important;
}
.navbar__title { color: #ededed !important; }
.navbar__link { color: #a1a1a1 !important; }
.navbar__link:hover, .navbar__link--active { color: #ededed !important; background: transparent !important; }
.navbar-sidebar { background-color: #0a0a0a !important; }
The !important is required — Infima sets navbar background with high specificity.
8. Professional Platform Navigation Structure
For a multi-application platform docs site, use this top-level structure (not just a flat "Applications" list):
Introduction ← What this platform is, app portfolio table
Platform ← Infrastructure, CI/CD, networking, auth
Products ← Each application (with C4 + Modules sub-structure)
Engineering ← Monorepo structure, tech stack, conventions
Runbooks ← Operational procedures, incident response
In sidebars.ts:
const sidebars = {
docsSidebar: [
{ type: 'doc', id: 'intro/index', label: 'Introduction' },
{ type: 'category', label: 'Platform', items: ['platform/overview', 'platform/cicd'] },
{ type: 'category', label: 'Products', items: [
{ type: 'category', label: 'App Name', link: { type: 'doc', id: 'products/app/index' }, items: [
{ type: 'category', label: 'Architecture (C4)', items: ['products/app/c4/context','products/app/c4/containers','products/app/c4/components'] },
{ type: 'category', label: 'Modules', items: [...] },
]},
]},
{ type: 'category', label: 'Engineering', items: ['engineering/guidelines'] },
{ type: 'category', label: 'Runbooks', collapsed: true, items: ['runbooks/index'] },
],
};
9. Modern Docusaurus Styling
For a dark-first, modern technical docs theme (Vercel/Linear aesthetic):
Font
@import url('https://cdn.jsdelivr.net/npm/geist@1.3.0/dist/fonts/geist-sans/style.css');
@import url('https://cdn.jsdelivr.net/npm/geist@1.3.0/dist/fonts/geist-mono/style.css');
:root {
--ifm-font-family-base: 'Geist', 'Inter', system-ui, sans-serif;
--ifm-font-family-monospace: 'Geist Mono', 'Fira Code', monospace;
}
Dark mode first in config
themeConfig: {
colorMode: {
defaultMode: 'dark',
disableSwitch: false,
respectPrefersColorScheme: true,
},
// ...
}
Key --ifm- tokens for dark theme
[data-theme='dark'] {
--ifm-background-color: #0a0a0a;
--ifm-background-surface-color: #111111;
--ifm-navbar-background-color: rgba(10,10,10,0.85);
--ifm-color-primary: #818cf8; /* indigo-400 */
--ifm-color-content: #ededed;
--ifm-color-content-secondary: #a1a1a1;
}
See references/modern-docusaurus-theme.md for the full CSS file.
7. draw.io Plugin Integration
npx pnpm add docusaurus-plugin-drawio raw-loader
Config (docusaurus.config.ts):
plugins: ['docusaurus-plugin-drawio'],
Usage in .mdx files — must use raw-loader to import the .drawio file:
import Drawio from '@theme/Drawio';
import myDiagram from '!!raw-loader!./diagrams/my-diagram.drawio';
<Drawio content={myDiagram} />
Pitfall: .drawio files must be co-located with the .mdx file or in a relative path — they cannot be imported from /static/. Place them in e.g. docs/architecture/diagrams/.
8. C4 Architecture with Mermaid
Install Mermaid theme:
npx pnpm add @docusaurus/theme-mermaid
Config:
themes: ['@docusaurus/theme-mermaid'],
markdown: { mermaid: true },
themeConfig: {
mermaid: { theme: { light: 'neutral', dark: 'dark' } },
},
Mermaid supports C4 diagrams natively: C4Context, C4Container, C4Component. Use these for the C4 architecture levels. See references/c4-docusaurus.md for example syntax per level.
9. Multi-Application Docs Structure with C4
docs/
├── intro/index.md
├── applications/
│ └── app-name/
│ ├── index.md
│ ├── c4/
│ │ ├── context.md # L1 — system in the world
│ │ ├── containers.md # L2 — deployable units
│ │ └── components.md # L3 — internal structure
│ └── modules/ # Detailed module docs
├── architecture/
│ ├── overview.md
│ └── diagrams/ # .drawio files live here
└── guides/
└── getting-started.md
Repository Collaborators and Admin-Level Access
Gitea repository permissions are read, write, and admin. When a user asks to make someone an "owner of this repository," use repository-level admin permission unless they explicitly ask for ownership of the entire organization.
# Confirm both the user and repository exist first
curl -fsS -H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
"https://gitea.lego-cloud.eu/api/v1/users/USERNAME"
curl -fsS -H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
"https://gitea.lego-cloud.eu/api/v1/repos/ORG/REPO"
# Add or update the collaborator with repository-admin permission
curl -fsS -X PUT \
-H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"https://gitea.lego-cloud.eu/api/v1/repos/ORG/REPO/collaborators/USERNAME" \
-d '{"permission":"admin"}'
# Verify the effective permission; do not rely only on the PUT returning 204
curl -fsS -H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
"https://gitea.lego-cloud.eu/api/v1/repos/ORG/REPO/collaborators/USERNAME/permission"
Expected add/update response is HTTP 204; the verification response must report permission: "admin" and the requested username.
Verify a Private Repository Push
After pushing, verify both Git refs and the remote artifact:
git fetch origin
test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)"
A private repository's unauthenticated /raw/branch/... web URL may return 404. That does not prove the file is absent. Verify through the authenticated Gitea API instead:
curl -fsS \
-H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
"https://gitea.lego-cloud.eu/api/v1/repos/ORG/REPO/raw/path/to/file.md?ref=main"
Read back a distinctive heading or marker from the response, not only the HTTP status. Before pushing into an actively maintained repository, run git fetch origin and inspect divergence. If the local branch is merely behind and remote changes do not overlap local edits, a fast-forward merge can preserve the working tree:
git merge --ff-only origin/main
Otherwise commit/stash safely, then rebase or merge normally; never overwrite concurrent remote work.
Automated Git Push from Scripts / Cron
When pushing to Gitea from a non-interactive context (cron job, shell script), use the SSH key directly via GIT_SSH_COMMAND:
GIT_SSH_COMMAND="ssh -i /opt/data/home/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o BatchMode=yes" \
git push origin main
Always include -o BatchMode=yes — without it, SSH may hang waiting for interactive input when host key verification fails.
No-op pattern (safe to run every sync, only commits when something changed):
git add -A
if git diff --cached --quiet; then
echo "No changes."
exit 0
fi
CHANGED=$(git diff --cached --name-only | wc -l)
git commit -m "sync $(date -u '+%Y-%m-%d %H:%M UTC') — $CHANGED file(s)"
GIT_SSH_COMMAND="ssh -i /opt/data/home/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o BatchMode=yes" git push origin main
Pitfalls (Continued)
-
"This account is prohibited from signing in": The Gitea user tied to the API token has been marked Prohibit Login by an admin. Both API calls (
/api/v1/user) and SSH pushes fail with this message. Fix: Gitea admin panel → Site Administration → User Accounts → find the user → uncheck "Prohibit Login" → Save. URL:https://gitea.lego-cloud.eu/-/admin/users. The API token itself remains valid once the account is re-enabled — no need to regenerate it. -
SSH key "Key check failed": If SSH push gives
error: Key check failed, the SSH public key is either not registered on the Gitea account or the account is prohibited (see pitfall #10). Check: Gitea → User Settings → SSH/GPG Keys. The key at/opt/data/home/.ssh/id_ed25519.pubmust appear there.
Research and Address-Book Repositories
When the requested Gitea project maps a broad ecosystem, global infrastructure, organizations, or other changing domain, do not default to a Docusaurus application scaffold. Establish a source-backed research repository first:
- Start with authoritative coordinating bodies, registries, and maintained public directories rather than claiming exhaustive participant coverage.
- Separate human-readable
address-book/, canonicaldata/, methodology/findings indocs/, unverified work inresearch/, and provenance policy insources/. - Use a controlled functional taxonomy; avoid imposing a false hierarchy on decentralized ecosystems.
- Include a dependency-free validator plus
make validateandmake summarytargets. - Document source verification dates, evidence status, coverage limitations, and public-contact safety.
- Run
git diff --cached --checkbefore committing, then verify the pushed ref and read back a distinctive remote artifact through the authenticated Gitea API.
See references/research-address-book-repositories.md for the full repository pattern, round-1 CSV schema, research method, and validation checklist.
Verification
curl -s -H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" https://gitea.lego-cloud.eu/api/v1/user— confirms account is active (returns user JSON, not error message)curl -s -H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" https://gitea.lego-cloud.eu/api/v1/orgs/ORG— org existsnpx pnpm build— docs compile without errors- Push triggers workflow (check Actions tab in Gitea UI)
References
references/docusaurus-config-template.md— full docusaurus.config.ts exampletemplates/gitea-actions-build.yml— CI workflow templatereferences/modern-docusaurus-theme.md— dark-first CSS theme (Geist font, indigo accent, forced dark navbar, sharp corners)references/c4-docusaurus.md— C4Context/C4Container/C4Component Mermaid syntax + sidebar structurereferences/go-monorepo-conventions.md— Go workspace monorepo layout, standard libraries, code conventions for SKIC appsreferences/migrating-existing-markdown-corpora.md— preserve canonical corpora while generating MDX-safe docs, stable routes, raw artifacts, and end-to-end Pages validationreferences/dedicated-homepage-local-search.md— separate a showcase landing page from docs routes, generate live homepage metrics, configure fully local search, avoid duplicate SearchBar instances, and verify public deploymentreferences/generated-evidence-registries.md— generate conservative cross-client registries with stable identity, separate docs plugins, flat sidebars, route/count validation, and auth-aware Pages verificationreferences/research-led-evidence-registry-ia.md— organize large entity/evidence portals around user tasks, generate scope and interpretation content from canonical data, and verify the resulting IA and deploymentreferences/reference-inspired-documentation-portals.md— adapt a selected Docusaurus reference into an original portal, preserve explicit visual constraints, manage non-repeating shortlists, and verify dedicated homepage routes plus compiled sharp-corner rulesreferences/behavioral-acceptance-for-doc-portals.md— requirement-to-proof gates for real theme switching, independent section sidebars, browser-local search, static-host query redirect handling, and deployment verificationreferences/multi-section-evidence-portals.md— split major areas into independent sidebars, preserve a default docs instance, generate reusable evidence cards, document internal IDs, add per-record References indexes, and verify exact coverage