Files
plugin-helper/README.md
T

276 lines
9.9 KiB
Markdown

# plugin-helper
`plugin-helper` is an early Python CLI for managing Beat Saber plugins in BSManager installs.
The first implementation focuses on safe local workflows:
- discover BSManager instances
- scan existing `Plugins/` and `Libs/` files
- read the checked-in plugin catalog and per-version locks
- generate a machine-readable install plan from local release assets
- apply exactly that plan and record managed install state
- uninstall only files recorded in managed install state
Default BSManager instance root:
```text
/home/pleb/.local/share/BSManager/BSInstances
```
Default plugin-helper state directory:
```text
$XDG_STATE_HOME/plugin-helper
```
If `XDG_STATE_HOME` is not set, the state directory defaults to:
```text
~/.local/state/plugin-helper
```
Override the instance root with `--instances-root`,
`PLUGIN_HELPER_INSTANCES_ROOT`, or `plugin-helper.local.toml`. To search
multiple explicit roots, separate them with `:` on Linux/macOS or `;` on
Windows.
Override the state directory with `--state-dir`, `PLUGIN_HELPER_STATE_DIR`, or
`plugin-helper.local.toml`.
## Local Configuration
This checkout is intended to manage the local Linux BSManager install. If you
also manage a Windows install, use a separate clone on that partition and point
both clones at the same state directory only when you intentionally want one
shared source of truth.
Copy the example config and adjust paths if needed:
```sh
cp plugin-helper.toml.example plugin-helper.local.toml
```
`plugin-helper.local.toml` is ignored by git and uses top-level fields:
```toml
instances_root = "~/.local/share/BSManager/BSInstances"
state_dir = "~/.local/state/plugin-helper"
```
For repo-local state, set:
```toml
state_dir = ".state"
```
For a shared Windows-partition state directory, set the same `state_dir` in both
clones, for example:
```toml
state_dir = "~/Windows/Users/pleb/ops/plugin-helper/.state"
```
CLI flags override environment variables, environment variables override local
config, and local config overrides built-in defaults.
## Native Windows
For a separate checkout on a Windows partition, copy the Windows example config
and adjust paths if needed:
```powershell
Copy-Item plugin-helper.windows.toml.example plugin-helper.windows.toml
```
`plugin-helper.windows.toml` is ignored by git. It uses profile entries:
```toml
[[profiles]]
id = "windows"
label = "Native Windows BSManager"
instances_root = "~/BSManager/BSInstances"
state_dir = ".state"
```
Run commands with `--config` and `--profile` so native Windows state stays in
that checkout's `.state/` directory. If `plugin-helper.windows.toml` has only
one `[[profiles]]` entry, that profile is selected automatically when
`--profile` is omitted:
```powershell
py -m venv .venv
.\.venv\Scripts\Activate.ps1
py -m pip install -e .
py -m plugin_helper
py -m plugin_helper --config plugin-helper.windows.toml --profile windows installed --instance 1.44.1
```
On native Windows, `bootstrap` runs `IPA.exe "Beat Saber.exe" -n` directly
instead of through Proton. `bootstrap-check` accepts a recorded native bootstrap
without requiring `Logs/_latest.log` when `IPA.exe "Beat Saber.exe" -n`
completed successfully.
When no config file is present on Windows, defaults are
`~/BSManager/BSInstances` and `%LOCALAPPDATA%/plugin-helper`.
## Building plugins
PC BSIPA plugins target .NET Framework (`net48`). On native Windows, install the
reference assemblies before `dotnet build`:
```powershell
winget install -e --id Microsoft.DotNet.Framework.DeveloperPack_4
```
Point `BeatSaberDir` at your BSManager instance and pass
`-p:DisableCopyToPlugins=True` so builds do not mutate the live install. See
[`.agents/skills/beatsaber-plugin-builder/`](.agents/skills/beatsaber-plugin-builder/)
for the full workflow.
## Commands
For normal use, run the Textual menu from the repo root:
```sh
PYTHONPATH=src python -m plugin_helper
```
That is equivalent to `PYTHONPATH=src python -m plugin_helper menu` when run
from an interactive terminal.
The menu reads `plugin-helper.local.toml` when present, shows each discovered
Beat Saber install with its resolved state directory, and lists plugins from
that instance's version lock. Managed install state decides whether each row is
currently checked or unchecked. In the plugin table, use `d` to disable all
currently enabled plugins (except `bsipa`) and `e` to enable all currently
unchecked plugins. With a single Beat Saber installation, the menu opens the
plugin table directly. Re-enabling a plugin auto-bootstraps BSIPA when needed.
Use `s` to save the set of currently enabled plugins as that instance's
known-good set, and `g` to restore it later: plugins outside the saved set are
disabled and any saved plugins that are currently disabled/missing are
re-enabled from local assets. This is handy after a newly added mod pulls in
extra dependencies you don't actually want — press `g` to snap back to
the combination you know works. Only one known-good set is kept per instance;
saving again overwrites it. The equivalent automation commands are
`save-known-good` and `restore-known-good`.
The individual subcommands are mostly for automation and debugging. If you use
them, pass `--state-dir` directly only when you intentionally want to override
the configured state directory for one command.
Install assets are currently expected to already exist locally, usually under:
```text
<state-dir>/cache/downloads/<instance>/<plugin-id>/
```
Lock entries may include `download_url`, the exact public zip or DLL URL for the
locked artifact. The URL is currently audit/recovery metadata; `plan`, `check`,
and `apply` still use local assets and do not fetch missing downloads
automatically.
Composite local assets should have an explicit reconstruction recipe checked in
with the helper. For example, Cinema's external Windows downloader tools are a
small deterministic bundle rebuilt from upstream executables with:
```sh
PYTHONPATH=src python -m plugin_helper.recipes.cinema_tools --instance 1.44.1
```
Use `updates` to audit the version lock against obvious public update sources:
```sh
PYTHONPATH=src python -m plugin_helper --state-dir .state updates --instance 1.44.1
```
The command checks GitHub release assets for ordinary repository-backed locks
and BeatMods verified metadata for `beatmods-*` locks. Patreon and Discord
paid/private sources are skipped, and local builds, PR builds, failed
compatibility trials, and manual installs are marked for review instead of being
treated as routine updates.
## Beat Saber Data Backups
`backup-userdata` copies the mounted Windows `UserData` folder and Beat Saber
Windows app data into the adjacent `../backups` repo. With the Windows mount at
`~/Windows`, the helper infers Beat Saber's Windows app data as:
```text
/home/pleb/Windows/Users/pleb/AppData/LocalLow/Hyperbolic Magnetism/Beat Saber
```
Example manual backup after mounting Windows:
```sh
PYTHONPATH=src python -m plugin_helper \
--instances-root /home/pleb/Windows/Users/pleb/BSManager/BSInstances \
backup-userdata \
--instance 1.44.1
```
By default the backup repo receives plain copied files under
`../backups/beat-saber/UserData` and `../backups/beat-saber/AppData`, plus
`../backups/beat-saber/backup-descriptor.json` describing the source paths from
the latest backup run. Use
`--appdata-path <path>` if the Windows profile path ever differs, `--no-appdata`
for a `UserData`-only sync, or `--backup-root <path>` to choose a different
destination.
`restore-userdata` copies those backup trees back into a target instance. It
moves the current `UserData` and AppData trees aside first as
`<name>.pre-restore-<timestamp>` snapshots. On Linux BSManager installs,
AppData is restored into the BSManager SharedContent Proton prefix unless
`--appdata-path` is provided. If `backup-descriptor.json` is present, its
`instance` field must match `--instance`.
Example restore into the local Linux instance:
```sh
PYTHONPATH=src python -m plugin_helper \
--instances-root /home/pleb/.local/share/BSManager/BSInstances \
restore-userdata \
--instance 1.44.1
```
The backup intentionally omits bulky/generated data:
- `UserData/BeatLeader/Replays`
- `UserData/BeatLeader/ReplayerCache`
- `UserData/BeatLeader/LeaderboardsCache`
- `UserData/BeatLeader/ReplayHeadersCache`
- `UserData/ScoreSaber/Replays`
- `UserData/BeatSaberPlus/Cache`
- `UserData/BeatSaverNotifier.json`
- `UserData/Accsaber/PlayerScoreCache.json`
- `UserData/NalulunaAvatars/cache`
- `UserData/SongDetailsCache.proto`
- `AppData/com.unity.addressables`
- `*.log`
Other large folders seen in the 1.44.1 `UserData` tree are
`Custom Campaigns`, `SongCore`, `AssetBundleLoadingTools`, `NalulunaMenu`, and
`NalulunaSkybox`. Those are not skipped by default because they can contain
custom content or non-obvious user choices rather than pure cache data.
## Operational notes
- BSManager can inherit launch arguments configured in Steam for Beat Saber.
Check both places before debugging black screens or startup hangs. Duplicating
arguments such as `--no-yeet fpfc` can make the game fail command-line
parsing after BSIPA and plugins have already loaded.
- BSIPA is managed as a first-class bootstrap phase. The `bootstrap` command
applies the locked `bsipa` root archive, runs `IPA.exe "Beat Saber.exe" -n`
(natively on Windows or through Proton on Linux), and records every
bootstrap-relevant file under root `IPA.exe*`, `winhttp.dll`, `Libs/`, and
`IPA/`, including backups created during patching.
- If an instance version lock includes `bsipa`, ordinary plugin plans require a
recorded bootstrap state plus a `Logs/_latest.log` that shows BSIPA startup.
Use `bootstrap-check` before planning a batch when you want a quick gate.
- Use [`docs/SMOKETEST.md`](docs/SMOKETEST.md) after installing or removing a
plugin batch. It documents the short Proton/BSManager launch loop, IPA log
checks, and teardown commands.
- The 1.44.1 migration tracker lives in
[`docs/notes/install-and-verify-plugins-1.44.1.md`](docs/notes/install-and-verify-plugins-1.44.1.md).