Wave 1 indri→ringtail migration: paperless, teslamate, mealie #363
4 changed files with 125 additions and 128 deletions
teslamate: port container from Dagger to Nix (default.nix)
teslamate is not in nixpkgs, so this is a from-scratch beamPackages mixRelease: an Elixir/Phoenix release with npm-built assets. Replaces container.py (+ entrypoint.sh, now inlined as the image Entrypoint). Pins erlang_27 + elixir_1_18 from the shared nixos-unstable rev (teslamate needs elixir ~> 1.17; stays off the default OTP 28). Source from the forge mirror, pinned by the v3.0.0 tag commit. Assets build in-release via npm ci (esbuild + sass are devDeps; esbuild platform binary is optional) + the custom node scripts/build.js, then mix phx.digest. ex_cldr locale data is pre-fetched and pointed at via LOCALES to avoid compile-time GitHub downloads the build sandbox blocks. Version unchanged (v3.0.0). Build verified on ringtail (exit 0, ~134 MB image). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit
39686c8a2e
|
|
@ -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"])
|
|
||||||
)
|
|
||||||
116
containers/teslamate/default.nix
Normal file
116
containers/teslamate/default.nix
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
# 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;
|
||||||
|
|
||||||
|
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 "$@"
|
|
||||||
|
|
@ -222,9 +222,17 @@ services:
|
||||||
|
|
||||||
- name: teslamate
|
- name: teslamate
|
||||||
type: argocd
|
type: argocd
|
||||||
last-reviewed: 2026-04-14
|
last-reviewed: "2026-06-03"
|
||||||
current-version: "v3.0.0"
|
current-version: "v3.0.0"
|
||||||
upstream-source: https://github.com/teslamate-org/teslamate/releases
|
upstream-source: https://github.com/teslamate-org/teslamate/releases
|
||||||
|
notes: >-
|
||||||
|
Tesla data logger. Container ported from Dagger (container.py) to Nix
|
||||||
|
(containers/teslamate/default.nix) — a from-scratch beamPackages
|
||||||
|
mixRelease (Elixir/Phoenix release with npm-built assets), since
|
||||||
|
teslamate is not in nixpkgs. Pins erlang_27 + elixir_1_18 from the
|
||||||
|
shared nixos-unstable rev; assets via in-release npm ci + esbuild;
|
||||||
|
ex_cldr locale data pre-fetched (LOCALES env) to avoid sandbox
|
||||||
|
downloads. Version unchanged (v3.0.0). Build verified on ringtail.
|
||||||
|
|
||||||
- name: transmission
|
- name: transmission
|
||||||
type: argocd
|
type: argocd
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue