blumeops/containers/teslamate/default.nix
Erich Blume fcac8e5a72 Wave 1 indri→ringtail migration: paperless, teslamate, mealie (#363)
Migrate paperless, teslamate, and mealie off the OOM-saturated minikube-indri node onto ringtail k3s, shedding ~1.1 GiB of resident load. Second chain in the indri-k8s decommission after immich.

**Containers ported to Nix (default.nix), build-verified on ringtail:**
- paperless → wraps nixpkgs paperless-ngx 2.20.15 (pinned unstable); runs as web/worker/beat/consumer
- mealie → wraps nixpkgs mealie 3.16.0 (forward 4-minor bump, breaking-change reviewed); single gunicorn, SQLite
- teslamate → from-scratch beamPackages mixRelease (not in nixpkgs); erlang_27+elixir_1_18, npm assets, ex_cldr locales pre-fetched

**Data:** cold downtime-tolerant cutover. paperless+teslamate postgres dump/restore from quiesced source into a new ringtail blumeops-pg CNPG cluster; mealie SQLite PVC copied. Source DBs untouched until verified (rollback = repoint).

**Also:** ringtail blumeops-pg cluster + ExternalSecrets scaffold; fixes pre-existing shower version-check drift.

Runbook: docs/how-to/ringtail/migrate-wave1-ringtail.md. Deploy-from-branch + cutover happens before merge; container images rebuilt from main after merge.
Reviewed-on: #363
2026-06-03 10:34:00 -07:00

122 lines
4.2 KiB
Nix

# Nix-built TeslaMate for ringtail (amd64).
#
# Replaces the Dagger container.py (Elixir+Node builder -> Debian slim).
# TeslaMate is NOT in nixpkgs, so this is a from-scratch beamPackages
# mixRelease: an Elixir/Phoenix release with npm-built assets.
#
# Pinned to the same nixos-unstable rev as paperless/mealie for a
# consistent toolchain. The BEAM combo is pinned to erlang_27 + elixir_1_18
# (teslamate requires elixir ~> 1.17; upstream's image uses OTP 26, so we
# stay off the default OTP 28 which elixir 1.18 does not target).
#
# Source comes from the forge mirror (supply-chain control), pinned by the
# v3.0.0 tag's commit so builtins.fetchGit needs no hash.
let
nixpkgs = fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/331800de5053fcebacf6813adb5db9c9dca22a0c.tar.gz";
sha256 = "1p54fm6dkbq62kpi55cr4wyx7b1nsajpsnjgs64cmp073fwi15f7";
};
pkgs = import nixpkgs { system = "x86_64-linux"; };
lib = pkgs.lib;
version = "3.0.0";
beamPackages = pkgs.beam.packages.erlang_27;
elixir = beamPackages.elixir_1_18;
src = builtins.fetchGit {
url = "https://forge.ops.eblu.me/mirrors/teslamate.git";
ref = "refs/tags/v${version}";
rev = "3281154d42330786a182c1bbe094ecda0b1c5578";
};
# ex_cldr downloads locale JSON from GitHub at compile time, which the
# build sandbox blocks. teslamate's cldr.ex reads the data dir from the
# LOCALES env var; point it at the pre-fetched elixir-cldr data so no
# download is attempted (with SKIP_LOCALE_DOWNLOAD=true disabling the
# forced refresh). CLDR data version matches the compile-time errors.
cldrData = pkgs.fetchFromGitHub {
owner = "elixir-cldr";
repo = "cldr";
rev = "v2.46.0";
sha256 = "1iwzk9dc754l72vpf8vsisdjncnjx26pz509552b6vnm49xbxyji";
};
teslamate = beamPackages.mixRelease {
pname = "teslamate";
inherit version src elixir;
# Keep the build-generated Erlang cookie in the release. mixRelease
# strips it by default (expecting RELEASE_COOKIE at runtime), but the
# start script reads releases/COOKIE. teslamate is single-node (no
# distributed Erlang exposed), so a baked-in cookie is fine.
removeCookie = false;
mixFodDeps = beamPackages.fetchMixDeps {
pname = "mix-deps-teslamate";
inherit src version elixir;
hash = "sha256-DDrREiM1BIMgD2qFPTK8QyjOYlnfE3XlnaH/jk7G2go=";
};
# Frontend assets. esbuild + sass are devDeps and the esbuild platform
# binary is an optional dep, so npm ci must include both. We run npm ci
# here (not a separate derivation) because assets/package.json has
# file:../deps/phoenix references that only resolve once mixFodDeps has
# populated deps/. npmConfigHook wires up the offline cache from npmDeps;
# then `node scripts/build.js` (custom esbuild) + `mix phx.digest`.
nativeBuildInputs = [ pkgs.nodejs pkgs.npmHooks.npmConfigHook ];
npmDeps = pkgs.fetchNpmDeps {
name = "teslamate-npm-deps";
src = src + "/assets";
hash = "sha256-XyiaUkT/c4rZnNxmxhVLb+vEXnc64A1hjOrnR5fhaEk=";
};
npmRoot = "assets";
preBuild = ''
export SKIP_LOCALE_DOWNLOAD=true
export LOCALES=${cldrData}/priv/cldr
( cd assets && npm ci --include=dev --include=optional && node scripts/build.js )
mix phx.digest --no-deps-check
'';
};
in
pkgs.dockerTools.buildLayeredImage {
name = "blumeops/teslamate";
contents = [
teslamate
pkgs.bashInteractive
pkgs.coreutils
pkgs.dash
pkgs.netcat-openbsd
pkgs.cacert
pkgs.tzdata
];
config = {
# Mirror entrypoint.sh: wait for postgres, run migrations, then start.
Entrypoint = [
"${pkgs.dash}/bin/dash"
"-c"
''
: "''${DATABASE_HOST:=127.0.0.1}"
: "''${DATABASE_PORT:=5432}"
while ! ${pkgs.netcat-openbsd}/bin/nc -z "$DATABASE_HOST" "$DATABASE_PORT" 2>/dev/null; do
echo "waiting for postgres at $DATABASE_HOST:$DATABASE_PORT"; sleep 1
done
${teslamate}/bin/teslamate eval "TeslaMate.Release.migrate"
exec ${teslamate}/bin/teslamate start
''
];
Env = [
"HOME=/opt/app"
"SRTM_CACHE=/opt/app/.srtm_cache"
"LANG=C.UTF-8"
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
];
ExposedPorts = {
"4000/tcp" = { };
};
};
}