140 lines
5.6 KiB
Nix
140 lines
5.6 KiB
Nix
{ config, lib, pkgs, homeyConfig, ... }:
|
|
|
|
# EmulatorJS — self-hosted web game emulator with per-user save sync.
|
|
#
|
|
# The Python sidecar (emulatorjs-server.py) serves:
|
|
# / → game launcher (lists ROMs)
|
|
# /roms/ → ROM listing (JSON) and ROM files
|
|
# /play/<name> → EmulatorJS player page for a specific game
|
|
# /saves/<name>→ GET/PUT save file (SRAM), keyed by Remote-User header
|
|
# /static/ → EmulatorJS JS/CSS/WASM assets (served from Nix store)
|
|
#
|
|
# Multi-device sync: saves are stored server-side keyed by Authelia
|
|
# Remote-User header, so any device the user logs into shares the same save.
|
|
#
|
|
# EmulatorJS assets are fetched from GitHub at build time — no manual
|
|
# download step. To upgrade, change `rev` and replace `hash` with the
|
|
# value Nix reports after a failed build (same pattern as caddy.nix).
|
|
#
|
|
# Setup after first enable:
|
|
# 1. Drop ROM files into /mnt/data/emulatorjs/roms/
|
|
# Supported: .gb .gbc .gba .nes .snes .sfc .md .gen .n64 .z64
|
|
# 2. Visit https://games.<domain> — saves go to
|
|
# /mnt/data/emulatorjs/saves/<username>/ automatically.
|
|
|
|
let
|
|
cfg = config.homey.emulatorjs;
|
|
dataDir = config.homey.storage.mountPoint;
|
|
domain = homeyConfig.domain;
|
|
port = 8085;
|
|
|
|
# EmulatorJS static assets fetched from GitHub at build time.
|
|
# Pre-built WASM cores are committed to the repo so no build step is needed.
|
|
#
|
|
# To upgrade to a new version:
|
|
# 1. Find the latest tag:
|
|
# git ls-remote --tags https://github.com/EmulatorJS/EmulatorJS | tail -10
|
|
# 2. Prefetch the tarball hash:
|
|
# nix-prefetch-url --unpack "https://github.com/EmulatorJS/EmulatorJS/archive/refs/tags/vX.Y.Z.tar.gz"
|
|
# 3. Convert to SRI format:
|
|
# nix hash convert --hash-algo sha256 --to sri <base32-from-step-2>
|
|
# 4. Update rev and hash below.
|
|
emulatorjsAssets = pkgs.fetchFromGitHub {
|
|
owner = "EmulatorJS";
|
|
repo = "EmulatorJS";
|
|
rev = "v4.2.3";
|
|
hash = "sha256-hIgvcVNjl9qGJw0GgkkLQQyd2JhdUOVOQhLAeZuQcMk=";
|
|
};
|
|
in
|
|
{
|
|
options.homey.emulatorjs.enable =
|
|
lib.mkEnableOption "EmulatorJS self-hosted game emulator";
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Dedicated system user so save-file ownership is stable across restarts
|
|
# -------------------------------------------------------------------------
|
|
users.users.emulatorjs = {
|
|
isSystemUser = true;
|
|
group = "emulatorjs";
|
|
description = "EmulatorJS save-sync server";
|
|
};
|
|
users.groups.emulatorjs = {};
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Launcher + save-sync server
|
|
# -------------------------------------------------------------------------
|
|
systemd.services.emulatorjs = {
|
|
description = "EmulatorJS launcher and save-sync server";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network.target" "mnt-data.mount" "systemd-tmpfiles-setup.service" ];
|
|
requires = [ "mnt-data.mount" ];
|
|
|
|
environment = {
|
|
PORT = toString port;
|
|
SAVE_DIR = "${dataDir}/emulatorjs/saves";
|
|
ROM_DIR = "${dataDir}/emulatorjs/roms";
|
|
# EmulatorJS assets live in the Nix store — read-only, no manual setup.
|
|
# The data/ subdirectory contains loader.js and the emulator cores.
|
|
STATIC_DIR = "${emulatorjsAssets}/data";
|
|
THUMBNAILS_DIR = "${dataDir}/emulatorjs/thumbnails";
|
|
};
|
|
|
|
serviceConfig = {
|
|
ExecStart = "${pkgs.python3}/bin/python3 ${./emulatorjs-server.py}";
|
|
User = "emulatorjs";
|
|
Group = "emulatorjs";
|
|
Restart = "on-failure";
|
|
RestartSec = "5s";
|
|
|
|
NoNewPrivileges = true;
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
PrivateTmp = true;
|
|
ReadWritePaths = [ "${dataDir}/emulatorjs" ];
|
|
};
|
|
};
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Caddy — one_factor SSO; Remote-User header forwarded to the sidecar
|
|
# -------------------------------------------------------------------------
|
|
homey.caddy.virtualHosts = [{
|
|
subdomain = "games";
|
|
port = port;
|
|
auth = true;
|
|
}];
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Storage directories (owned by the service user)
|
|
# -------------------------------------------------------------------------
|
|
homey.storage.extraDirs = [
|
|
{ path = "emulatorjs"; user = "emulatorjs"; group = "emulatorjs"; }
|
|
{ path = "emulatorjs/saves"; user = "emulatorjs"; group = "emulatorjs"; }
|
|
{ path = "emulatorjs/roms"; user = "emulatorjs"; group = "emulatorjs"; }
|
|
{ path = "emulatorjs/thumbnails"; user = "emulatorjs"; group = "emulatorjs"; }
|
|
];
|
|
|
|
# Only saves need backing up; ROMs and static assets are easy to restore
|
|
homey.backup.extraPaths = [ "${dataDir}/emulatorjs/saves" ];
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Authelia access control — one_factor for all household users
|
|
# -------------------------------------------------------------------------
|
|
homey.authelia.accessControlRules = [{
|
|
priority = 57;
|
|
domain = [ "games.${domain}" ];
|
|
policy = "one_factor";
|
|
}];
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Uptime Kuma monitor
|
|
# -------------------------------------------------------------------------
|
|
homey.monitoring.monitors = [{
|
|
name = "EmulatorJS";
|
|
url = "https://games.${domain}";
|
|
interval = 60;
|
|
}];
|
|
};
|
|
}
|