bringing our school’s ‘dead’ TVs back to life

There are TVs at my school that have been off for as long as I’ve been a student there. Two in the cafeteria, one by the office (i think), one in the library. They’re mounted on the walls like they’re supposed to be doing something, and they just kinda aren’t. They are pretty much just decoration.

I first asked about them in eighth grade when I was doing yearbook. The middle school had the same setup: matching TVs in the cafeteria, the office, a hallway. We asked to use them for yearbook and were told: “They don’t work.”

That was it? Yea okay, no way that was the whole answer. Nobody told us what didn’t work. Were the TVs broken? The computers behind them? Was the software just old? (spoiler: yes, it was) Did somebody lose the password in 2017 and just gave up using them? I still don’t fully know. The signage computers behind them (they’re Acer Veriton N4620Gs from around 2012) were running Windows 7, and the whole system simply aged out while nobody was watching. As far as I know, nobody at the school had seen these things run in years. I certainly hadn’t; until recently.

On Monday, four of those TVs are turning back on. Here’s how that happened.

To the library

Before winter break last year, the Weekly Wildcat staff (our school newspaper) had a planning meeting about outreach. How do we get people to actually read our articles? How do we reach students who never visit the website? Somebody brought up the dead TVs, and I just began planning it.

But the first idea wasn’t “build a signage platform.” It was way smaller. students (me included) kept walking to the library during lunch only to find out it was closed, or already at capacity. There was no way to know before you got there. So I prototyped a little status site. It shows open, at capacity, or closed, plus an optional message and a history of updates. The librarian marks it open, and eventually the ID-scanning system can flip it to “at capacity” automatically when the room fills up.

simple. all we needed was a way to put that website on a TV (and show our newspaper articles) problem solved.

Looking for something that didn’t exist

I looked at the obvious options, and to be fair to all of them, none of them are inherently bad. They just didn’t fit.

Yodeck has a permanent free plan… for exactly one screen. if you add more, every screen needs a paid plan, which currently costs around $8/screen/month. We needed at least three screens, and our software budget is a very round number: $0. So $24/month forever was a no.

Xibo looked promising because you can self-host the CMS and a lot of it is open source. But the Android player is a paid, per-device license if you self-host, and the Linux player has been “in development” with no release date, so that was also a no-go.

AbleSign was the one that actually worked. No per-screen fees, supports Fire TV and Android TV, playlists, schedules, websites, offline media. An actual, genuinely free option, and it’s what we used at first.

Tilecast exists because I have a problem where I keep wanting one more thing. AbleSign could show the website. Great. But then I wanted a custom integration with our Weekly Wildcat stuff. Then custom recovery behavior when a player crashes. Then control over the data model, and how scheduling works, and proof that a screen is actually showing what I think it’s showing. somewhere around the fifth “it would be nice if it could also…” the conclusion became obvious: we just need to build this ourselves. I scrapped the “which app for what thing” Google doc I had made, and began working on a custom app.

a pageant disaster

Before I explain what Tilecast is, I need to tell you about the night that shaped basically its entire design philosophy.

In March, I did production for the middle school pageant at the middle school. As part of the setup, I turned on the front-right cafeteria TV, which was the first time I’d ever personally seen one of these “broken” TVs powered on. It worked fine, which was both exciting and kind of frustrating knowing they did work. I hooked up a Fire TV running AbleSign to show the Miss EMS logo and a bit info about each performer.

I’ve written about the whole event on this blog before (The Tech Behind Miss EMS Pageant 2026), but the short version of the signage part: the Fire TV was on the school network instead of our show Wi-Fi, and the school network gave out on us during the pageant. I’d actually anticipated network weirdness and set up an ADB workaround that would reboot the Fire TV if things went wrong. And it worked!

The problem? AbleSign never relaunched once it restarted.

I still don’t really know why. Maybe Amazon changed something about auto-launch behavior. What I do know is that the TV landed on the Fire TV home screen, which autoplays video ads. So in the middle of a school pageant, on the big screen, the audience got about an hour of streaming ads, including one for Heated Rivalry, on loop.

Perfect!

I had no idea. I was at the production desk and behind the stage, my phone was mounted as part of our multicam setup (which also failed, funnily enough), and my mom, who could see the screen from the audience, was frantically texting a phone I didn’t even have with me. Her phone was also on a tripod recording, so she was literally borrowing phones from strangers in the audience trying to reach me. I didn’t find out until intermission, at which point the only fix I knew of was turning the TV off.

