diff --git a/.agents/skills/documentation-sync/SKILL.md b/.agents/skills/documentation-sync/SKILL.md index 31d4b4852..42c562731 100644 --- a/.agents/skills/documentation-sync/SKILL.md +++ b/.agents/skills/documentation-sync/SKILL.md @@ -1,6 +1,6 @@ --- name: documentation-sync -description: Keep Homarr's Docusaurus documentation aligned with user-facing code. Use when adding or changing integrations, widgets, APIs, environment variables, CLI commands, authentication, permissions, cron jobs, settings, or UI behavior that users must understand. +description: Keep Homarr's Fumadocs documentation aligned with user-facing code. Use when adding or changing integrations, widgets, APIs, environment variables, CLI commands, authentication, permissions, cron jobs, settings, or UI behavior that users must understand. --- # Documentation Sync @@ -48,4 +48,4 @@ For a widget: 2. Keep names, defaults, paths, screenshots, links, and prerequisites consistent with code. 3. Update every affected page and remove superseded guidance. 4. Run the narrowest useful docs validation. Use `pnpm turbo build --filter=@homarr/docs` when links, MDX, generated definitions, or navigation can fail; otherwise run the docs package formatter on touched files. -5. Treat broken links and anchors as failures; Docusaurus checks them strictly. +5. Treat broken links and anchors as failures; run `pnpm --filter @homarr/docs validate:links` after the static build. diff --git a/.github/workflows/deployment-docs.yml b/.github/workflows/deployment-docs.yml index eeb53d101..22b925c81 100644 --- a/.github/workflows/deployment-docs.yml +++ b/.github/workflows/deployment-docs.yml @@ -42,7 +42,7 @@ jobs: - name: Upload pages artifact uses: actions/upload-pages-artifact@v5 with: - path: "apps/docs/build" + path: "apps/docs/out" deploy: runs-on: ubuntu-latest diff --git a/.github/workflows/docs-algolia-recrawl.yml b/.github/workflows/docs-algolia-recrawl.yml deleted file mode 100644 index 1d8bdd42d..000000000 --- a/.github/workflows/docs-algolia-recrawl.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: "[Docs] Algolia Recrawl" - -on: - workflow_run: - workflows: ["[Deployment] Documentation"] - types: [completed] - branches: [dev] - workflow_dispatch: - -permissions: - contents: read - -jobs: - recrawl: - if: >- - ${{ - (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'pull_request') - || github.event_name == 'workflow_dispatch' - }} - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v7 - - - name: Setup - uses: ./tooling/github/setup - - - name: Check Algolia crawler secrets - run: | - missing=0 - for secret in ALGOLIA_CRAWLER_ID ALGOLIA_CRAWLER_USER_ID ALGOLIA_CRAWLER_API_KEY; do - if [ -z "${!secret}" ]; then - echo "::warning::Missing repository secret: $secret" - missing=1 - fi - done - if [ "$missing" -eq 1 ]; then - echo "Add crawler secrets to enable automatic recrawls." - exit 1 - fi - env: - ALGOLIA_CRAWLER_ID: ${{ secrets.ALGOLIA_CRAWLER_ID }} - ALGOLIA_CRAWLER_USER_ID: ${{ secrets.ALGOLIA_CRAWLER_USER_ID }} - ALGOLIA_CRAWLER_API_KEY: ${{ secrets.ALGOLIA_CRAWLER_API_KEY }} - - - name: Wait for GitHub Pages to serve the new deployment - run: sleep 60 - - - name: Trigger Algolia DocSearch recrawl - uses: cssnr/algolia-crawler-action@v2 - with: - crawler_id: ${{ secrets.ALGOLIA_CRAWLER_ID }} - crawler_user_id: ${{ secrets.ALGOLIA_CRAWLER_USER_ID }} - crawler_api_key: ${{ secrets.ALGOLIA_CRAWLER_API_KEY }} - - - name: Verify search index - working-directory: apps/docs - run: pnpm verify:search diff --git a/.github/workflows/docs-update-contributors.yml b/.github/workflows/docs-update-contributors.yml index bbfbc10cb..7904f5694 100644 --- a/.github/workflows/docs-update-contributors.yml +++ b/.github/workflows/docs-update-contributors.yml @@ -33,7 +33,7 @@ jobs: - name: Commit updated contributors uses: EndBug/add-and-commit@v10 with: - add: "apps/docs/static" + add: "apps/docs/public" default_author: github_actor message: "chore(docs): update contributors" push: true diff --git a/.github/workflows/docs-update-openapi.yml b/.github/workflows/docs-update-openapi.yml index a2f1f7c30..c6bde0558 100644 --- a/.github/workflows/docs-update-openapi.yml +++ b/.github/workflows/docs-update-openapi.yml @@ -54,13 +54,13 @@ jobs: exit 1 } - cp /tmp/openapi.json apps/docs/static/api/open-api-schema.json + cp /tmp/openapi.json apps/docs/public/api/open-api-schema.json echo "OpenAPI spec updated ($(jq '.paths | length' /tmp/openapi.json) paths)" - name: Commit updated spec uses: EndBug/add-and-commit@v10 with: - add: "apps/docs/static/api/" + add: "apps/docs/public/api/" default_author: github_actor message: "chore(docs): update OpenAPI spec" push: true diff --git a/AGENTS.md b/AGENTS.md index b0e8a9f9e..8811ce0b3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ homarr/ ├── apps/ │ ├── nextjs/ # Main Next.js application (port 3000) -│ ├── docs/ # Docusaurus 3 documentation site (@homarr/docs) +│ ├── docs/ # Next.js + Fumadocs documentation site (@homarr/docs) │ ├── tasks/ # Cron-job initialization and scheduling runtime │ ├── websocket/ # Standalone tRPC WebSocket server (port 3001) │ └── workshop/ # Go/PocketBase Workshop service @@ -57,7 +57,7 @@ homarr/ ## Documentation Sync -The documentation site lives at `apps/docs/` (Docusaurus 3, `@homarr/docs`). +The documentation site lives at `apps/docs/` (Next.js + Fumadocs, `@homarr/docs`). When modifying user-facing code, you MUST also update the corresponding documentation: @@ -75,7 +75,7 @@ When modifying user-facing code, you MUST also update the corresponding document - `pnpm dev:cli -- dev` — run the developer CLI without installing a global binary - `pnpm db:seed` — seed default database data explicitly - `pnpm docker:dev:up` — start the Redis development service in the background -- `pnpm dev:docs` — Docusaurus docs site only +- `pnpm dev:docs` — Fumadocs site only - `pnpm turbo build` — build all packages - `pnpm turbo build --filter=@homarr/docs` — build docs only - `pnpm turbo typecheck` — typecheck all packages diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore index 8708641b9..b95495724 100644 --- a/apps/docs/.gitignore +++ b/apps/docs/.gitignore @@ -13,6 +13,7 @@ # next.js /.next/ /out/ +/.source/ # production /build @@ -55,4 +56,4 @@ static/data/configs # CI Stuff screenshots -test-results \ No newline at end of file +test-results diff --git a/apps/docs/AGENTS.md b/apps/docs/AGENTS.md new file mode 100644 index 000000000..643577dfa --- /dev/null +++ b/apps/docs/AGENTS.md @@ -0,0 +1,9 @@ + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/apps/docs/CLAUDE.md b/apps/docs/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/apps/docs/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/apps/docs/PRODUCT.md b/apps/docs/PRODUCT.md new file mode 100644 index 000000000..2ac8cf1da --- /dev/null +++ b/apps/docs/PRODUCT.md @@ -0,0 +1,37 @@ +# Homarr Documentation + +## Register + +product + +## Users + +People who run Homarr, administrators configuring self-hosted infrastructure, and contributors extending Homarr. They arrive with a concrete task such as installing Homarr, connecting a service, configuring a widget, managing users, or building a custom widget, and need an answer they can scan and apply quickly. + +## Product Purpose + +The documentation is the complete, authoritative operating guide for Homarr. It keeps setup steps, configuration contracts, screenshots, generated integration and widget metadata, API references, and Workshop tooling next to the application code. Success means a reader can find the right page quickly, understand the next action, copy reliable instructions, and verify the result without searching elsewhere. + +## Brand Personality + +Focused, dependable, and approachable. The writing is direct and practical. The interface should feel calm and familiar during long technical sessions while retaining Homarr's red identity. + +## Anti-references + +- Generic marketing layouts that replace Homarr's product identity with unrelated slogans or decorative conversion copy. +- Decorative dashboard aesthetics that compete with reading, including glass effects, large radii, card grids, and gratuitous motion. +- Generic generated-docs styling that obscures Homarr's identity or makes navigation feel detached from the main product. +- Dense reference portals that expose structure without giving readers a clear path through a task. + +## Design Principles + +1. Put the reader's task first: search, navigation, page outline, and the answer must remain easy to reach. +2. Preserve one source of truth: typed Homarr metadata and existing custom components stay connected to the monorepo. +3. Prefer familiar documentation patterns: the interface should disappear while a reader scans, copies, and follows instructions. +4. Make complete content feel manageable through strong hierarchy, progressive disclosure, and precise cross-links. +5. Treat machine readers as users through canonical Markdown, structured metadata, and standard LLM discovery endpoints. +6. Keep the root landing page recognizably Homarr: preserve its self-hosted dashboard promise and real product evidence while documentation routes remain task-first. + +## Accessibility & Inclusion + +Meet WCAG 2.2 AA for color contrast, keyboard operation, focus visibility, landmarks, headings, and interactive controls. Respect reduced-motion and system color-scheme preferences. Diagrams, screenshots, icons, and emoji must have meaningful text alternatives when they carry information. diff --git a/apps/docs/README.md b/apps/docs/README.md index 7646148aa..8be30b9af 100644 --- a/apps/docs/README.md +++ b/apps/docs/README.md @@ -1,112 +1,52 @@ -# Homarr Documentation (`@homarr/docs`) +# Homarr documentation (`@homarr/docs`) -The official [homarr.dev](https://homarr.dev) documentation site, built with [Docusaurus 3](https://docusaurus.io/) and living inside the Homarr monorepo. +The source for [homarr.dev](https://homarr.dev), built with Next.js and Fumadocs UI inside the Homarr monorepo. -## Why it's in the monorepo +## Develop -Having documentation alongside the application code enables: - -- **Two-way linking** — the docs app imports from `@homarr/definitions` for type-safe integration/widget metadata, and the main app references doc paths via generated sitemap types -- **Atomic PRs** — code changes and their documentation updates ship in the same pull request -- **Shared tooling** — same Node version, pnpm catalog, oxlint/oxfmt, CI pipeline -- **Build-time validation** — Docusaurus strict mode catches broken links and references at build time - -## Development - -From the **monorepo root**: +From the repository root: ```bash -# Start the docs dev server (port 3003) pnpm dev:docs +``` -# Or from within this directory -pnpm dev +The site runs on `http://127.0.0.1:3003`. To use a local Workshop API: -# Point the docs at a local PocketBase instance +```bash WORKSHOP_API_URL=http://127.0.0.1:8090 pnpm dev:docs ``` -The standalone docs use `WORKSHOP_API_URL` for PocketBase and `HOMARR_WEBSITE_URL` for the public site origin. The old -`WORKSHOP_URL` name is a deprecated one-release alias. - -## Build +## Validate ```bash -# Build docs only -pnpm turbo build --filter=@homarr/docs - -# Build everything (docs + main app + tasks + websocket) -pnpm build +pnpm --filter @homarr/docs typecheck +pnpm --filter @homarr/docs build +pnpm --filter @homarr/docs validate:links ``` -## Lint & Format +The static export is written to `out/`. The build validates that every registered integration and widget has a docs +page. The link check validates internal Markdown and MDX links against that export. -Uses oxlint and oxfmt (same as the rest of the monorepo): +## Write content -```bash -pnpm lint # oxlint -pnpm format # oxfmt (check) -pnpm format:fix # oxfmt (fix) -``` +Documentation lives in `docs/`; project posts live in `blog/`. Navigation is controlled by the nearest `meta.json`. +Integration and widget folders pair typed `index.ts` metadata with `index.mdx` content. -## Typecheck +Use standard Markdown and MDX. Shared components live in `src/components/`. Mermaid fences, tabs, callouts, zoomable +images, and syntax-highlighted code are supported by the Fumadocs pipeline. -```bash -pnpm typecheck -``` +When a user-facing change affects setup or behavior, update the matching page in the same pull request. See the +[documentation-sync skill](../../.agents/skills/documentation-sync/SKILL.md) for the code-to-content map. -## Content Structure +## Published formats -``` -docs/ -├── getting-started/ # Installation guides, glossary, prerequisites -├── management/ # Boards, apps, users, integrations, settings, tasks, API -├── integrations/ # 56 integration guides (one folder each) -├── widgets/ # 43 widget guides (one folder each) -├── advanced/ # SSO, env vars, proxy, styling, CLI, development -└── community/ # FAQ, donate, license, translations, get-in-touch -``` +- `/docs` — searchable documentation with typed navigation +- `/api-reference` — interactive reference generated from the OpenAPI schema +- `/llms.txt` — compact documentation and blog index for agents +- `/llms-full.txt` — complete processed Markdown corpus +- `/llms.mdx/docs/.../content.md` — raw Markdown for each docs page +- `/llms.mdx/blog/.../content.md` — raw Markdown for each blog post +- `/blog/rss.xml` — RSS feed for project posts -Each integration/widget doc follows a consistent pattern: - -- `index.ts` — typed metadata (`IntegrationDefinition` / `WidgetDefinition`) -- `index.mdx` — content using shared React components (`IntegrationHeader`, `WidgetHeader`, etc.) - -## Importing from homarr packages - -The docs app has `@homarr/definitions` as a workspace dependency. You can import integration kinds, widget kinds, and other definitions directly: - -```typescript -import { IntegrationKind } from "@homarr/definitions"; -``` - -This enables type-safe references to homarr domain concepts within documentation code. - -## Search (Algolia DocSearch) - -Search uses the `Docusaurus` Algolia index (crawler `cd77a285-2756-4557-bf21-ee703748df15`). A separate `markdown` index is built for LLM markdown indexing. - -Crawler reference config: [`docsearch.config.js`](docsearch.config.js). - -**Verify search health:** - -```bash -pnpm verify:search -``` - -## Tech Stack - -| Layer | Technology | -| -------------- | ------------------------------------------- | -| Site generator | Docusaurus 3 (`@docusaurus/preset-classic`) | -| Styling | Tailwind CSS 4 + Docusaurus Infima | -| Icons | `@tabler/icons-react` | -| Diagrams | Mermaid (`@docusaurus/theme-mermaid`) | -| Search | Algolia DocSearch | -| Analytics | PostHog | -| Charts | @nivo/line | - -## Contributing - -When a Homarr change affects user-facing behavior, update the corresponding documentation in this app. See the -[documentation-sync skill](../../.agents/skills/documentation-sync/SKILL.md) for the code-to-documentation mapping. +Every docs page and blog post includes copy-Markdown and view-source actions. Search is generated at build time and +runs locally in the browser; it does not depend on an external crawler. diff --git a/apps/docs/blog/2022/06-22-documentation/index.mdx b/apps/docs/blog/2022/06-22-documentation/index.mdx index c344f32ea..b7a4bc3b8 100644 --- a/apps/docs/blog/2022/06-22-documentation/index.mdx +++ b/apps/docs/blog/2022/06-22-documentation/index.mdx @@ -1,6 +1,7 @@ --- slug: documentation-migration title: Migration of Documentation +date: 2022-06-22 authors: - ajnart - manuel-rw @@ -12,7 +13,7 @@ The documentation has been re-written partly and includes now many animations, g We are still figuring things out and highly depend on your feedback. - +{/* Historical excerpt boundary. */} ## Why we migrated @@ -32,4 +33,4 @@ After we've reviewed your changes, we'll merge your PR and this documentation wi ## TL;DR -The documentation on GitHub Wiki will soon disappear. It will be replaced with this Docusaurus-powered website. You'll find much more information here with higher detail and searchability. \ No newline at end of file +The documentation on GitHub Wiki will soon disappear. It will be replaced with this Docusaurus-powered website. You'll find much more information here with higher detail and searchability. diff --git a/apps/docs/blog/2022/08-28-translation/index.mdx b/apps/docs/blog/2022/08-28-translation/index.mdx index 4af9cbd77..dbf133efc 100644 --- a/apps/docs/blog/2022/08-28-translation/index.mdx +++ b/apps/docs/blog/2022/08-28-translation/index.mdx @@ -1,6 +1,7 @@ --- slug: translations title: Help us make Homarr multilingual +date: 2022-08-28 authors: - ajnart - manuel-rw @@ -13,8 +14,8 @@ We've been working hard to offer Homarr in many languages. The next update of Ho ![](img/homarr-we-need-you.webp)
Yes, we need **YOU**! We want to make Homarr accessible to as many people as possible. Thus, we need to translate all content into many different languages. We need your help for that! [You can help us now to translate Homarr into as many languages as possible](/docs/community/translations). - +{/* Historical excerpt boundary. */} ### About global translations / translations per user -We are aware that many people would like to configure translations globally or configure language per user. Currently, translations are stored in the Cookies, which will expire after some time (currently, it's set to 30 days). We will implement more granular language settings in future versions of Homarr, since it requires major changes to Homarr. Stay tuned! 😎 \ No newline at end of file +We are aware that many people would like to configure translations globally or configure language per user. Currently, translations are stored in the Cookies, which will expire after some time (currently, it's set to 30 days). We will implement more granular language settings in future versions of Homarr, since it requires major changes to Homarr. Stay tuned! 😎 diff --git a/apps/docs/blog/2023/01-11-version0.11/index.mdx b/apps/docs/blog/2023/01-11-version0.11/index.mdx index 5ab9a78bc..6cc2cfe9a 100644 --- a/apps/docs/blog/2023/01-11-version0.11/index.mdx +++ b/apps/docs/blog/2023/01-11-version0.11/index.mdx @@ -1,5 +1,6 @@ --- title: "We present: Homarr 0.11 - the most complex Homarr version yet!" +date: 2023-01-11 authors: - ajnart - manuel-rw @@ -20,7 +21,7 @@ It contains many breaking changes and huge improvements to your favourite dashbo - Huge design and usability improvements to older components to Homarr - Better handeling of exceptions and unexpected issues - +{/* Historical excerpt boundary. */} ## Complete implementation of highly customizable drag and drop library "Gridstack" diff --git a/apps/docs/blog/2023/04-16-version0.12-more-widgets/index.mdx b/apps/docs/blog/2023/04-16-version0.12-more-widgets/index.mdx index 66334bfbc..9dc0df175 100644 --- a/apps/docs/blog/2023/04-16-version0.12-more-widgets/index.mdx +++ b/apps/docs/blog/2023/04-16-version0.12-more-widgets/index.mdx @@ -1,5 +1,6 @@ --- title: "We present: Homarr 0.12 - The fastest homarr version ever!" +date: 2023-04-16 authors: - ajnart - manuel-rw @@ -15,14 +16,12 @@ This applies to all widgets, but in the case of the IFrame widget, it enables yo - Integrate monitoring tools like Grafana, Splunk or Elasticsearch - Integrate your services (for example Sonarr, Radarr and Overseerr) directly into Homarr -![](https://media.discordapp.net/attachments/988335098493542410/1094665745226539168/update-teaser.png?width=1046&height=666) - ## Edit mode password We've added a setting to disable editing completely. This will allow you to restrict edit access without password-protection. use the `EDIT_MODE_PASSWORD` ENV variable to set a password for edit mode. turn on `DISABLE_EDIT_MODE=TRUE` to disable edit mode on startup. - +{/* Historical excerpt boundary. */} ## 😎 Blazingly fast We've implemented a new caching system, that will cache all data in your browser, meaning that Homarr will load within milliseconds, as if it was *powered by rust and blazingly fast* !!! @@ -72,4 +71,4 @@ Now, you can use the following environment variables to configure this: - ``DOCKER_HOST`` - ``DOCKER_PORT`` -**Full Changelog**: https://github.com/ajnart/homarr/compare/v0.11.5...v0.12.0 \ No newline at end of file +**Full Changelog**: https://github.com/ajnart/homarr/compare/v0.11.5...v0.12.0 diff --git a/apps/docs/blog/2023/11-10-authentication/index.mdx b/apps/docs/blog/2023/11-10-authentication/index.mdx index 39c5466f0..eb3dbf55a 100644 --- a/apps/docs/blog/2023/11-10-authentication/index.mdx +++ b/apps/docs/blog/2023/11-10-authentication/index.mdx @@ -1,5 +1,6 @@ --- title: "Authentication in Homarr - 0.14 is here!" +date: 2023-11-10 authors: - ajnart - manuel-rw @@ -23,7 +24,7 @@ provide you a viable solution today. - 📝 Overhauled notepad widget with a complex graphical editor that enables easy editing right from your browser. - :globe_with_meridians: Live Translation on Crowdin that enable you to directly translate using images to see what exactly you're translating - +{/* Historical excerpt boundary. */} ## Breaking changes @@ -83,4 +84,4 @@ We are aware that this update has many breaking changes and may confuse some use For that reason, we updated our documentation and will enhance it further within the next few weeks. We've also added this small button at the bottom of the page were you can ask **your questions** to an AI assistant: -![image](https://github.com/ajnart/homarr/assets/30572287/a56f721a-d022-42ad-813a-d85c89462a8c) \ No newline at end of file +![image](https://github.com/ajnart/homarr/assets/30572287/a56f721a-d022-42ad-813a-d85c89462a8c) diff --git a/apps/docs/blog/2023/12-22-updated-documentation/index.mdx b/apps/docs/blog/2023/12-22-updated-documentation/index.mdx index 52da0fb22..6d963c6b0 100644 --- a/apps/docs/blog/2023/12-22-updated-documentation/index.mdx +++ b/apps/docs/blog/2023/12-22-updated-documentation/index.mdx @@ -1,5 +1,6 @@ --- title: "Updated documentation with better organization & design" +date: 2023-12-22 authors: - manuel-rw tags: [homarr, documentation, design, docs] @@ -8,7 +9,7 @@ tags: [homarr, documentation, design, docs] We'd like to let you know that we've updated our documentation and reorganized the pages. This means, that some links may no longer be valid - we apologise if this causes any inconvenience for you. - +{/* Historical excerpt boundary. */} A few highlights include: @@ -18,4 +19,4 @@ A few highlights include: - Bug fixes for image zoom - Added documentation for management pages and [new CLI tool](/docs/advanced/command-line/password-recovery/) -Please [let us know](/docs/community/get-in-touch) is something is not working as expected. \ No newline at end of file +Please [let us know](/docs/community/get-in-touch) is something is not working as expected. diff --git a/apps/docs/blog/2024/09-23-version-1.0/index.mdx b/apps/docs/blog/2024/09-23-version-1.0/index.mdx index d3de1e7e9..4cfdc2431 100644 --- a/apps/docs/blog/2024/09-23-version-1.0/index.mdx +++ b/apps/docs/blog/2024/09-23-version-1.0/index.mdx @@ -1,9 +1,10 @@ --- +title: Homarr 1.0 - complete rewrite of all systems +date: 2024-09-23 authors: - manuel-rw --- -# Homarr 1.0 - complete rewrite of all systems We proudly present to you - Homarr ``1.0``. We have started work on this new version back in 2023 and have worked tirelessly for months on this brand-new dashboard. @@ -28,7 +29,7 @@ reliability and scalability compared to < ``1.0``. - **Improved performance**: We have upgraded the underlying framework of Homarr and take advantage of an in-memory message bus and websockets to deliver data near realtime to you. - +{/* Historical excerpt boundary. */} ## Breaking changes :::warning diff --git a/apps/docs/blog/2024/12-17-open-beta-1.0/index.mdx b/apps/docs/blog/2024/12-17-open-beta-1.0/index.mdx index 91f0a7b62..cf652fa57 100644 --- a/apps/docs/blog/2024/12-17-open-beta-1.0/index.mdx +++ b/apps/docs/blog/2024/12-17-open-beta-1.0/index.mdx @@ -1,16 +1,16 @@ --- +title: Open Beta 1.0 +date: 2024-12-17 authors: - meierschlumpf --- -# Open Beta 1.0 - We are proud to announce the open beta of Homarr 1.0. This is a major milestone for us and we are excited to share it with you. The open beta allows us to polish the last rough edges and gather feedback from you. We are looking forward to your feedback and hope you enjoy the new features. Let us know throgh discord or a [github issues](https://github.com/homarr-labs/homarr/issues/new/choose) if you encounter any issues. - +{/* Historical excerpt boundary. */} ## How to use the beta with docker @@ -36,4 +36,4 @@ services: Then, run `docker compose up -d` in the same directory. This will start the Homarr container in the background. Please note that the mounts have changed and it's not backwards compatible with versions before 1.0.0-beta.1. -You can find a list of all breaking changes [here](/blog/2024/09/23/version-1.0) \ No newline at end of file +You can find a list of all breaking changes [here](/blog/2024/09/23/version-1.0) diff --git a/apps/docs/blog/2025/01-19-migration-guide-1.0/index.mdx b/apps/docs/blog/2025/01-19-migration-guide-1.0/index.mdx index 48ce153fa..392a14b3f 100644 --- a/apps/docs/blog/2025/01-19-migration-guide-1.0/index.mdx +++ b/apps/docs/blog/2025/01-19-migration-guide-1.0/index.mdx @@ -1,14 +1,14 @@ --- +title: Migrate from 0.15.10+ to 1.0.0 +date: 2025-01-19 authors: - meierschlumpf --- -# Migrate from 0.15.10+ to 1.0.0 - In this guide, we will show you how to migrate from Homarr 0.15.10+ to 1.0.0. If you are still on a version before 0.15.10, please update to 0.15.10 where we introduced the migration export page. - +{/* Historical excerpt boundary. */} ## Preserve your old installation diff --git a/apps/docs/blog/2025/08-02-using-argus/index.mdx b/apps/docs/blog/2025/08-02-using-argus/index.mdx index d5b76e0e6..0a6a9e6ad 100644 --- a/apps/docs/blog/2025/08-02-using-argus/index.mdx +++ b/apps/docs/blog/2025/08-02-using-argus/index.mdx @@ -1,14 +1,14 @@ --- +title: How to use Argus with Homarr +date: 2025-08-02 authors: - meierschlumpf --- -# How to use Argus with Homarr - In `v1.32.0` we added the endpoint `/api/info` which returns the current version of Homarr and can therefore be used in Argus to watch for updates. This guide will show you how to set up a user with apiKey to access the endpoint without providing admin access through it. - +{/* Historical excerpt boundary. */} ![Argus](./img/argus.png) @@ -64,4 +64,4 @@ Now you can configure Argus to use the user and apiKey we created. ![Argus configuration](./img/argus-configuration.png) -Replace the `` with the apiKey you created and the deployed version url with the url of your Homarr instance. \ No newline at end of file +Replace the `` with the apiKey you created and the deployed version url with the url of your Homarr instance. diff --git a/apps/docs/blog/2026/09-03-homarr-2.0/index.mdx b/apps/docs/blog/2026/09-03-homarr-2.0/index.mdx index 54399ba6a..876ba932a 100644 --- a/apps/docs/blog/2026/09-03-homarr-2.0/index.mdx +++ b/apps/docs/blog/2026/09-03-homarr-2.0/index.mdx @@ -1,4 +1,6 @@ --- +title: Homarr 2.0.0 - Workshop, Custom Widgets and a rebuilt board +date: 2026-09-03 authors: - ajnart tags: [homarr, update, version, assistant, custom widgets, dashboards, docker] @@ -17,7 +19,7 @@ Homarr 2.0 is an upgrade, not a brand-new app wearing the same logo. Here is wha ![A populated Homarr 2.0 dashboard with compact widgets, an Assistant, and an application rail](./img/homarr-v2-dashboard.webp) - +{/* Historical excerpt boundary. */} :::warning Back up before upgrading diff --git a/apps/docs/components.json b/apps/docs/components.json index 15e244db4..0c6487f18 100644 --- a/apps/docs/components.json +++ b/apps/docs/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "", - "css": "src/css/custom.css", + "css": "src/app/global.css", "baseColor": "mist", "cssVariables": true, "prefix": "" diff --git a/apps/docs/docs/advanced/_category_.json b/apps/docs/docs/advanced/_category_.json deleted file mode 100644 index 55a84fc00..000000000 --- a/apps/docs/docs/advanced/_category_.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "label": "Advanced", - "collapsed": true, - "position": 5, - "link": { - "type": "generated-index", - "title": "Advanced Concepts or features" - } -} diff --git a/apps/docs/docs/advanced/command-line/_category_.json b/apps/docs/docs/advanced/command-line/_category_.json deleted file mode 100644 index 4b4aec4c3..000000000 --- a/apps/docs/docs/advanced/command-line/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "collapsed": true, - "label": "Command line interface" -} diff --git a/apps/docs/docs/advanced/command-line/meta.json b/apps/docs/docs/advanced/command-line/meta.json new file mode 100644 index 000000000..c46ccce01 --- /dev/null +++ b/apps/docs/docs/advanced/command-line/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Command line interface", + "pages": ["index", "users", "integrations", "password-recovery", "fix-usernames", "development"] +} diff --git a/apps/docs/docs/advanced/development/_category_.json b/apps/docs/docs/advanced/development/_category_.json deleted file mode 100644 index bf0464672..000000000 --- a/apps/docs/docs/advanced/development/_category_.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "Developer guides", - "position": 6, - "collapsed": true, - "link": { - "type": "generated-index" - } -} diff --git a/apps/docs/docs/advanced/development/getting-started.mdx b/apps/docs/docs/advanced/development/getting-started.mdx index 1ab74a197..bd0494463 100644 --- a/apps/docs/docs/advanced/development/getting-started.mdx +++ b/apps/docs/docs/advanced/development/getting-started.mdx @@ -57,8 +57,8 @@ Homarr becomes the instance administrator. Use `pnpm docker:dev:up` instead when you only need Redis in the background. Running the migration also seeds the default data; use `pnpm db:seed` to seed it explicitly later. -Workshop contributors can set up PocketBase and Docusaurus with the dedicated -[Workshop development guide](./workshop). +Workshop contributors can set up PocketBase and Fumadocs with the dedicated +[Workshop development guide](/docs/advanced/development/workshop). ### Useful commands @@ -69,7 +69,7 @@ Workshop contributors can set up PocketBase and Docusaurus with the dedicated - `pnpm dev:cli -- build --pr ` — build a pull request from a temporary checkout. - `pnpm dev:cli -- rebuild ` — rebuild an image from its recorded checkout or pull request. - `pnpm dev:cli:install` — optionally install the developer CLI as a `homarr` binary. -- `pnpm dev:docs` — Docusaurus only on port 3003. +- `pnpm dev:docs` — Fumadocs on port 3003. - `pnpm docker:dev:up` — Redis development service in the background. - `pnpm cli` — Homarr operations CLI. - `pnpm db:migration:sqlite:run` — create or update a SQLite database. diff --git a/apps/docs/docs/advanced/development/meta.json b/apps/docs/docs/advanced/development/meta.json new file mode 100644 index 000000000..9acf595a5 --- /dev/null +++ b/apps/docs/docs/advanced/development/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Developer guides", + "pages": ["getting-started", "widgets-and-integrations", "kubernetes", "workshop", "workshop-operator"] +} diff --git a/apps/docs/docs/advanced/development/workshop.mdx b/apps/docs/docs/advanced/development/workshop.mdx index 34ecde97e..a55395290 100644 --- a/apps/docs/docs/advanced/development/workshop.mdx +++ b/apps/docs/docs/advanced/development/workshop.mdx @@ -4,13 +4,13 @@ sidebar_position: 4 tags: - Workshop - Developer - - Docusaurus + - Fumadocs - PocketBase --- # Workshop development -See the [main development guide](./getting-started) for repository prerequisites. +See the [main development guide](/docs/advanced/development/getting-started) for repository prerequisites. Start PocketBase: @@ -19,7 +19,7 @@ cp apps/workshop/.env.example apps/workshop/.env docker compose --env-file apps/workshop/.env -f apps/workshop/docker-compose.yml up --build workshop ``` -Start Docusaurus in another terminal: +Start Fumadocs in another terminal: ```sh WORKSHOP_API_URL=http://127.0.0.1:8090 pnpm dev:docs diff --git a/apps/docs/docs/advanced/environment-variables/index.mdx b/apps/docs/docs/advanced/environment-variables/index.mdx index d81c57160..d9ce8a116 100644 --- a/apps/docs/docs/advanced/environment-variables/index.mdx +++ b/apps/docs/docs/advanced/environment-variables/index.mdx @@ -139,4 +139,4 @@ The advanced deployments environment variables should only be used if you know w | `DB_MIGRATIONS_DISABLED` | Disable db migrations. For example for helm charts. | All public URL variables reject non-HTTP protocols, embedded credentials, query strings, and fragments, and normalize -trailing slashes when Homarr or Docusaurus starts. +trailing slashes when Homarr or the documentation site starts. diff --git a/apps/docs/docs/advanced/index.mdx b/apps/docs/docs/advanced/index.mdx new file mode 100644 index 000000000..0f1ca894e --- /dev/null +++ b/apps/docs/docs/advanced/index.mdx @@ -0,0 +1,60 @@ +--- +title: Advanced configuration +description: Configure deployment, authentication, networking, styling, automation, and development workflows. +--- + +import { + IconCode, + IconIcons, + IconKey, + IconKeyboard, + IconNetwork, + IconPalette, + IconTerminal2, + IconUserCog, + IconVariable, +} from "@tabler/icons-react"; + +These guides cover changes that affect how Homarr is deployed, secured, operated, or extended. + +## Deploy and secure + + + } title="Environment variables" href="/docs/advanced/environment-variables"> + Configure Homarr at deployment time. + + } title="Single sign-on" href="/docs/advanced/single-sign-on"> + Connect an external identity provider. + + } title="Proxies and certificates" href="/docs/advanced/proxy"> + Run Homarr behind a reverse proxy. + + } title="Run as a different user" href="/docs/advanced/running-as-different-user"> + Change the user and group used by the container. + + + +## Customize + + + } title="Custom styling" href="/docs/advanced/styling"> + Apply custom CSS to Homarr. + + } title="Icons" href="/docs/advanced/icons"> + Configure icon sources and behavior. + + } title="Keyboard shortcuts" href="/docs/advanced/keyboard-shortcuts"> + Navigate and operate Homarr with the keyboard. + + + +## Develop and automate + + + } title="Command line interface" href="/docs/advanced/command-line"> + Use administrative commands from the terminal. + + } title="Developer guides" href="/docs/advanced/development/getting-started"> + Set up a development environment and contribute code. + + diff --git a/apps/docs/docs/advanced/meta.json b/apps/docs/docs/advanced/meta.json new file mode 100644 index 000000000..6df9bf7c2 --- /dev/null +++ b/apps/docs/docs/advanced/meta.json @@ -0,0 +1,19 @@ +{ + "title": "Advanced", + "defaultOpen": false, + "pages": [ + "index", + "---Deploy and secure---", + "environment-variables", + "single-sign-on", + "proxy", + "running-as-different-user", + "---Customize---", + "styling", + "icons", + "keyboard-shortcuts", + "---Develop and automate---", + "command-line", + "development" + ] +} diff --git a/apps/docs/docs/advanced/proxy/index.mdx b/apps/docs/docs/advanced/proxy/index.mdx index 3ab2cecf5..e8c3abdab 100644 --- a/apps/docs/docs/advanced/proxy/index.mdx +++ b/apps/docs/docs/advanced/proxy/index.mdx @@ -9,14 +9,14 @@ tags: - Security --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; +import Tabs from "@/components/mdx/legacy-tabs"; +import { TabItem } from "@/components/mdx/legacy-tabs"; ## Allowing self-signed certificates Some users may come across a barrier, where they're unable to receive a 200 response from the Ping widget for some apps, while using self-signed certificates or a local certificate authory. -:::note What's going on? +:::note[What's going on?] Homarr is trying to communicate to your apps via the integrations. It usually doesn't matter if Homarr is running on `http` or `https`. diff --git a/apps/docs/docs/advanced/single-sign-on/index.mdx b/apps/docs/docs/advanced/single-sign-on/index.mdx index 1f88185d8..4ec58bab0 100644 --- a/apps/docs/docs/advanced/single-sign-on/index.mdx +++ b/apps/docs/docs/advanced/single-sign-on/index.mdx @@ -10,9 +10,6 @@ tags: - Active Directory --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - # Single Sign On Homarr supports multiple authentication options, from internal userbase (credentials), to LDAP (with Active directory support), and OIDC. @@ -247,7 +244,7 @@ Homarr supports multiple authentication options, from internal userbase (credent Generate `SECRET_ENCRYPTION_KEY` with `openssl rand -hex 32`; do not copy an example key between installs. - ```env + ```bash OIDC_CLIENT_ID= OIDC_CLIENT_SECRET= OIDC_SLUG=homarr #Application slug in Authentik @@ -326,7 +323,7 @@ Homarr supports multiple authentication options, from internal userbase (credent Generate `SECRET_ENCRYPTION_KEY` with `openssl rand -hex 32`; do not copy an example key between installs. - ```env + ```bash OIDC_CLIENT_ID= OIDC_CLIENT_SECRET= HOMARR_FQDN=homarr.example.com #Homarr FQDN diff --git a/apps/docs/docs/community/_category_.json b/apps/docs/docs/community/_category_.json deleted file mode 100644 index 87b7d0af2..000000000 --- a/apps/docs/docs/community/_category_.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "Community", - "position": 6, - "collapsed": true, - "link": { - "type": "generated-index" - } -} diff --git a/apps/docs/docs/community/index.mdx b/apps/docs/docs/community/index.mdx new file mode 100644 index 000000000..45b2f7d06 --- /dev/null +++ b/apps/docs/docs/community/index.mdx @@ -0,0 +1,10 @@ +--- +title: Community +description: Get help, contribute to Homarr, and support the project. +--- + +- [Frequently asked questions](/docs/community/faq): resolve common setup and operation issues. +- [Get in touch](/docs/community/get-in-touch): choose the right support or discussion channel. +- [Translations](/docs/community/translations): improve Homarr in your language. +- [Donate](/docs/community/donate): support ongoing development. +- [License](/docs/community/license): understand how Homarr is licensed. diff --git a/apps/docs/docs/community/meta.json b/apps/docs/docs/community/meta.json new file mode 100644 index 000000000..c3b0ace5a --- /dev/null +++ b/apps/docs/docs/community/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Community", + "defaultOpen": false, + "pages": ["index", "faq", "get-in-touch", "translations", "donate", "license"] +} diff --git a/apps/docs/docs/getting-started/_category_.json b/apps/docs/docs/getting-started/_category_.json deleted file mode 100644 index 28623c903..000000000 --- a/apps/docs/docs/getting-started/_category_.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "label": "Getting started", - "position": 1, - "collapsed": false, - "link": { - "type": "generated-index", - "title": "Getting started" - } -} diff --git a/apps/docs/docs/getting-started/after-the-installation.mdx b/apps/docs/docs/getting-started/after-the-installation.mdx index 7e5712c83..bf815ec85 100644 --- a/apps/docs/docs/getting-started/after-the-installation.mdx +++ b/apps/docs/docs/getting-started/after-the-installation.mdx @@ -6,11 +6,68 @@ tags: - Configuration --- -On first launch, onboarding creates the administrator, connects optional services, and builds the first board. A -compatible SQLite backup can also be restored from the welcome screen. +import { + IconAdjustments, + IconChecklist, + IconLayoutDashboard, + IconPlugConnected, + IconSearch, + IconSparkles, +} from "@tabler/icons-react"; -If onboarding returns after a restart, check that `/appdata` is writable and persisted. See the -[Docker installation guide](/docs/getting-started/installation/docker). +import onboardingWelcome from "./img/onboarding-v2-welcome.png"; +import onboardingBoard from "./img/onboarding-v2-board.png"; +import onboardingReview from "./img/onboarding-v2-review.png"; + +After Homarr starts, open its URL. An unfinished installation redirects to the onboarding screen at `/init`. Choose +**Get started** for a new installation or **Restore backup** to import a compatible SQLite backup. + + + Onboarding writes the administrator and board configuration to the database. If onboarding returns after a restart, + check that `/appdata` is writable and persisted. See the + [Docker installation guide](/docs/getting-started/installation/docker). + + +## Complete onboarding + +Onboarding is split into six short sections. Integrations, Workshop, Assistant, and MCP are optional; skip them when you +only need a working board. + + + } title="1. Essentials"> + Choose the language, theme, usual server address, and anonymous analytics preference. + + } title="2. Discover"> + Review the database, Docker, Kubernetes, Assistant, and Workshop capabilities Homarr can reach. + + } title="3. Connect"> + Import detected services or add integrations manually. Unfinished integrations can be skipped. + + } title="4. Board"> + Set the board name, colors, corner radius, column count, and optional sidebars. + + } title="5. Extend"> + Optionally connect the Workshop, configure an Assistant provider, or review the MCP endpoint. + + } title="6. Review"> + Check the final summary, then build the board. Homarr opens the exact board it configured. + + + +The final screen links directly to the new board and the management area. Board layout, integrations, apps, and +appearance can all be changed later. + + + + Homarr onboarding welcome screen with Get started and Restore backup actions + + + Board onboarding section for colors, columns, corner radius, and sidebars + + + Onboarding review showing the board settings Homarr will create + + ## Core concepts @@ -40,7 +97,7 @@ Docker. Integrations provide data and actions to widgets. They can also be linke ![Integration catalog with compatible widgets](./img/after-installation-integrations.png) See [Managing integrations](/docs/management/integrations) and the -[integration catalog](/docs/category/integrations). +[integration catalog](/docs/integrations). ### Widgets @@ -49,7 +106,7 @@ supported integrations, and non-obvious limitations are documented on each widge ![Board in edit mode with the add-content menu](./img/after-installation-widgets.png) -See the [widget catalog](/docs/category/widgets). +See the [widget catalog](/docs/widgets). ### Containers and rails diff --git a/apps/docs/docs/getting-started/img/onboarding-v2-board.png b/apps/docs/docs/getting-started/img/onboarding-v2-board.png new file mode 100644 index 000000000..3976148ed Binary files /dev/null and b/apps/docs/docs/getting-started/img/onboarding-v2-board.png differ diff --git a/apps/docs/docs/getting-started/img/onboarding-v2-review.png b/apps/docs/docs/getting-started/img/onboarding-v2-review.png new file mode 100644 index 000000000..744e85037 Binary files /dev/null and b/apps/docs/docs/getting-started/img/onboarding-v2-review.png differ diff --git a/apps/docs/docs/getting-started/img/onboarding-v2-welcome.png b/apps/docs/docs/getting-started/img/onboarding-v2-welcome.png new file mode 100644 index 000000000..ea5ea29f2 Binary files /dev/null and b/apps/docs/docs/getting-started/img/onboarding-v2-welcome.png differ diff --git a/apps/docs/docs/getting-started/installation/_category_.json b/apps/docs/docs/getting-started/installation/_category_.json deleted file mode 100644 index ee30498ee..000000000 --- a/apps/docs/docs/getting-started/installation/_category_.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "label": "Installation", - "position": 1, - "collapsed": true, - "link": { - "type": "generated-index", - "title": "Getting started" - } -} diff --git a/apps/docs/docs/getting-started/installation/index.mdx b/apps/docs/docs/getting-started/installation/index.mdx new file mode 100644 index 000000000..a5e92a34a --- /dev/null +++ b/apps/docs/docs/getting-started/installation/index.mdx @@ -0,0 +1,84 @@ +--- +title: Install Homarr +description: Choose the installation method that matches your infrastructure. +full: true +--- + +import { + IconApps, + IconBox, + IconBrandDocker, + IconBrandGithub, + IconCloud, + IconContainer, + IconDatabase, + IconHexagons, + IconHome, + IconLayoutDashboard, + IconServer2, + IconTrain, +} from "@tabler/icons-react"; + +Docker Compose is the best starting point for most self-hosters. Choose a platform-specific guide when Homarr will run inside an existing server manager or managed service. + + + Start with **Docker Compose**. It is the recommended path, has the broadest community support, and makes updates and + backups straightforward. + + +## Recommended + + + } title="Docker Compose" href="/docs/getting-started/installation/docker"> + The recommended installation for most Linux and Windows servers. + + } title="Kubernetes with Helm" href="/docs/getting-started/installation/helm"> + Deploy Homarr into an existing Kubernetes cluster. + + } title="Build from source" href="/docs/getting-started/installation/source"> + Build and run Homarr directly from the repository. + + + +## Self-hosting platforms + + + } title="Unraid" href="/docs/getting-started/installation/unraid"> + Install Homarr from the Unraid Community Apps catalog. + + } title="Synology" href="/docs/getting-started/installation/synology"> + Run Homarr on a Synology NAS with Container Manager. + + } title="Proxmox" href="/docs/getting-started/installation/proxmox"> + Add Homarr to a Proxmox-based homelab. + + } title="Home Assistant Add-on" href="/docs/getting-started/installation/home-assistant"> + Install Homarr alongside your Home Assistant environment. + + } title="Portainer" href="/docs/getting-started/installation/portainer"> + Deploy and manage Homarr through Portainer stacks. + + } title="QNAP" href="/docs/getting-started/installation/qnap"> + Run Homarr on a QNAP NAS. + + } title="Runtipi" href="/docs/getting-started/installation/runtipi"> + Add Homarr from the Runtipi app store. + + + +## Managed and app platforms + + + } title="EasyPanel" href="/docs/getting-started/installation/easy-panel"> + Deploy Homarr from an Easypanel template. + + } title="Railway" href="/docs/getting-started/installation/railway"> + Launch Homarr as a managed Railway service. + + } title="PikaPods" href="/docs/getting-started/installation/pika-pods"> + Use a hosted Homarr instance without managing a server. + + } title="Saltbox" href="/docs/getting-started/installation/saltbox"> + Add Homarr to a Saltbox deployment. + + diff --git a/apps/docs/docs/getting-started/installation/meta.json b/apps/docs/docs/getting-started/installation/meta.json new file mode 100644 index 000000000..570d1e29c --- /dev/null +++ b/apps/docs/docs/getting-started/installation/meta.json @@ -0,0 +1,24 @@ +{ + "title": "Installation", + "defaultOpen": false, + "pages": [ + "index", + "---Recommended---", + "docker", + "helm", + "source", + "---Self-hosting platforms---", + "unraid", + "synology", + "proxmox", + "home-assistant", + "portainer", + "qnap", + "runtipi", + "---Managed and app platforms---", + "easy-panel", + "railway", + "pika-pods", + "saltbox" + ] +} diff --git a/apps/docs/docs/getting-started/installation/unraid.mdx b/apps/docs/docs/getting-started/installation/unraid.mdx index 18588c5d4..c634ae387 100644 --- a/apps/docs/docs/getting-started/installation/unraid.mdx +++ b/apps/docs/docs/getting-started/installation/unraid.mdx @@ -1,4 +1,4 @@ -import Link from "@docusaurus/Link"; +import Link from "@/components/mdx/link"; import { IconDownload } from "@tabler/icons-react"; # Unraid diff --git a/apps/docs/docs/getting-started/meta.json b/apps/docs/docs/getting-started/meta.json new file mode 100644 index 000000000..4883ef647 --- /dev/null +++ b/apps/docs/docs/getting-started/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Getting started", + "defaultOpen": false, + "pages": ["index", "installation", "after-the-installation", "upgrade-v2", "glossary"] +} diff --git a/apps/docs/docs/index.mdx b/apps/docs/docs/index.mdx new file mode 100644 index 000000000..0ee581d67 --- /dev/null +++ b/apps/docs/docs/index.mdx @@ -0,0 +1,72 @@ +--- +title: Homarr documentation +description: Install, configure, operate, and extend Homarr. +--- + +import { + IconApi, + IconArrowUp, + IconBuildingStore, + IconChecklist, + IconCode, + IconComponents, + IconDownload, + IconLayoutDashboard, + IconPlugConnected, + IconSettings, + IconUsers, + IconVocabulary, +} from "@tabler/icons-react"; + +Use these guides to get Homarr running, connect your services, build useful boards, and manage an installation over time. + +## Install or upgrade + + + } title="Install Homarr" href="/docs/getting-started/installation"> + Choose the installation method for your system. + + } title="Upgrade from v1" href="/docs/getting-started/upgrade-v2"> + Move an existing Homarr installation to v2. + + } title="Finish setup" href="/docs/getting-started/after-the-installation"> + Add apps, integrations, and widgets after installation. + + } title="Learn the concepts" href="/docs/getting-started"> + Understand boards, apps, integrations, and widgets. + + + +## Configure Homarr + + + } title="Boards and content" href="/docs/management"> + Configure boards, apps, users, settings, and administration. + + } title="Connect services" href="/docs/integrations"> + Find the setup guide for an integration. + + } title="Choose widgets" href="/docs/widgets"> + Find the content and controls available for boards. + + } title="Advanced configuration" href="/docs/advanced"> + Configure deployment, authentication, networking, and automation. + + + +## Extend and contribute + + + } title="Custom Widgets" href="/docs/management/custom-widgets"> + Author and publish Custom JSX widgets. + + } title="Community Workshop" href="/docs/workshop"> + Use and operate the community extension registry. + + } title="API reference" href="/api-reference"> + Inspect Homarr's HTTP API. + + } title="Get help and contribute" href="/docs/community"> + Find support and ways to contribute. + + diff --git a/apps/docs/docs/integrations/_category_.json b/apps/docs/docs/integrations/_category_.json deleted file mode 100644 index 896594e32..000000000 --- a/apps/docs/docs/integrations/_category_.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "label": "Integrations", - "collapsed": true, - "position": 3, - "link": { - "type": "generated-index", - "title": "Integrations", - "description": "Integration are a principal part of homarr, it's where you can connect your other apps to interact with them with widgets or other capabilities, Homarr currently integrates with the following applications:" - } -} diff --git a/apps/docs/docs/integrations/coolify/index.mdx b/apps/docs/docs/integrations/coolify/index.mdx index 7b828cc18..5aa2522c0 100644 --- a/apps/docs/docs/integrations/coolify/index.mdx +++ b/apps/docs/docs/integrations/coolify/index.mdx @@ -21,7 +21,7 @@ import { coolifyWidget } from "../../widgets/coolify"; -:::info URL configuration +:::info[URL configuration] Use the root URL of your Coolify instance, for example `https://coolify.example.com`. Homarr calls Coolify endpoints under `/api` and `/api/v1` automatically. ::: diff --git a/apps/docs/docs/integrations/deluge/index.mdx b/apps/docs/docs/integrations/deluge/index.mdx index 89f906c0c..3c996d34f 100644 --- a/apps/docs/docs/integrations/deluge/index.mdx +++ b/apps/docs/docs/integrations/deluge/index.mdx @@ -9,7 +9,7 @@ import { IntegrationCapabilites } from "@site/src/components/integrations/widget import { AddingIntegration } from "@site/src/components/integrations/adding"; import { IntegrationSecrets } from "@site/src/components/integrations/secrets"; import { IconDownload } from "@tabler/icons-react"; -import Alert from "@theme/Admonition"; +import Alert from "@/components/mdx/admonition"; import { delugeIntegration } from "."; import { downloadsWidget } from "@site/docs/widgets/downloads"; diff --git a/apps/docs/docs/integrations/docker/index.mdx b/apps/docs/docs/integrations/docker/index.mdx index c086b317d..80a9819d3 100644 --- a/apps/docs/docs/integrations/docker/index.mdx +++ b/apps/docs/docs/integrations/docker/index.mdx @@ -117,7 +117,7 @@ updating a Homarr resource still requires confirmation. If one endpoint is unava endpoints remain visible. The `homarr.hide` label excludes a container. Other `homarr.*` labels can provide apps, integrations, groups, and widgets -during onboarding; see [Docker label discovery](../docker-labels). +during onboarding; see [Docker label discovery](/docs/integrations/docker-labels). ## Security diff --git a/apps/docs/docs/integrations/index.mdx b/apps/docs/docs/integrations/index.mdx new file mode 100644 index 000000000..0246fa17c --- /dev/null +++ b/apps/docs/docs/integrations/index.mdx @@ -0,0 +1,21 @@ +--- +title: Integrations +description: Connect Homarr to the applications and infrastructure you operate. +--- + +import { DocsDirectory } from "@/components/docs/docs-directory"; + +An integration stores the connection and authentication details Homarr needs to communicate with another service. Widgets and management tools can then reuse that connection. + +Each guide identifies the required URL and credentials, supported capabilities, and the widgets that use the integration. + + + +## Before you connect a service + +1. Confirm that Homarr can reach the service from its own network. +2. Create the least-privileged account, token, or API key supported by the service. +3. Add the integration in **Manage → Integrations**. +4. Test the connection before adding dependent widgets. + +See [Manage integrations](/docs/management/integrations) for the shared workflow. diff --git a/apps/docs/docs/integrations/meta.json b/apps/docs/docs/integrations/meta.json new file mode 100644 index 000000000..b335af28d --- /dev/null +++ b/apps/docs/docs/integrations/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Integrations", + "defaultOpen": false, + "pages": ["index", "..."] +} diff --git a/apps/docs/docs/integrations/nextcloud/index.mdx b/apps/docs/docs/integrations/nextcloud/index.mdx index f7d7e05fa..ce1704500 100644 --- a/apps/docs/docs/integrations/nextcloud/index.mdx +++ b/apps/docs/docs/integrations/nextcloud/index.mdx @@ -9,7 +9,7 @@ import { IntegrationCapabilites } from "@site/src/components/integrations/widget import { AddingIntegration } from "@site/src/components/integrations/adding"; import { IntegrationSecrets } from "@site/src/components/integrations/secrets"; import { IconCalendar } from "@tabler/icons-react"; -import Alert from "@theme/Admonition"; +import Alert from "@/components/mdx/admonition"; import { nextcloudIntegration } from "."; import { calendarWidget } from "@site/docs/widgets/calendar"; import { notificationsWidget } from "@site/docs/widgets/notifications"; diff --git a/apps/docs/docs/integrations/nzbget/index.mdx b/apps/docs/docs/integrations/nzbget/index.mdx index 1b7c23a1f..06de9621c 100644 --- a/apps/docs/docs/integrations/nzbget/index.mdx +++ b/apps/docs/docs/integrations/nzbget/index.mdx @@ -10,7 +10,7 @@ import { AddingIntegration } from "@site/src/components/integrations/adding"; import { IntegrationSecrets } from "@site/src/components/integrations/secrets"; import { IconDownload } from "@tabler/icons-react"; import { nzbgetIntegration } from "."; -import Alert from "@theme/Admonition"; +import Alert from "@/components/mdx/admonition"; import { downloadsWidget } from "@site/docs/widgets/downloads"; diff --git a/apps/docs/docs/integrations/open-media-vault/index.mdx b/apps/docs/docs/integrations/open-media-vault/index.mdx index b934bf797..c44e175c0 100644 --- a/apps/docs/docs/integrations/open-media-vault/index.mdx +++ b/apps/docs/docs/integrations/open-media-vault/index.mdx @@ -10,7 +10,7 @@ import { AddingIntegration } from "@site/src/components/integrations/adding"; import { IntegrationSecrets } from "@site/src/components/integrations/secrets"; import { IconHeartRateMonitor } from "@tabler/icons-react"; import { openMediaVaultIntegration } from "."; -import Alert from "@theme/Admonition"; +import Alert from "@/components/mdx/admonition"; import { healthMonitoringWidget } from "@site/docs/widgets/health-monitoring"; import { systemResourcesWidget } from "@site/docs/widgets/system-resources"; diff --git a/apps/docs/docs/integrations/pi-hole/index.mdx b/apps/docs/docs/integrations/pi-hole/index.mdx index 18b52840e..65612dbe2 100644 --- a/apps/docs/docs/integrations/pi-hole/index.mdx +++ b/apps/docs/docs/integrations/pi-hole/index.mdx @@ -8,7 +8,6 @@ import { IntegrationHeader } from "@site/src/components/integrations/header"; import { IntegrationCapabilites } from "@site/src/components/integrations/widgets"; import { AddingIntegration } from "@site/src/components/integrations/adding"; import { IntegrationSecrets } from "@site/src/components/integrations/secrets"; -import Alert from "@theme/Admonition"; import { dnsHoleSummaryWidget } from "@site/docs/widgets/dns-hole-summary"; import { dnsHoleControlsWidget } from "@site/docs/widgets/dns-hole-controls"; import { piHoleIntegration } from "."; diff --git a/apps/docs/docs/integrations/q-bittorent/index.mdx b/apps/docs/docs/integrations/q-bittorent/index.mdx index 0bf2474ec..f4e32ee37 100644 --- a/apps/docs/docs/integrations/q-bittorent/index.mdx +++ b/apps/docs/docs/integrations/q-bittorent/index.mdx @@ -10,7 +10,7 @@ import { AddingIntegration } from "@site/src/components/integrations/adding"; import { IntegrationSecrets } from "@site/src/components/integrations/secrets"; import { IconDownload } from "@tabler/icons-react"; import { qBittorentIntegration } from "."; -import Alert from "@theme/Admonition"; +import Alert from "@/components/mdx/admonition"; import { downloadsWidget } from "@site/docs/widgets/downloads"; diff --git a/apps/docs/docs/integrations/sabnzbd/index.mdx b/apps/docs/docs/integrations/sabnzbd/index.mdx index 8c91c6fef..2c3cfdc1e 100644 --- a/apps/docs/docs/integrations/sabnzbd/index.mdx +++ b/apps/docs/docs/integrations/sabnzbd/index.mdx @@ -10,7 +10,7 @@ import { AddingIntegration } from "@site/src/components/integrations/adding"; import { IntegrationSecrets } from "@site/src/components/integrations/secrets"; import { IconDownload } from "@tabler/icons-react"; import { sabnzbdIntegration } from "."; -import Alert from "@theme/Admonition"; +import Alert from "@/components/mdx/admonition"; import { downloadsWidget } from "@site/docs/widgets/downloads"; diff --git a/apps/docs/docs/integrations/synology/index.mdx b/apps/docs/docs/integrations/synology/index.mdx index c3f50fc3f..4306fcc89 100644 --- a/apps/docs/docs/integrations/synology/index.mdx +++ b/apps/docs/docs/integrations/synology/index.mdx @@ -12,7 +12,7 @@ import { synologyIntegration } from "."; import { systemResourcesWidget } from "@site/docs/widgets/system-resources"; import { systemDisksWidget } from "@site/docs/widgets/system-disks"; import { healthMonitoringWidget } from "@site/docs/widgets/health-monitoring"; -import Alert from "@theme/Admonition"; +import Alert from "@/components/mdx/admonition"; diff --git a/apps/docs/docs/integrations/technitium-dns/index.mdx b/apps/docs/docs/integrations/technitium-dns/index.mdx index 3bf39d820..97783ce53 100644 --- a/apps/docs/docs/integrations/technitium-dns/index.mdx +++ b/apps/docs/docs/integrations/technitium-dns/index.mdx @@ -8,7 +8,7 @@ import { IntegrationHeader } from "@site/src/components/integrations/header"; import { IntegrationCapabilites } from "@site/src/components/integrations/widgets"; import { AddingIntegration } from "@site/src/components/integrations/adding"; import { IntegrationSecrets } from "@site/src/components/integrations/secrets"; -import Alert from "@theme/Admonition"; +import Alert from "@/components/mdx/admonition"; import { technitiumDnsIntegration } from "."; import { dnsHoleSummaryWidget } from "@site/docs/widgets/dns-hole-summary"; import { dnsHoleControlsWidget } from "@site/docs/widgets/dns-hole-controls"; diff --git a/apps/docs/docs/integrations/traefik/index.mdx b/apps/docs/docs/integrations/traefik/index.mdx index 819ede6c6..b4ccfe8d6 100644 --- a/apps/docs/docs/integrations/traefik/index.mdx +++ b/apps/docs/docs/integrations/traefik/index.mdx @@ -21,11 +21,11 @@ import { traefikWidget } from "../../widgets/traefik"; -:::info URL configuration +:::info[URL configuration] Use the URL where Traefik exposes its dashboard/API, for example `http://traefik:8080` or `https://traefik.example.com`. Homarr calls Traefik endpoints under `/api` automatically. ::: -:::warning API requirement +:::warning[API requirement] Traefik must have the API enabled. For local or internal setups this is commonly exposed through `api.insecure=true`; for production setups, expose the `api@internal` service through an authenticated router. ::: diff --git a/apps/docs/docs/integrations/transmission/index.mdx b/apps/docs/docs/integrations/transmission/index.mdx index c17dbe862..893af8fb5 100644 --- a/apps/docs/docs/integrations/transmission/index.mdx +++ b/apps/docs/docs/integrations/transmission/index.mdx @@ -10,7 +10,7 @@ import { AddingIntegration } from "@site/src/components/integrations/adding"; import { IntegrationSecrets } from "@site/src/components/integrations/secrets"; import { IconDownload } from "@tabler/icons-react"; import { transmissionIntegration } from "."; -import Alert from "@theme/Admonition"; +import Alert from "@/components/mdx/admonition"; import { downloadsWidget } from "@site/docs/widgets/downloads"; diff --git a/apps/docs/docs/integrations/truenas/index.mdx b/apps/docs/docs/integrations/truenas/index.mdx index 3ec09bbcf..c63081d4b 100644 --- a/apps/docs/docs/integrations/truenas/index.mdx +++ b/apps/docs/docs/integrations/truenas/index.mdx @@ -11,7 +11,7 @@ import { IntegrationSecrets } from "@site/src/components/integrations/secrets"; import { truenasIntegration } from "."; import { systemResourcesWidget } from "@site/docs/widgets/system-resources"; import { healthMonitoringWidget } from "@site/docs/widgets/health-monitoring"; -import Alert from "@theme/Admonition"; +import Alert from "@/components/mdx/admonition"; diff --git a/apps/docs/docs/integrations/umami/index.mdx b/apps/docs/docs/integrations/umami/index.mdx index 6aa5ddced..a4956f829 100644 --- a/apps/docs/docs/integrations/umami/index.mdx +++ b/apps/docs/docs/integrations/umami/index.mdx @@ -21,7 +21,7 @@ import { umamiWidget } from "../../widgets/umami"; -:::tip URL configuration +:::tip[URL configuration] - **Umami Cloud:** Use `https://api.umami.is/v1` - **Self-hosted:** Use `http://your-umami-host:3000/api` diff --git a/apps/docs/docs/integrations/whats-up-docker/index.mdx b/apps/docs/docs/integrations/whats-up-docker/index.mdx index 31a0127f5..a32363cef 100644 --- a/apps/docs/docs/integrations/whats-up-docker/index.mdx +++ b/apps/docs/docs/integrations/whats-up-docker/index.mdx @@ -23,7 +23,7 @@ The What's Up Docker integration lets Homarr connect to your [WUD](https://githu -:::info URL configuration +:::info[URL configuration] Use the root URL of your WUD instance, for example `https://wud.example.com`. Homarr calls the `/api/app` and `/api/containers` endpoints under that URL automatically. ::: diff --git a/apps/docs/docs/management/_category_.json b/apps/docs/docs/management/_category_.json deleted file mode 100644 index e3b87148d..000000000 --- a/apps/docs/docs/management/_category_.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "label": "Management", - "collapsed": true, - "position": 2, - "link": { - "type": "generated-index", - "title": "Management pages" - } -} diff --git a/apps/docs/docs/management/api/index.mdx b/apps/docs/docs/management/api/index.mdx index 7da2c975d..55b95caf4 100644 --- a/apps/docs/docs/management/api/index.mdx +++ b/apps/docs/docs/management/api/index.mdx @@ -75,5 +75,5 @@ integration fails, the query returns an error. Authenticated administrators can retrieve the current Custom Widget authoring prompt, schema, component catalog, skill, and focused references under `/api/custom-widgets/`. -Use [Custom Widget agent authoring](../custom-widgets/agent-authoring) for the lifecycle and -[MCP](../mcp) when the client supports Model Context Protocol. +Use [Custom Widget agent authoring](/docs/management/custom-widgets/agent-authoring) for the lifecycle and +[MCP](/docs/management/mcp) when the client supports Model Context Protocol. diff --git a/apps/docs/docs/management/assistant.mdx b/apps/docs/docs/management/assistant.mdx index ade26b418..06cc2cec4 100644 --- a/apps/docs/docs/management/assistant.mdx +++ b/apps/docs/docs/management/assistant.mdx @@ -27,13 +27,13 @@ model, and enable Assistant. Users can choose another available model for a conv Provider presets cover common APIs. **Custom endpoint** accepts an OpenAI-compatible Chat Completions API. For a model server outside the Homarr container, use a hostname that the container can resolve instead of `localhost`. -The [Homarr provider](../workshop/homarr-provider) is available through the Community Workshop without an administrator +The [Homarr provider](/docs/workshop/homarr-provider) is available through the Community Workshop without an administrator API key and has a daily request allowance. ## Access Open Assistant from the user menu, with `Shift+A`, from search with `Cmd/Ctrl+/`, or through the -[Assistant widget](../widgets/assistant). +[Assistant widget](/docs/widgets/assistant). Assistant can work with boards, apps, integrations, widgets, Custom Widgets, and supported connected services. Mention Homarr resources with `@`; the model loads additional live context only when needed. @@ -47,8 +47,8 @@ Conversations are stored per user and can be searched, renamed, exported, or del - Provider keys and custom headers are encrypted and are not returned to the browser. - Prompts, attachments, tool definitions, and tool results are sent to the selected model provider. -See [MCP](./mcp) to connect an external AI client and -[Custom Widget agent authoring](./custom-widgets/agent-authoring) for widget generation. +See [MCP](/docs/management/mcp) to connect an external AI client and +[Custom Widget agent authoring](/docs/management/custom-widgets/agent-authoring) for widget generation. ## Troubleshooting diff --git a/apps/docs/docs/management/boards/index.mdx b/apps/docs/docs/management/boards/index.mdx index 762563acd..53ab1c958 100644 --- a/apps/docs/docs/management/boards/index.mdx +++ b/apps/docs/docs/management/boards/index.mdx @@ -91,4 +91,4 @@ inside a board. Defaults can be set for a user, group, or the whole server. User settings take priority, followed by matching groups, the `Everyone` group, and server defaults. Server-wide fallback boards must be public. -See [Users and groups](../users) and [Server settings](../settings#boards). +See [Users and groups](/docs/management/users) and [Server settings](/docs/management/settings#boards). diff --git a/apps/docs/docs/management/custom-widgets/_category_.json b/apps/docs/docs/management/custom-widgets/_category_.json deleted file mode 100644 index 23e215294..000000000 --- a/apps/docs/docs/management/custom-widgets/_category_.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "label": "Custom Widgets", - "collapsed": true, - "position": 8, - "link": { - "type": "doc", - "id": "management/custom-widgets/index" - } -} diff --git a/apps/docs/docs/management/custom-widgets/agent-authoring.mdx b/apps/docs/docs/management/custom-widgets/agent-authoring.mdx index 64e1b86bd..2e6c70e82 100644 --- a/apps/docs/docs/management/custom-widgets/agent-authoring.mdx +++ b/apps/docs/docs/management/custom-widgets/agent-authoring.mdx @@ -41,5 +41,5 @@ authenticated administrator completes it in Homarr; the agent receives the statu Agents can also search, inspect, install, and configure existing Workshop widgets instead of regenerating them. -See [MCP](/docs/management/mcp) for client configuration and [requests and security](./requests-and-security) for the +See [MCP](/docs/management/mcp) for client configuration and [requests and security](/docs/management/custom-widgets/requests-and-security) for the runtime limits. diff --git a/apps/docs/docs/management/custom-widgets/authoring.mdx b/apps/docs/docs/management/custom-widgets/authoring.mdx index 809371d70..d2257303b 100644 --- a/apps/docs/docs/management/custom-widgets/authoring.mdx +++ b/apps/docs/docs/management/custom-widgets/authoring.mdx @@ -17,8 +17,9 @@ Changing the definition, options, or credentials invalidates the previous previe Widget options support text, textarea, number, switch, select, multi-select, slider, date, time, color, icon, URL, duration, time zone, and JSON controls. Dynamic choices can come from a load query. -See the [safe JSX runtime](./custom-jsx), [component reference](./component-reference), and -[request reference](./requests-and-security) for the authoring contract. +See the [safe JSX runtime](/docs/management/custom-widgets/custom-jsx), +[component reference](/docs/management/custom-widgets/component-reference), and +[request reference](/docs/management/custom-widgets/requests-and-security) for the authoring contract. ## Create with another AI service @@ -26,4 +27,4 @@ See the [safe JSX runtime](./custom-jsx), [component reference](./component-refe must return one fenced `json` block containing the complete definition. Paste that block into the workbench, add credentials separately, and validate it against a real preview. -For an MCP-connected agent, use the [Homarr Custom Widget skill](./agent-authoring) instead. +For an MCP-connected agent, use the [Homarr Custom Widget skill](/docs/management/custom-widgets/agent-authoring) instead. diff --git a/apps/docs/docs/management/custom-widgets/index.mdx b/apps/docs/docs/management/custom-widgets/index.mdx index aa8886119..4f6480be2 100644 --- a/apps/docs/docs/management/custom-widgets/index.mdx +++ b/apps/docs/docs/management/custom-widgets/index.mdx @@ -35,12 +35,12 @@ boards; actions require authentication and the permission declared by the reques ## Authoring -- [Create a Custom Widget](./authoring) in the workbench or from a copied AI prompt. -- [Use the safe JSX runtime](./custom-jsx) for data bindings, temporary inputs, and components. -- [Browse the component reference](./component-reference). -- [Configure requests and security](./requests-and-security). -- [Connect an agent](./agent-authoring) through MCP and the Homarr skill. -- [Troubleshoot a definition](./troubleshooting). +- [Create a Custom Widget](/docs/management/custom-widgets/authoring) in the workbench or from a copied AI prompt. +- [Use the safe JSX runtime](/docs/management/custom-widgets/custom-jsx) for data bindings, temporary inputs, and components. +- [Browse the component reference](/docs/management/custom-widgets/component-reference). +- [Configure requests and security](/docs/management/custom-widgets/requests-and-security). +- [Connect an agent](/docs/management/custom-widgets/agent-authoring) through MCP and the Homarr skill. +- [Troubleshoot a definition](/docs/management/custom-widgets/troubleshooting). The workbench validates the same schema used by imports, Workshop, HTTP authoring resources, and MCP. Test every query and simulate relevant actions before saving. Live preview actions are opt-in because they can change the source service. diff --git a/apps/docs/docs/management/custom-widgets/meta.json b/apps/docs/docs/management/custom-widgets/meta.json new file mode 100644 index 000000000..854c878c9 --- /dev/null +++ b/apps/docs/docs/management/custom-widgets/meta.json @@ -0,0 +1,12 @@ +{ + "title": "Custom Widgets", + "pages": [ + "index", + "authoring", + "agent-authoring", + "custom-jsx", + "component-reference", + "requests-and-security", + "troubleshooting" + ] +} diff --git a/apps/docs/docs/management/custom-widgets/troubleshooting.mdx b/apps/docs/docs/management/custom-widgets/troubleshooting.mdx index 07bd0c2fc..48a281eb2 100644 --- a/apps/docs/docs/management/custom-widgets/troubleshooting.mdx +++ b/apps/docs/docs/management/custom-widgets/troubleshooting.mdx @@ -39,7 +39,7 @@ stale cached query on another replica. ## Runtime warnings `UNKNOWN_MANTINE_PROP` means the installed Mantine component does not document that property. Check the -[component reference](./component-reference). `BLOCKED_CAPABILITY` means the runtime rejected an unsafe component, +[component reference](/docs/management/custom-widgets/component-reference). `BLOCKED_CAPABILITY` means the runtime rejected an unsafe component, property, URL, or style capability. ## Workshop unavailable diff --git a/apps/docs/docs/management/index.mdx b/apps/docs/docs/management/index.mdx new file mode 100644 index 000000000..dce269650 --- /dev/null +++ b/apps/docs/docs/management/index.mdx @@ -0,0 +1,82 @@ +--- +title: Manage Homarr +description: Configure boards, content, access, automation, and server-wide behavior. +--- + +import { + IconApi, + IconApps, + IconCertificate, + IconChecklist, + IconComponents, + IconDatabaseExport, + IconLayoutDashboard, + IconLogs, + IconPhoto, + IconPlugConnected, + IconRobot, + IconSearch, + IconSettings, + IconTerminal2, + IconUsers, +} from "@tabler/icons-react"; + +## Everyday configuration + + + } title="Boards" href="/docs/management/boards"> + Create, organize, share, and configure boards. + + } title="Apps" href="/docs/management/apps"> + Add the services and links shown on boards. + + } title="Integrations" href="/docs/management/integrations"> + Configure reusable service connections. + + } title="Users and groups" href="/docs/management/users"> + Control access and permissions. + + } title="Settings" href="/docs/management/settings"> + Change server and user preferences. + + + +## Content and extensions + + + } title="Media" href="/docs/management/media"> + Manage uploaded assets. + + } title="Search engines" href="/docs/management/search-engines"> + Configure search shortcuts. + + } title="Custom Widgets" href="/docs/management/custom-widgets"> + Build Custom JSX widgets. + + } title="Assistant" href="/docs/management/assistant"> + Configure Homarr's assistant features. + + + +## Operations + + + } title="API" href="/docs/management/api"> + Configure API keys and use the generated reference. + + } title="Model Context Protocol" href="/docs/management/mcp"> + Connect external assistants to Homarr. + + } title="Tasks" href="/docs/management/tasks"> + Inspect scheduled and background tasks. + + } title="Logs" href="/docs/management/logs"> + Inspect application logs. + + } title="Certificates" href="/docs/management/certificates"> + Trust certificates used by connected services. + + } title="Backups" href="/docs/management/backup"> + Export and restore Homarr data. + + diff --git a/apps/docs/docs/management/integrations/index.mdx b/apps/docs/docs/management/integrations/index.mdx index 2bb019883..794bcfc67 100644 --- a/apps/docs/docs/management/integrations/index.mdx +++ b/apps/docs/docs/management/integrations/index.mdx @@ -14,7 +14,7 @@ actions to widgets and can be linked to apps. ![The integration catalog](../img/integrations-catalog.png) -See the [integration catalog](/docs/category/integrations) for service-specific URLs, credentials, and supported +See the [integration catalog](/docs/integrations) for service-specific URLs, credentials, and supported widgets. ## Browser and server routes @@ -57,6 +57,6 @@ If a connection test fails: 1. Check the integration page for the required base URL and credentials. 2. Test name resolution and HTTP access from the Homarr container or pod, not only from the host or browser. 3. Check Homarr and upstream-service logs, then any reverse proxy, firewall, VLAN, VPN, or DNS between them. -4. For a self-signed certificate, add the expected certificate under [Certificates](../certificates). +4. For a self-signed certificate, add the expected certificate under [Certificates](/docs/management/certificates). Do not disable upstream security controls as a general troubleshooting step. diff --git a/apps/docs/docs/management/mcp.mdx b/apps/docs/docs/management/mcp.mdx index 36683a39f..19a9e4c10 100644 --- a/apps/docs/docs/management/mcp.mdx +++ b/apps/docs/docs/management/mcp.mdx @@ -85,8 +85,8 @@ Homarr procedures. Queries are read-only; mutations change state. The exact cata user permissions, so inspect `tools/list` or the table in Homarr instead of relying on a static list. For Custom Widgets, install the official skill and follow the preview-and-evidence workflow described in -[Connect an agent](./custom-widgets/agent-authoring). Secret values are configured in Homarr and are never returned +[Connect an agent](/docs/management/custom-widgets/agent-authoring). Secret values are configured in Homarr and are never returned through MCP. -The built-in [Assistant](./assistant) uses the same tool catalog with the current Homarr session and asks for approval +The built-in [Assistant](/docs/management/assistant) uses the same tool catalog with the current Homarr session and asks for approval before mutations by default. diff --git a/apps/docs/docs/management/meta.json b/apps/docs/docs/management/meta.json new file mode 100644 index 000000000..fb9610243 --- /dev/null +++ b/apps/docs/docs/management/meta.json @@ -0,0 +1,25 @@ +{ + "title": "Management", + "defaultOpen": false, + "pages": [ + "index", + "---Everyday configuration---", + "boards", + "apps", + "integrations", + "users", + "settings", + "---Content and extensions---", + "search-engines", + "media", + "custom-widgets", + "assistant", + "---Operations---", + "api", + "mcp", + "tasks", + "logs", + "certificates", + "backup" + ] +} diff --git a/apps/docs/docs/management/users/index.mdx b/apps/docs/docs/management/users/index.mdx index 00fc8ac67..7697af013 100644 --- a/apps/docs/docs/management/users/index.mdx +++ b/apps/docs/docs/management/users/index.mdx @@ -62,7 +62,7 @@ specific users or groups: - Board access uses View, Modify, and Full levels. - Integration access uses Use, Interact, and Full levels. -See [Boards](../boards#access-control) and [Integrations](../integrations#permissions). +See [Boards](/docs/management/boards#access-control) and [Integrations](/docs/management/integrations#permissions). ## Invitations diff --git a/apps/docs/docs/meta.json b/apps/docs/docs/meta.json new file mode 100644 index 000000000..037ef2ec0 --- /dev/null +++ b/apps/docs/docs/meta.json @@ -0,0 +1,3 @@ +{ + "pages": ["index", "getting-started", "management", "integrations", "widgets", "workshop", "advanced", "community"] +} diff --git a/apps/docs/docs/widgets/_category_.json b/apps/docs/docs/widgets/_category_.json deleted file mode 100644 index df8d80354..000000000 --- a/apps/docs/docs/widgets/_category_.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "label": "Widgets", - "position": 3, - "collapsed": true, - "link": { - "type": "generated-index", - "title": "Widgets", - "description": "Widgets allow you to show data on your board or control applications. Click on one of the widgets below for further information:", - "image": "/img/docusaurus.png" - } -} diff --git a/apps/docs/docs/widgets/firewall/index.mdx b/apps/docs/docs/widgets/firewall/index.mdx index 856331f40..42faec6d0 100644 --- a/apps/docs/docs/widgets/firewall/index.mdx +++ b/apps/docs/docs/widgets/firewall/index.mdx @@ -9,8 +9,8 @@ import { WidgetInteractionGuide } from "@site/src/components/widgets/interaction import { WidgetHeader } from "@site/src/components/widgets/header"; import { AddingWidget } from "@site/src/components/widgets/adding"; import { WidgetIntegrations } from "@site/src/components/widgets/integrations"; -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; +import Tabs from "@/components/mdx/legacy-tabs"; +import { TabItem } from "@/components/mdx/legacy-tabs"; import { firewallWidget } from "."; import { opnsenseIntegration } from "../../integrations/opnsense"; diff --git a/apps/docs/docs/widgets/health-monitoring/index.mdx b/apps/docs/docs/widgets/health-monitoring/index.mdx index f1a50c3a3..245de6905 100644 --- a/apps/docs/docs/widgets/health-monitoring/index.mdx +++ b/apps/docs/docs/widgets/health-monitoring/index.mdx @@ -11,8 +11,8 @@ import { AddingWidget } from "@site/src/components/widgets/adding"; import { WidgetConfig } from "@site/src/components/widgets/configuration"; import { WidgetIntegrations } from "@site/src/components/widgets/integrations"; import { IconAd } from "@tabler/icons-react"; -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; +import Tabs from "@/components/mdx/legacy-tabs"; +import { TabItem } from "@/components/mdx/legacy-tabs"; import { healthMonitoringWidget } from "."; import { dashDotIntegration } from "@site/docs/integrations/dash-dot"; import { openMediaVaultIntegration } from "@site/docs/integrations/open-media-vault"; diff --git a/apps/docs/docs/widgets/index.mdx b/apps/docs/docs/widgets/index.mdx new file mode 100644 index 000000000..7d28f623b --- /dev/null +++ b/apps/docs/docs/widgets/index.mdx @@ -0,0 +1,21 @@ +--- +title: Widgets +description: Configure the information and controls shown on Homarr boards. +--- + +import { DocsDirectory } from "@/components/docs/docs-directory"; + +Widgets turn apps, integrations, and Homarr data into useful board content. Some work on their own; others require one or more configured integrations. + +Each widget guide explains its configuration, required integrations, supported interactions, permissions, and important empty or error states. + +## Add a widget + +1. Open a board and enter edit mode. +2. Select **Add item**, then choose **Widget**. +3. Pick a widget and complete its configuration. +4. Move or resize it, then leave edit mode to use it. + + + +See [After installation](/docs/getting-started/after-the-installation) for the complete first-board workflow. diff --git a/apps/docs/docs/widgets/media-request-list/index.mdx b/apps/docs/docs/widgets/media-request-list/index.mdx index 9a4599a3d..33f408258 100644 --- a/apps/docs/docs/widgets/media-request-list/index.mdx +++ b/apps/docs/docs/widgets/media-request-list/index.mdx @@ -11,8 +11,8 @@ import { AddingWidget } from "@site/src/components/widgets/adding"; import { WidgetConfig } from "@site/src/components/widgets/configuration"; import { WidgetIntegrations } from "@site/src/components/widgets/integrations"; import { IconAd } from "@tabler/icons-react"; -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; +import Tabs from "@/components/mdx/legacy-tabs"; +import { TabItem } from "@/components/mdx/legacy-tabs"; import { mediaRequestListWidget } from "."; import { jellyseerrIntegration } from "@site/docs/integrations/jellyseerr"; import { overseerrIntegration } from "@site/docs/integrations/overseerr"; diff --git a/apps/docs/docs/widgets/media-request-stats/index.mdx b/apps/docs/docs/widgets/media-request-stats/index.mdx index 7a06bf365..1fdacfd0b 100644 --- a/apps/docs/docs/widgets/media-request-stats/index.mdx +++ b/apps/docs/docs/widgets/media-request-stats/index.mdx @@ -11,8 +11,8 @@ import { AddingWidget } from "@site/src/components/widgets/adding"; import { WidgetConfig } from "@site/src/components/widgets/configuration"; import { WidgetIntegrations } from "@site/src/components/widgets/integrations"; import { IconAd } from "@tabler/icons-react"; -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; +import Tabs from "@/components/mdx/legacy-tabs"; +import { TabItem } from "@/components/mdx/legacy-tabs"; import { mediaRequestStatsWidget } from "."; import { jellyseerrIntegration } from "@site/docs/integrations/jellyseerr"; import { overseerrIntegration } from "@site/docs/integrations/overseerr"; diff --git a/apps/docs/docs/widgets/media-transcoding/index.mdx b/apps/docs/docs/widgets/media-transcoding/index.mdx index 7edde95ca..7fa198165 100644 --- a/apps/docs/docs/widgets/media-transcoding/index.mdx +++ b/apps/docs/docs/widgets/media-transcoding/index.mdx @@ -11,8 +11,8 @@ import { AddingWidget } from "@site/src/components/widgets/adding"; import { WidgetConfig } from "@site/src/components/widgets/configuration"; import { WidgetIntegrations } from "@site/src/components/widgets/integrations"; import { IconAd } from "@tabler/icons-react"; -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; +import Tabs from "@/components/mdx/legacy-tabs"; +import { TabItem } from "@/components/mdx/legacy-tabs"; import { mediaTranscodingWidget } from "."; import { tdarrIntegration } from "@site/docs/integrations/tdarr"; diff --git a/apps/docs/docs/widgets/meta.json b/apps/docs/docs/widgets/meta.json new file mode 100644 index 000000000..7ba8991e3 --- /dev/null +++ b/apps/docs/docs/widgets/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Widgets", + "defaultOpen": false, + "pages": ["index", "..."] +} diff --git a/apps/docs/docs/widgets/minecraft-server-status/index.mdx b/apps/docs/docs/widgets/minecraft-server-status/index.mdx index e66344c85..7d5c02af0 100644 --- a/apps/docs/docs/widgets/minecraft-server-status/index.mdx +++ b/apps/docs/docs/widgets/minecraft-server-status/index.mdx @@ -11,8 +11,8 @@ import { WidgetConfig } from "@site/src/components/widgets/configuration"; import { WidgetHeader } from "@site/src/components/widgets/header"; import { WidgetIntegrations } from "@site/src/components/widgets/integrations"; import { IconAd } from "@tabler/icons-react"; -import TabItem from "@theme/TabItem"; -import Tabs from "@theme/Tabs"; +import { TabItem } from "@/components/mdx/legacy-tabs"; +import Tabs from "@/components/mdx/legacy-tabs"; import { minecraftServerStatusWidget } from "."; diff --git a/apps/docs/docs/workshop/_category_.json b/apps/docs/docs/workshop/_category_.json deleted file mode 100644 index fb3d29a08..000000000 --- a/apps/docs/docs/workshop/_category_.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "label": "Workshop", - "collapsed": true, - "position": 4, - "link": { - "type": "doc", - "id": "workshop/index" - } -} diff --git a/apps/docs/docs/workshop/index.mdx b/apps/docs/docs/workshop/index.mdx index 1be8b1f48..5b29b5289 100644 --- a/apps/docs/docs/workshop/index.mdx +++ b/apps/docs/docs/workshop/index.mdx @@ -11,7 +11,7 @@ import { WorkshopFlowDiagram } from "@site/src/components/pages/core-concepts/te # Community Workshop -The [Community Workshop](pathname:///workshop) hosts community Custom Widgets and dashboard CSS. +The [Community Workshop](/workshop) hosts community Custom Widgets and dashboard CSS. :::warning Experimental @@ -23,7 +23,7 @@ Workshop content is community-maintained. Review its source and permissions befo -Workshop also exposes the [Homarr Assistant provider](./homarr-provider). Its model-routing and quota path is separate +Workshop also exposes the [Homarr Assistant provider](/docs/workshop/homarr-provider). Its model-routing and quota path is separate from Custom Widget and CSS submissions. ## Install content diff --git a/apps/docs/docs/workshop/meta.json b/apps/docs/docs/workshop/meta.json new file mode 100644 index 000000000..227e733cc --- /dev/null +++ b/apps/docs/docs/workshop/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Workshop", + "defaultOpen": false, + "pages": ["index", "homarr-provider"] +} diff --git a/apps/docs/docsearch.config.js b/apps/docs/docsearch.config.js deleted file mode 100644 index 2cf493485..000000000 --- a/apps/docs/docsearch.config.js +++ /dev/null @@ -1,134 +0,0 @@ -// Algolia DocSearch crawler for homarr.dev -// Crawler ID: cd77a285-2756-4557-bf21-ee703748df15 -// Paste this in the Algolia crawler editor. - -new Crawler({ - appId: "N69WSPZTID", - apiKey: "YOUR_CRAWLER_ADMIN_API_KEY", - indexPrefix: "", - rateLimit: 32, - maxDepth: 4, - startUrls: ["https://homarr.dev/"], - sitemaps: ["https://homarr.dev/sitemap.xml"], - ignoreCanonicalTo: true, - discoveryPatterns: ["https://homarr.dev/**"], - exclusionPatterns: [ - "https://homarr.dev/docs/tags/**", - "https://homarr.dev/blog/tags/**", - "https://homarr.dev/blog/authors/**", - "https://homarr.dev/blog/archive/**", - "https://homarr.dev/search/**", - "https://homarr.dev/docs/category/**", - ], - actions: [ - { - indexName: "Docusaurus", - pathsToMatch: ["https://homarr.dev/**"], - recordExtractor: ({ $, helpers }) => { - const lvl0 = - $(".menu__link.menu__link--sublist.menu__link--active, .navbar__item.navbar__link--active").last().text() || - "Documentation"; - - return helpers.docsearch({ - recordProps: { - lvl0: { - selectors: "", - defaultValue: lvl0, - }, - lvl1: ["header h1", "article h1"], - lvl2: "article h2", - lvl3: "article h3", - lvl4: "article h4", - lvl5: "article h5, article td:first-child", - lvl6: "article h6", - content: "article p, article li, article td:last-child", - }, - indexHeadings: true, - aggregateContent: true, - recordVersion: "v3", - }); - }, - }, - { - indexName: "markdown", - pathsToMatch: ["https://homarr.dev/**"], - recordExtractor: ({ $, url, helpers }) => { - const text = helpers.markdown("article > *:not(nav):not(header):not(.breadcrumb)"); - - if (text === "") { - return []; - } - - const language = $("html").attr("lang") || "en"; - const title = $("head > title").text(); - const h1 = $("article h1").first().text(); - - return helpers.splitTextIntoRecords({ - text, - baseRecord: { - url, - objectID: url, - title: title || h1, - heading: h1, - lang: language, - language, - version: "current", - docusaurus_tag: "docs-default-current", - }, - maxRecordBytes: 100000, - orderingAttributeName: "part", - }); - }, - }, - ], - initialIndexSettings: { - Docusaurus: { - attributesForFaceting: ["type", "lang", "language", "version", "docusaurus_tag"], - attributesToRetrieve: ["hierarchy", "content", "anchor", "url", "url_without_anchor", "type"], - attributesToHighlight: ["hierarchy", "content"], - attributesToSnippet: ["content:10"], - camelCaseAttributes: ["hierarchy", "content"], - searchableAttributes: [ - "unordered(hierarchy.lvl0)", - "unordered(hierarchy.lvl1)", - "unordered(hierarchy.lvl2)", - "unordered(hierarchy.lvl3)", - "unordered(hierarchy.lvl4)", - "unordered(hierarchy.lvl5)", - "unordered(hierarchy.lvl6)", - "content", - ], - distinct: true, - attributeForDistinct: "url", - customRanking: ["desc(weight.pageRank)", "desc(weight.level)", "asc(weight.position)"], - ranking: ["words", "filters", "typo", "attribute", "proximity", "exact", "custom"], - highlightPreTag: '', - highlightPostTag: "", - minWordSizefor1Typo: 3, - minWordSizefor2Typos: 7, - allowTyposOnNumericTokens: false, - minProximity: 1, - ignorePlurals: true, - advancedSyntax: true, - attributeCriteriaComputedByMinProximity: true, - removeWordsIfNoResults: "allOptional", - }, - markdown: { - attributesForFaceting: ["lang", "language", "version", "docusaurus_tag"], - ignorePlurals: true, - minProximity: 1, - removeStopWords: false, - searchableAttributes: ["title", "heading", "unordered(text)"], - removeWordsIfNoResults: "lastWords", - attributesToHighlight: ["title", "text"], - typoTolerance: false, - advancedSyntax: false, - }, - }, - apiKey: "YOUR_CRAWLER_ADMIN_API_KEY", - schedule: "on saturday", - renderJavaScript: false, - maxUrls: null, - cache: { enabled: false }, - safetyChecks: { beforeIndexPublishing: { maxLostRecordsPercentage: 99 } }, -}); diff --git a/apps/docs/docusaurus.config.ts b/apps/docs/docusaurus.config.ts deleted file mode 100644 index cf7dcc354..000000000 --- a/apps/docs/docusaurus.config.ts +++ /dev/null @@ -1,422 +0,0 @@ -import type * as Preset from "@docusaurus/preset-classic"; -import type { Config } from "@docusaurus/types"; -import { themes as prismThemes } from "prism-react-renderer"; -import { resolveHomarrUrlConfig } from "@homarr/workshop/schema"; -const a11yEmoji = require("@fec/remark-a11y-emoji"); - -const deprecatedWorkshopUrl = process.env.WORKSHOP_URL; -if (deprecatedWorkshopUrl && !process.env.WORKSHOP_API_URL) { - console.warn("WORKSHOP_URL is deprecated; use WORKSHOP_API_URL instead."); -} - -const publicUrls = resolveHomarrUrlConfig({ - homarrWebsiteUrl: process.env.HOMARR_WEBSITE_URL, - workshopApiUrl: process.env.WORKSHOP_API_URL ?? deprecatedWorkshopUrl, - workshopWebUrl: process.env.WORKSHOP_WEB_URL, -}); -const config: Config = { - title: "Homarr documentation", - tagline: "A simple yet powerful dashboard for your server.", - url: publicUrls.homarrWebsiteUrl, - baseUrl: "/", - trailingSlash: undefined, - favicon: "img/logo.png", - organizationName: "homarr-labs", - projectName: "homarr", - customFields: { - workshopUrl: publicUrls.workshopApiUrl, - workshopWebUrl: publicUrls.workshopWebUrl, - }, - scripts: [{ src: "/workshop-runtime-config.js" }], - i18n: { - defaultLocale: "en", - locales: ["en"], - }, - onBrokenLinks: "throw", - onBrokenAnchors: "throw", - onDuplicateRoutes: "throw", - - future: { - v4: { - removeLegacyPostBuildHeadAttribute: true, - useCssCascadeLayers: true, - siteStorageNamespacing: true, - fasterByDefault: true, - mdx1CompatDisabledByDefault: false, - }, - faster: { - swcHtmlMinimizer: false, - }, - }, - - markdown: { - mermaid: true, - format: "detect", - hooks: { - onBrokenMarkdownLinks: "throw", - }, - }, - - themes: ["@docusaurus/theme-mermaid"], - - presets: [ - [ - "classic", - { - docs: { - sidebarPath: require.resolve("./sidebars.js"), - editUrl: ({ docPath }) => `https://github.com/homarr-labs/homarr/edit/dev/apps/docs/docs/${docPath}`, - remarkPlugins: [a11yEmoji], - exclude: [], - showLastUpdateAuthor: false, - showLastUpdateTime: false, - }, - blog: { - showReadingTime: true, - // Please change this to your repo. - // Remove this to remove the "edit this page" links. - editUrl: "https://github.com/homarr-labs/homarr/edit/dev/apps/docs", - authorsMapPath: "authors.yml", - }, - theme: { - customCss: require.resolve("./src/css/custom.css"), - }, - sitemap: { - changefreq: "weekly", - priority: 0.5, - ignorePatterns: ["/tags/**", "/docs/category/**"], - filename: "sitemap.xml", - createSitemapItems: async ({ routes, siteConfig, defaultCreateSitemapItems }) => { - const items = await defaultCreateSitemapItems({ routes, siteConfig }); - return items.map((item) => { - const path = new URL(item.url).pathname; - if (path === "/" || path === "") { - return { ...item, priority: 1.0, changefreq: "weekly" }; - } - if (path.startsWith("/docs/")) { - return { ...item, priority: 0.7, changefreq: "monthly" }; - } - if (path.startsWith("/blog/")) { - return { ...item, priority: 0.3, changefreq: "never" }; - } - if (path.startsWith("/about-us")) { - return { ...item, priority: 0.4, changefreq: "yearly" }; - } - return item; - }); - }, - }, - } satisfies Preset.Options, - ], - ], - - themeConfig: { - navbar: { - title: "Homarr", - logo: { - alt: "Homarr Logo", - src: "img/logo.png", - }, - items: [ - { - label: "Documentation", - type: "doc", - position: "left", - docId: "getting-started/index", - }, - { - label: "Blog", - position: "left", - to: "/blog", - className: "navbar-item--secondary", - }, - { - label: "Workshop", - position: "left", - to: "/workshop", - }, - { - label: "About us", - position: "left", - to: "/about-us", - className: "navbar-item--collapse-first", - }, - { - to: "https://demo.homarr.dev/", - label: "Demo", - position: "right", - className: "navbar-item--secondary", - }, - { - to: "https://opencollective.com/homarr", - label: "💴 Donate", - position: "right", - className: "navbar-item--collapse-first", - }, - { - type: "dropdown", - label: "Community", - position: "right", - items: [ - { - to: "https://discord.com/invite/aCsmEV5RgA", - label: "Discord", - }, - { - to: "https://github.com/homarr-labs/homarr", - label: "GitHub", - }, - { - to: "https://www.answeroverflow.com/c/972958686051962910", - label: "Answer Overflow", - }, - { - to: "https://crowdin.com/project/homarr_labs", - label: "Community translations (Crowdin)", - }, - { - to: "https://www.reddit.com/r/homarr/", - label: "Reddit", - }, - { - to: "https://opencollective.com/homarr", - label: "OpenCollective", - }, - { - to: "https://x.com/homarr_labs", - label: "X / Twitter", - }, - ], - }, - { - type: "search", - position: "right", - }, - ], - hideOnScroll: false, - }, - algolia: { - appId: "N69WSPZTID", - apiKey: "b2b00f4ed8ca3dc87b5d211c55121416", - indexName: "Docusaurus", - contextualSearch: false, - searchPagePath: "search", - insights: false, - replaceSearchResultPathname: { - from: "/docs/(next|\\d+(?:\\.\\d+)*)/", - to: "/docs/", - }, - }, - footer: { - links: [ - { - title: "Documentation", - items: [ - { - label: "Installation", - to: "/docs/category/getting-started", - }, - ], - }, - { - title: "Community", - items: [ - { - label: "Discord", - to: "https://discord.com/invite/aCsmEV5RgA", - }, - { - label: "GitHub", - to: "https://github.com/homarr-labs/homarr", - }, - { - label: "Donate", - to: "https://opencollective.com/homarr", - }, - ], - }, - { - title: "More", - items: [ - { - label: "Blog", - to: "/blog", - }, - { - label: "About us", - to: "/about-us", - }, - ], - }, - ], - logo: { - alt: "Homarr home", - src: "img/logo.svg", - href: "/", - width: 86, - height: 58, - }, - copyright: ` - - `, - }, - prism: { - theme: prismThemes.github, - darkTheme: prismThemes.dracula, - defaultLanguage: "bash", - }, - colorMode: { - defaultMode: "dark", - respectPrefersColorScheme: true, - }, - metadata: [ - { - name: "keywords", - content: "Homarr, Dashboard, Selfhosted, Hosting, Modules, Open-Source", - }, - ], - zoom: { - selector: ".markdown :not(em) > img", - background: { - light: "rgb(255, 255, 255)", - dark: "rgb(50, 50, 50)", - }, - config: { - // options you can specify via https://github.com/francoischalifour/medium-zoom#usage - margin: 80, - }, - }, - tableOfContents: { - minHeadingLevel: 2, - maxHeadingLevel: 4, - }, - } satisfies Preset.ThemeConfig, - plugins: [ - function homarrPackagesPlugin() { - return { - name: "resolve-homarr-packages", - configureWebpack(_config, isServer, { getJSLoader }) { - return { - resolve: { - symlinks: false, - alias: { "@": require("path").resolve(__dirname, "src") }, - }, - module: { - rules: [ - { - test: /\.[jt]sx?$/iu, - include: /node_modules[\\/]@homarr/u, - use: [getJSLoader({ isServer })], - }, - ], - }, - }; - }, - }; - }, - [ - "posthog-docusaurus", - { - apiKey: "phc_pWxeD1hbl4ip02JYReX1Crjkt5DhB3dduigirHMCtFE", - appUrl: "https://hog.homarr.dev", - enableInDevelopment: true, - ui_host: "https://eu.posthog.com", - defaults: "2026-01-30", - autocapture: true, - disable_session_recording: true, - advanced_disable_feature_flags: true, - }, - ], - "docusaurus-plugin-image-zoom", - require.resolve("./plugins/validate-docs-coverage"), - function disableExpensiveBundlerOptimizationPlugin() { - return { - name: "disable-expensive-bundler-optimizations", - configureWebpack(_config: unknown, isServer: boolean) { - return { - optimization: { - concatenateModules: process.env.CI != null && process.env.CI !== "false" ? !isServer : false, - }, - }; - }, - }; - }, - function workshopRoutesPlugin() { - return { - name: "workshop-routes", - injectHtmlTags() { - return { - headTags: [ - { - tagName: "style", - innerHTML: ` -#workshop-detail-route-fallback { display: none; } -html[data-workshop-detail-loading] #__docusaurus { display: none; } -html[data-workshop-detail-loading] #workshop-detail-route-fallback { - align-items: center; - background: #fff; - box-sizing: border-box; - color: #4b5563; - display: flex; - font: 500 0.875rem/1.5 "Work Sans", system-ui, sans-serif; - justify-content: center; - min-height: 100vh; - padding: 2rem; -} -@media (prefers-color-scheme: dark) { - html[data-workshop-detail-loading] #workshop-detail-route-fallback { - background: #18191a; - color: #d1d5db; - } -}`, - }, - { - tagName: "script", - innerHTML: `(function () { - var match = window.location.pathname.match(/^\\/workshop\\/([^/]+)\\/?$/); - if (match && match[1] !== "admin") { - document.documentElement.setAttribute("data-workshop-detail-loading", ""); - } -})();`, - }, - ], - preBodyTags: [ - { - tagName: "div", - attributes: { - id: "workshop-detail-route-fallback", - role: "status", - "aria-live": "polite", - }, - innerHTML: "Loading Workshop…", - }, - ], - }; - }, - async contentLoaded({ actions }) { - actions.addRoute({ - path: "/workshop/:id", - component: "@site/src/components/workshop/WorkshopDetailRoutePage", - exact: true, - priority: -1, - }); - }, - }; - }, - async function tailwindCssPlugin() { - return { - name: "docusaurus-tailwindcss", - configurePostCss(postcssOptions) { - postcssOptions.plugins.push(require("@tailwindcss/postcss")); - return postcssOptions; - }, - }; - }, - ], -}; - -export default config; diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs new file mode 100644 index 000000000..7f821dd80 --- /dev/null +++ b/apps/docs/next.config.mjs @@ -0,0 +1,16 @@ +import { createMDX } from "fumadocs-mdx/next"; + +const withMDX = createMDX(); + +/** @type {import("next").NextConfig} */ +const config = { + output: "export", + reactStrictMode: true, + trailingSlash: true, + images: { + unoptimized: true, + }, + transpilePackages: ["@homarr/custom-widgets", "@homarr/definitions", "@homarr/workshop"], +}; + +export default withMDX(config); diff --git a/apps/docs/package.json b/apps/docs/package.json index ce8dd21ca..82530655d 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -3,37 +3,28 @@ "version": "0.2.3", "private": true, "license": "Apache-2.0", + "type": "module", "scripts": { - "docusaurus": "docusaurus", - "dev": "docusaurus start --no-open --port 3003", - "start": "docusaurus start --port 3003", - "build": "docusaurus build", - "serve": "docusaurus serve", - "clear": "docusaurus clear", - "typecheck": "tsc --noEmit", + "dev": "next dev --port 3003", + "start": "serve out --listen 3003", + "build": "pnpm validate:coverage && next build", + "typecheck": "next typegen && tsc --noEmit", "lint": "oxlint", "format": "oxfmt .", - "clean": "rm -rf build .docusaurus", - "swizzle": "docusaurus swizzle", - "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids", + "clean": "git clean -xdf .next out node_modules", "api-dump-contributors": "node ./scripts/dump-contributors.mjs", - "verify:search": "node ./scripts/verify-algolia-search.mjs" + "validate:coverage": "tsx ./scripts/validate-docs-coverage.ts", + "validate:links": "tsx ./scripts/validate-links.ts" }, "dependencies": { "@base-ui/react": "^1.5.0", - "@docusaurus/core": "^3.10.1", - "@docusaurus/faster": "^3.10.1", - "@docusaurus/plugin-content-docs": "^3.10.1", - "@docusaurus/plugin-sitemap": "^3.10.1", - "@docusaurus/preset-classic": "^3.10.1", "@homarr/custom-widgets": "workspace:^", "@homarr/definitions": "workspace:^", "@homarr/workshop": "workspace:^", "@mantine/core": "catalog:", "@mdx-js/react": "^3.1.1", "@nivo/line": "^0.99.0", - "@scalar/api-reference-react": "catalog:", + "@scalar/api-client-react": "catalog:", "@shadcn/react": "catalog:", "@tabler/icons-react": "catalog:", "@tailwindcss/postcss": "^4.3.2", @@ -42,51 +33,43 @@ "cmdk": "catalog:", "date-fns": "catalog:", "dayjs": "catalog:", - "docusaurus-plugin-image-zoom": "^3.0.1", "embla-carousel-react": "catalog:", + "feed": "catalog:", + "fumadocs-core": "catalog:", + "fumadocs-mdx": "catalog:", + "fumadocs-openapi": "catalog:", + "fumadocs-ui": "catalog:", "input-otp": "catalog:", - "leader-line-new": "^1.1.9", + "json-schema-typed": "catalog:", + "mermaid": "catalog:", + "next": "catalog:", "next-themes": "catalog:", "pocketbase": "catalog:", "postcss": "^8.5.16", - "posthog-docusaurus": "^2.0.5", - "prism-react-renderer": "^2.4.1", + "posthog-js": "catalog:", "react": "catalog:", "react-day-picker": "catalog:", "react-dom": "catalog:", "react-resizable-panels": "catalog:", "recharts": "3.8.0", + "remark-directive": "catalog:", + "shiki": "catalog:", "sonner": "catalog:", "swiper": "^14.0.5", "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.2" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.10.1", - "@docusaurus/tsconfig": "^3.10.1", - "@docusaurus/types": "^3.10.1", "@fec/remark-a11y-emoji": "^4.0.2", - "@tsconfig/docusaurus": "^2.0.9", + "@types/mdx": "catalog:", + "@types/node": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", "cheerio": "^1.2.0", + "next-validate-link": "1.6.7", + "serve": "catalog:", + "tsx": "catalog:", "typescript": "catalog:", "zod": "catalog:" - }, - "peerDependencies": { - "@docusaurus/theme-common": "^3.10.1", - "@docusaurus/theme-mermaid": "^3.10.1" - }, - "browserslist": { - "production": [ - ">0.5%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] } } diff --git a/apps/docs/plugins/validate-docs-coverage.ts b/apps/docs/plugins/validate-docs-coverage.ts deleted file mode 100644 index d677a22f7..000000000 --- a/apps/docs/plugins/validate-docs-coverage.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { LoadContext, Plugin } from "@docusaurus/types"; -import fs from "node:fs"; -import path from "node:path"; - -import { widgetDocSlugs } from "@homarr/definitions/docs/widget-doc-slugs"; -import { getIntegrationDocumentationSlug, integrationKinds } from "@homarr/definitions/integration"; - -const documentationEntries = [ - { - label: "Integration", - folder: "integrations", - entries: integrationKinds.map((kind) => [kind, getIntegrationDocumentationSlug(kind)] as const), - }, - { label: "Widget", folder: "widgets", entries: Object.entries(widgetDocSlugs) }, -] as const; - -export default function validateDocsCoveragePlugin(context: LoadContext): Plugin { - return { - name: "validate-docs-coverage", - async loadContent() { - const docsDir = path.resolve(context.siteDir, "docs"); - const missing: string[] = []; - - for (const { label, folder, entries } of documentationEntries) { - for (const [kind, slug] of entries) { - if (!slug) continue; - if (!fs.existsSync(path.join(docsDir, folder, slug))) { - missing.push(`${label} "${kind}" -> docs/${folder}/${slug}/`); - } - } - } - - if (missing.length > 0) { - throw new Error( - `Missing documentation for ${missing.length} feature(s):\n` + - missing.map((m) => ` - ${m}`).join("\n") + - "\n\nCreate the missing doc folders with index.ts + index.mdx.", - ); - } - }, - }; -} diff --git a/apps/docs/postcss.config.mjs b/apps/docs/postcss.config.mjs new file mode 100644 index 000000000..c2ddf7482 --- /dev/null +++ b/apps/docs/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/apps/docs/static/.nojekyll b/apps/docs/public/.nojekyll similarity index 100% rename from apps/docs/static/.nojekyll rename to apps/docs/public/.nojekyll diff --git a/apps/docs/static/api/.gitkeep b/apps/docs/public/api/.gitkeep similarity index 100% rename from apps/docs/static/api/.gitkeep rename to apps/docs/public/api/.gitkeep diff --git a/apps/docs/static/api/open-api-schema.json b/apps/docs/public/api/open-api-schema.json similarity index 100% rename from apps/docs/static/api/open-api-schema.json rename to apps/docs/public/api/open-api-schema.json diff --git a/apps/docs/static/custom-widgets/component-catalog-v1.json b/apps/docs/public/custom-widgets/component-catalog-v1.json similarity index 100% rename from apps/docs/static/custom-widgets/component-catalog-v1.json rename to apps/docs/public/custom-widgets/component-catalog-v1.json diff --git a/apps/docs/static/data/.gitkeep b/apps/docs/public/data/.gitkeep similarity index 100% rename from apps/docs/static/data/.gitkeep rename to apps/docs/public/data/.gitkeep diff --git a/apps/docs/static/data/contributions.json b/apps/docs/public/data/contributions.json similarity index 100% rename from apps/docs/static/data/contributions.json rename to apps/docs/public/data/contributions.json diff --git a/apps/docs/static/data/translation-contributions.json b/apps/docs/public/data/translation-contributions.json similarity index 100% rename from apps/docs/static/data/translation-contributions.json rename to apps/docs/public/data/translation-contributions.json diff --git a/apps/docs/static/img/favicon.png b/apps/docs/public/img/favicon.png similarity index 100% rename from apps/docs/static/img/favicon.png rename to apps/docs/public/img/favicon.png diff --git a/apps/docs/static/img/logo.png b/apps/docs/public/img/logo.png similarity index 100% rename from apps/docs/static/img/logo.png rename to apps/docs/public/img/logo.png diff --git a/apps/docs/static/img/logo.svg b/apps/docs/public/img/logo.svg similarity index 100% rename from apps/docs/static/img/logo.svg rename to apps/docs/public/img/logo.svg diff --git a/apps/docs/static/img/pictures/apps/screenshot-apps-dark.jpg b/apps/docs/public/img/pictures/apps/screenshot-apps-dark.jpg similarity index 100% rename from apps/docs/static/img/pictures/apps/screenshot-apps-dark.jpg rename to apps/docs/public/img/pictures/apps/screenshot-apps-dark.jpg diff --git a/apps/docs/static/img/pictures/apps/screenshot-apps-light.jpg b/apps/docs/public/img/pictures/apps/screenshot-apps-light.jpg similarity index 100% rename from apps/docs/static/img/pictures/apps/screenshot-apps-light.jpg rename to apps/docs/public/img/pictures/apps/screenshot-apps-light.jpg diff --git a/apps/docs/static/img/pictures/homarr-devices-preview/compressed/homarr-devices-2d-mockup-flat-shadow-dark-compressed.webp b/apps/docs/public/img/pictures/homarr-devices-preview/compressed/homarr-devices-2d-mockup-flat-shadow-dark-compressed.webp similarity index 100% rename from apps/docs/static/img/pictures/homarr-devices-preview/compressed/homarr-devices-2d-mockup-flat-shadow-dark-compressed.webp rename to apps/docs/public/img/pictures/homarr-devices-preview/compressed/homarr-devices-2d-mockup-flat-shadow-dark-compressed.webp diff --git a/apps/docs/static/img/pictures/homarr-devices-preview/compressed/homarr-devices-2d-mockup-flat-shadow-light-compressed.webp b/apps/docs/public/img/pictures/homarr-devices-preview/compressed/homarr-devices-2d-mockup-flat-shadow-light-compressed.webp similarity index 100% rename from apps/docs/static/img/pictures/homarr-devices-preview/compressed/homarr-devices-2d-mockup-flat-shadow-light-compressed.webp rename to apps/docs/public/img/pictures/homarr-devices-preview/compressed/homarr-devices-2d-mockup-flat-shadow-light-compressed.webp diff --git a/apps/docs/static/img/pictures/homarr-devices-preview/uncompressed/homarr-devices-2d-mockup-flat-shadow-dark.webp b/apps/docs/public/img/pictures/homarr-devices-preview/uncompressed/homarr-devices-2d-mockup-flat-shadow-dark.webp similarity index 100% rename from apps/docs/static/img/pictures/homarr-devices-preview/uncompressed/homarr-devices-2d-mockup-flat-shadow-dark.webp rename to apps/docs/public/img/pictures/homarr-devices-preview/uncompressed/homarr-devices-2d-mockup-flat-shadow-dark.webp diff --git a/apps/docs/static/img/pictures/homarr-devices-preview/uncompressed/homarr-devices-2d-mockup-flat-shadow-light.webp b/apps/docs/public/img/pictures/homarr-devices-preview/uncompressed/homarr-devices-2d-mockup-flat-shadow-light.webp similarity index 100% rename from apps/docs/static/img/pictures/homarr-devices-preview/uncompressed/homarr-devices-2d-mockup-flat-shadow-light.webp rename to apps/docs/public/img/pictures/homarr-devices-preview/uncompressed/homarr-devices-2d-mockup-flat-shadow-light.webp diff --git a/apps/docs/static/img/pictures/lang/screenshot-customization-dark.jpg b/apps/docs/public/img/pictures/lang/screenshot-customization-dark.jpg similarity index 100% rename from apps/docs/static/img/pictures/lang/screenshot-customization-dark.jpg rename to apps/docs/public/img/pictures/lang/screenshot-customization-dark.jpg diff --git a/apps/docs/static/img/pictures/lang/screenshot-customization-light.jpg b/apps/docs/public/img/pictures/lang/screenshot-customization-light.jpg similarity index 100% rename from apps/docs/static/img/pictures/lang/screenshot-customization-light.jpg rename to apps/docs/public/img/pictures/lang/screenshot-customization-light.jpg diff --git a/apps/docs/static/img/pictures/partner/pika-pods.svg b/apps/docs/public/img/pictures/partner/pika-pods.svg similarity index 100% rename from apps/docs/static/img/pictures/partner/pika-pods.svg rename to apps/docs/public/img/pictures/partner/pika-pods.svg diff --git a/apps/docs/static/img/pictures/reviews/ibracorp.webp b/apps/docs/public/img/pictures/reviews/ibracorp.webp similarity index 100% rename from apps/docs/static/img/pictures/reviews/ibracorp.webp rename to apps/docs/public/img/pictures/reviews/ibracorp.webp diff --git a/apps/docs/static/img/pictures/reviews/maeglin.webp b/apps/docs/public/img/pictures/reviews/maeglin.webp similarity index 100% rename from apps/docs/static/img/pictures/reviews/maeglin.webp rename to apps/docs/public/img/pictures/reviews/maeglin.webp diff --git a/apps/docs/static/img/pictures/reviews/r_selfhosted.webp b/apps/docs/public/img/pictures/reviews/r_selfhosted.webp similarity index 100% rename from apps/docs/static/img/pictures/reviews/r_selfhosted.webp rename to apps/docs/public/img/pictures/reviews/r_selfhosted.webp diff --git a/apps/docs/static/img/pictures/reviews/r_unraid.webp b/apps/docs/public/img/pictures/reviews/r_unraid.webp similarity index 100% rename from apps/docs/static/img/pictures/reviews/r_unraid.webp rename to apps/docs/public/img/pictures/reviews/r_unraid.webp diff --git a/apps/docs/static/img/pictures/widgets/widget-dark.jpg b/apps/docs/public/img/pictures/widgets/widget-dark.jpg similarity index 100% rename from apps/docs/static/img/pictures/widgets/widget-dark.jpg rename to apps/docs/public/img/pictures/widgets/widget-dark.jpg diff --git a/apps/docs/static/img/pictures/widgets/widget-light.jpg b/apps/docs/public/img/pictures/widgets/widget-light.jpg similarity index 100% rename from apps/docs/static/img/pictures/widgets/widget-light.jpg rename to apps/docs/public/img/pictures/widgets/widget-light.jpg diff --git a/apps/docs/static/img/undraw_illustrations/integrate.svg b/apps/docs/public/img/undraw_illustrations/integrate.svg similarity index 100% rename from apps/docs/static/img/undraw_illustrations/integrate.svg rename to apps/docs/public/img/undraw_illustrations/integrate.svg diff --git a/apps/docs/static/img/undraw_illustrations/modern_design.svg b/apps/docs/public/img/undraw_illustrations/modern_design.svg similarity index 100% rename from apps/docs/static/img/undraw_illustrations/modern_design.svg rename to apps/docs/public/img/undraw_illustrations/modern_design.svg diff --git a/apps/docs/static/img/undraw_illustrations/open_source.svg b/apps/docs/public/img/undraw_illustrations/open_source.svg similarity index 100% rename from apps/docs/static/img/undraw_illustrations/open_source.svg rename to apps/docs/public/img/undraw_illustrations/open_source.svg diff --git a/apps/docs/static/img/undraw_illustrations/review.svg b/apps/docs/public/img/undraw_illustrations/review.svg similarity index 100% rename from apps/docs/static/img/undraw_illustrations/review.svg rename to apps/docs/public/img/undraw_illustrations/review.svg diff --git a/apps/docs/static/img/undraw_illustrations/undraw_taken_re_yn20.svg b/apps/docs/public/img/undraw_illustrations/undraw_taken_re_yn20.svg similarity index 100% rename from apps/docs/static/img/undraw_illustrations/undraw_taken_re_yn20.svg rename to apps/docs/public/img/undraw_illustrations/undraw_taken_re_yn20.svg diff --git a/apps/docs/static/img/videos/showcase.mp4 b/apps/docs/public/img/videos/showcase.mp4 similarity index 100% rename from apps/docs/static/img/videos/showcase.mp4 rename to apps/docs/public/img/videos/showcase.mp4 diff --git a/apps/docs/src/components/pages/home/drag-and-drop/showcase-dark.mp4 b/apps/docs/public/videos/home/showcase-dark.mp4 similarity index 100% rename from apps/docs/src/components/pages/home/drag-and-drop/showcase-dark.mp4 rename to apps/docs/public/videos/home/showcase-dark.mp4 diff --git a/apps/docs/src/components/pages/home/drag-and-drop/showcase-light.mp4 b/apps/docs/public/videos/home/showcase-light.mp4 similarity index 100% rename from apps/docs/src/components/pages/home/drag-and-drop/showcase-light.mp4 rename to apps/docs/public/videos/home/showcase-light.mp4 diff --git a/apps/docs/static/workshop-runtime-config.js b/apps/docs/public/workshop-runtime-config.js similarity index 100% rename from apps/docs/static/workshop-runtime-config.js rename to apps/docs/public/workshop-runtime-config.js diff --git a/apps/docs/scripts/dump-contributors.mjs b/apps/docs/scripts/dump-contributors.mjs index b16057c99..111d9daec 100644 --- a/apps/docs/scripts/dump-contributors.mjs +++ b/apps/docs/scripts/dump-contributors.mjs @@ -89,10 +89,10 @@ const distinctGithubContributors = githubContributors .sort((a, b) => b.contributions - a.contributions) .map(({ contributions, ...props }) => props) .filter((contributor) => !contributor.login.includes("[bot]")); -await fs.writeFile("./static/data/contributions.json", JSON.stringify(distinctGithubContributors)); +await fs.writeFile("./public/data/contributions.json", JSON.stringify(distinctGithubContributors)); for (const { projectId, tokenName } of sources.crowdin) { crowdinContributors.push(...(await fetchCrowdinMembers(projectId, tokenName))); } const distinctCrowdinContributors = crowdinContributors.filter(distinctBy((contributor) => contributor.username)); -await fs.writeFile("./static/data/translation-contributions.json", JSON.stringify(distinctCrowdinContributors)); +await fs.writeFile("./public/data/translation-contributions.json", JSON.stringify(distinctCrowdinContributors)); diff --git a/apps/docs/scripts/validate-docs-coverage.ts b/apps/docs/scripts/validate-docs-coverage.ts new file mode 100644 index 000000000..a808a1683 --- /dev/null +++ b/apps/docs/scripts/validate-docs-coverage.ts @@ -0,0 +1,34 @@ +import fs from "node:fs"; +import path from "node:path"; + +import { widgetDocSlugs } from "@homarr/definitions/docs/widget-doc-slugs"; +import { getIntegrationDocumentationSlug, integrationKinds } from "@homarr/definitions/integration"; + +const docsDir = path.resolve(import.meta.dirname, "../docs"); +const documentationEntries = [ + { + label: "Integration", + folder: "integrations", + entries: integrationKinds.map((kind) => [kind, getIntegrationDocumentationSlug(kind)] as const), + }, + { label: "Widget", folder: "widgets", entries: Object.entries(widgetDocSlugs) }, +] as const; + +const missing: string[] = []; + +for (const { label, folder, entries } of documentationEntries) { + for (const [kind, slug] of entries) { + if (!slug) continue; + if (!fs.existsSync(path.join(docsDir, folder, slug, "index.mdx"))) { + missing.push(`${label} "${kind}" -> docs/${folder}/${slug}/index.mdx`); + } + } +} + +if (missing.length > 0) { + throw new Error( + `Missing documentation for ${missing.length} feature(s):\n${missing.map((item) => ` - ${item}`).join("\n")}`, + ); +} + +console.log("Integration and widget documentation coverage passed"); diff --git a/apps/docs/scripts/validate-links.ts b/apps/docs/scripts/validate-links.ts new file mode 100644 index 000000000..c44019fb0 --- /dev/null +++ b/apps/docs/scripts/validate-links.ts @@ -0,0 +1,90 @@ +import { readFile, readdir } from "node:fs/promises"; +import path from "node:path"; +import { load } from "cheerio"; +import { printErrors, readFiles, validateFiles, type ScanResult } from "next-validate-link"; + +const outputDirectory = path.resolve("out"); + +const toContentUrl = (file: string) => { + const normalized = file.replaceAll(path.sep, "/"); + + if (normalized.startsWith("docs/")) { + const slug = normalized + .slice("docs/".length) + .replace(/\/(index)?\.mdx?$/, "") + .replace(/\.mdx?$/, ""); + return `/docs${slug ? `/${slug}` : ""}/`; + } + + if (normalized.includes("06-22-documentation")) return "/blog/documentation-migration/"; + if (normalized.includes("08-28-translation")) return "/blog/translations/"; + + const match = /^blog\/(\d{4})\/(\d{2})-(\d{2})-(.+)\/index\.mdx?$/.exec(normalized); + return match ? `/blog/${match[1]}/${match[2]}/${match[3]}/${match[4]}/` : undefined; +}; + +const files = await readFiles(["docs/**/*.{md,mdx}", "blog/**/*.{md,mdx}"], { pathToUrl: toContentUrl }); +const output = await readdir(outputDirectory, { recursive: true, withFileTypes: true }); +const urls = new Map(); +const renderedPages: { file: string; url: string }[] = []; + +for (const entry of output) { + if (!entry.isFile()) continue; + const file = path.join(entry.parentPath, entry.name); + const relative = path.relative(outputDirectory, file).replaceAll(path.sep, "/"); + if (entry.name !== "index.html") { + urls.set(`/${relative}`, {}); + continue; + } + const url = `/${relative.replace(/\/?index\.html$/, "")}`.replace(/\/$/, "") || "/"; + urls.set(url, {}); + urls.set(`${url}/`, {}); + renderedPages.push({ file, url }); +} + +const scanned: ScanResult = { urls, fallbackUrls: [] }; +const results = await validateFiles(files, { + scanned, + checkExternal: false, + checkRelativePaths: "exists", + ignoreFragment: true, + markdown: { + components: { + Link: { attributes: ["href", "to"] }, + }, + }, + whitelist: (url) => + /^(?:https?:|mailto:|tel:|#)/.test(url) || + /^\/(?:api\/|custom-widgets\/|data\/|img\/|workshop(?:\/|$)|llms(?:-|\.|\/)|robots\.txt|sitemap\.xml)/.test(url), +}); + +printErrors( + results.filter((result) => result.errors.length > 0), + true, +); + +const renderedLinkErrors = new Set(); + +for (const page of renderedPages) { + const $ = load(await readFile(page.file, "utf8")); + + $("main a[href]").each((_, element) => { + const href = $(element).attr("href"); + if (!href || /^(?:https?:|mailto:|tel:|#)/.test(href)) return; + + const target = new URL(href, `https://homarr.dev${page.url}`).pathname; + if (/^\/(?:_next\/|api\/|custom-widgets\/|data\/|img\/|videos\/)/.test(target)) return; + if (/\.(?:avif|gif|jpe?g|json|md|mp4|png|svg|webm|webp)$/i.test(target)) return; + if (urls.has(target) || urls.has(target.replace(/\/$/, "")) || urls.has(`${target}/`)) return; + + renderedLinkErrors.add(`${page.url}: ${href} resolves to missing route ${target}`); + }); +} + +if (renderedLinkErrors.size > 0) { + console.error(`\n${renderedLinkErrors.size} broken rendered link(s):`); + for (const error of renderedLinkErrors) console.error(`- ${error}`); + process.exitCode = 1; +} else { + console.log("0 broken rendered links"); +} diff --git a/apps/docs/scripts/verify-algolia-search.mjs b/apps/docs/scripts/verify-algolia-search.mjs deleted file mode 100644 index 93e83baa8..000000000 --- a/apps/docs/scripts/verify-algolia-search.mjs +++ /dev/null @@ -1,166 +0,0 @@ -const APP_ID = "N69WSPZTID"; -const API_KEY = "b2b00f4ed8ca3dc87b5d211c55121416"; -const DOCSEARCH_INDEX = "Docusaurus"; -const MARKDOWN_INDEX = "markdown"; -const MIN_DOCSEARCH_HITS = 100; -const MIN_MARKDOWN_HITS = 10; - -const DOCSEARCH_FACETS = ["docusaurus_tag", "language", "lang", "version", "type"]; - -const SEARCH_CASES = [ - { term: "docker", minHits: 1 }, - { term: "coolify", minHits: 1 }, - { term: "jellyseerr", minHits: 1 }, - { term: "keyboard shortcuts", minHits: 1 }, - { term: "version 0.12", minHits: 1 }, -]; - -const STALE_URL_PATTERN = /\/docs\/(next|\d+(?:\.\d+)+)\//; - -async function algoliaQuery(index, body) { - const response = await fetch(`https://${APP_ID}-dsn.algolia.net/1/indexes/${index}/query`, { - method: "POST", - headers: { - "X-Algolia-Application-Id": APP_ID, - "X-Algolia-API-Key": API_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - }); - - if (!response.ok) { - throw new Error(`Algolia query on "${index}" failed: HTTP ${response.status}`); - } - - return response.json(); -} - -function reportLine(ok, message) { - const prefix = ok ? "OK" : "FAIL"; - console.log(`${prefix}: ${message}`); - return ok; -} - -async function resolveDocsearchIndex() { - const candidates = [DOCSEARCH_INDEX, `${DOCSEARCH_INDEX}.tmp`]; - - for (const index of candidates) { - const result = await algoliaQuery(index, { query: "", hitsPerPage: 0 }); - const hitCount = result.nbHits ?? 0; - if (hitCount >= MIN_DOCSEARCH_HITS) { - return { index, hitCount }; - } - } - - const fallback = await algoliaQuery(DOCSEARCH_INDEX, { query: "", hitsPerPage: 0 }); - return { index: DOCSEARCH_INDEX, hitCount: fallback.nbHits ?? 0 }; -} - -async function resolveMarkdownIndex() { - const candidates = [MARKDOWN_INDEX, `${MARKDOWN_INDEX}.tmp`]; - - for (const index of candidates) { - const result = await algoliaQuery(index, { query: "", hitsPerPage: 0 }); - const hitCount = result.nbHits ?? 0; - if (hitCount >= MIN_MARKDOWN_HITS) { - return { index, hitCount }; - } - } - - const fallback = await algoliaQuery(MARKDOWN_INDEX, { query: "", hitsPerPage: 0 }); - return { index: MARKDOWN_INDEX, hitCount: fallback.nbHits ?? 0 }; -} - -async function verifyDocsearchIndex(index, hitCount) { - let ok = reportLine( - hitCount >= MIN_DOCSEARCH_HITS, - `index "${index}" has ${hitCount} records (need >= ${MIN_DOCSEARCH_HITS})`, - ); - - const sample = await algoliaQuery(index, { - query: "docker", - hitsPerPage: 1, - attributesToRetrieve: DOCSEARCH_FACETS, - }); - - const record = sample.hits?.[0] ?? {}; - const recordFields = ["docusaurus_tag", "language", "lang", "version", "type"]; - for (const field of recordFields) { - const present = record[field] !== undefined && record[field] !== null; - ok = reportLine(present, `records include "${field}"`) && ok; - } - - for (const searchCase of SEARCH_CASES) { - const result = await algoliaQuery(index, { - query: searchCase.term, - hitsPerPage: 5, - attributesToRetrieve: ["url"], - }); - - const caseHits = result.nbHits ?? 0; - ok = - reportLine( - caseHits >= searchCase.minHits, - `"${searchCase.term}" returns ${caseHits} hits (need >= ${searchCase.minHits})`, - ) && ok; - - const staleUrls = (result.hits ?? []).map((hit) => hit.url).filter((url) => STALE_URL_PATTERN.test(url)); - - ok = - reportLine( - staleUrls.length === 0, - `"${searchCase.term}" has no stale versioned URLs (${staleUrls.length} stale)`, - ) && ok; - } - - return ok; -} - -async function verifyMarkdownIndex(index, hitCount) { - let ok = reportLine( - hitCount >= MIN_MARKDOWN_HITS, - `markdown index "${index}" has ${hitCount} records (need >= ${MIN_MARKDOWN_HITS})`, - ); - - const coolify = await algoliaQuery(index, { - query: "coolify", - hitsPerPage: 3, - attributesToRetrieve: ["url", "title", "heading", "text"], - }); - - ok = - reportLine((coolify.nbHits ?? 0) >= 1, `markdown index "${index}" returns coolify (${coolify.nbHits ?? 0} hits)`) && - ok; - - const sample = coolify.hits?.[0]; - ok = reportLine(Boolean(sample?.text?.length), `markdown records include chunked text content`) && ok; - - return ok; -} - -async function main() { - console.log("Verifying Algolia DocSearch + markdown indices...\n"); - - const docsearch = await resolveDocsearchIndex(); - console.log(`Using DocSearch index: ${docsearch.index}\n`); - const docsearchOk = await verifyDocsearchIndex(docsearch.index, docsearch.hitCount); - - console.log(""); - const markdown = await resolveMarkdownIndex(); - console.log(`Using markdown index: ${markdown.index}\n`); - const markdownOk = await verifyMarkdownIndex(markdown.index, markdown.hitCount); - - const allOk = docsearchOk && markdownOk; - console.log(""); - console.log( - allOk - ? "All checks passed." - : "Some checks failed — ensure the crawler config in docsearch.config.js is applied and a full recrawl has finished.", - ); - process.exit(allOk ? 0 : 1); -} - -main().catch((error) => { - console.error(error); - process.exit(1); -}); diff --git a/apps/docs/sidebars.js b/apps/docs/sidebars.js deleted file mode 100644 index 50b2a2723..000000000 --- a/apps/docs/sidebars.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Creating a sidebar enables you to: - - create an ordered group of docs - - render a sidebar for each doc of that group - - provide next/previous navigation - - The sidebars can be generated from the filesystem, or explicitly defined here. - - Create as many sidebars as you want. - */ - -// @ts-check - -/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const sidebars = { - // By default, Docusaurus generates a sidebar from the docs folder structure - GettingStarted: [{ type: "autogenerated", dirName: "getting-started" }], - Docs: [{ type: "autogenerated", dirName: "." }], - - // But you can create a sidebar manually - /* - tutorialSidebar: [ - { - type: 'category', - label: 'Tutorial', - items: ['about'], - }, - ], - */ -}; - -module.exports = sidebars; diff --git a/apps/docs/source.config.ts b/apps/docs/source.config.ts new file mode 100644 index 000000000..216006f7c --- /dev/null +++ b/apps/docs/source.config.ts @@ -0,0 +1,32 @@ +import { remarkDirectiveAdmonition, remarkMdxMermaid } from "fumadocs-core/mdx-plugins"; +import { defineConfig } from "fumadocs-mdx/config"; +import remarkDirective from "remark-directive"; + +export default defineConfig({ + mdxOptions: { + remarkPlugins: [ + remarkDirective, + [ + remarkDirectiveAdmonition, + { + types: { + note: "info", + tip: "idea", + info: "info", + warn: "warning", + warning: "warning", + caution: "warning", + important: "warning", + danger: "error", + error: "error", + success: "success", + }, + }, + ], + remarkMdxMermaid, + ], + remarkImageOptions: { + onError: "hide", + }, + }, +}); diff --git a/apps/docs/src/app/(home)/about-us/page.tsx b/apps/docs/src/app/(home)/about-us/page.tsx new file mode 100644 index 000000000..75713b959 --- /dev/null +++ b/apps/docs/src/app/(home)/about-us/page.tsx @@ -0,0 +1,36 @@ +import type { Metadata } from "next"; + +import { CodeContributorList } from "@/components/pages/about/code-contributors/code-contributor-list"; +import { TranslationContributorList } from "@/components/pages/about/translations-contributors/translation-constributor-list"; +import { SiteFooter } from "@/components/site-footer"; + +export const metadata: Metadata = { + title: "About Homarr", + description: "The volunteers and contributors behind the Homarr open-source project.", +}; + +export default function AboutPage() { + return ( + <> +
+
+

