diff --git a/.github/workflows/check-llms.yml b/.github/workflows/check-llms.yml new file mode 100644 index 000000000..bcc37facc --- /dev/null +++ b/.github/workflows/check-llms.yml @@ -0,0 +1,39 @@ +name: Check llms.txt + +# Guards against llms.txt drifting out of sync with the site product menu. +# Runs the dependency-free scripts/check-llms-links.mjs; fails the check if a +# product in documents.ts has no matching link in llms.txt. Kept separate from +# the deploy workflow so it never blocks a production deploy. + +on: + pull_request: + paths: + - "docs/.vuepress/public/llms.txt" + - "docs/.vuepress/config-client/documents.ts" + - "scripts/check-llms-links.mjs" + - ".github/workflows/check-llms.yml" + push: + branches: + - master + paths: + - "docs/.vuepress/public/llms.txt" + - "docs/.vuepress/config-client/documents.ts" + - "scripts/check-llms-links.mjs" + +jobs: + check-llms: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Check llms.txt is in sync with the product menu + run: node scripts/check-llms-links.mjs diff --git a/.gitignore b/.gitignore index 5b7fc77c3..28d363076 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ docs/.vuepress/dist docs/.vuepress/.cache docs/.vuepress/.temp package-lock.json + +# Generated at build time by scripts/generate-llms-full.mjs +docs/.vuepress/public/llms-full.txt diff --git a/.cursor/mcp.json b/.mcp.json similarity index 100% rename from .cursor/mcp.json rename to .mcp.json diff --git a/docs/.vuepress/public/llms.txt b/docs/.vuepress/public/llms.txt new file mode 100644 index 000000000..1569cba4a --- /dev/null +++ b/docs/.vuepress/public/llms.txt @@ -0,0 +1,41 @@ +# TuxCare Documentation + +> TuxCare provides automated Linux patch management, live kernel patching, extended lifecycle support (ELS) for end-of-life operating systems, libraries, runtimes and applications, and SecureChain — verified, signed, continuously patched open-source packages. This site (docs.tuxcare.com) is the official product documentation. Each link below points to a product area; append the path to https://docs.tuxcare.com to read it. + +The full text of every documentation page, concatenated into a single file, is available at [https://docs.tuxcare.com/llms-full.txt](https://docs.tuxcare.com/llms-full.txt). + +## Products + +- [TuxCare](https://docs.tuxcare.com/tuxcare/): Overview of TuxCare services that automate, simplify, and enhance Linux operations. +- [TuxCare Radar](https://docs.tuxcare.com/radar/): Vulnerability risk assessment and prioritization that reveals real-world risk beyond conventional scanner scores. +- [TuxCare Enterprise Support](https://docs.tuxcare.com/enterprise-support-for-almalinux/): Extended support and security fixes for AlmaLinux and Rocky Linux. + +## Live patching + +- [KernelCare](https://docs.tuxcare.com/live-patching-services/): Live kernel patching — security patches and bugfixes with no reboot. +- [KernelCare for IoT](https://docs.tuxcare.com/kernelcare-for-iot/): Live patching for ARM64 embedded/IoT systems. +- [ePortal](https://docs.tuxcare.com/eportal/): Web management console for KernelCare Enterprise live patch management. +- [ePortal API](https://docs.tuxcare.com/eportal-api/): API reference for patch management and ePortal automation. + +## Extended Lifecycle Support (ELS) + +- [ELS for Operating Systems](https://docs.tuxcare.com/els-for-os/): Post-EOL security support for end-of-life Linux distributions. +- [ELS for Libraries](https://docs.tuxcare.com/els-for-libraries/): Extended security fixes for open-source libraries. +- [ELS for Runtimes](https://docs.tuxcare.com/els-for-runtimes/): Extended support for language runtimes (PHP, Python, Node.js, etc.). +- [ELS for Applications](https://docs.tuxcare.com/els-for-applications/): Security patches for end-of-life open-source applications. + +## SecureChain + +- [SecureChain for Open Source](https://docs.tuxcare.com/securechain/): Verified, signed, continuously patched open-source packages from a TuxCare-managed registry — drop-in replacements that extend coverage beyond upstream EOL. +- [SecureChain JavaScript Ecosystem Guide](https://docs.tuxcare.com/securechain/javascript/): Connect to the SecureChain npm registry, authenticate, install dependencies, and troubleshoot resolution. +- [Managing the SecureChain Repository](https://docs.tuxcare.com/securechain/managing-securechain-repository/): Upgrade installed SecureChain packages to newer releases across ecosystems. + +## Account & reference + +- [Subscription Management Portal](https://docs.tuxcare.com/tuxcare-cln/): Manage licenses and subscriptions for TuxCare products and services. +- [Service Descriptions](https://docs.tuxcare.com/service-descriptions/): Service-level and technical support details. + +## Optional + +- [TuxCare product site](https://tuxcare.com/): Marketing/product overview and contact. +- [CVE Tracker](https://tuxcare.com/cve-tracker/): Track vulnerability fixes and update status across TuxCare-supported packages. diff --git a/package.json b/package.json index fe08cf4a1..e5edb67b6 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "docs:dev": "vuepress dev docs", - "docs:build": "vuepress build docs", + "docs:build": "node scripts/generate-llms-full.mjs && vuepress build docs", "dev": "vite", "build": "vue-tsc && vite build", "preview": "vite preview" diff --git a/scripts/check-llms-links.mjs b/scripts/check-llms-links.mjs new file mode 100644 index 000000000..b90e8a6da --- /dev/null +++ b/scripts/check-llms-links.mjs @@ -0,0 +1,86 @@ +#!/usr/bin/env node +/** + * Drift guard for llms.txt. + * + * The curated llms.txt is hand-maintained. Its one real staleness risk is a + * product being added to (or renamed in) the site's product menu without a + * matching entry in llms.txt. This check compares the product links in + * docs/.vuepress/config-client/documents.ts (the source of truth for the + * product menu) against llms.txt and fails if any product is missing. + * + * It intentionally does NOT require the reverse: llms.txt may include extra + * links (SecureChain subsections, the Optional marketing/CVE-tracker links) + * that are not products in documents.ts. + * + * No dependencies — runs on plain Node built-ins. + */ +import { existsSync, readFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const REPO = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const DOCS = join(REPO, "docs"); +const DOCUMENTS = join(DOCS, ".vuepress", "config-client", "documents.ts"); +const LLMS = join(DOCS, ".vuepress", "public", "llms.txt"); +const SITE = "https://docs.tuxcare.com"; + +const documentsSrc = readFileSync(DOCUMENTS, "utf-8"); +const llms = readFileSync(LLMS, "utf-8"); + +// Extract each product's title + link from the documents.ts array literal. +const productRe = /title:\s*"([^"]+)"[\s\S]*?link:\s*"([^"]+)"/g; +const products = [...documentsSrc.matchAll(productRe)].map((m) => ({ + title: m[1], + link: m[2], +})); + +if (products.length === 0) { + console.error( + `[check-llms-links] Could not parse any products from ${DOCUMENTS}. ` + + `Has the file format changed?`, + ); + process.exit(1); +} + +const missing = products.filter(({ link }) => !llms.includes(SITE + link)); + +if (missing.length > 0) { + console.error( + "[check-llms-links] llms.txt is out of sync with the site product menu.\n" + + "The following products in docs/.vuepress/config-client/documents.ts " + + "have no matching link in docs/.vuepress/public/llms.txt:\n", + ); + for (const { title, link } of missing) { + console.error(` - ${title} -> ${SITE + link}`); + } + console.error( + "\nAdd each missing product to llms.txt (with a curated one-line " + + "description), then re-run this check.", + ); + process.exit(1); +} + +// Validity: every docs.tuxcare.com page link in llms.txt must resolve to a +// real page on disk. Catches typos and links left stale when a product is +// renamed or removed in documents.ts. Page URLs end in "/"; the llms-full.txt +// file link (and any other non-page link) is skipped. +const pageRe = /https:\/\/docs\.tuxcare\.com(\/[\w/-]*\/)/g; +const broken = [...new Set([...llms.matchAll(pageRe)].map((m) => m[1]))].filter( + (path) => !existsSync(join(DOCS, path, "README.md")), +); + +if (broken.length > 0) { + console.error( + "[check-llms-links] llms.txt contains links to pages that do not exist " + + "on disk (typo, or a product renamed/removed in documents.ts?):\n", + ); + for (const path of broken) { + console.error(` - ${SITE + path} -> docs${path}README.md (not found)`); + } + process.exit(1); +} + +console.log( + `[check-llms-links] OK — all ${products.length} product links present, ` + + `every page link resolves on disk.`, +); diff --git a/scripts/generate-llms-full.mjs b/scripts/generate-llms-full.mjs new file mode 100644 index 000000000..542cf0253 --- /dev/null +++ b/scripts/generate-llms-full.mjs @@ -0,0 +1,123 @@ +#!/usr/bin/env node +/** + * Generate docs/.vuepress/public/llms-full.txt from the documentation source. + * + * Runs before `vuepress build` (see the `docs:build` script in package.json), + * so the file is always regenerated from the current docs and copied to the + * site root by VuePress (public/ -> dist/). The file is git-ignored; it exists + * only in the build output. + * + * Each page is emitted under a `Source:` header with its published URL. + * Cleaning applied: + * - strip YAML frontmatter + * - strip HTML comments + * - strip