that night taught me the most important lesson in this whole project: there’s a huge difference between “something is assigned to the screen” and “the audience is actually seeing it.” The device was on, the app was installed, the web server we had pulled up on one of our MacBooks thought everything was fine. And the screen was playing ads for an hour.

So, Tilecast

Tilecast is an open-source, self-hosted digital signage platform. Go server, React/TypeScript dashboard, PostgreSQL, and a native Android player for Fire TV, Google TV, and Android TV. There’s also an Electron port of that same player for regular Linux computers, which is basically why this whole rollout ended up happening the way it did. It’s AGPLv3, it’s on GitHub, and it’s built for exactly the situation my school is in: you already own TVs, you have no recurring budget, and you need more than one screen.

The tagline is “Open signage, built to stay on,” and after the pageant, “built to stay on” is not marketing nonsense. A ridiculous amount of the project is about failure:

  • Offline caching. each item gets checked against its size and SHA-256 before the player uses it. if any item in a new content set fails, the set doesn’t activate and the player keeps running the current one. the swap is atomic, all items flip together or none do. on boot it loads from local cache before contacting the server, so a screen coming back from a power cut is playing within a few seconds instead of waiting on the network.
  • Boot recovery and a watchdog. when playback breaks the player steps through fixes in order: retry the item, skip the item, rebuild the renderer, restart the app, restart the process. if none of that works it drops into a safe mode that stops playback but keeps pairing and health reporting running, so it reports itself as broken instead of sitting in a restart loop.
  • Health checks. a live playback session doesn’t tell you the screen is fine, it can be frozen on one frame or fully black with the session still up. so health is based on forward progress instead: video frames incrementing, playlist items transitioning on schedule, the render loop ticking. if those stop moving the player treats itself as dead. however, right now, there are still some issues with heartbeats on Linux (custom display resolutions mess it up)
  • Proof of play. the player logs what it actually rendered, not what it was scheduled to render, and queues those events locally so it keeps logging with no network. it flushes the backlog to the server on reconnect. the schedule is intent, the proof-of-play log is the record.
  • Never landing on the launcher. no Fire TV home screen, no Google TV launcher, no autoplaying ads at lunch (or at a pageant, for that matter). if the app crashes it relaunches back into the app instead of the OS launcher.

There’s also all the normal signage CMS stuff you’d expect: multi-zone layouts, playlists, schedules that handle timezones right, a media library, and widgets like clocks, tickers, menu boards, weather, and QR codes. data sources are kept separate from presentation too, so one CSV of lunch menus can show up as a plain list on one screen and a full menu board on another without me having to enter it twice.

then there’s synchronization groups, which i needed because the two cafeteria TVs face each other. because even if i assign the same playlist to two screen they aren’t actually in sync, and when two screens are side by side even a little bit of drift looks broken (and distracting). so synced screens share a playback epoch and compute the same item and offset from it, which means they line up even after one reconnects late and has to catch up. (it can speed up/slow down playback if the gap is under 250ms)

Things that fought back

Some highlights from the bug graveyard:

  • Over-the-air player updates, which I assumed would be a weekend of work, turned into the single most time-consuming part of the project. A proper unattended update has to verify a signed manifest, verify the checksum, verify the APK signature, preserve credentials and pairing, get Android to actually allow the install, relaunch, and confirm the new version reconnects. And “success” isn’t real until that last step happens.
  • The dashboard ui and the server got into a fight over whether 4:00 PM is 16:00 or 16:00:00.
  • A crossfade feature I added to make transitions look nice introduced a long flash during the crossfade, which is a very funny way for a polish feature to fail.

turns out i didn’t need the fire sticks

going into this week, the plan was still to run the screens using android players. i had two fire sticks and one chromecast with google tv, which is enough for three screens, cafeteria left, cafeteria right, and the office. the library was just gonna have to wait.

then i actually opened the back of one of the TVs and there it was. the acer veriton, still plugged in, still running windows 7 professional. looked like nobody had touched it since like 2013 or 2014. this whole time everyone thought the TV was the broken part, and it was actually just sitting there behind it doing nothing for over a decade.