About us

+

+ Homarr is a community driven open source project that is being maintained by volunteers. Thanks to these + people, Homarr has been a growing project since 2021. Our team is working completely remote from many + different countries on Homarr in their leisure time for no compensation. +

+
+
+

Code contributions

+ +
+
+

Translation contributions

+ +
+
+ + + ); +} diff --git a/apps/docs/src/app/(home)/blog/[...slug]/page.tsx b/apps/docs/src/app/(home)/blog/[...slug]/page.tsx new file mode 100644 index 000000000..f989890a5 --- /dev/null +++ b/apps/docs/src/app/(home)/blog/[...slug]/page.tsx @@ -0,0 +1,63 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { InlineTOC } from "fumadocs-ui/components/inline-toc"; +import { DocsBody, MarkdownCopyButton, ViewOptionsPopover } from "fumadocs-ui/layouts/docs/page"; + +import { getMDXComponents } from "@/components/mdx"; +import { getPostBySegments, getPostMarkdownUrl, getPostSegments, getPostUrl, posts } from "@/lib/source"; + +interface PageProps { + params: Promise<{ slug: string[] }>; +} + +export default async function BlogPostPage({ params }: PageProps) { + const { slug } = await params; + const post = getPostBySegments(slug); + if (!post) notFound(); + + const MDX = post.body; + const markdownUrl = getPostMarkdownUrl(post); + const hasBodyTitle = post.toc[0]?.depth === 1; + const toc = hasBodyTitle ? post.toc.filter((item) => item.depth !== 1) : post.toc; + + return ( +
+
+
+ +

{post.title}

+

By {post.authors.join(", ")}

+
+ + +
+
+ + + + +
+
+ ); +} + +export function generateStaticParams() { + return posts.entries.map((post) => ({ slug: getPostSegments(post) })); +} + +export async function generateMetadata({ params }: PageProps): Promise { + const { slug } = await params; + const post = getPostBySegments(slug); + if (!post) notFound(); + + return { + title: post.title, + alternates: { canonical: getPostUrl(post) }, + authors: post.authors.map((name) => ({ name })), + }; +} diff --git a/apps/docs/src/app/(home)/blog/page.tsx b/apps/docs/src/app/(home)/blog/page.tsx new file mode 100644 index 000000000..93df4710a --- /dev/null +++ b/apps/docs/src/app/(home)/blog/page.tsx @@ -0,0 +1,39 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { getPostUrl, posts } from "@/lib/source"; + +export const metadata: Metadata = { + title: "Blog", + description: "Release notes, migration guides, and project updates from Homarr.", + alternates: { types: { "application/rss+xml": "/blog/rss.xml" } }, +}; + +export default function BlogIndexPage() { + const entries = posts.entries.toSorted((left, right) => right.date.localeCompare(left.date)); + + return ( +
+
+

Homarr project

+

Blog

+

Release notes, migration guides, and project updates.

+
+
+ {entries.map((post) => ( +
+ +

+ + {post.title} + +

+

By {post.authors.join(", ")}

+
+ ))} +
+
+ ); +} diff --git a/apps/docs/src/app/(home)/carbon-test/page.tsx b/apps/docs/src/app/(home)/carbon-test/page.tsx new file mode 100644 index 000000000..ea157629e --- /dev/null +++ b/apps/docs/src/app/(home)/carbon-test/page.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; + +import { CarbonCoverCssOnlySecond, CarbonCoverObserver, CarbonCoverStrict } from "@/components/carbon"; + +export const metadata: Metadata = { + title: "Carbon Ads test", + robots: { index: false, follow: false }, +}; + +export default function CarbonTestPage() { + return ( +
+

Carbon Ads test

+

Compare the three cover-unit implementations.

+
+

Strict re-initialization

+ +
+
+

Observer trimming

+ +
+
+

CSS-only

+ +
+
+ ); +} diff --git a/apps/docs/src/app/(home)/layout.tsx b/apps/docs/src/app/(home)/layout.tsx new file mode 100644 index 000000000..aaf1bf62b --- /dev/null +++ b/apps/docs/src/app/(home)/layout.tsx @@ -0,0 +1,8 @@ +import { HomeLayout } from "fumadocs-ui/layouts/home"; +import type { ReactNode } from "react"; + +import { baseOptions } from "@/lib/layout.shared"; + +export default function Layout({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/apps/docs/src/app/(home)/page.tsx b/apps/docs/src/app/(home)/page.tsx new file mode 100644 index 000000000..e7df31a70 --- /dev/null +++ b/apps/docs/src/app/(home)/page.tsx @@ -0,0 +1,53 @@ +import type { Metadata } from "next"; + +import { SectionContainer } from "@/components/pages/home/container/section-container"; +import { HomeDataflow } from "@/components/pages/home/dataflow/dataflow"; +import { DocsGateway } from "@/components/pages/home/docs-gateway"; +import { DragAndDropShowcase } from "@/components/pages/home/drag-and-drop/drag-and-drop-showcase"; +import HomepageFeatures from "@/components/pages/home/features/features"; +import HomeHero from "@/components/pages/home/hero/hero"; +import { AvailableIntegrations } from "@/components/pages/home/integrations/available-integrations"; +import HomepageUserReviews from "@/components/pages/home/review-list/review-list"; +import { SiteFooter } from "@/components/site-footer"; + +import styles from "../../pages/index.module.css"; + +export const metadata: Metadata = { + title: "A simple, powerful dashboard for your server", + description: + "Simplify the management of your server with Homarr, a sleek, modern dashboard that puts all of your apps and services at your fingertips.", +}; + +export default function HomePage() { + return ( +
+ + + + + + + + + + + + + + + + +
+ ); +} diff --git a/apps/docs/src/app/(home)/workshop/admin/page.tsx b/apps/docs/src/app/(home)/workshop/admin/page.tsx new file mode 100644 index 000000000..a3323f5e8 --- /dev/null +++ b/apps/docs/src/app/(home)/workshop/admin/page.tsx @@ -0,0 +1,12 @@ +import type { Metadata } from "next"; + +import { WorkshopAdminRoute } from "@/components/workshop/WorkshopRoutes"; + +export const metadata: Metadata = { + title: "Workshop moderation", + robots: { index: false, follow: false }, +}; + +export default function WorkshopAdminPage() { + return ; +} diff --git a/apps/docs/src/app/(home)/workshop/page.tsx b/apps/docs/src/app/(home)/workshop/page.tsx new file mode 100644 index 000000000..6d5e29d72 --- /dev/null +++ b/apps/docs/src/app/(home)/workshop/page.tsx @@ -0,0 +1,25 @@ +import type { Metadata } from "next"; + +import { WorkshopListingFallback } from "@/components/workshop/WorkshopApp"; +import { WorkshopErrorBoundary } from "@/components/workshop/WorkshopErrorBoundary"; +import { WorkshopRoute } from "@/components/workshop/WorkshopRoutes"; + +export const metadata: Metadata = { + title: "Workshop", + description: "Community custom CSS and custom widgets for Homarr.", +}; + +export default function WorkshopPage() { + const configuredWorkshopUrl = process.env.WORKSHOP_API_URL ?? process.env.WORKSHOP_URL ?? ""; + + return ( +
+ + + + +
+ ); +} diff --git a/apps/docs/src/app/api-reference/[...slug]/page.tsx b/apps/docs/src/app/api-reference/[...slug]/page.tsx new file mode 100644 index 000000000..e17cabfc3 --- /dev/null +++ b/apps/docs/src/app/api-reference/[...slug]/page.tsx @@ -0,0 +1,38 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { DocsPage, DocsTitle } from "fumadocs-ui/layouts/docs/page"; + +import { OpenAPIPage } from "@/components/openapi-page"; +import { apiSource } from "@/lib/openapi"; + +interface PageProps { + params: Promise<{ slug: string[] }>; +} + +export default async function ApiPage({ params }: PageProps) { + const { slug } = await params; + const page = apiSource.getPage(slug); + if (!page) notFound(); + + return ( + + {page.data.title} + + + ); +} + +export function generateStaticParams() { + return apiSource.generateParams(); +} + +export async function generateMetadata({ params }: PageProps): Promise { + const { slug } = await params; + const page = apiSource.getPage(slug); + if (!page) notFound(); + + return { + title: page.data.title, + description: page.data.description, + }; +} diff --git a/apps/docs/src/app/api-reference/layout.tsx b/apps/docs/src/app/api-reference/layout.tsx new file mode 100644 index 000000000..5cf315a71 --- /dev/null +++ b/apps/docs/src/app/api-reference/layout.tsx @@ -0,0 +1,13 @@ +import { DocsLayout } from "fumadocs-ui/layouts/docs"; +import type { ReactNode } from "react"; + +import { apiSource } from "@/lib/openapi"; +import { baseOptions } from "@/lib/layout.shared"; + +export default function ApiReferenceLayout({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/apps/docs/src/app/api-reference/page.tsx b/apps/docs/src/app/api-reference/page.tsx new file mode 100644 index 000000000..be10744ee --- /dev/null +++ b/apps/docs/src/app/api-reference/page.tsx @@ -0,0 +1,32 @@ +import { IconArrowRight } from "@tabler/icons-react"; +import type { Metadata } from "next"; +import Link from "next/link"; + +import { apiSource } from "@/lib/openapi"; + +export const metadata: Metadata = { + title: "API reference", + description: "Interactive reference for Homarr's HTTP API.", +}; + +export default function ApiReferenceIndexPage() { + const pages = apiSource.getPages(); + + return ( +
+

OpenAPI

+

Homarr API reference

+

+ Browse request parameters and response schemas, then generate and test examples from the source-of-truth schema. +

+
+ {pages.map((page) => ( + + {page.data.title} + + + ))} +
+
+ ); +} diff --git a/apps/docs/src/app/api/search/route.ts b/apps/docs/src/app/api/search/route.ts new file mode 100644 index 000000000..f2b162490 --- /dev/null +++ b/apps/docs/src/app/api/search/route.ts @@ -0,0 +1,9 @@ +import { createFromSource } from "fumadocs-core/search/server"; + +import { source } from "@/lib/source"; + +export const revalidate = false; + +export const { staticGET: GET } = createFromSource(source, { + language: "english", +}); diff --git a/apps/docs/src/app/blog/rss.xml/route.ts b/apps/docs/src/app/blog/rss.xml/route.ts new file mode 100644 index 000000000..881d56dd4 --- /dev/null +++ b/apps/docs/src/app/blog/rss.xml/route.ts @@ -0,0 +1,36 @@ +import { Feed } from "feed"; + +import { getPostExcerpt, getPostUrl, posts } from "@/lib/source"; + +export const revalidate = false; + +export async function GET() { + const siteUrl = (process.env.HOMARR_WEBSITE_URL ?? "https://homarr.dev").replace(/\/$/, ""); + const feed = new Feed({ + title: "Homarr blog", + description: "Release notes, migration guides, and project updates from Homarr.", + id: `${siteUrl}/blog`, + link: `${siteUrl}/blog`, + language: "en", + favicon: `${siteUrl}/img/favicon.png`, + feedLinks: { rss: `${siteUrl}/blog/rss.xml` }, + copyright: `Homarr contributors ${new Date().getUTCFullYear()}`, + }); + + for (const post of posts.entries.toSorted((left, right) => right.date.localeCompare(left.date))) { + const link = `${siteUrl}${getPostUrl(post)}`; + feed.addItem({ + title: post.title, + id: link, + link, + date: new Date(post.date), + author: post.authors.map((name) => ({ name })), + description: await getPostExcerpt(post), + category: post.tags?.map((name) => ({ name })), + }); + } + + return new Response(feed.rss2(), { + headers: { "Content-Type": "application/rss+xml; charset=utf-8" }, + }); +} diff --git a/apps/docs/src/app/docs/[[...slug]]/page.tsx b/apps/docs/src/app/docs/[[...slug]]/page.tsx new file mode 100644 index 000000000..53b985810 --- /dev/null +++ b/apps/docs/src/app/docs/[[...slug]]/page.tsx @@ -0,0 +1,94 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { findNeighbour } from "fumadocs-core/page-tree"; +import { + DocsBody, + DocsDescription, + EditOnGitHub, + DocsPage, + DocsTitle, + MarkdownCopyButton, + ViewOptionsPopover, +} from "fumadocs-ui/layouts/docs/page"; +import { createRelativeLink } from "fumadocs-ui/mdx"; + +import { getMDXComponents } from "@/components/mdx"; +import { getPageMarkdownUrl, source } from "@/lib/source"; + +interface PageProps { + params: Promise<{ slug?: string[] }>; +} + +export default async function Page({ params }: PageProps) { + const { slug } = await params; + const page = source.getPage(slug); + if (!page) notFound(); + + const MDX = page.data.body; + const markdownUrl = getPageMarkdownUrl(page).url; + const sourcePath = `apps/docs/docs/${page.path}`; + const hasLegacyBodyTitle = !page.data.hide_title && page.data.toc[0]?.depth === 1; + const toc = hasLegacyBodyTitle ? page.data.toc.filter((item) => item.depth !== 1) : page.data.toc; + const isInstallationGuide = + page.slugs.length > 2 && page.slugs[0] === "getting-started" && page.slugs[1] === "installation"; + const footerItems = isInstallationGuide + ? { + ...findNeighbour(source.pageTree, page.url), + next: { + name: "After the installation", + description: "Complete onboarding and configure your first board.", + url: "/docs/getting-started/after-the-installation", + }, + } + : undefined; + + return ( + + {!page.data.hide_title && ( + <> + {page.data.title} + {page.data.description && ( + {page.data.description} + )} + + )} +
+ + +
+ + + +
+ + Edit this page + +
+
+ ); +} + +export function generateStaticParams() { + return source.generateParams(); +} + +export async function generateMetadata({ params }: PageProps): Promise { + const { slug } = await params; + const page = source.getPage(slug); + if (!page) notFound(); + + return { + title: page.data.title, + description: page.data.description, + alternates: { + canonical: page.url, + }, + }; +} diff --git a/apps/docs/src/app/docs/layout.tsx b/apps/docs/src/app/docs/layout.tsx new file mode 100644 index 000000000..953ac3215 --- /dev/null +++ b/apps/docs/src/app/docs/layout.tsx @@ -0,0 +1,13 @@ +import { DocsLayout } from "fumadocs-ui/layouts/docs"; +import type { ReactNode } from "react"; + +import { baseOptions } from "@/lib/layout.shared"; +import { source } from "@/lib/source"; + +export default function Layout({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/apps/docs/src/app/global.css b/apps/docs/src/app/global.css new file mode 100644 index 000000000..3a3e709c2 --- /dev/null +++ b/apps/docs/src/app/global.css @@ -0,0 +1,231 @@ +@import "tailwindcss"; +@import "fumadocs-ui/css/neutral.css"; +@import "fumadocs-ui/css/preset.css"; +@import "fumadocs-openapi/css/preset.css"; + +@source "../components/**/*.{ts,tsx}"; +@source "../../docs/**/*.{md,mdx}"; + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --radius-sm: calc(var(--radius) * 0.75); + --radius-md: var(--radius); + --radius-lg: calc(var(--radius) * 1.25); + --radius-xl: calc(var(--radius) * 1.5); +} + +:root { + --background: oklch(0.985 0.006 25.7); + --foreground: oklch(0.22 0 0); + --card: oklch(0.997 0.002 25.7); + --card-foreground: var(--foreground); + --popover: var(--card); + --popover-foreground: var(--foreground); + --primary: oklch(0.665 0.222 25.7); + --primary-foreground: oklch(1 0 0); + --secondary: oklch(0.96 0.012 25.7); + --secondary-foreground: var(--foreground); + --muted: oklch(0.965 0.008 25.7); + --muted-foreground: oklch(0.46 0.012 25.7); + --accent: oklch(0.955 0.025 25.7); + --accent-foreground: oklch(0.46 0.17 25.7); + --destructive: oklch(0.637 0.208 25.3); + --border: oklch(0.88 0.012 25.7); + --input: var(--border); + --ring: var(--primary); + --radius: 0.375rem; + --ifm-color-primary: #fa5252; + --ifm-navbar-background-color: var(--card); + --ifm-font-family-base: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --homarr-secondary-bg: oklch(0.965 0 0); + --color-fd-background: var(--background); + --color-fd-foreground: var(--foreground); + --color-fd-card: var(--card); + --color-fd-card-foreground: var(--card-foreground); + --color-fd-popover: var(--popover); + --color-fd-popover-foreground: var(--popover-foreground); + --color-fd-primary: var(--primary); + --color-fd-primary-foreground: var(--primary-foreground); + --color-fd-secondary: var(--secondary); + --color-fd-secondary-foreground: var(--secondary-foreground); + --color-fd-muted: var(--muted); + --color-fd-muted-foreground: var(--muted-foreground); + --color-fd-accent: var(--accent); + --color-fd-accent-foreground: var(--accent-foreground); + --color-fd-border: color-mix(in oklch, var(--border) 82%, transparent); + --color-fd-ring: var(--ring); + --fd-layout-width: 96rem; + --fd-page-width: 52rem; +} + +.dark { + --background: oklch(0.185 0.012 25.7); + --foreground: oklch(0.965 0.004 25.7); + --card: oklch(0.225 0.014 25.7); + --card-foreground: var(--foreground); + --popover: var(--card); + --popover-foreground: var(--foreground); + --primary: oklch(0.665 0.222 25.7); + --primary-foreground: oklch(0.15 0.025 25.7); + --secondary: oklch(0.265 0.016 25.7); + --secondary-foreground: var(--foreground); + --muted: oklch(0.255 0.014 25.7); + --muted-foreground: oklch(0.74 0.014 25.7); + --accent: oklch(0.31 0.055 25.7); + --accent-foreground: oklch(0.88 0.09 25.7); + --border: oklch(0.34 0.018 25.7); + --input: var(--border); + --ring: var(--primary); + --ifm-navbar-background-color: var(--card); + --homarr-secondary-bg: var(--card); +} + +* { + border-color: var(--border); +} + +body { + background: var(--background); + color: var(--foreground); + font-family: var(--ifm-font-family-base); + letter-spacing: -0.01em; +} + +:focus-visible { + outline: 2px solid var(--ring); + outline-offset: 2px; +} + +.homarr-brand { + display: inline-flex; + align-items: center; + gap: 0.625rem; + font-size: 1.125rem; + font-weight: 750; + letter-spacing: -0.025em; +} + +.homarr-brand-logo { + width: 1.875rem; + height: auto; +} + +#nd-nav { + height: 4.25rem; +} + +#nd-nav > div { + background-color: color-mix(in oklch, var(--color-fd-primary) 3%, var(--color-fd-background)); + border-bottom-color: color-mix(in oklch, var(--color-fd-primary) 18%, var(--color-fd-border)); +} + +#nd-nav > div > div > nav { + height: 4.25rem; +} + +#nd-nav a[data-active="true"], +#nd-sidebar a[data-active="true"] { + color: var(--color-fd-accent-foreground); +} + +#nd-page > div:first-child .text-fd-primary { + color: var(--color-fd-foreground); +} + +#nd-sidebar { + background-color: color-mix(in oklch, var(--color-fd-primary) 2.5%, var(--color-fd-background)); + border-color: color-mix(in oklch, var(--color-fd-primary) 14%, var(--color-fd-border)); +} + +.homarr-docs-title { + max-width: 22ch; + font-size: clamp(2.15rem, 3.4vw, 2.75rem); + font-weight: 750; + line-height: 1.08; + letter-spacing: -0.035em; +} + +.homarr-docs-description { + max-width: 62ch; + color: var(--color-fd-muted-foreground); +} + +.homarr-page-actions { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 1.25rem; + padding-bottom: 1rem; + border-bottom: 1px solid var(--border); +} + +.homarr-docs-body--hide-title > h1:first-of-type { + display: none; +} + +.prose :where(h1, h2, h3) { + text-wrap: balance; +} + +.prose :where(p, li) { + text-wrap: pretty; +} + +.prose a[data-card="true"] { + border-color: color-mix(in oklch, var(--color-fd-primary) 12%, var(--color-fd-border)); + transition: + transform 180ms cubic-bezier(0.22, 1, 0.36, 1), + border-color 180ms cubic-bezier(0.22, 1, 0.36, 1), + background-color 180ms cubic-bezier(0.22, 1, 0.36, 1); +} + +.prose a[data-card="true"] > .not-prose:first-child { + color: var(--color-fd-primary); + background-color: color-mix(in oklch, var(--color-fd-primary) 9%, var(--color-fd-card)); + border-color: color-mix(in oklch, var(--color-fd-primary) 22%, var(--color-fd-border)); + box-shadow: none; +} + +.prose a[data-card="true"]:hover { + transform: translateY(-1px); + border-color: color-mix(in oklch, var(--color-fd-primary) 38%, var(--color-fd-border)); +} + +@media print { + #nd-docs-layout { + --fd-sidebar-width: 0px !important; + } + + #nd-sidebar, + .homarr-page-actions { + display: none !important; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/apps/docs/src/app/layout.tsx b/apps/docs/src/app/layout.tsx new file mode 100644 index 000000000..2d1943ace --- /dev/null +++ b/apps/docs/src/app/layout.tsx @@ -0,0 +1,43 @@ +import type { Metadata, Viewport } from "next"; +import Script from "next/script"; +import type { ReactNode } from "react"; + +import { Analytics } from "@/components/analytics"; +import { Provider } from "@/components/provider"; + +import "./global.css"; + +const siteUrl = process.env.HOMARR_WEBSITE_URL ?? "https://homarr.dev"; + +export const metadata: Metadata = { + metadataBase: new URL(siteUrl), + title: { + default: "Homarr documentation", + template: "%s | Homarr documentation", + }, + description: "Install, configure, operate, and extend Homarr.", + keywords: ["Homarr", "dashboard", "self-hosted", "documentation"], + icons: { + icon: "/img/favicon.png", + }, +}; + +export const viewport: Viewport = { + colorScheme: "light dark", + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "#ffffff" }, + { media: "(prefers-color-scheme: dark)", color: "#171719" }, + ], +}; + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + +