Compare commits

...

2 commits

Author SHA1 Message Date
1ffd479013 fix: nix module without bot & cli dependencies 2026-05-07 23:14:55 +02:00
b067d76e3a feat: nix module 2026-05-07 22:52:15 +02:00
8 changed files with 716 additions and 5903 deletions

6009
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,23 +3,17 @@ resolver = "2"
members = [
"store",
"clients/cli",
"clients/backbone-lib",
"clients/web",
"server/protocol",
"server/relay-server",
"bot",
"spiel_bot",
]
default-members = [
"store",
"clients/cli",
"clients/backbone-lib",
"server/protocol",
"server/relay-server",
"bot",
"spiel_bot",
]
# For the server we will need opt-level='3'

128
container/flake.lock generated Normal file
View file

@ -0,0 +1,128 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1778003029,
"narHash": "sha256-q/nkKLDtHIyLjZpKhWk3cSK5IYsFqtMd6UtXF3ddjgA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0c88e1f2bdb93d5999019e99cb0e61e1fe2af4c5",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1778003029,
"narHash": "sha256-q/nkKLDtHIyLjZpKhWk3cSK5IYsFqtMd6UtXF3ddjgA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0c88e1f2bdb93d5999019e99cb0e61e1fe2af4c5",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"trictrac": "trictrac"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1778123869,
"narHash": "sha256-hV9D8ET33kXjdoMXBT2bwM/j8WQM1SP/dVKZtjQKhAQ=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "592e5dedf04f0eaff1ed0f01ce5db7407d9fc7be",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"rust-overlay_2": {
"inputs": {
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1778123869,
"narHash": "sha256-hV9D8ET33kXjdoMXBT2bwM/j8WQM1SP/dVKZtjQKhAQ=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "592e5dedf04f0eaff1ed0f01ce5db7407d9fc7be",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"trictrac": {
"inputs": {
"nixpkgs": "nixpkgs_3",
"rust-overlay": "rust-overlay_2"
},
"locked": {
"path": "..",
"type": "path"
},
"original": {
"path": "..",
"type": "path"
},
"parent": []
}
},
"root": "root",
"version": 7
}

49
container/flake.nix Normal file
View file

@ -0,0 +1,49 @@
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
# inputs.trictrac.url = "github:mmai/trictrac";
inputs.trictrac.url = "..";
inputs.rust-overlay.url = "github:oxalica/rust-overlay";
outputs = { self, nixpkgs, trictrac, rust-overlay }:
{
nixosConfigurations = {
container = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
trictrac.nixosModule
({ pkgs, ... }:
let
hostname = "trictrac";
in
{
boot.isContainer = true;
# Let 'nixos-version --json' know about the Git revision
# of this flake.
system.configurationRevision = nixpkgs.lib.mkIf (self ? rev) self.rev;
system.stateVersion = "25.11";
# Network configuration.
networking.useDHCP = false;
networking.firewall.allowedTCPPorts = [ 80 ];
networking.hostName = hostname;
# rust-overlay must be applied first so trictrac.overlay can use rust-bin
nixpkgs.overlays = [ rust-overlay.overlays.default trictrac.overlay ];
services.trictrac = {
enable = true;
protocol = "http";
hostname = hostname;
};
environment.systemPackages = with pkgs; [ neovim ];
})
];
};
};
};
}

62
flake.lock generated Normal file
View file

@ -0,0 +1,62 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1778003029,
"narHash": "sha256-q/nkKLDtHIyLjZpKhWk3cSK5IYsFqtMd6UtXF3ddjgA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0c88e1f2bdb93d5999019e99cb0e61e1fe2af4c5",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1778123869,
"narHash": "sha256-hV9D8ET33kXjdoMXBT2bwM/j8WQM1SP/dVKZtjQKhAQ=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "592e5dedf04f0eaff1ed0f01ce5db7407d9fc7be",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

196
flake.nix
View file

