Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/docs-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ permissions:
jobs:
build:
runs-on: ubuntu-latest
env:
DOCS_SITE_URL: ${{ vars.DOCS_SITE_URL }}
DOCS_BASE_URL: ${{ vars.DOCS_BASE_URL }}
defaults:
run:
working-directory: website
Expand Down
56 changes: 34 additions & 22 deletions .github/workflows/docs-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,26 @@ on:
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write
contents: write

concurrency:
group: pages
group: docs-static-branch
cancel-in-progress: true

jobs:
build:
build-and-publish:
runs-on: ubuntu-latest
env:
DOCS_SITE_URL: ${{ vars.DOCS_SITE_URL }}
DOCS_BASE_URL: ${{ vars.DOCS_BASE_URL }}
DOCS_DEPLOY_BRANCH: ${{ vars.DOCS_DEPLOY_BRANCH }}
defaults:
run:
working-directory: website
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Setup Pages
uses: actions/configure-pages@v5

- name: Setup Node
uses: actions/setup-node@v4
with:
Expand All @@ -43,18 +42,31 @@ jobs:
GITHUB_REPOSITORY: ${{ github.repository }}
run: npm run build

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v4
with:
path: website/build
- name: Publish built static files to deploy branch
working-directory: .
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
BRANCH="${DOCS_DEPLOY_BRANCH:-gh-pages}"
BUILD_DIR="${GITHUB_WORKSPACE}/website/build"
PUBLISH_DIR="$(mktemp -d)"

if [ ! -f "${BUILD_DIR}/index.html" ]; then
echo "Expected built docs at ${BUILD_DIR}/index.html" >&2
exit 1
fi

cp -a "${BUILD_DIR}/." "${PUBLISH_DIR}/"
touch "${PUBLISH_DIR}/.nojekyll"

cd "${PUBLISH_DIR}"
git init
git checkout -b "${BRANCH}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add --all
git commit -m "Publish docs for ${GITHUB_SHA}"
git remote add origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
git push --force origin "HEAD:${BRANCH}"
39 changes: 39 additions & 0 deletions scripts/self-hosted-docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Self-Hosted Docs Scripts

These scripts are for a pull-based static docs deploy.

The idea is:

1. GitHub Actions builds the Docusaurus site.
2. GitHub Actions publishes only the built static files to a deploy branch.
3. The server pulls that deploy branch.
4. The server copies the static files into a timestamped release directory.
5. The web server serves the `current` symlink target.

These scripts are meant to run on the server as a dedicated non-root docs user.

## Default Assumptions

- repo URL: `https://github.com/morganross/apicostx-docs.git`
- deploy branch: `gh-pages`
- docs root: `/srv/apicostx-docs`

## Scripts

- `clone-or-update-docs-static.sh`
- clones or updates the deploy branch checkout
- `publish-docs-static.sh`
- copies the checkout into a new release directory and atomically switches `current`
- `verify-docs-static.sh`
- checks that the release looks like a safe Docusaurus static site
- `rollback-docs-static.sh`
- points `current` back to an older release
- `deploy-docs-static.sh`
- convenience wrapper: update repo, publish release, verify result

## Important Security Notes

- Do not run these scripts as `root`.
- Do not point the web server directly at the Git checkout.
- If the deploy branch is private, use a read-only deploy key on the server.
- Only the built static branch should be cloned onto the server.
26 changes: 26 additions & 0 deletions scripts/self-hosted-docs/clone-or-update-docs-static.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail

if [ "$(id -u)" -eq 0 ]; then
echo "Do not run this script as root. Use a dedicated docs user." >&2
exit 1
fi

DOCS_REPO_URL="${DOCS_REPO_URL:-https://github.com/morganross/apicostx-docs.git}"
DOCS_BRANCH="${DOCS_BRANCH:-gh-pages}"
DOCS_ROOT="${DOCS_ROOT:-/srv/apicostx-docs}"
DOCS_REPO_DIR="${DOCS_REPO_DIR:-${DOCS_ROOT}/repo}"

mkdir -p "${DOCS_ROOT}"

if [ ! -d "${DOCS_REPO_DIR}/.git" ]; then
git clone --depth 1 --branch "${DOCS_BRANCH}" "${DOCS_REPO_URL}" "${DOCS_REPO_DIR}"
else
git -C "${DOCS_REPO_DIR}" remote set-url origin "${DOCS_REPO_URL}"
git -C "${DOCS_REPO_DIR}" fetch --depth 1 origin "${DOCS_BRANCH}"
git -C "${DOCS_REPO_DIR}" checkout -B "${DOCS_BRANCH}" "origin/${DOCS_BRANCH}"
git -C "${DOCS_REPO_DIR}" reset --hard "origin/${DOCS_BRANCH}"
git -C "${DOCS_REPO_DIR}" clean -fdx
fi

git -C "${DOCS_REPO_DIR}" rev-parse HEAD
7 changes: 7 additions & 0 deletions scripts/self-hosted-docs/deploy-docs-static.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"

"${SCRIPT_DIR}/clone-or-update-docs-static.sh"
"${SCRIPT_DIR}/publish-docs-static.sh"
45 changes: 45 additions & 0 deletions scripts/self-hosted-docs/publish-docs-static.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail

if [ "$(id -u)" -eq 0 ]; then
echo "Do not run this script as root. Use a dedicated docs user." >&2
exit 1
fi

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"

