bitspire/deploy/nixos/configuration.nix
Padreug 01fcff45c1 chore(nix): tighten ATM GC — daily at 03:30, persistent
Was: weekly, no persistence. Problem: 15GB eMMC + ~7GB closure means
cross-release upgrades are always tight. Weekly cadence stacked up to
a week of generations under the 04:00 auto-upgrade. Persistent=false
also meant a Sintra powered off at 03:30 skipped GC until next week.

Now runs daily at 03:30, 30 min before the 04:00 nixos-upgrade so each
upgrade attempt gets the freshest headroom. 7d retention unchanged.

This is a periodic-cleanup fix only — cross-release in-place upgrades
on 15GB eMMC will still hit the disk-full wall (caught 2026-05-26 on
24.05 → 24.11). Reflash remains the answer there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 19:12:11 +02:00

216 lines
5.8 KiB
Nix

# bitSpire ATM NixOS Configuration
# Base system configuration for ATM kiosk
{ config, lib, pkgs, pkgs-unstable, ... }:
{
# System basics
system.stateVersion = "24.05";
# Networking
networking = {
hostName = "bitspire";
# Use NetworkManager for easy WiFi configuration
networkmanager.enable = true;
# Firewall - minimal exposure
firewall = {
enable = true;
allowedTCPPorts = [ ]; # ATM initiates all connections
allowedUDPPorts = [ 51820 ]; # WireGuard
};
# WireGuard VPN tunnel to VPS for remote SSH access
# IP address set per-machine in hardware/*.nix via networking.wireguard.interfaces.wg0.ips
wireguard.interfaces.wg0 = {
listenPort = 51820;
privateKeyFile = "/var/lib/wireguard/wg0.key";
peers = [{
publicKey = "R6uB4o5ELEKEHCvK+llRYbzdkZGDHegVmS0f08aRtWM=";
endpoint = "170.75.161.21:51820";
allowedIPs = [ "10.0.0.0/24" ];
persistentKeepalive = 25;
}];
};
};
# Timezone - set to your location
time.timeZone = "America/Guatemala";
# Locale
i18n.defaultLocale = "en_US.UTF-8";
# Users
users.groups.bitspire = { };
users.users.bitspire = {
isNormalUser = true;
group = "bitspire";
description = "bitSpire ATM";
home = "/home/bitspire";
extraGroups = [
"wheel" # For admin access
"video" # GPU access
"audio" # Sound
"dialout" # Serial ports
"plugdev" # USB devices
"networkmanager" # Network config
];
# No password - kiosk mode
initialPassword = "bitspire"; # pragma: allowlist secret
};
# Kiosk display configuration
services.xserver = {
enable = true;
# No desktop environment - just the ATM app
desktopManager.xterm.enable = false;
# Basic window manager for Electron
windowManager.openbox.enable = true;
# Disable screen blanking
serverFlagsSection = ''
Option "BlankTime" "0"
Option "StandbyTime" "0"
Option "SuspendTime" "0"
Option "OffTime" "0"
'';
# Intel driver
videoDrivers = [ "modesetting" ];
};
# Display manager - auto-login (top-level since NixOS 24.11+)
services.displayManager.autoLogin = {
enable = true;
user = "bitspire";
};
# Audio (for transaction sounds)
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
pulse.enable = true;
};
# System packages
environment.systemPackages = with pkgs; [
# System utilities
htop
vim
git
curl
wget
# Hardware debugging
usbutils
pciutils
lsof
# Serial port tools
minicom
screen
# For the Electron app
pkgs-unstable.electron
# Node.js for the application
pkgs-unstable.nodejs_22
# Camera support
v4l-utils
fswebcam
# ATM operations
sqlite
(writeShellScriptBin "atm-transactions" (builtins.readFile ./atm-transactions.sh))
];
# Enable SSH for remote administration
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
PermitRootLogin = "prohibit-password";
};
};
# Root SSH key access
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIES8I43AgXppATvtBqnUycakMMs68T2J52cTwNt6qPak padreug@gizmo"
];
# Auto-updates (optional - disabled by default for stability)
# system.autoUpgrade.enable = false;
# pragma: allowlist secret
# Ensure WireGuard private key directory exists with correct permissions
system.activationScripts.wireguard-key = ''
mkdir -p /var/lib/wireguard
chmod 700 /var/lib/wireguard
if [ -f /var/lib/wireguard/wg0.key ]; then
chmod 600 /var/lib/wireguard/wg0.key
fi
'';
# In-place rename migration: lamassu user → bitspire user.
# Runs after `users` activation so the bitspire user exists with its UID.
# Idempotent: re-running on an already-migrated system is a chown no-op.
# Leaves /home/lamassu in place as evidence — operator can `rm -rf` after
# confirming bitspire works.
system.activationScripts.bitspire-user-migration = {
deps = [ "users" ];
text = ''
# SSH key migration: copy authorized_keys to /home/bitspire if missing,
# so the dev box can still SSH in as bitspire after the rename.
if [ -f /home/lamassu/.ssh/authorized_keys ] \
&& [ ! -f /home/bitspire/.ssh/authorized_keys ]; then
mkdir -p /home/bitspire/.ssh
cp /home/lamassu/.ssh/authorized_keys /home/bitspire/.ssh/authorized_keys
chown -R bitspire:bitspire /home/bitspire/.ssh
chmod 700 /home/bitspire/.ssh
chmod 600 /home/bitspire/.ssh/authorized_keys
fi
# Data dir ownership: state.db / .env / branding/ may still be owned by
# the now-removed lamassu UID. Reset every boot — cheap no-op once done.
if [ -d /var/lib/bitspire ]; then
chown -R bitspire:bitspire /var/lib/bitspire
fi
'';
};
# Journal configuration
services.journald = {
extraConfig = ''
SystemMaxUse=100M
MaxRetentionSec=1week
'';
};
# Nix settings
nix = {
settings = {
experimental-features = [ "nix-command" "flakes" ];
auto-optimise-store = true;
};
# Garbage collection — daily at 03:30, 30 min before the 04:00 auto-
# upgrade so each upgrade attempt gets the freshest headroom. 15GB
# eMMC + ~7GB closure means cross-release upgrades are always tight;
# weekly was leaving up to a week of generations stacked when the
# upgrade ran (caught 2026-05-26 on the 24.05 → 24.11 attempt).
# persistent so a Sintra that was powered off at 03:30 still runs the
# GC on next boot rather than skipping until next week.
gc = {
automatic = true;
dates = "03:30";
options = "--delete-older-than 7d";
persistent = true;
};
};
}