diff --git a/CHANGELOG.md b/CHANGELOG.md
index 63f4087f..bf25ae74 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
+- The Documentation and Support links in Settings, About now open the
+ documentation site.
+
- Small System Summary tiles now label each figure, as the medium size does.
- The Stats widget is now two widgets, System Summary and Disk Health. Existing
diff --git a/README.md b/README.md
index fce94208..042a7ce8 100644
--- a/README.md
+++ b/README.md
@@ -18,24 +18,13 @@
Most dashboards are a wall of numbers and charts. Stackyard is the opposite: a calm, launcher-style grid of app tiles, folders, and a small number of
*genuinely useful* widgets, running in a single container. Built to be glanced at a hundred times a day without feeling cluttered.
-## Contents
-
-- [Why Stackyard](#why-stackyard)
-- [Widgets](#widgets)
-- [Live activity badges](#live-activity-badges)
-- [Icons](#icons)
-- [Getting started](#getting-started)
-- [Security](#security)
-- [Building from source](#building-from-source)
-- [Contributing](#contributing)
-- [Changelog](#changelog)
-- [License](#license)
+**Documentation: [stackyard.sandobserver.com/docs](https://stackyard.sandobserver.com/docs/)**
## Why Stackyard
- **Attention goes where it's needed, not everywhere at once.** A calm grid, no charts or counters. Health badges only appear when something's wrong.
- **A glance should tell you more than a number would.** Widgets are small visuals, not readouts.
-- **Anything can be a badge.** Point Stackyard at any API, pick a value from the response, and show it as a [live activity badge](#live-activity-badges). No custom widget, no code.
+- **Anything can be a badge.** Point Stackyard at any API, pick a value from the response, and show it as a [live activity badge](https://stackyard.sandobserver.com/docs/badges/). No custom widget, no code.
- **Configured by clicking, not by editing files.** Everything is set up in the web UI, with config import and export.
- **No dependencies.** Review it once and stop worrying about the supply chain.
- **Six languages, right-to-left included.** Contrast and screen-reader labels are covered by tests.
@@ -56,25 +45,12 @@ Widgets and the services they read:
- **Backup**: Duplicati, Kopia
- **Connections**: Gluetun, Psiphon Conduit, Netbird, Plausible, Umami
-A Glances running in Docker reports its own filesystems, not the host's. Mount
-the host paths into the Glances container for a disk slot to read them.
-
-Adding one is a folder plus one registry entry, with no changes to the rest of the app. See [docs/widgets.md](docs/widgets.md).
-
-## Live activity badges
-
-Give Stackyard an API endpoint and it lists the numbers in the response, so you pick the one you want on the tile. Point it at Sonarr's queue and the Sonarr tile carries a count of episodes still downloading. **Show From** sets a floor, so a queue that is never quite empty stays quiet until it matters.
-
-## Icons
-
-App icons resolve automatically by name from the community [dashboard-icons](https://github.com/homarr-labs/dashboard-icons) set. You can also upload your own; custom icons are stored in `./icons`.
+Each widget is documented at [Widgets](https://stackyard.sandobserver.com/docs/widgets/). Adding one is a folder plus one registry entry, with no changes to the rest of the app; see [docs/widgets.md](docs/widgets.md).
## Getting started
You need [Docker](https://docs.docker.com/get-started/get-docker/).
-**Using Docker Compose:**
-
```yaml
services:
stackyard:
@@ -92,41 +68,27 @@ services:
docker compose up -d
```
-**Or with `docker run`:**
-
-```sh
-docker run -d \
- --name stackyard \
- --restart unless-stopped \
- -p 8700:80 \
- -v ./data:/data \
- -v ./icons:/icons \
- ghcr.io/sandobserver/stackyard:latest
-```
-
-**On Unraid:** install it from [Community Apps](https://ca.unraid.net/apps/stackyard-0ara4ku0sjjwqy).
-
Then open `http://localhost:8700` and set everything up at `/admin`. Config and uploaded icons persist in `./data` and `./icons`.
+On Unraid, install it from [Community Apps](https://ca.unraid.net/apps/stackyard-0ara4ku0sjjwqy).
+
The same image is on Docker Hub as `sandobserver/stackyard`. Prefer `ghcr.io`: it is the registry the [release signature](docs/security.md#verifying-a-release-image) covers.
The repo's [`docker-compose.yml`](docker-compose.yml) is the recommended version: it adds resource limits, dropped capabilities, and commented options for a reverse proxy, host access, and Docker health checks.
-Every section of the admin UI is shown in [docs/screenshots.md](docs/screenshots.md).
+Full instructions, including building from source, are at [Installation](https://stackyard.sandobserver.com/docs/installation/docker/).
-## Security
-
-Stackyard never returns stored secrets to the browser, guards the URLs you test in the admin UI against SSRF and pins the resolved IP, and bounds every upstream call so one slow service cannot hang the dashboard. Some features trade safety for convenience and are opt-in with warnings. Read [docs/security.md](docs/security.md) before exposing Stackyard beyond your LAN.
+## Documentation
-## Building from source
+- [First setup](https://stackyard.sandobserver.com/docs/first-setup/)
+- [Settings reference](https://stackyard.sandobserver.com/docs/settings-reference/)
+- [Badges](https://stackyard.sandobserver.com/docs/badges/)
+- [Customization](https://stackyard.sandobserver.com/docs/customization/)
+- [Troubleshooting](https://stackyard.sandobserver.com/docs/troubleshooting/) and [Support](https://stackyard.sandobserver.com/docs/support/)
-```sh
-git clone https://github.com/SandObserver/stackyard.git
-cd stackyard
-docker build -t stackyard:local .
-```
+## Security
-Then run `stackyard:local` the same way as above. For working on the code without Docker, see [CONTRIBUTING.md](CONTRIBUTING.md).
+Stackyard never returns stored secrets to the browser, guards the URLs you test in the admin UI against SSRF and pins the resolved IP, and bounds every upstream call so one slow service cannot hang the dashboard. Some features trade safety for convenience and are opt-in with warnings. Read [docs/security.md](docs/security.md) before exposing Stackyard beyond your LAN.
## Contributing
diff --git a/docs/screenshots.md b/docs/screenshots.md
deleted file mode 100644
index 3e86ebfb..00000000
--- a/docs/screenshots.md
+++ /dev/null
@@ -1,32 +0,0 @@
-# Screenshots
-
-The dashboard, and each section of the admin UI at `/admin`.
-
-## Dashboard
-
-
-
-## General
-
-Title, language, password protection, and config import and export. Import also
-reads a gethomepage or Dashy YAML file.
-
-
-
-## Appearance
-
-Wallpaper, and how the settings pages themselves are displayed.
-
-
-
-## Dashboard
-
-Apps, folders, and widgets, in the order they appear on the home screen.
-
-
-
-## Editing an app
-
-Icon, colour, dock placement, and the badge the tile carries.
-
-
diff --git a/docs/support.md b/docs/support.md
index f146d5e5..8fb16869 100644
--- a/docs/support.md
+++ b/docs/support.md
@@ -1,224 +1,5 @@
# Support
-## Supporting Stackyard
+Moved to .
-- Like it? Tell someone about it.
-- Found a bug, or have an idea? [Open an issue](https://github.com/SandObserver/stackyard/issues).
-- Want to buy me a coffee? https://buymeacoffee.com/sandobserver
-
-## Where to get help
-
-- Bugs and feature requests: https://github.com/SandObserver/stackyard/issues
-- Questions and setup help: open an issue with the `question` label.
-
-Search existing issues first.
-
-## Filing a bug report
-
-Include the version (Settings app, About), how you deployed and the image tag,
-what you expected and what happened, logs from around the problem, and steps to
-reproduce. For anything visual, add your browser and a screenshot.
-
-Redact secrets before pasting logs or config.
-
-## Reading the logs
-
-The API logs to the container's stdout. Nginx logs request errors to stderr and
-keeps no access log, so both reach the same place:
-
-```
-docker logs
-```
-
-With Compose:
-
-```
-docker compose logs -f
-```
-
-In Portainer, open the container and use the **Logs** view.
-
-### What a log line looks like
-
-Every API record is one line:
-
-```
- msg= key=value
-```
-
-`LVL` is one of `DBG`, `INF`, `WRN`, `ERR` or `AUD`. `AUD` marks a
-security-relevant event and is never filtered by the level setting.
-
-- Warnings are emitted at both `warn` and `error`, so choosing Errors in
- Settings still shows them.
-- The startup banner is not a record. It has no timestamp and no level, and it
- always prints.
-- Secrets are never logged. Values that could carry one, such as the address of
- a service that failed a connection test, are reduced to the host.
-- Lines from supervisord and Nginx have their own shapes and are not Stackyard
- records.
-
-The format is logfmt, so anything shipping to Loki or Grafana parses it with
-`| logfmt` and no custom rules.
-
-To change how much is logged, see the `LOG_LEVEL` row in
-[architecture.md](architecture.md#environment).
-
-## Common problems
-
-### I can't log in, or I get bounced back to the login screen
-
-The session cookie sets `Secure` only on HTTPS. Behind a TLS-terminating reverse
-proxy, the browser is sent a cookie it refuses to store and login fails
-silently. Set `TRUST_PROXY=true` and make the proxy send
-`X-Forwarded-Proto: https`. Only set it when a proxy you control is actually in
-front of the app; see [security.md](security.md).
-
-Logins are rate-limited to 5 attempts per IP per 15 minutes.
-
-### One person's failed logins lock everyone out
-
-Behind another reverse proxy, Stackyard's nginx sees that proxy as the client, so
-every request through it shares one rate-limit bucket. Set `TRUSTED_PROXY` to
-where the proxy is, for example `TRUSTED_PROXY=172.18.0.0/16`; see
-[security.md](security.md).
-
-### A setting has no effect
-
-Values typed into a Docker UI's environment editor, such as Portainer's stack
-environment panel, do not reach the container by themselves. Compose uses them
-to substitute `${NAME}` in the Compose file, and passes nothing else through.
-The shipped `docker-compose.yml` carries a `${NAME}` line for every operator
-setting, so the panel works. A Compose file that hardcodes values ignores the
-panel. Confirm what the container actually received:
-
-```
-docker exec stackyard env
-```
-
-### An upload or a save is rejected as too large
-
-Stackyard limits request sizes so a single request cannot consume the memory of
-a small machine:
-
-| What | Limit | Why |
-|---|---|---|
-| Icon upload | 2 MB | every icon shipped with Stackyard is under 34 KB |
-| Config save | 2 MB | a 300-app dashboard is about 155 KB |
-
-nginx allows 3 MB, above both, so the API is always the component that refuses
-an oversized request and can say what the limit is.
-
-### A widget shows "Blocked: ... is a private address"
-
-The proxy blocks private and loopback addresses by default as an SSRF safeguard.
-Most homelab services live on private IPs, so set `ALLOW_PRIVATE_IPS=true`.
-
-Docker service names (hostnames with no dot, such as `adguard`) are trusted and
-are not blocked, so linking to containers on the same network works without it.
-
-### Every app with a container shows as unhealthy
-
-The container list comes from a Docker socket proxy, a separate container you
-run yourself. Stackyard only stores its address. When that address cannot be
-reached no container is found, and a container that cannot be found counts as
-not running, so every app checked by container name turns red at once.
-
-Settings refuses an address that is plainly wrong when you save it, and names the
-reason. An address that is accepted but stops working later shows as
-`container health fetch failed` in `docker logs`.
-
-Two addresses that look interchangeable are not, and they fail for opposite
-reasons.
-
-**A service name**, such as `http://socket-proxy:2375`, is resolved by Docker's
-own DNS, which answers only for containers that share a network. Add the proxy's
-network to the Stackyard service and the name resolves:
-
-```yaml
-services:
- stackyard:
- networks:
- - socket_proxy_network
-
-networks:
- socket_proxy_network:
- external: true
- name: socket_proxy_network
-```
-
-A service on `network_mode: bridge` is on Docker's default bridge, which has no
-name resolution at all, so no service name can work from there.
-
-**An IP address** reaches only what the proxy published. The usual socket proxy
-compose publishes on the host's loopback:
-
-```yaml
- ports:
- - "127.0.0.1:2375:2375"
-```
-
-That port exists on the host and nowhere else. A container's own `127.0.0.1` is
-not the host's, and the host's LAN address does not carry the port either, so no
-IP reaches the proxy from inside a container. Because packets to it are dropped
-rather than refused, this appears as a timeout rather than a connection error.
-
-To use an IP, republish the proxy's port on an interface containers can reach.
-The Docker bridge address, usually `172.17.0.1`, reaches containers on that host
-without exposing anything to the network:
-
-```yaml
- ports:
- - "172.17.0.1:2375:2375"
-```
-
-Publishing on `0.0.0.0` also works and is worth avoiding. Even with `POST=0` and
-`EXEC=0`, it offers an unauthenticated read of every container, image, network
-and log on that host to anyone who can reach the port.
-
-Reaching a proxy on a **different host** needs that port published on the host's
-LAN address, with the exposure above. Running a socket proxy on each host and
-pointing each Stackyard at its own is usually better, since container names on
-one host mean nothing on another.
-
-### A widget says "Not configured" or shows an error instead of data
-
-- Confirm the server URL and credentials in the Settings app. Secret fields show
- as "set" without revealing the value; leaving them untouched keeps the secret.
-- Confirm the container can reach the URL you entered (right network, right
- port, no firewall in between).
-- For HTTPS services with self-signed certificates, enable the per-widget or
- global TLS-skip option.
-
-### A widget briefly shows "Unavailable" then recovers
-
-Widgets keep the last good reading through a transient failure and only surface
-an error after repeated failures. A flash that clears on its own means one poll
-timed out.
-
-### My dashboards disappeared after a restart
-
-If the config fails to parse on startup, Stackyard copies it to
-`apps.json.corrupt` and starts empty rather than overwriting the broken file.
-Your previous config is preserved there, in the data volume.
-
-### An icon I re-uploaded still shows the old image
-
-Icons are served with revalidation, so a re-upload should appear on the next
-load. If it does not, hard refresh.
-
-### I updated the image but the UI looks the same
-
-UI files ship inside the image. Pull the new image and recreate the container
-(in Portainer, redeploy the stack). A browser refresh alone will not do it.
-
-### Container health check is failing
-
-The health check runs through Nginx to the API, so it covers both processes.
-Check the logs for either failing to start. A common cause is the data volume
-not being writable by the container's `node` user.
-
-## Security issues
-
-Do not post suspected vulnerabilities in public issues. Report them privately to
-the maintainer. See [security.md](security.md).
+Troubleshooting is at .
diff --git a/scripts/dockerhub-description.js b/scripts/dockerhub-description.js
index a192334b..9a32785f 100644
--- a/scripts/dockerhub-description.js
+++ b/scripts/dockerhub-description.js
@@ -33,7 +33,9 @@ const HEADER = `# Stackyard
Try it: **[demo.sandobserver.com](https://demo.sandobserver.com)**
The first demo visit may take up to a minute due to Render's free-tier cold start.
-Source, issues and full documentation: **[github.com/${REPO}](https://github.com/${REPO})**
+Documentation: **[stackyard.sandobserver.com/docs](https://stackyard.sandobserver.com/docs/)**
+
+Source and issues: **[github.com/${REPO}](https://github.com/${REPO})**
`;
/** Everything before the first `##` heading, replaced by the markdown header.
diff --git a/ui/admin/index.html b/ui/admin/index.html
index c678b69c..4ede3b57 100644
--- a/ui/admin/index.html
+++ b/ui/admin/index.html
@@ -253,10 +253,10 @@ General
Version