diff --git a/docs/MULTI_ROOT_WORKSPACE_SETUP.md b/docs/MULTI_ROOT_WORKSPACE_SETUP.md
index 2fc0a833..56bbab2f 100644
--- a/docs/MULTI_ROOT_WORKSPACE_SETUP.md
+++ b/docs/MULTI_ROOT_WORKSPACE_SETUP.md
@@ -1,169 +1,154 @@
# Multi-Root Workspace Setup Guide
-This document explains how the LEDMatrix project uses a multi-root workspace to manage plugins as separate Git repositories.
+This document explains how to work on LEDMatrix and the official plugins side
+by side, with one editor workspace and the plugins loaded straight from your
+plugin checkout.
## Overview
-The LEDMatrix project has been migrated from a git submodule implementation to a **multi-root workspace** implementation for managing plugins. This allows:
+Official plugins live in a single repository,
+[ledmatrix-plugins](https://github.com/ChuckBuilds/ledmatrix-plugins), with one
+directory per plugin under `plugins/`. There are no separate per-plugin
+repositories. For development you clone that monorepo **next to** LEDMatrix
+and symlink its plugin directories into LEDMatrix's `plugin-repos/`, which is
+where the plugin loader looks by default.
-- ✅ Plugins to exist as independent Git repositories
-- ✅ Updates to plugins without modifying the LEDMatrix project
-- ✅ Easy development workflow with all repos in one workspace
-- ✅ Plugin system discovers plugins via symlinks in `plugin-repos/`
+- ✅ Plugin code stays in the monorepo checkout, with its own git history
+- ✅ LEDMatrix discovers the plugins through symlinks in `plugin-repos/`
+- ✅ `LEDMatrix.code-workspace` opens both repositories in VS Code/Cursor
## Directory Structure
```text
-/home/chuck/Github/
-├── LEDMatrix/ # Main project
-│ ├── plugin-repos/ # Symlinks to actual repos (managed automatically)
-│ │ ├── ledmatrix-clock-simple -> ../../ledmatrix-clock-simple
-│ │ ├── ledmatrix-weather -> ../../ledmatrix-weather
+~/Github/
+├── LEDMatrix/ # Main project
+│ ├── plugin-repos/ # Plugin directory the loader scans
+│ │ ├── starlark-apps/ # Bundled with LEDMatrix (tracked in git)
+│ │ ├── web-ui-info/ # Bundled with LEDMatrix (tracked in git)
+│ │ ├── clock-simple -> ../../ledmatrix-plugins/plugins/clock-simple
+│ │ ├── ledmatrix-weather -> ../../ledmatrix-plugins/plugins/ledmatrix-weather
│ │ └── ...
-│ ├── LEDMatrix.code-workspace # Multi-root workspace configuration
+│ ├── LEDMatrix.code-workspace # Opens LEDMatrix and ../ledmatrix-plugins
│ └── ...
-├── ledmatrix-clock-simple/ # Plugin repository (actual git repo)
-├── ledmatrix-weather/ # Plugin repository (actual git repo)
-├── ledmatrix-football-scoreboard/ # Plugin repository (actual git repo)
-└── ... # Other plugin repos
+└── ledmatrix-plugins/ # Plugin monorepo (git repo)
+ ├── plugins/
+ │ ├── clock-simple/
+ │ ├── ledmatrix-weather/
+ │ └── ...
+ ├── plugins.json # Store registry
+ └── update_registry.py
```
## How It Works
-### 1. Plugin Repositories
+### 1. The plugin monorepo
-All plugin repositories are cloned to `/home/chuck/Github/` (parent directory of LEDMatrix) as regular Git repositories:
+Clone ledmatrix-plugins into the same parent directory as LEDMatrix (the
+scripts below look for `../ledmatrix-plugins` relative to the LEDMatrix
+root):
-- `ledmatrix-clock-simple/`
-- `ledmatrix-weather/`
-- `ledmatrix-football-scoreboard/`
-- etc.
+```bash
+cd ~/Github
+git clone https://github.com/ChuckBuilds/ledmatrix-plugins.git
+```
### 2. Symlinks in plugin-repos/
-The `LEDMatrix/plugin-repos/` directory contains symlinks pointing to the actual repositories in the parent directory. This allows the plugin system to discover plugins without modifying the project structure.
+`scripts/setup_plugin_repos.py` creates one symlink per plugin in
+`LEDMatrix/plugin-repos/`, named after the plugin's manifest `id` and pointing
+at `../ledmatrix-plugins/plugins/
`.
-### 3. Multi-Root Workspace
+### 3. Multi-root workspace
-The `LEDMatrix.code-workspace` file configures VS Code/Cursor to open all plugin repositories as separate workspace roots, allowing easy development across all repos.
+`LEDMatrix.code-workspace` has two roots: LEDMatrix itself and
+`../ledmatrix-plugins`.
## Setup Scripts
### Initial Setup
-If you already have plugin repositories cloned, use the setup script:
-
```bash
-cd /home/chuck/Github/LEDMatrix
+cd ~/Github/LEDMatrix
python3 scripts/setup_plugin_repos.py
```
This script:
-- Reads the workspace configuration
-- Creates symlinks in `plugin-repos/` pointing to actual repos
-- Verifies all links are created correctly
+- Reads each `manifest.json` under `../ledmatrix-plugins/plugins/`
+- Creates `plugin-repos/` symlinks (relative) to those directories
+- Leaves correct links alone, replaces links that point elsewhere, and skips
+ (does not overwrite) a real directory of the same name — for example a
+ plugin you installed from the Plugin Store. Remove that directory first if
+ you want the linked copy.
### Updating Plugins
-To update all plugin repositories:
-
```bash
-cd /home/chuck/Github/LEDMatrix
+cd ~/Github/LEDMatrix
python3 scripts/update_plugin_repos.py
```
-This script:
-- Finds all plugins in the workspace
-- Runs `git pull` on each repository
-- Reports which plugins were updated
+This runs `git pull` in `../ledmatrix-plugins` and prints the result. The
+symlinks pick up the new code; restart the display to load it.
## Configuration
-The plugin system is configured in `config/config.json`:
+The loader reads plugins from `plugin_system.plugins_directory` in
+`config/config.json`. The default is already right for this setup:
```json
{
"plugin_system": {
- "plugins_directory": "plugin-repos",
- "auto_discover": true,
- "auto_load_enabled": true
+ "plugins_directory": "plugin-repos"
}
}
```
-The `plugins_directory` points to `plugin-repos/`, which contains symlinks to the actual repositories.
-
## Workflow
### Daily Development
1. **Open Workspace**: Open `LEDMatrix.code-workspace` in VS Code/Cursor
-2. **All Repos Available**: All plugin repos appear as separate folders in the workspace
-3. **Edit Plugins**: Edit plugin code directly in their repositories
-4. **Update Plugins**: Run `update_plugin_repos.py` to pull latest changes
+2. **Edit Plugins**: Edit code under `ledmatrix-plugins/plugins//`
+3. **Test**: `python3 run.py -e` (emulator) or
+ `python3 scripts/check_plugin.py --plugin ` from LEDMatrix
+4. **Ship**: Bump `version` in the plugin's `manifest.json`, run
+ `python update_registry.py` in ledmatrix-plugins, commit there
### Adding New Plugins
-1. **Clone Repository**: Clone the new plugin repo to `/home/chuck/Github/`
-2. **Add to Workspace**: Add the plugin folder to `LEDMatrix.code-workspace`
-3. **Create Symlink**: Run `setup_plugin_repos.py` to create the symlink
-
-### Updating Individual Plugins
-
-Since plugins are regular Git repositories, you can update them individually:
-
-```bash
-cd /home/chuck/Github/ledmatrix-weather
-git pull origin master
-```
-
-Or update all at once:
-
-```bash
-cd /home/chuck/Github/LEDMatrix
-python3 scripts/update_plugin_repos.py
-```
-
-## Benefits
-
-1. **No Submodule Hassle**: No need to update `.gitmodules` or run `git submodule update`
-2. **Independent Updates**: Update plugins independently without touching LEDMatrix
-3. **Clean Separation**: Each plugin is a separate repository with its own history
-4. **Easy Development**: Multi-root workspace makes it easy to work across repos
-5. **Automatic Discovery**: Plugin system automatically discovers plugins via symlinks
+1. Create `plugins//` in the monorepo checkout
+2. Run `python3 scripts/setup_plugin_repos.py` in LEDMatrix to link it
## Troubleshooting
-### Symlinks Not Working
-
-If plugins aren't being discovered:
+### Plugins not discovered
```bash
-cd /home/chuck/Github/LEDMatrix
-python3 scripts/setup_plugin_repos.py
+cd ~/Github/LEDMatrix
+ls -la plugin-repos/ # links present and not broken?
+python3 scripts/setup_plugin_repos.py # recreate them
```
-This will recreate all symlinks.
+Also check that `plugin_system.plugins_directory` is `plugin-repos`.
-### Missing Plugins
+### "Monorepo plugins directory not found"
-If a plugin is in the workspace but not found:
+`setup_plugin_repos.py` expects the monorepo at `../ledmatrix-plugins`. Clone
+it there (or symlink it there).
-1. Check if the repo exists in `/home/chuck/Github/`
-2. Check if the symlink exists in `plugin-repos/`
-3. Run `setup_plugin_repos.py` to recreate symlinks
+### Plugin updates not showing
-### Plugin Updates Not Showing
-
-If changes to plugins aren't appearing:
-
-1. Verify the symlink points to the correct directory: `ls -la plugin-repos/ledmatrix-weather`
-2. Check that you're editing in the actual repo, not a copy
-3. Restart the LEDMatrix service if running
+1. Verify the link target: `ls -la plugin-repos/`
+2. Check that you're editing the monorepo checkout, not a store-installed copy
+3. Restart the LEDMatrix service (or `run.py`)
## Notes
-- The `plugin-repos/` directory is tracked in git, but only contains symlinks
-- Actual plugin code lives in `/home/chuck/Github/ledmatrix-*/`
-- Each plugin repo can be updated independently via `git pull`
-- The LEDMatrix project doesn't need to be updated when plugins change
+- `plugin-repos/` is tracked in git only for the bundled plugins
+ (`starlark-apps`, `web-ui-info`). The symlinks you create are untracked
+ files; don't commit them.
+- For linking a single plugin into `plugins/` instead (without a sibling
+ checkout), see `scripts/dev/dev_plugin_setup.sh` in the
+ [Plugin Development Guide](PLUGIN_DEVELOPMENT_GUIDE.md).
+- When changing a plugin in the monorepo, bump its manifest `version` and run
+ `python update_registry.py`, or users won't receive the update.
diff --git a/scripts/fix_perms/README.md b/scripts/fix_perms/README.md
index 5616e51a..77575a71 100644
--- a/scripts/fix_perms/README.md
+++ b/scripts/fix_perms/README.md
@@ -7,8 +7,10 @@ install as the wrong user, after a manual file copy that didn't preserve
ownership, or after a permissions-related error from the display or
web service.
-Most of these scripts require `sudo` since they touch directories
-owned by the `ledmatrix` service user or by `root`.
+Most of these scripts require `sudo` since they touch directories owned
+by `root` (the display service's user) or by the user you installed
+LEDMatrix as (the web service's user). There is no dedicated `ledmatrix`
+system user.
## Scripts
@@ -16,11 +18,12 @@ owned by the `ledmatrix` service user or by `root`.
permissions on the `assets/` tree so plugins can download and cache
team logos, fonts, and other static content.
-- **`fix_cache_permissions.sh`** — Fixes permissions on every cache
- directory the project may use (`/var/cache/ledmatrix/`,
- `~/.cache/ledmatrix/`, `/opt/ledmatrix/cache/`, project-local
- `cache/`). Also creates placeholder logo subdirectories used by the
- sports plugins.
+- **`fix_cache_permissions.sh`** — Creates (if missing) and fixes
+ permissions on `/var/cache/ledmatrix/` and `~/.ledmatrix_cache/` of the
+ user running `sudo`, and creates
+ `/var/cache/ledmatrix/placeholder_logos/` for the sports plugins. It does
+ not touch the cache manager's other fallbacks (`/opt/ledmatrix/cache`,
+ `$TMPDIR/ledmatrix_cache`).
- **`fix_plugin_permissions.sh`** — Fixes ownership on the plugins
directory so both the root display service and the web service user
@@ -31,6 +34,11 @@ owned by the `ledmatrix` service user or by `root`.
systemd journal access, and the sudoers entries the web interface
needs to control the display service.
+- **`safe_pip_install.sh`** — Installs a `requirements.txt` as root
+ after checking it is the project's own or one under `plugin-repos/` or
+ `plugins/`. Used by the web interface (via sudo) to install plugin
+ dependencies where `ledmatrix.service` can import them.
+
- **`safe_plugin_rm.sh`** — Validates that a plugin removal path is
inside an allowed base directory before deleting it. Used by the web
interface (via sudo) when a user clicks **Uninstall** on a plugin —
diff --git a/scripts/install/README.md b/scripts/install/README.md
index 0813f286..dc092d9d 100644
--- a/scripts/install/README.md
+++ b/scripts/install/README.md
@@ -7,14 +7,18 @@ This directory contains scripts for installing and configuring the LEDMatrix sys
- **`one-shot-install.sh`** - Single-command installer; clones the
repo, checks prerequisites, then runs `first_time_install.sh`.
Invoked via `curl ... | bash` from the project root README.
-- **`install_service.sh`** - Installs the main LED Matrix display service (systemd)
-- **`install_web_service.sh`** - Installs the web interface service (systemd)
+- **`install_service.sh`** - Installs, enables and starts the display
+ service (`ledmatrix.service`), the web interface service
+ (`ledmatrix-web.service`) and the update-verify units (systemd)
+- **`install_web_service.sh`** - Installs only the web interface service
+ and the update-verify units (systemd)
- **`install_wifi_monitor.sh`** - Installs the WiFi monitor daemon service
- **`setup_cache.sh`** - Sets up persistent cache directory with proper permissions
- **`configure_web_sudo.sh`** - Configures passwordless sudo access for web interface actions
-- **`configure_wifi_permissions.sh`** - Grants the `ledmatrix` user
- the WiFi management permissions needed by the web interface and
- the WiFi monitor service
+- **`configure_wifi_permissions.sh`** - Grants the web interface's user
+ (the user who runs the script, i.e. the one you installed LEDMatrix as;
+ there is no `ledmatrix` system user) the passwordless `nmcli` and related
+ WiFi permissions the web interface needs
- **`migrate_config.sh`** - Migrates configuration files to new formats (if needed)
- **`debug_install.sh`** - Diagnostic helper used when an install
fails; collects environment info and recent logs