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
This commit is contained in:
parent
40bd929820
commit
fcac8e5a72
45 changed files with 1422 additions and 445 deletions
|
|
@ -1,104 +0,0 @@
|
|||
"""TeslaMate — Tesla data logger.
|
||||
|
||||
Two-stage build: Elixir+Node (builder), Debian slim (runtime).
|
||||
Source cloned from forge mirror.
|
||||
"""
|
||||
|
||||
import dagger
|
||||
from dagger import dag
|
||||
|
||||
from blumeops.containers import clone_from_forge, oci_labels
|
||||
|
||||
VERSION = "v3.0.0"
|
||||
|
||||
|
||||
async def build(src: dagger.Directory) -> dagger.Container:
|
||||
source = clone_from_forge("teslamate", VERSION)
|
||||
|
||||
# Stage 1: Build Elixir release with Node.js assets
|
||||
builder = (
|
||||
dag.container()
|
||||
.from_("elixir:1.19.5-otp-26")
|
||||
.with_exec(
|
||||
[
|
||||
"bash",
|
||||
"-c",
|
||||
"apt-get update"
|
||||
" && apt-get install -y ca-certificates curl gnupg git zstd brotli"
|
||||
" && mkdir -p /etc/apt/keyrings"
|
||||
" && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key"
|
||||
" | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg"
|
||||
' && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg]'
|
||||
' https://deb.nodesource.com/node_22.x nodistro main"'
|
||||
" > /etc/apt/sources.list.d/nodesource.list"
|
||||
" && apt-get update"
|
||||
" && apt-get install -y nodejs"
|
||||
" && apt-get clean"
|
||||
" && rm -rf /var/lib/apt/lists/*",
|
||||
]
|
||||
)
|
||||
.with_exec(["mix", "local.rebar", "--force"])
|
||||
.with_exec(["mix", "local.hex", "--force"])
|
||||
.with_directory("/opt/app", source)
|
||||
.with_workdir("/opt/app")
|
||||
.with_env_variable("MIX_ENV", "prod")
|
||||
.with_exec(["mix", "deps.get", "--only", "prod"])
|
||||
.with_exec(["mix", "deps.compile"])
|
||||
.with_exec(
|
||||
[
|
||||
"npm",
|
||||
"ci",
|
||||
"--prefix",
|
||||
"./assets",
|
||||
"--progress=false",
|
||||
"--no-audit",
|
||||
"--loglevel=error",
|
||||
]
|
||||
)
|
||||
.with_exec(["mix", "assets.deploy"])
|
||||
.with_exec(["mix", "compile"])
|
||||
.with_exec(
|
||||
["bash", "-c", "SKIP_LOCALE_DOWNLOAD=true mix release --path /opt/built"]
|
||||
)
|
||||
)
|
||||
|
||||
# Stage 2: Debian slim runtime
|
||||
entrypoint = src.file("containers/teslamate/entrypoint.sh")
|
||||
|
||||
runtime = (
|
||||
dag.container()
|
||||
.from_("debian:trixie-slim")
|
||||
.with_exec(
|
||||
[
|
||||
"bash",
|
||||
"-c",
|
||||
"apt-get update && apt-get install -y --no-install-recommends"
|
||||
" libodbc2 libsctp1 libssl3t64 libstdc++6"
|
||||
" netcat-openbsd tini tzdata"
|
||||
" && apt-get clean"
|
||||
" && rm -rf /var/lib/apt/lists/*"
|
||||
" && groupadd --gid 10001 --system nonroot"
|
||||
" && useradd --uid 10000 --system --gid nonroot"
|
||||
" --home-dir /home/nonroot --shell /sbin/nologin nonroot",
|
||||
]
|
||||
)
|
||||
)
|
||||
runtime = oci_labels(
|
||||
runtime,
|
||||
title="TeslaMate",
|
||||
description="Tesla data logger and visualization",
|
||||
version=VERSION,
|
||||
)
|
||||
return (
|
||||
runtime.with_env_variable("LANG", "C.UTF-8")
|
||||
.with_env_variable("SRTM_CACHE", "/opt/app/.srtm_cache")
|
||||
.with_env_variable("HOME", "/opt/app")
|
||||
.with_workdir("/opt/app")
|
||||
.with_directory("/opt/app", builder.directory("/opt/built"), owner="nonroot")
|
||||
.with_exec(["mkdir", "-p", "/opt/app/.srtm_cache"])
|
||||
.with_file("/entrypoint.sh", entrypoint, permissions=0o555, owner="nonroot")
|
||||
.with_user("nonroot")
|
||||
.with_exposed_port(4000)
|
||||
.with_entrypoint(["tini", "--", "/bin/dash", "/entrypoint.sh"])
|
||||
.with_default_args(args=["bin/teslamate", "start"])
|
||||
)
|
||||
122
containers/teslamate/default.nix
Normal file
122
containers/teslamate/default.nix
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
# 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" = { };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
#!/usr/bin/env dash
|
||||
set -e
|
||||
|
||||
: "${DATABASE_HOST:="127.0.0.1"}"
|
||||
: "${DATABASE_PORT:=5432}"
|
||||
: "${ULIMIT_MAX_NOFILE:=65536}"
|
||||
|
||||
# prevent memory bloat in some misconfigured versions of Docker/containerd
|
||||
# where the nofiles limit is very large. 0 means don't set it.
|
||||
if test "${ULIMIT_MAX_NOFILE}" != 0 && test "$(ulimit -n)" -gt "${ULIMIT_MAX_NOFILE}"; then
|
||||
ulimit -n "${ULIMIT_MAX_NOFILE}"
|
||||
fi
|
||||
|
||||
# wait until Postgres is ready
|
||||
while ! nc -z "${DATABASE_HOST}" "${DATABASE_PORT}" 2>/dev/null; do
|
||||
echo waiting for postgres at "${DATABASE_HOST}":"${DATABASE_PORT}"
|
||||
sleep 1s
|
||||
done
|
||||
|
||||
# apply migrations
|
||||
bin/teslamate eval "TeslaMate.Release.migrate"
|
||||
|
||||
exec "$@"
|
||||
Loading…
Add table
Add a link
Reference in a new issue