@ -1,41 +1,169 @@
{
description = "Trictrac";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
# let pkgs = nixpkgs.legacyPackages.${system}; in
let pkgs = import nixpkgs {
inherit system;
config = { allowUnfree = true; };
}; in
{
# devShell = import ./shell.nix { inherit pkgs; };
devShell = with pkgs; mkShell rec {
nativeBuildInputs = [
pkg-config
llvmPackages.bintools # To use lld linker
];
buildInputs = [
cargo rustc rustfmt rustPackages.clippy # rust
# pre-commit
alsa-lib udev
vulkan-loader # needed for GPU acceleration
xlibsWrapper xorg.libXcursor xorg.libXrandr xorg.libXi # To use x11 feature
# libxkbcommon wayland # To use wayland feature
];
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
shellHook = ''
export HOST=127.0.0.1
export PORT=7000
'';
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
rust-overlay.url = "github:oxalica/rust-overlay";
};
outputs = { self, nixpkgs, rust-overlay }:
let
systems = [ "x86_64-linux" "i686-linux" "aarch64-linux" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
# rust-overlay must be applied before self.overlay so that rust-bin is available
nixpkgsFor = forAllSystems (system:
import nixpkgs {
inherit system;
overlays = [ rust-overlay.overlays.default self.overlay ];
}
);
in
{
overlay = final: prev: {
trictrac-front =
let
# WASM build needs wasm32-unknown-unknown target in the Rust toolchain
rustToolchain = final.rust-bin.stable.latest.default.override {
targets = [ "wasm32-unknown-unknown" ];
};
rustPlatform = final.makeRustPlatform {
cargo = rustToolchain;
rustc = rustToolchain;
};
# Must match the wasm-bindgen version in Cargo.lock
wasm-bindgen-version = "0.2.121";
wasm-bindgen-cli = final.buildWasmBindgenCli rec {
version = wasm-bindgen-version;
src = final.fetchCrate {
pname = "wasm-bindgen-cli";
inherit version;
hash = "sha256-ZOMgFNOcGkO66Jz/Z83eoIu+DIzo3Z/vq6Z5g6BDY/w=";
};
cargoDeps = rustPlatform.fetchCargoVendor {
inherit src;
name = "wasm-bindgen-cli-vendor";
hash = "sha256-DPdCDPTAPBrbqLUqnCwQu1dePs9lGg85JCJOCIr9qjU=";
};
};
frontendCargoDeps = rustPlatform.fetchCargoVendor {
src = ./.;
name = "trictrac-frontend-vendor";
hash = "sha256-eCuQcgKhdqHDRmRRK2cjmvRZZ661ecDYn0HIZWKDpSE=";
};
in
final.stdenv.mkDerivation {
name = "trictrac-front";
src = ./.;
nativeBuildInputs = with final; [
rustToolchain
lld
rustPlatform.cargoSetupHook
wasm-bindgen-cli
trunk
binaryen
];
cargoDeps = frontendCargoDeps;
buildPhase = ''
runHook preBuild
export HOME=$TMPDIR
# Pin tool versions so trunk finds them in PATH instead of downloading
cat >> clients/web/Trunk.toml << 'EOF'
[tools]
wasm-bindgen = { version = "${wasm-bindgen-version}" }
wasm-opt = { version = "version_124" }
EOF
pushd clients/web
trunk build --release --offline
popd
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out
cp -R clients/web/dist/. $out/
runHook postInstall
'';
};
trictrac = with final; rustPlatform.buildRustPackage {
pname = "trictrac";
version = "0.2.0";
src = ./.;
nativeBuildInputs = [ pkg-config ];
buildInputs = [ openssl ];
# Build only the relay server; skip WASM/bot crates
cargoBuildFlags = [ "-p" "relay-server" ];
doCheck = false;
cargoLock = {
lockFile = ./Cargo.lock;
};
postInstall = ''
install -m 644 ${./server/relay-server/GameConfig.json} $out/GameConfig.json
'';
meta = with lib; {
description = "A online game of trictrac";
homepage = "https://github.com/mmai/trictrac";
license = licenses.gpl3;
platforms = platforms.unix;
};
};
trictrac-docker = with final;
let
port = "8080";
entrypoint = writeScript "entrypoint.sh" ''
#!${runtimeShell}
# Populate a writable working dir with static files + config
mkdir -p /var/lib/trictrac
for f in ${trictrac-front}/*; do
ln -sf "$f" "/var/lib/trictrac/$(basename "$f")"
done
cp -n ${trictrac}/GameConfig.json /var/lib/trictrac/ 2>/dev/null || true
cd /var/lib/trictrac
echo "Starting trictrac server on port ${port}"
exec ${trictrac}/bin/relay-server
'';
in
dockerTools.buildImage {
name = "mmai/trictrac";
tag = "latest";
copyToRoot = buildEnv {
name = "trictrac-env";
paths = [ busybox ];
};
config = {
Entrypoint = [ entrypoint ];
ExposedPorts = {
"${port}/tcp" = { };
};
};
};
};
packages = forAllSystems (system: {
inherit (nixpkgsFor.${system}) trictrac trictrac-front trictrac-docker;
});
defaultPackage = forAllSystems (system: self.packages.${system}.trictrac);
# trictrac service module
nixosModule = import ./module.nix;
};
}

View file

@ -34,6 +34,23 @@ build-relay:
cp target/release/relay-server deploy
cp -u server/relay-server/GameConfig.json deploy/
# start a trictrac container with nixos-container
# `boot.enableContainers = true` must be set on local nixos system
local:
cd container && nix flake update nixpkgs trictrac && cd -
sudo nixos-container destroy trictrac
sudo nixos-container create trictrac --flake ./container/
nixos-container start trictrac
machinectl
docker-build:
nix build .#trictrac-docker
docker-run: docker-build
docker load < ./result
docker run mmai/trictrac -P
docker-publish: docker-build
docker push mmai/trictrac
runclibots:
cargo run --bin=client_cli -- --bot random,dqnburn:./bot/models/burnrl_dqn_40.mpk
#cargo run --bin=client_cli -- --bot dqn:./bot/models/dqn_model_final.json,dummy

162
module.nix Normal file
View file

@ -0,0 +1,162 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.trictrac;
in
{
options = {
services.trictrac = {
enable = mkEnableOption "trictrac";
user = mkOption {
type = types.str;
default = "trictrac";
description = "User under which trictrac is ran.";
};
group = mkOption {
type = types.str;
default = "trictrac";
description = "Group under which trictrac is ran.";
};
protocol = mkOption {
type = types.enum [ "http" "https" ];
default = "https";
description = "Web server protocol.";
};
hostname = mkOption {
type = types.str;
default = "trictrac.localhost";
description = "Public domain name of the trictrac web app.";
};
apiPort = mkOption {
type = types.port;
default = 8080;
description = "Port the relay server listens on.";
};
createDatabaseLocally = mkOption {
type = types.bool;
default = true;
example = false;
description = "Create a local PostgreSQL database for trictrac.";
};
};
};
config = mkIf cfg.enable {
users.users.trictrac = mkIf (cfg.user == "trictrac") {
group = cfg.group;
isSystemUser = true;
};
users.groups.trictrac = mkIf (cfg.group == "trictrac") { };
services.nginx = {
enable = true;
# map needed for WebSocket Connection header upgrade
appendHttpConfig = ''
upstream trictrac-api {
server 127.0.0.1:${toString cfg.apiPort};
}
map $http_upgrade $connection_upgrade {
default upgrade;
"" close;
}
'';
virtualHosts =
let
proxyConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
'';
withSSL = cfg.protocol == "https";
in
{
"${cfg.hostname}" = {
enableACME = withSSL;
forceSSL = withSSL;
locations."/" = {
extraConfig = proxyConfig;
proxyPass = "http://trictrac-api/";
};
};
};
};
services.postgresql = mkIf cfg.createDatabaseLocally {
enable = mkDefault true;
ensureDatabases = [ "trictrac" ];
ensureUsers = [
{
name = cfg.user;
ensureDBOwnership = true;
}
];
# Allow the trictrac service user to connect via TCP without a password
authentication = mkAfter ''
host trictrac ${cfg.user} 127.0.0.1/32 trust
host trictrac ${cfg.user} ::1/128 trust
'';
};
systemd.services.trictrac-server =
let
setupScript = pkgs.writeShellScript "trictrac-setup" ''
set -euo pipefail
# Symlink frontend static files into the state directory so the
# relay server can serve them from its working directory.
for f in ${pkgs.trictrac-front}/*; do
ln -sf "$f" "$STATE_DIRECTORY/$(basename "$f")"
done
# Seed a writable GameConfig.json on first run; admins may edit it later.
if [ ! -f "$STATE_DIRECTORY/GameConfig.json" ]; then
install -m 644 ${pkgs.trictrac}/GameConfig.json "$STATE_DIRECTORY/GameConfig.json"
fi
'';
in
{
description = "trictrac relay server";
after = [ "network.target" ] ++ optional cfg.createDatabaseLocally "postgresql.service";
requires = optional cfg.createDatabaseLocally "postgresql.service";
wantedBy = [ "multi-user.target" ];
environment = {
# Use TCP + trust auth (matches NixOS postgresql.authentication above)
DATABASE_URL = "postgresql://${cfg.user}@127.0.0.1/${cfg.user}";
};
serviceConfig = {
User = cfg.user;
Group = cfg.group;
# systemd creates /var/lib/trictrac and sets STATE_DIRECTORY accordingly
StateDirectory = "trictrac";
StateDirectoryMode = "0755";
WorkingDirectory = "/var/lib/trictrac";
ExecStartPre = "${setupScript}";
ExecStart = "${pkgs.trictrac}/bin/relay-server";
Restart = "on-failure";
RestartSec = "5s";
};
};
};
meta = {
maintainers = with lib.maintainers; [ mmai ];
};
}