|
| 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. |
0 commit comments