# Scratching Post — Sunbeam Digital Signage A self-hosted digital notice board / signage system built on ASP.NET Core 8, designed for IIS on Windows Server 2019/2022. ## Terminology | Component | Name | Description | |-----------|------|-------------| | Individual slide | **Meow** | A single content slide — WYSIWYG, embed, or calendar | | Slide collection | **PURR** | Persistent User Requests and Reminders — a device playlist | | Priority slide | **ASK** | Attention Seeking Kitty — overrides collection order *(planned)* | | Display node | **Kitten** | A device that plays a playlist of Meows | | Backend | **Scratching Post** | The admin panel where all content is managed | | Framework | **Sunbeam** | The underlying system powering everything | ## Features - **WYSIWYG Editor** — TinyMCE with image upload, tables, rich text - **Embed Web Content** — iframe any URL as a slide - **ICS Calendar** — display upcoming events from any .ics feed - **Device Playlists** — assign slides to specific devices with individual durations - **Per-Device Resolution** — configure display resolution per device - **URL-Based Devices** — access displays via `http://server/frontofhouse`, `http://server/staffroom` - **Drag-and-Drop Playlist** — reorder slides in the admin - **Live Updates** — display polls for changes every 30 seconds - **Full-Screen Display** — hidden cursor, clock overlay, progress bar - **Smooth Transitions** — fade or slide transitions - **Meow Expiration** — give any slide an expiry date/time; it's automatically skipped on displays afterwards (and stays in the library, unedited, in case you need it again) - **Docker Support** — run via `docker compose up` as an alternative to IIS, with volumes for the database and uploads ## Requirements **IIS (Windows Server):** - Windows Server 2019 or 2022 - IIS with ASP.NET Core Hosting Bundle - .NET 8 SDK (for building) / .NET 8 Runtime (for hosting) **Docker (any host):** - Docker Engine + Docker Compose — see [Docker Deployment](#docker-deployment) below ## Quick Start ### 1. Install Prerequisites Download and install: - [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) - [ASP.NET Core Hosting Bundle](https://dotnet.microsoft.com/download/dotnet/8.0) (under Hosting Bundle) ### 2. Build ```powershell cd ScratchingPost dotnet restore dotnet publish -c Release -o ./publish ``` ### 3. Deploy to IIS 1. Open **IIS Manager** 2. Create a new **Site** (e.g. ScratchingPost) 3. Set the **physical path** to the `publish` folder 4. Set the **binding** (e.g. port 80, or a hostname like `sign.local`) 5. Set the **Application Pool** to **No Managed Code** (the Hosting Bundle handles .NET) 6. Ensure the App Pool identity has **write access** to the publish folder (for SQLite DB and uploads) ### 4. First Run The SQLite database (`noticeboard.db`) is created automatically on first start with two demo devices seeded: - **Front of House** at `http://server/frontofhouse` - **Staff Room** at `http://server/staffroom` ### 5. Access | URL | Purpose | |-----|---------| | `http://server/admin` | Scratching Post admin panel | | `http://server/admin/slides` | Manage Meows (slides) | | `http://server/admin/devices` | Manage Kittens (devices) | | `http://server/frontofhouse` | Front of House display | | `http://server/staffroom` | Staff Room display | ## Docker Deployment An alternative to IIS — runs anywhere Docker does (Linux, Windows with WSL2 backend, Synology/QNAP NAS, a Raspberry Pi, etc.). ### 1. Build and run ```bash git clone https://gitea.hideawaygaming.com.au/jessikitty/ScratchingPost.git cd ScratchingPost docker compose up -d --build ``` That's it — the container listens on port **8080**, so open `http://server:8080`. ### 2. What the compose file sets up `docker-compose.yml` builds the image from the included `Dockerfile` and wires up two bind-mounted folders next to the compose file so your data survives container rebuilds/updates: | Host path | Container path | Purpose | |-----------|-----------------|---------| | `./data` | `/data` | `noticeboard.db` (SQLite) | | `./uploads` | `/app/wwwroot/uploads` | Images uploaded via TinyMCE, plus uploaded ICS files | Both folders are created automatically on first run. ### 3. Configuration Same settings as IIS, just as environment variables in `docker-compose.yml` (nested keys use a double underscore): ```yaml environment: - DataDirectory=/data - TimeZone=UTC - Admin__Username=admin - Admin__Password=ScratchingPost2026! - MaxUploadSizeMB=20 ``` **Change the admin password before exposing this beyond your local network.** ### 4. Updating ```bash git pull docker compose up -d --build ``` The database and uploads persist across rebuilds since they live in the bind-mounted `./data` and `./uploads` folders, not inside the image. On startup the app checks the SQLite schema and adds any new columns it needs (e.g. the `ExpiresAt` column below) automatically — no manual migration step. ### 5. Reverse proxy / HTTPS The container serves plain HTTP on 8080. For a public-facing deployment, put it behind a reverse proxy (Caddy, nginx, Traefik) that terminates HTTPS and forwards to `scratchingpost:8080`. ### Running IIS and Docker side by side The IIS deployment and the Docker image are built from the same codebase and don't share a database or uploads folder by default — they're independent instances unless you point them at the same SQLite file and uploads directory yourself. ## Configuration Edit `appsettings.json`: ```json { "MaxUploadSizeMB": 20 } ``` ## How It Works 1. **Create Meows** (slides) — use the WYSIWYG editor for rich content, embed external URLs, or point to an ICS calendar feed 2. **Create Kittens** (devices) — give each a name, URL slug, and display resolution 3. **Build PURRs** (playlists) — assign Meows to Kittens with individual durations and drag-and-drop ordering 4. **Display** — point a browser at `http://server/{slug}` and it runs full-screen with smooth transitions The display page polls for changes every 30 seconds, so updates appear on devices without restarting. ## Meow Expiration Any Meow can be given an **Expires** date/time in its editor (under **Scheduling**). Once that time passes: - The Meow is automatically skipped by every Kitten it's assigned to — no manual removal needed - It stays fully intact in **Meows** (with a red **Expired** badge) so it can be re-used or have its expiry pushed back later - Nothing is deleted — expiry only affects playback, not the stored slide Leave it blank for a Meow that should run indefinitely. **Time zone:** expiry is checked against the `TimeZone` setting, not the raw OS clock: - **IIS** — `appsettings.json` ships with `"TimeZone": ""`, which falls back to the Windows Server's own local time zone (the original behaviour — no change needed on upgrade). - **Docker** — `docker-compose.yml` sets `TimeZone=UTC` by default, since containers are otherwise timezone-ambiguous. Override it to an IANA id (e.g. `Australia/Melbourne`) if you'd rather enter expiry times in local time than UTC; the image includes `tzdata` so real zone names resolve correctly. ## Display Controls Keyboard shortcuts on display pages (useful during setup): | Key | Action | |-----|--------| | Right Arrow or Space | Skip to next slide | | Left Arrow | Go to previous slide | | F or F11 | Toggle fullscreen | | C | Toggle cursor visibility | ## Data Storage - **Database**: SQLite (`noticeboard.db`) — created automatically, zero config - **Uploads**: `wwwroot/uploads/` — images uploaded via TinyMCE or the file upload API - On IIS, both need write permissions for the App Pool identity - On Docker, both live in bind-mounted host folders (`./data`, `./uploads`) — see [Docker Deployment](#docker-deployment) ## TinyMCE Note The WYSIWYG editor uses TinyMCE CDN with `no-api-key`. This shows a small notification banner. For production, register for a free TinyMCE API key at [tiny.cloud](https://www.tiny.cloud/) and replace `no-api-key` in `Views/Slides/Create.cshtml` and `Edit.cshtml`. ## Project Structure ``` ScratchingPost/ ├── Controllers/ # MVC controllers │ ├── AdminController # Dashboard │ ├── SlidesController # Meow CRUD │ ├── DevicesController # Kitten CRUD + Playlist management │ ├── DisplayController # Device display + playlist API │ └── ApiController # Image upload, ICS parsing ├── Data/ # EF Core DbContext + seed data ├── Infrastructure/ # Clock (time-zone-aware "now" for Meow expiry) ├── Models/ # Slide, Device, DeviceSlide ├── Routing/ # DeviceSlugConstraint ├── Views/ # Razor views (Admin, Slides, Devices, Display) ├── wwwroot/ # Static files │ ├── css/ # admin.css, display.css │ ├── js/ # admin.js, display.js (Sunbeam engine) │ └── uploads/ # User-uploaded images ├── appsettings.json # Configuration ├── web.config # IIS configuration ├── Dockerfile # Container build (alternative to IIS) ├── docker-compose.yml # Container run config (ports, volumes) ├── .dockerignore └── NoticeBoard.csproj # Project file ``` ## Version History - **v1.1.1** — Meow expiry now checked against a configurable `TimeZone` setting instead of the raw OS clock: IIS keeps its existing local-time behaviour by default, Docker defaults to UTC (overridable to any IANA zone; the image ships with `tzdata`) - **v1.1.0** — Docker deployment (`Dockerfile` + `docker-compose.yml`, configurable data directory, volume-backed DB/uploads), Meow expiration (`ExpiresAt` field, automatic playback filtering, auto-migrating column for existing databases) - **v1.0.0** — Initial release: WYSIWYG slides, embed, ICS calendar, device playlists, drag-and-drop ordering, live polling, IIS-ready deployment