So that changed the plan pretty quick. i don’t have four fire sticks but i do have four mini PCs already mounted behind the TVs with power already run to them, so why would i go buy more hardware. instead of three screens with the library “next,” it just turned into all four today.

I had actually made a rough Electron version of the Tilecast player a few days ago, just in case, wasn’t really planning on using it for anything yet. good thing i did because i spent today adding stuff to it on the fly while also trying to get four TVs working by tonight.

On the old mini PCs, I wiped windows 7 and put Debian Trixie on fresh. this is also where i got stuck for a while. the installer defaults to the new uefi boot method and it just wouldn’t boot on hardware this old. I had to redo the whole install using the non uefi option instead, took me a minute to even figure out that was the problem, because the boot loader doesn’t make it obvious.

Once debian was on i set both boxes up to boot straight into the player with no login screen, one account that auto logs in and just runs Tilecast fullscreen, and a separate account i can actually SSH into to fix stuff without messing with what’s on screen. also turned off sleep and screen blanking completely, because a signage box that falls asleep is just a broken TV again.

deploying is simple at least. i build the player on my laptop and copy it over to each mini pc. updating later just means sending a new file over, nothing else has to change. Once I update the Linux firmware to support OTA updates, the players will just be able to auto-update using an image on the server

Installing Debian on the mini PC

Monday

All four screens go up July 20, for Meet the Teacher, with the first day of school two days later: cafeteria left, cafeteria right, the main entrance by the office, and the library. Lunch menus, announcements, events, the library status during lunch, and eventually Weekly Wildcat content once the paper relaunches. Maybe the middle school TVs are next, because why not?

The server is accessible through a Cloudflare Tunnel, the players are the old mini PCs that were already hiding behind the TVs, and the software budget is still $0. These TVs were written off as broken years ago, so I’m glad they are able to get put back to use.

Most people won’t know I built any of this, and that’s what I want; the reaction I’m actually hoping for on Wednesday morning is someone walking into the cafeteria, looking up, and going:

“Wait, I didn’t even know that TV worked.”

update: the middle school, two days later

so at the end of this i said the middle school TVs might be next. turns out next meant right away. I have way too much free time. (but a book report due tomorrow)

today, the day before the first day of school, i went over to the middle school and got Tilecast running on four more screens. same exact situation as before, TVs mounted on the walls that everybody just assumed were dead.

the library one is running off the Chromecast with Google TV. That’s actually one of the devices i thought i was gonna need for the high school before i found the mini PCs behind everything would suffice, so it finally got a job. the two cafeteria TVs and the hallway one are all on mini PCs, because i looked behind them and it was the same story as the high school. old boxes still plugged in, still powered, just sitting back there doing nothing for the last decade

I wiped them the same way, Debian Trixie, auto login straight into the player, sleep and screen blanking off. The non-UEFI boot thing came back to get me again on one of them (i brought 2 USB sticks so i could multitask — turns out i don’t do that very well), but at least this time i knew what it was instead of losing an hour figuring it out.

The office is the one screen i didn’t get to. there’s an unused mini PC back there too so it’s the exact same job, i just ran out of time today (not actually, I just wanted to go home). So that one’s happening later this week.

So, between the two schools that’s eight screens now, nine once i do the office, all running on hardware nobody was using and still $0 in software. Kinda funny that this whole thing started because i couldn’t tell if the library was open at lunch.

In total, I spent around 17 hours total setting it up on-site (10AM-7:30PM on Monday, 7:50AM-2:50PM Tuesday). For the pre-development side, probably about a week, 4 hours a day)

why (and how) we rebuilt weekly wildcat’s website from scratch

Weekly Wildcat launched its first website back in January 2026. At the time it was just a normal WordPress site. Cheap theme, hosting through 1&1, some random Christmas deal that made the first year cost like $7. For a brand new student newspaper with basically no budget, that was a no brainer. We needed a site, it was cheap, and WordPress seemed easy enough that we could just start publishing.

We spent the whole school year writing articles, posting photos, building out sections, figuring out what kind of paper Weekly Wildcat was even going to be. The problem was the site itself never got any better. We all complained about it constantly. (it was absolutely terrible)