DOCS_ROOT="${DOCS_ROOT:-/srv/apicostx-docs}"
DOCS_REPO_DIR="${DOCS_REPO_DIR:-${DOCS_ROOT}/repo}"
DOCS_RELEASES_DIR="${DOCS_RELEASES_DIR:-${DOCS_ROOT}/releases}"
DOCS_CURRENT_LINK="${DOCS_CURRENT_LINK:-${DOCS_ROOT}/current}"

if ! command -v rsync >/dev/null 2>&1; then
echo "rsync is required for publish-docs-static.sh" >&2
exit 1
fi

if [ ! -d "${DOCS_REPO_DIR}" ]; then
echo "Docs repo checkout not found: ${DOCS_REPO_DIR}" >&2
exit 1
fi

mkdir -p "${DOCS_RELEASES_DIR}"

TIMESTAMP="$(date -u +%Y-%m-%dT%H%M%SZ)"
RELEASE_DIR="${DOCS_RELEASES_DIR}/${TIMESTAMP}"
TEMP_LINK="${DOCS_CURRENT_LINK}.tmp"

mkdir -p "${RELEASE_DIR}"

rsync -a --delete \
--exclude '.git' \
--exclude '.github' \
"${DOCS_REPO_DIR}/" "${RELEASE_DIR}/"

"${SCRIPT_DIR}/verify-docs-static.sh" "${RELEASE_DIR}"

ln -sfn "${RELEASE_DIR}" "${TEMP_LINK}"
mv -Tf "${TEMP_LINK}" "${DOCS_CURRENT_LINK}"

echo "Published static docs release: ${RELEASE_DIR}"
echo "Current symlink now points to: ${DOCS_CURRENT_LINK}"
35 changes: 35 additions & 0 deletions scripts/self-hosted-docs/rollback-docs-static.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail

if [ "$(id -u)" -eq 0 ]; then
echo "Do not run this script as root. Use a dedicated docs user." >&2
exit 1
fi

DOCS_ROOT="${DOCS_ROOT:-/srv/apicostx-docs}"
DOCS_RELEASES_DIR="${DOCS_RELEASES_DIR:-${DOCS_ROOT}/releases}"
DOCS_CURRENT_LINK="${DOCS_CURRENT_LINK:-${DOCS_ROOT}/current}"
TEMP_LINK="${DOCS_CURRENT_LINK}.tmp"

TARGET_INPUT="${1:-}"

if [ -z "${TARGET_INPUT}" ]; then
echo "Usage: $0 RELEASE_NAME_OR_ABSOLUTE_PATH" >&2
exit 1
fi

if [[ "${TARGET_INPUT}" = /* ]]; then
TARGET_DIR="${TARGET_INPUT}"
else
TARGET_DIR="${DOCS_RELEASES_DIR}/${TARGET_INPUT}"
fi

if [ ! -d "${TARGET_DIR}" ]; then
echo "Release directory does not exist: ${TARGET_DIR}" >&2
exit 1
fi

ln -sfn "${TARGET_DIR}" "${TEMP_LINK}"
mv -Tf "${TEMP_LINK}" "${DOCS_CURRENT_LINK}"

echo "Rolled back current docs release to: ${TARGET_DIR}"
43 changes: 43 additions & 0 deletions scripts/self-hosted-docs/verify-docs-static.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail

TARGET_DIR="${1:-}"

if [ -z "${TARGET_DIR}" ]; then
echo "Usage: $0 /path/to/release" >&2
exit 1
fi

if [ ! -d "${TARGET_DIR}" ]; then
echo "Release directory does not exist: ${TARGET_DIR}" >&2
exit 1
fi

if [ ! -f "${TARGET_DIR}/index.html" ]; then
echo "Missing index.html in ${TARGET_DIR}" >&2
exit 1
fi

if [ ! -d "${TARGET_DIR}/assets" ]; then
echo "Missing assets directory in ${TARGET_DIR}" >&2
exit 1
fi

for blocked in .git .github website docs node_modules package.json package-lock.json tsconfig.json; do
if [ -e "${TARGET_DIR}/${blocked}" ]; then
echo "Blocked file or directory found in release: ${blocked}" >&2
exit 1
fi
done

if find "${TARGET_DIR}" -type d \( -name .git -o -name .github \) | grep -q .; then
echo "Nested git metadata found inside the release." >&2
exit 1
fi

if find "${TARGET_DIR}" -perm -002 | grep -q .; then
echo "World-writable files found in the release." >&2
exit 1
fi

echo "Verified static docs release: ${TARGET_DIR}"
11 changes: 5 additions & 6 deletions website/docusaurus.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';

const repository = process.env.GITHUB_REPOSITORY ?? 'your-org/your-repo';
const repository = process.env.GITHUB_REPOSITORY ?? 'morganross/apicostx-docs';
const [organizationName, projectName] = repository.split('/');
const isUserOrOrgPagesSite = projectName === `${organizationName}.github.io`;
const url = process.env.DOCS_SITE_URL ?? `https://${organizationName}.github.io`;
const baseUrl =
process.env.DOCS_BASE_URL ??
(isUserOrOrgPagesSite ? '/' : `/${projectName}/`);
const defaultSiteUrl = 'https://apicostx.com';
const defaultBaseUrl = '/docs-static/';
const url = process.env.DOCS_SITE_URL ?? defaultSiteUrl;
const baseUrl = process.env.DOCS_BASE_URL ?? defaultBaseUrl;

const config: Config = {
title: 'ACM Documentation',
Expand Down
Loading