Files
documentation-docusaurus/references/gitea-docusaurus-delivery.md
2026-08-14 12:32:08 +00:00

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_TOKEN for gitea.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 pnpm if 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

  1. pnpm not globally available: Use npx pnpm as wrapper — corepack may fail with EACCES on permission-restricted systems.

  2. create docusaurus fails on non-empty dirs: The .git folder counts. Create in a temp dir first, then copy files into the git repo.

  3. Interactive prompt blocks: Always use --typescript flag to skip the language selection prompt.

  4. pnpm install fails first time: Run npx pnpm approve-builds @swc/core core-js to approve post-install scripts, then retry the build.

  5. Broken link on build — /blog: Docusaurus throws on build if the navbar links to /blog but no blog posts exist. Remove the blog nav item until posts exist.

  6. External links in navbar: Use href: not to: for external URLs (GitHub, Gitea). to: triggers internal routing and breaks.

  7. onBrokenMarkdownLinks deprecation: In Docusaurus v3, move this option to markdown.hooks.onBrokenMarkdownLinks — the top-level siteConfig version shows a deprecation warning.

  8. draw.io files must be co-located: .drawio files imported via !!raw-loader! must be in the same docs/ subtree as the .mdx importing them. Placing them in /static/ and importing by path does NOT work.

  9. C4 Mermaid diagrams need the theme package: C4Context / C4Container / C4Component require @docusaurus/theme-mermaid to be installed AND listed in themes: in config AND markdown.mermaid: true.

  10. Root-only pnpm workspace still needs packages: If pnpm-workspace.yaml exists, include packages: ['.'] (YAML list form is preferred). Otherwise pnpm 9 fails in CI with packages field missing or empty before Docusaurus builds.

  11. 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.

  12. 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.

  13. A custom homepage conflicts with a docs page at /: Before adding src/pages/index.tsx, move the docs intro from slug: / to a stable route such as slug: /overview. Update relative Markdown links from the new route and keep the docs sidebar pointed at the same document ID.

  14. Local search should have one theme-owned search instance: Use @easyops-cn/docusaurus-search-local as a Docusaurus theme and let the navbar render @theme/SearchBar. Do not render a second SearchBar on 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.

  15. Adding packages at a root-only pnpm workspace requires workspace intent: use pnpm add -w PACKAGE when pnpm-workspace.yaml declares packages: ['.']; otherwise pnpm rejects the add as an accidental workspace-root mutation.

  16. 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 run head_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.

  17. Authentication can change the public verification result without breaking publication: if Pages starts returning 302 to 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.

  18. 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. Derive ROUTE from 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 Kubernetes NotFound on 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.

  19. 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.

  20. 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.

  21. Changing only navbar.title leaves inconsistent page titles: update the top-level Docusaurus title as well as the visible navbar label, then verify document.title on the homepage and a docs route.

  22. 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. Follow references/multi-section-evidence-portals.md.

  23. 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.

  24. 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 id values for new generators; otherwise derive IDs deliberately and prove the first numeric-leading and ordinary records in a production build. Follow references/multi-section-evidence-portals.md.

  25. 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. Follow references/cross-linking-source-and-derived-dossiers.md.

  26. 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.

  27. 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.

  28. 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. See references/reference-inspired-documentation-portals.md.

  29. 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. Follow references/behavioral-acceptance-for-doc-portals.md.

  30. Local-search query hashing can conflict with static hosting redirects: hashed: true fetches search-index.json?_=HASH. If the static host redirects asset requests with query strings, search can remain indefinitely loading despite a valid index. Use hashed: 'filename', verify the emitted search-index-HASH.json, and exercise a real browser query under the configured baseUrl. Follow references/behavioral-acceptance-for-doc-portals.md.

  31. 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 docSidebar items carrying both docsPluginId and sidebarId. Keep a default docs instance for overview/global compatibility, then inspect every rendered section sidebar. Follow references/behavioral-acceptance-for-doc-portals.md.

  32. Renaming a Pages-backed Gitea repository is also a deployment-path migration: the publication workflow derives its destination from ${{ gitea.repository }}, while Docusaurus baseUrl, 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-rename head_sha has 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.

For documentation sites that need a showcase-style product landing page rather than docs content at /:

  1. Move the docs intro to /overview (or another explicit route).
  2. Build the homepage in src/pages/index.tsx with <Layout>, semantic sections, and CSS Modules.
  3. Keep changing registry counts generated from canonical data rather than hard-coding them in JSX.
  4. Add @easyops-cn/docusaurus-search-local under themes, set indexPages: true, and include every docs plugin route in docsRouteBasePath (for example ['/', 'clients']). Do not configure Ask AI or any external search provider when the requirement is native/offline search.
  5. 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.
  6. 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-server exists but requires a separate deployment
  • Recommended self-hosted alternative: MinIO for static file hosting (S3-compatible, bucket with public policy + mc cp in CI) or a thin Nginx/Caddy container
  • MinIO caveat: no clean-URL routing natively → add a reverse proxy or use trailingSlash: true in 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-common is a transitive dep under pnpm — add it explicitly: npx pnpm add @docusaurus/theme-common
  • shadcn v2+ requires server-only package: 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)

  1. "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.

  2. 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.pub must 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:

  1. Start with authoritative coordinating bodies, registries, and maintained public directories rather than claiming exhaustive participant coverage.
  2. Separate human-readable address-book/, canonical data/, methodology/findings in docs/, unverified work in research/, and provenance policy in sources/.
  3. Use a controlled functional taxonomy; avoid imposing a false hierarchy on decentralized ecosystems.
  4. Include a dependency-free validator plus make validate and make summary targets.
  5. Document source verification dates, evidence status, coverage limitations, and public-contact safety.
  6. Run git diff --cached --check before 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 exists
  • npx 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 example
  • templates/gitea-actions-build.yml — CI workflow template
  • references/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 structure
  • references/go-monorepo-conventions.md — Go workspace monorepo layout, standard libraries, code conventions for SKIC apps
  • references/migrating-existing-markdown-corpora.md — preserve canonical corpora while generating MDX-safe docs, stable routes, raw artifacts, and end-to-end Pages validation
  • references/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 deployment
  • references/generated-evidence-registries.md — generate conservative cross-client registries with stable identity, separate docs plugins, flat sidebars, route/count validation, and auth-aware Pages verification
  • references/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 deployment
  • references/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 rules
  • references/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 verification
  • references/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