If something on the header broke, I’d have to sit there and wait forever for the admin dashboard to load. Then trying to change something that felt like it should be simple meant digging through random theme files, Elementor settings, and plugin menus that never worked. Switching between pages in the WordPress admin took like ten seconds every time. I guess that’s what $7 gets you

The public side wasn’t much better. Mobile layout was awful, homepage felt generic, Elementor blocks didn’t work consistently and sometimes just didn’t show up at all. Lists would randomly turn into checkboxes for no reason (drove us insane). Changing the header, footer, basically any big layout piece meant digging through files and settings until something finally moved.

It sucked.

There aren’t many things worse than the Netherlands’ performance in the World Cup, but our website was definitely one of them.

We wanted the site to look like it was made on purpose. Like an actual newspaper, not just a website that happened to have articles on it. But Elementor didn’t give us any real way to manage fonts without paying for more features, and the theme wasn’t built in a way that made typography easy to touch. We’d try to make what felt like a pretty normal design change and the whole thing would just start falling apart. I remember editing a config file once to fix a layout issue and the entire homepage just disappeared.

At some point it was obvious we were spending way more time fighting the website than improving it. So we decided it was time to just rebuild the whole thing.

we tried to stay in WordPress first

Our first idea was to build a custom WordPress theme. That lasted for a little while.

I figured it’d be the easier path since we were already in WordPress. The articles were there, the media library was there, writers already knew the dashboard, the basic publishing system worked fine. In theory a custom theme would let us keep everything and just replace the parts we hated.

An absolute mess.

I only had a little PHP experience at the time and WordPress themes get complicated fast. Templates, hooks, plugin behavior, theme settings, block behavior, old conventions, new conventions, and then all the little stuff that only becomes obvious once something breaks. I got far enough in to realize I did not want to build the future of this site on top of a theme I’d be untangling forever.

So I gave up on that and switched to something I actually knew better, Next.js. The idea was pretty simple. WordPress stays, it just stops being the thing readers actually see.

Instead WordPress becomes the CMS running quietly in the background. Writers and editors keep logging in, drafting stories, uploading photos, assigning categories, scheduling posts, same workflow they already know. But the actual public site gets built separately with Next.js.

That gave us a clean break from the old theme without making the whole staff learn a brand new system.

The site is still WordPress under the hood. It just doesn’t look like it anymore.

the new setup

The new setup is split into two sides now.

The public site is still at weeklywildcat.com, that’s what readers actually see. Built with Next.js, deployed through Cloudflare Workers.

The CMS lives at cms.weeklywildcat.com. That’s the back room of the newspaper now, where stories get written, images get uploaded, author info gets managed, everything gets organized before it goes live.

Here’s the part I still think is kind of funny. WordPress is running in a Docker container on my homelab server. Same server that used to just sit there running Home Assistant for my house. That was legit its entire job before this. Now it’s also quietly running a newspaper’s entire CMS. I got lucky that it’s a fairly nice box with 16GB of RAM so it handles the extra load pretty easily, but it’s still a little weird knowing the paper’s whole backend lives on the same machine that we use to turn off the lights.

The server in question that runs the newspaper.

It’s exposed through a Cloudflare Tunnel, and the WordPress admin is behind Cloudflare Access too, so it’s not just sitting wide open on the internet for bots to find (hopefully)

Is this more complicated than the old setup? Yes, a lot more.

A normal WordPress site is one thing, you pay a host, install WordPress, pick a theme, done. This has a CMS, a separate frontend, a GitHub repo, Cloudflare Workers, a tunnel, Docker containers, a custom plugin, and deployment logic tying it all together. More pieces. More stuff that can break. More things I personally have to keep in my head.

But there’s also just way more control. The old site gave us a box full of limitations. This one gives us an actual foundation we can keep building on.

trial and error

The first version of the Next.js site was not good. It technically existed but it didn’t feel like Weekly Wildcat yet. Layouts were rough, stuff broke constantly, and the homepage changed practically every day for a while. I redesigned it over and over before it started looking like something I’d actually want people using.

That first week was mostly just trial and error. Build something, look at it, absolutely hate it, change it, break it, fix it, move to another part of the site, repeat. The homepage went through the most versions because that’s the page that matters most, and it needed to feel focused instead of crowded.

The author pages might be my favorite part of this whole redesign. On the old site, authors got a small box with their name (not even a picture) Now authors get their own pages where readers can see who’s writing, browse their other stories, get more of a sense of the actual people behind the paper.

