diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 464ae9c..0a64b34 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -16,5 +16,15 @@ RUN HUGO_VERSION="$(cat /tmp/.hugo-version)" \
&& apt-get install /tmp/hugo.deb -y \
&& rm /tmp/hugo.deb /tmp/.hugo-version
+# Install Node.js (needed to `npm ci` the FontAwesome/Bootstrap build-time
+# deps in themes/pydata). Version is pinned in .node-version at the repo
+# root — the same file CI's actions/setup-node reads — so the devcontainer
+# and GitHub Actions never drift apart.
+COPY .node-version /tmp/.node-version
+RUN NODE_VERSION="$(cat /tmp/.node-version)" \
+ && curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" -o /tmp/node.tar.xz \
+ && tar -xJf /tmp/node.tar.xz --strip-components=1 -C /usr/local \
+ && rm /tmp/node.tar.xz /tmp/.node-version
+
# Help avoid "unsupported locale setting" in Sphinx
RUN echo "export LC_ALL=C.UTF-8" > ~/.bashrc
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index abe7bc6..c59e473 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -17,17 +17,18 @@
},
// Features to add to the dev container. More info: https://containers.dev/features.
- // "features": {
- // node / npm is required for markdownlint-cli2
- // By default, installs lts version
- // "ghcr.io/devcontainers/features/node:1": {}
- // },
+ // Node.js itself is installed directly in the Dockerfile (pinned via the
+ // root .node-version file), not via this feature — avoids two divergent
+ // install paths.
+ // "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
- // Use 'postCreateCommand' to run commands after the container is created.
- // "postCreateCommand": ""
+ // Install the theme's build-time npm deps (FontAwesome/Bootstrap) after
+ // the workspace is mounted — a Dockerfile RUN step would get shadowed by
+ // the workspace bind mount, since node_modules isn't part of the repo.
+ "postCreateCommand": "cd themes/pydata && npm ci",
// There is also a postStartCommand that executes every time the container starts.
// The parameters behave exactly like postCreateCommand, but the commands execute on start rather than create.
diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml
index 27fcccc..8974130 100644
--- a/.github/workflows/gh-pages.yml
+++ b/.github/workflows/gh-pages.yml
@@ -49,6 +49,15 @@ jobs:
uses: actions/checkout@v4
- name: Install Hugo CLI
uses: ./.github/actions/setup-hugo
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ cache: 'npm'
+ cache-dependency-path: themes/pydata/package-lock.json
+ - name: Install npm dependencies
+ working-directory: themes/pydata
+ run: npm ci
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml
index 3a4aa3f..497db6d 100644
--- a/.github/workflows/pr-checks.yml
+++ b/.github/workflows/pr-checks.yml
@@ -27,6 +27,15 @@ jobs:
uses: actions/checkout@v4
- name: Install Hugo CLI
uses: ./.github/actions/setup-hugo
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ cache: 'npm'
+ cache-dependency-path: themes/pydata/package-lock.json
+ - name: Install npm dependencies
+ working-directory: themes/pydata
+ run: npm ci
- name: Build with Hugo
env:
HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
diff --git a/.gitignore b/.gitignore
index dfb1e1c..b2cf406 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,5 @@ static/images/.DS_Store
.env.*
*.pem
*.key
+sandbox/
+node_modules/
diff --git a/.node-version b/.node-version
new file mode 100644
index 0000000..ba33190
--- /dev/null
+++ b/.node-version
@@ -0,0 +1 @@
+20.19.2
diff --git a/README.md b/README.md
index 15e6049..cd6eb0c 100644
--- a/README.md
+++ b/README.md
@@ -8,12 +8,17 @@ This repository is for the main static site of https://saltproject.io, built wit
- The exact version pinned for this project is tracked in the root `.hugo-version` file. The devcontainer and CI both install that exact version automatically (see `.devcontainer/Dockerfile` and `.github/actions/setup-hugo`), so it only needs to be updated in one place.
- Must be the `extended` version, and at least the minimum declared in the vendored `themes/pydata` theme's `hugo.toml` (`module.hugoVersion.min`).
- This was built with the `extended` version of Hugo, which is required.
+- Node.js — version pinned in the root `.node-version` file, same single-source-of-truth pattern as `.hugo-version`. Needed to install the theme's FontAwesome/Bootstrap build-time dependencies (see [FontAwesome and Bootstrap](#fontawesome-and-bootstrap-npm-at-build-time)).
- Git
- Python 3.14+ (only needed to run `scripts/validate-tags.py` locally)
### Build a local preview
```bash
+# Install the theme's build-time npm dependencies (FontAwesome/Bootstrap) —
+# only needs to be re-run when themes/pydata/package.json changes.
+cd themes/pydata && npm ci && cd -
+
# Serves for viewing changes locally
# Dynamically loads updates when changes happen in repo
hugo serve
@@ -195,6 +200,14 @@ The `scripts` folder holds standalone helper scripts used in local development a
- `.github/workflows/gh-pages.yml` — builds the site on every push, and deploys it to GitHub Pages when the push is a tag matching `v**` (see [Push the current `main` branch to the live site](#push-the-current-main-branch-to-the-live-site)). Deploy-time (`pages`/`id-token` write) permissions are scoped to just the `deploy` job; it never triggers on `pull_request`.
- `.github/actions/setup-hugo` — local composite action shared by both workflows above to install the Hugo CLI. Defaults to the version pinned in the root `.hugo-version` file — the same file the devcontainer's `Dockerfile` reads — so the required Hugo version only needs to be maintained in that one place.
+### FontAwesome and Bootstrap (npm, at build time)
+
+`themes/pydata` doesn't vendor FontAwesome or Bootstrap as committed files. `themes/pydata/package.json` pins the exact versions (FontAwesome's JS+SVG icon kit, Bootstrap's JS bundle); Hugo's `[[module.mounts]]` (in `themes/pydata/hugo.toml`) mounts the relevant prebuilt files straight out of `node_modules` into the asset pipeline, and `themes/pydata/layouts/_partials/head/{fontawesome,bootstrap-js}.html` load them through Hugo Pipes (fingerprinted in production builds). This means:
+
+- You need to run `npm ci` inside `themes/pydata` before `hugo build`/`hugo server` will work — the devcontainer does this automatically via `postCreateCommand`; CI does it via an `actions/setup-node` + `npm ci` step in both workflows.
+- Their license text isn't duplicated anywhere in this repo — it ships inside the respective (gitignored) npm packages under `themes/pydata/node_modules/.../LICENSE*`.
+- The Node.js version used for this is pinned in the root `.node-version` file, following the same single-source-of-truth pattern as `.hugo-version`.
+
## Credits
The `themes/pydata` theme vendored in this repository is a Hugo port of the [PyData Sphinx Theme](https://github.com/pydata/pydata-sphinx-theme) ([docs](https://pydata-sphinx-theme.readthedocs.io/en/stable/)), originally built for Sphinx documentation sites. Credit to the PyData Sphinx Theme authors and contributors for the design and functionality this port is based on.
diff --git a/hugo.toml b/hugo.toml
index cc75298..406caad 100644
--- a/hugo.toml
+++ b/hugo.toml
@@ -51,6 +51,14 @@ theme = 'pydata'
image_dark = 'images/SaltProject_altlogo_teal.png'
alt_text = 'Salt Project - Home'
+# ---------------------------------------------------------------------------
+# External links — any link pointing off-site opens in a new tab, except
+# base URLs listed under `exceptions` (matched by prefix).
+# ---------------------------------------------------------------------------
+[params.external_links]
+ new_tab = true
+ exceptions = ['https://docs.saltproject.io/']
+
[params.landing]
title = "Welcome to the Salt Project"
description = "Salt is an open-source automation framework for remote execution, configuration management, and infrastructure orchestration — used by teams to manage thousands of systems from a single control plane."
@@ -60,31 +68,37 @@ theme = 'pydata'
title = "Install Salt"
description = "Step-by-step instructions for every platform and operating system."
url = "https://docs.saltproject.io/salt/install-guide/en/latest/"
+ icon = "fa-solid fa-download"
[[params.landing.nav_boxes]]
title = "User Guide"
description = "Learn Salt's core concepts: states, grains, pillars, and more."
url = "https://docs.saltproject.io/salt/user-guide/en/latest/"
+ icon = "fa-solid fa-book"
[[params.landing.nav_boxes]]
title = "Reference Docs"
description = "Full module and API reference for Salt."
url = "https://docs.saltproject.io/en/latest/contents.html"
+ icon = "fa-solid fa-code"
[[params.landing.nav_boxes]]
title = "Blog"
description = "Release announcements, community updates, and Salt news."
url = "/blog/"
+ icon = "fa-solid fa-newspaper"
[[params.landing.nav_boxes]]
title = "Discord Community"
description = "Join the Salt Project Discord, attend events, and connect with contributors."
url = "https://discord.gg/J7b7EscrAs"
+ icon = "fa-brands fa-discord"
[[params.landing.nav_boxes]]
title = "GitHub"
description = "Browse the source, open issues, and contribute to Salt."
url = "https://github.com/saltstack/salt"
+ icon = "fa-brands fa-github"
[[params.icon_links]]
name = 'Discord'
diff --git a/layouts/community/event-calendar.html b/layouts/community/event-calendar.html
index 20dac6c..bf94481 100644
--- a/layouts/community/event-calendar.html
+++ b/layouts/community/event-calendar.html
@@ -27,9 +27,10 @@