Skip to content

Commit da31e38

Browse files
authored
Add stash-scheduler plugin (#708)
1 parent 27a72ab commit da31e38

7 files changed

Lines changed: 1459 additions & 0 deletions

File tree

plugins/stash-scheduler/README.md

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# Stash Scheduler Plugin
2+
3+
A plugin for [Stash](https://github.com/stashapp/stash) that automatically runs library scans on a schedule (hourly, daily, or weekly), with an optional identify pass after each scan.
4+
5+
---
6+
7+
## Features
8+
9+
- Schedule scans at **hourly**, **daily**, or **weekly** intervals
10+
- Configure the **hour of day** (and day of week for weekly)
11+
- Optionally run an **Identify** task automatically after each scan completes
12+
- Identify is skipped safely if the scan fails, is cancelled, or no job ID is returned
13+
- **Run Now** task for instant manual trigger (useful for testing)
14+
- **Auto-start on system boot** via systemd, Windows startup, or a cron job (see below)
15+
- All settings managed through Stash's built-in plugin settings UI — no config files to edit
16+
17+
---
18+
19+
## Requirements
20+
21+
- **Stash** v0.17.0 or later
22+
- **Python 3.8+** (must be available as `python3` in your system PATH)
23+
- pip packages: `apscheduler` and `stashapp-tools` (see Installation below)
24+
- `curl` (only needed if you use the auto-start scripts)
25+
26+
---
27+
28+
## Installation
29+
30+
### 1 — Copy the plugin folder
31+
32+
Copy the entire `stash-scheduler/` directory into your Stash plugins folder:
33+
34+
```
35+
<Stash data directory>/plugins/stash-scheduler/
36+
```
37+
38+
The final layout should look like:
39+
40+
```
41+
plugins/
42+
└── stash-scheduler/
43+
├── stash-scheduler.yml
44+
├── stash_scheduler.py
45+
├── requirements.txt
46+
├── startup/
47+
│ ├── autostart.sh (Linux/macOS)
48+
│ ├── autostart.bat (Windows)
49+
│ └── stash-scheduler.service (systemd)
50+
└── README.md
51+
```
52+
53+
> Your Stash data directory is shown under **Settings → System**.
54+
> Common locations: `~/.stash` (Linux/macOS) or `C:\Users\<you>\.stash` (Windows).
55+
56+
### 2 — Install Python dependencies
57+
58+
Open a terminal and run:
59+
60+
```bash
61+
pip install apscheduler "stashapp-tools>=0.2.40"
62+
```
63+
64+
Or install from the requirements file:
65+
66+
```bash
67+
pip install -r /path/to/plugins/stash-scheduler/requirements.txt
68+
```
69+
70+
### 3 — Reload plugins in Stash
71+
72+
In Stash, go to **Settings → Plugins** and click **Reload Plugins**. "Stash Scheduler" will appear in the list.
73+
74+
> **Note on auto-start:** After dropping the plugin into the `plugins/` folder, the scheduler does **not** start automatically — Stash's plugin system has no built-in startup hook. You must either start it manually each time (Settings → Tasks → Start Scheduler) or configure OS-level auto-start using the scripts in `startup/` (see the [Auto-start on boot](#auto-start-on-boot-recommended) section). The systemd unit is the most complete option, as it covers Stash restarts as well as system boot.
75+
76+
---
77+
78+
## Configuration
79+
80+
Open **Settings → Plugins → Stash Scheduler** and set your preferences:
81+
82+
| Setting | Description | Default |
83+
|---|---|---|
84+
| **Scan Frequency** | `hourly`, `daily`, or `weekly` | `daily` |
85+
| **Time of Day (HH:MM)** | Time to run the scan in 24-hour `HH:MM` format. Used by Daily and Weekly; ignored for Hourly. | `02:00` |
86+
| **Day of Week** | Day to scan when Frequency is Weekly. Use `mon`, `tue`, `wed`, `thu`, `fri`, `sat`, or `sun`. | `sun` |
87+
| **Timezone** | IANA timezone name for interpreting Time of Day and Day of Week. Examples: `America/New_York`, `Europe/London`, `Asia/Tokyo`. Leave blank for UTC. | `UTC` |
88+
| **Run Identify After Scan** | When enabled, runs an Identify task after each scan finishes successfully. | `false` |
89+
| **Scan Completion Timeout (minutes)** | Max time to wait for the scan before giving up on Identify. | `120` |
90+
| **Limit to Paths** | Restrict the scan (and the follow-up Identify) to specific directories. One path per line, or comma-separated. Leave blank for the full library. | *(full library)* |
91+
| **Generate Covers** | Generate cover images for scenes during scan. | `false` |
92+
| **Generate Video Previews** | Generate video preview clips during scan. | `false` |
93+
| **Generate Image Previews** | Generate image preview strips during scan. | `false` |
94+
| **Generate Sprites** | Generate sprite sheets (seek-bar previews) during scan. | `false` |
95+
| **Generate Video Phashes** | Generate perceptual hashes for video files (duplicate detection). | `false` |
96+
| **Generate Image Phashes** | Generate perceptual hashes for image files (duplicate detection). | `false` |
97+
| **Generate Image Thumbnails** | Generate thumbnails for image files during scan. | `false` |
98+
| **Generate Image Clip Previews** | Generate animated clip previews for image gallery files. | `false` |
99+
| **Force Rescan** | Rescan all files even if modification time is unchanged. Useful after Stash upgrades. | `false` |
100+
101+
### Timezone configuration example
102+
103+
To run a daily scan at **2:00 AM New York time**:
104+
105+
```
106+
Scan Frequency: daily
107+
Time of Day: 02:00
108+
Timezone: America/New_York
109+
```
110+
111+
To run a weekly scan at **3:30 AM London time every Sunday**:
112+
113+
```
114+
Scan Frequency: weekly
115+
Time of Day: 03:30
116+
Day of Week: sun
117+
Timezone: Europe/London
118+
```
119+
120+
A full list of valid timezone names is available at
121+
https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
122+
123+
> **Note:** If the Timezone field is left blank or set to an unrecognised value, the scheduler falls back to **UTC** and logs a warning.
124+
125+
---
126+
127+
## Starting the Scheduler
128+
129+
The scheduler runs as a **long-lived task** inside Stash. You can start it manually or configure it to start automatically on system boot (recommended).
130+
131+
### Manual start
132+
133+
1. Go to **Settings → Tasks**.
134+
2. Under **Stash Scheduler**, click **Start Scheduler**.
135+
3. The task will appear as running and stay active until you stop it or restart Stash.
136+
137+
### Auto-start on boot (recommended)
138+
139+
Because Stash restarts the plugin process after each Stash restart, auto-start scripts are the most reliable way to ensure the scheduler is always running. Choose the method that fits your setup:
140+
141+
#### Linux — systemd (recommended — handles Stash restarts automatically)
142+
143+
The systemd unit uses `BindsTo=stash.service`, which means it stops when Stash stops and starts when Stash starts. This covers both the initial system boot **and** any subsequent Stash restarts — ensuring the scheduler is always running as long as Stash is running.
144+
145+
```bash
146+
# 1. Make the startup script executable
147+
chmod +x ~/.stash/plugins/stash-scheduler/startup/autostart.sh
148+
149+
# 2. Copy the systemd unit to the system directory
150+
sudo cp ~/.stash/plugins/stash-scheduler/startup/stash-scheduler.service \
151+
/etc/systemd/system/stash-scheduler.service
152+
153+
# 3. Edit the unit file — set your Stash unit name, URL, and plugin path
154+
# Find your Stash unit name with: systemctl list-units | grep -i stash
155+
sudo nano /etc/systemd/system/stash-scheduler.service
156+
157+
# 4. Enable and start the unit
158+
sudo systemctl daemon-reload
159+
sudo systemctl enable stash-scheduler.service
160+
sudo systemctl start stash-scheduler.service
161+
```
162+
163+
The key settings inside the unit file:
164+
```ini
165+
BindsTo=stash.service # restart this unit whenever Stash restarts
166+
After=stash.service # wait for Stash to start before trying to connect
167+
Restart=on-failure # retry if the connection script fails
168+
STASH_URL=http://localhost:9999
169+
PLUGIN_DIR=/path/to/.stash/plugins/stash-scheduler
170+
# Uncomment to enable API key auth:
171+
# STASH_API_KEY=your-api-key-here
172+
```
173+
174+
#### Linux / macOS — cron @reboot
175+
176+
```bash
177+
crontab -e
178+
```
179+
180+
Add this line (adjust paths):
181+
182+
```cron
183+
@reboot sleep 30 && /path/to/plugins/stash-scheduler/startup/autostart.sh >> /tmp/stash-scheduler-autostart.log 2>&1
184+
```
185+
186+
The `sleep 30` gives Stash time to start before the script tries to connect.
187+
188+
#### Windows — Startup folder
189+
190+
1. Press `Win + R`, type `shell:startup`, press Enter.
191+
2. Create a shortcut to `startup\autostart.bat` in that folder.
192+
3. The script will run each time you log in.
193+
194+
Alternatively, use Task Scheduler to trigger `autostart.bat` on system startup (without needing a user session):
195+
196+
- Trigger: **At startup**
197+
- Action: Start `C:\path\to\plugins\stash-scheduler\startup\autostart.bat`
198+
- Check: **Run whether user is logged in or not**
199+
200+
#### Docker / custom entrypoint
201+
202+
Add the following to your Docker entrypoint or startup script, after Stash starts:
203+
204+
```bash
205+
/app/plugins/stash-scheduler/startup/autostart.sh http://localhost:9999
206+
```
207+
208+
---
209+
210+
## Testing Your Settings
211+
212+
Without waiting for the next scheduled run:
213+
214+
1. Go to **Settings → Tasks**.
215+
2. Under **Stash Scheduler**, click **Run Scan Now**.
216+
217+
This triggers a scan (and identify, if enabled) immediately and marks itself complete when done.
218+
219+
---
220+
221+
## How It Works
222+
223+
```
224+
System boots → autostart script runs → polls until Stash is ready
225+
226+
227+
Calls runPluginTask via GraphQL
228+
to start "Start Scheduler" task
229+
230+
231+
stash_scheduler.py reads plugin settings from Stash API
232+
233+
234+
APScheduler registers a cron job (hourly / daily / weekly)
235+
236+
(fires at each scheduled time)
237+
238+
239+
metadataScan mutation → Stash starts a full library scan
240+
241+
┌─────────────────────┴──────────────────────┐
242+
│ │
243+
run_identify = false run_identify = true
244+
│ │
245+
done Poll job queue until scan finishes
246+
or timeout elapses
247+
248+
┌──────────┴──────────┐
249+
│ │
250+
Scan succeeded Scan failed /
251+
│ timed out /
252+
▼ no job ID
253+
metadataIdentify mutation → Identify skipped
254+
(uses Settings → Identify (logged as warning)
255+
sources & options)
256+
```
257+
258+
### About Identify sources
259+
260+
The Identify step uses whatever sources you have configured in **Settings → Metadata → Identify** (e.g., Stash-box connections, scrapers). If no sources are configured there, the identify step will be skipped and a warning will appear in the Stash log.
261+
262+
Identify is also skipped if:
263+
- The scan returns no trackable job ID
264+
- The scan fails or is cancelled
265+
- The scan exceeds the configured timeout
266+
267+
---
268+
269+
## Logs
270+
271+
All activity is written to the Stash log. To view it:
272+
273+
- Go to **Settings → Logs** (or the Stash log panel).
274+
- Look for lines prefixed with `[Stash Scheduler]`.
275+
276+
---
277+
278+
## Troubleshooting
279+
280+
| Symptom | Fix |
281+
|---|---|
282+
| Plugin doesn't appear after Reload | Check that `stash-scheduler.yml` is in the correct folder and YAML syntax is valid. |
283+
| `ModuleNotFoundError: No module named 'apscheduler'` | Run `pip install apscheduler stashapp-tools`. |
284+
| `Could not connect to Stash` | Make sure Stash is running and accessible. |
285+
| Identify is skipped every run | Go to **Settings → Metadata → Identify** and add at least one scraper or Stash-box source. |
286+
| Identify skipped with "no job ID" warning | This is a safety measure — the scan still ran. It can happen on older Stash versions that don't return a job ID for the scan mutation. |
287+
| Scan runs but Identify never starts | Increase **Scan Completion Timeout** if your library is large. |
288+
| Auto-start script says "Stash not available" | Increase the `sleep` delay before calling the script, or raise `MAX_WAIT` inside `autostart.sh`. |
289+
| Schedule fires at wrong time | Check the **Timezone** setting. Set it to your local IANA timezone (e.g. `America/Chicago`) so the Time of Day is interpreted correctly. |
290+
291+
---
292+
293+
## Version History
294+
295+
| Version | Notes |
296+
|---|---|
297+
| 0.6.0 | Added "Limit to Paths" setting — scan and identify can now be restricted to specific directories |
298+
| 0.5.0 | Fixed identify-after-scan (null jobQueue crash); added 9 scan generation flag settings (covers, previews, sprites, phashes, thumbnails, clip previews, force rescan) |
299+
| 0.4.0 | Added Check Status task; daemon logs written to file (`/tmp/stash-scheduler-daemon.log`) |
300+
| 0.3.0 | Added Timezone setting — Time of Day and Day of Week are now interpreted in any IANA timezone instead of always UTC |
301+
| 0.2.0 | Added auto-start scripts (Linux/macOS/Windows/systemd), configurable identify timeout, strict scan→identify sequencing (identify skipped on scan failure/unknown/timeout), improved logging |
302+
| 0.1.0 | Initial release |
303+
304+
---
305+
306+
## License
307+
308+
MIT — use freely, modify as you like.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
apscheduler>=3.10,<4
2+
stashapp-tools>=0.2.40
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
@echo off
2+
REM autostart.bat — Windows auto-start script for the Stash Scheduler.
3+
REM
4+
REM Usage:
5+
REM autostart.bat [STASH_URL] [API_KEY]
6+
REM
7+
REM Examples:
8+
REM autostart.bat
9+
REM autostart.bat http://localhost:9999
10+
REM autostart.bat http://localhost:9999 your-api-key-here
11+
REM
12+
REM To run automatically on Windows startup, place a shortcut to this
13+
REM script in: shell:startup (press Win+R, type shell:startup, hit Enter)
14+
15+
setlocal
16+
17+
SET STASH_URL=%~1
18+
IF "%STASH_URL%"=="" SET STASH_URL=http://localhost:9999
19+
20+
SET API_KEY=%~2
21+
SET GRAPHQL=%STASH_URL%/graphql
22+
SET MUTATION={"query":"mutation { runPluginTask(plugin_id: \"stash-scheduler\", task_name: \"Start Scheduler\") }"}
23+
SET MAX_WAIT=120
24+
SET INTERVAL=5
25+
SET ELAPSED=0
26+
27+
echo [Stash Scheduler] Waiting for Stash at %STASH_URL% ...
28+
29+
:WAIT_LOOP
30+
curl -sf --max-time 5 %GRAPHQL% -d "{\"query\":\"{health}\"}" >nul 2>&1
31+
IF %ERRORLEVEL% EQU 0 GOTO STASH_UP
32+
33+
IF %ELAPSED% GEQ %MAX_WAIT% (
34+
echo [Stash Scheduler] ERROR: Stash not available after %MAX_WAIT%s. Aborting.
35+
exit /b 1
36+
)
37+
38+
timeout /t %INTERVAL% /nobreak >nul
39+
SET /A ELAPSED=%ELAPSED%+%INTERVAL%
40+
GOTO WAIT_LOOP
41+
42+
:STASH_UP
43+
echo [Stash Scheduler] Stash is up. Starting scheduler task...
44+
timeout /t 3 /nobreak >nul
45+
46+
IF "%API_KEY%"=="" (
47+
curl -sf --max-time 30 -H "Content-Type: application/json" -d "%MUTATION%" %GRAPHQL%
48+
) ELSE (
49+
curl -sf --max-time 30 -H "Content-Type: application/json" -H "ApiKey: %API_KEY%" -d "%MUTATION%" %GRAPHQL%
50+
)
51+
52+
IF %ERRORLEVEL% EQU 0 (
53+
echo [Stash Scheduler] Scheduler task started successfully.
54+
) ELSE (
55+
echo [Stash Scheduler] ERROR: Failed to start scheduler task.
56+
exit /b 1
57+
)
58+
59+
endlocal

0 commit comments

Comments
 (0)