WordPress is great once you stop asking it to be the whole website

One of the biggest things I learned doing this: WordPress was never really the problem. It’s still good at what it does.

For writers nothing really changed. Write a story, add media, pick categories, save a draft, hit publish. No code, no thinking about how the frontend works at all. And we don’t have to deal with Elementor anymore (Thank God — that was a mess)

I still remember opening the rebuilt CMS the first time and clicking between pages, and it was instant. There was a fade transition and I genuinely didn’t know WordPress even had that, because I’d never once seen it not buried under ten seconds of loading.

We also ended up building a custom WordPress plugin, which was its own whole adventure since I’d never built one before and again, pretty limited PHP going in. The plugin handles stuff that doesn’t fit neatly into a default WordPress setup. Image credits, author details, sports info, event stuff. Things a student paper actually needs but that no generic theme or random plugin ever handles well. Building our own tools means the CMS gets shaped around how Weekly Wildcat actually works, instead of us bending our workflow around someone else’s idea of a news site.

verifying our sports data with MaxPreps

We had a lot of raw sports data pulled in from different sources, and we wanted to actually verify it was accurate. MaxPreps doesn’t let you reuse its data directly (it’s against the TOS), but pulling scores from it gave us something to compare against, and that’s how we found and fixed a bunch of issues in the data we already had. I ended up writing a simple script to help with that.

publishing still feels simple

For staff, publishing is still just WordPress. Write a story, add images, fill in the info, and hit publish. Once the public site rebuilds it shows up on weeklywildcat.com.

The images still live in WordPress but the public site itself gets deployed as a static site. That means readers get a way faster experience without us giving up the media library or changing how staff upload photos. It also means the site isn’t generating every page live every time someone visits, readers just get a finished page instead of waiting on WordPress, PHP, plugins, and the old theme to all do their thing at once. Way better fit for a news site.

hello, spam

One of the less fun parts of running a public site is bots find you almost instantly. We got hit with a lot of spam traffic and spam comments, a good chunk of it from outside the US. To be clear that doesn’t mean everyone outside the country is some kind of problem, obviously our reporting should be accessible to anyone who wants to read it. But automated/bot traffic is a way different story.

definitely not the final version

The site now is way better than what we launched with back in January, but it’s still early days. The design system needs more work, there’s still stuff to clean up around consistency and components, and the publishing pipeline needs to get more automatic. The whole setup needs to get easier to maintain in general.

And eventually, honestly, the CMS probably needs to move off the server that’s sitting in my living room under the TV. Hosting WordPress on my homelab is great right now, it keeps costs down and gives us total control, but I’m not going to be running this newspaper’s infrastructure forever. A student paper that depends entirely on one guy’s home server humming away in his house isn’t really a long term plan.

We want the publication to last longer than our time @ NSHS. That’ll probably mean simplifying the stack down the line, moving the CMS somewhere more permanent, making deployment less manual, cutting down the number of moving parts. Probably all of it eventually. Maybe even moving back to a Full WordPress stack.

why the code is open source

The Weekly Wildcat site is open source on GitHub. Right now it’s not exactly plug and play, another school couldn’t just download it, swap a logo, and have a finished site by that afternoon. There’s still a lot of work before it gets to that point.

But that’s the direction I want to take it. Student newspapers shouldn’t have to pick between expensive publishing platforms, ugly templates, or sites that feel impossible to touch. A small student org should be able to have a modern site without a huge monthly bill or a full dev team behind it.

There are already solid low cost and free options out there for hosting parts of a project like this. I use Wasmer’s hobby plan for this blog and it’s worked well, and Cloudflare Pages is a good option too when you need more bandwidth. (note that they serve different purposes)The bigger point is just that schools and student orgs deserve more control over their own websites.

This whole thing is still at the beginning. It’s not perfect and it’s definitely more complicated than buying a theme and clicking install. But it’s ours. WordPress still gives us a solid place to write and publish, Next.js gives us a public site that actually feels designed for Weekly Wildcat, Cloudflare handles deployment and keeps the bots off our backs, and a server that used to just run my Home Assistant install is somehow now also running our newsroom. And we’re finally not stuck trying to force a seven dollar theme to do things it was never built for.