Centralize Tensamin packages modules and release automation
Some checks failed
action.yml / Centralize Tensamin packages modules and release automation (push) Failing after 0s
Canary release / release (push) Failing after 21s

This commit is contained in:
Alois 2026-10-04 19:19:56 +02:00
commit 123205c97d
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
28 changed files with 32292 additions and 10 deletions

137
packages/client.nix Normal file
View file

@ -0,0 +1,137 @@
{ pkgs, rust, rustPlatform, inputs, hashes, mtpSource, mtpVendor }:
let
inherit (pkgs) lib;
pnpm = pkgs.pnpm;
sdk-deps = pkgs.fetchPnpmDeps {
pname = "mtp-sdk";
version = "0.4.0";
src = inputs.mtp;
inherit pnpm;
fetcherVersion = 4;
hash = hashes.sdk;
};
wasmVersion = (lib.findFirst (p: p.name == "wasm-bindgen") (throw "Missing wasm-bindgen")
(builtins.fromTOML (builtins.readFile (inputs.mtp + "/Cargo.lock"))).package).version;
wasmBindgen = pkgs.${"wasm-bindgen-cli_" + builtins.replaceStrings [ "." ] [ "_" ] wasmVersion};
mtp-sdk = pkgs.stdenv.mkDerivation {
pname = "mtp-sdk";
version = "0.4.0";
src = mtpSource;
pnpmDeps = sdk-deps;
cargoDeps = mtpVendor;
nativeBuildInputs = [ rust pkgs.nodejs pnpm pkgs.pnpmConfigHook rustPlatform.cargoSetupHook wasmBindgen pkgs.lld ];
MTP_TYPE_MAPS = "${inputs.mtp-type-maps}/type-maps.yaml";
RUSTFLAGS = "--cfg=web_sys_unstable_apis";
buildPhase = ''
runHook preBuild
cargo build --locked --offline --release -p mtp-wasm --target wasm32-unknown-unknown
mkdir -p sdk/bindings/wasm/pkg
wasm-bindgen target/wasm32-unknown-unknown/release/mtp_wasm.wasm \
--target web --out-dir sdk/bindings/wasm/pkg --out-name mtp_wasm
pnpm --dir sdk run build:ts
# The Nix SDK already contains WASM compiled with the authoritative maps.
# Keep the plugin's YAML generation and aliases, but reuse that build.
substituteInPlace sdk/dist/vite/index.js \
--replace-fail 'await runWasmPack(state);' \
'await fs.cp(path.join(packageRoot, "bindings/wasm/pkg"), state.outDir, { recursive: true });'
runHook postBuild
'';
installPhase = ''
mkdir -p "$out"
cp -r sdk/dist sdk/bindings sdk/type-map sdk/package.json "$out/"
mkdir -p "$out/node_modules"
cp -rL sdk/node_modules/yaml "$out/node_modules/"
'';
passthru.source = inputs.mtp;
};
client-source = pkgs.runCommand "client-source" { nativeBuildInputs = [ pkgs.python3 ]; } ''
cp -r ${inputs.client} "$out"
chmod -R u+w "$out"
rm -rf "$out/mtp-type-maps"
cp -r ${inputs.mtp-type-maps} "$out/mtp-type-maps"
mkdir -p "$out/.mtp-sdk"
cp ${inputs.mtp}/sdk/package.json "$out/.mtp-sdk/package.json"
python - "$out" <<'PY'
import json, sys, os, re
from pathlib import Path
root = Path(sys.argv[1])
workspace = root / 'pnpm-workspace.yaml'
workspace.write_text(re.sub(r'^ mtp:.*\n', "", workspace.read_text(), flags=re.M))
for path in root.rglob('package.json'):
if '.mtp-sdk' in path.parts:
continue
data = json.loads(path.read_text())
for section in ['dependencies', 'devDependencies']:
if 'mtp' in data.get(section, {}):
data[section]['mtp'] = 'link:' + os.path.relpath(root / '.mtp-sdk', path.parent)
path.write_text(json.dumps(data, indent=2) + '\n')
PY
${lib.optionalString (builtins.pathExists ./locks/client.yaml) ''cp ${./locks/client.yaml} "$out/pnpm-lock.yaml"''}
'';
client-deps = pkgs.fetchPnpmDeps {
pname = "tensamin";
version = "0.0.11";
src = client-source;
inherit pnpm;
fetcherVersion = 4;
hash = hashes.client;
};
common = {
version = "0.0.11";
src = client-source;
pnpmDeps = client-deps;
nativeBuildInputs = [ pkgs.nodejs pnpm pkgs.pnpmConfigHook pkgs.makeWrapper ];
ELECTRON_SKIP_BINARY_DOWNLOAD = "1";
postPatch = ''
rm -rf .mtp-sdk
cp -r ${mtp-sdk} .mtp-sdk
chmod -R u+w .mtp-sdk
'';
passthru = { source = inputs.client; inherit mtp-sdk; };
meta.platforms = [ "x86_64-linux" "aarch64-linux" ];
};
client-web = pkgs.stdenv.mkDerivation (common // {
pname = "tensamin-web";
buildPhase = ''
runHook preBuild
pnpm run build:web
runHook postBuild
'';
installPhase = ''mkdir -p "$out"; cp -r apps/web/dist/. "$out/"'';
});
client = pkgs.stdenv.mkDerivation (common // {
pname = "tensamin";
buildPhase = ''
runHook preBuild
TENSAMIN_WEB_BASE=./ pnpm run build:web
pnpm --dir apps/electron run build
runHook postBuild
'';
installPhase = ''
mkdir -p "$out/lib/tensamin" "$out/bin"
cp -r apps/electron/dist "$out/lib/tensamin/"
cp apps/electron/package.json "$out/lib/tensamin/"
mkdir -p "$out/lib/web/dist"
cp -r apps/web/dist/. "$out/lib/web/dist/"
makeWrapper ${pkgs.electron}/bin/electron "$out/bin/tensamin" \
--add-flags "$out/lib/tensamin" \
--prefix PATH : ${lib.makeBinPath [ pkgs.pulseaudio ]} \
--set ELECTRON_OZONE_PLATFORM_HINT auto
if [ -d apps/electron/build/icons ]; then
mkdir -p "$out/lib/tensamin/build"
cp -r apps/electron/build/icons "$out/lib/tensamin/build/icons"
install -Dm644 apps/electron/build/icons/icon.png "$out/share/icons/hicolor/512x512/apps/tensamin.png"
fi
mkdir -p "$out/share/applications"
cp ${pkgs.makeDesktopItem {
name = "tensamin";
desktopName = "Tensamin";
exec = "tensamin %U";
icon = "tensamin";
categories = [ "Network" ];
mimeTypes = [ "x-scheme-handler/tensamin" ];
}}/share/applications/* "$out/share/applications/"
'';
meta = common.meta // { mainProgram = "tensamin"; };
});
in { inherit client client-web mtp-sdk client-source client-deps sdk-deps; }

196
packages/default.nix Normal file
View file

@ -0,0 +1,196 @@
{ inputs, pkgs, system, hashes ? import ./hashes.nix }:
let
inherit (pkgs) lib;
rust = pkgs.rust-bin.stable.latest.default.override {
targets = [ "wasm32-unknown-unknown" "${pkgs.stdenv.hostPlatform.parsed.cpu.name}-unknown-linux-musl" ];
extensions = [ "rust-src" "rustfmt" "clippy" ];
};
rustPlatform = pkgs.makeRustPlatform { cargo = rust; rustc = rust; };
projects = [ "iota" "omikron" "omega" "mtp" ];
prepared = lib.genAttrs projects (name: pkgs.runCommand "${name}-source" {
nativeBuildInputs = [ pkgs.python3 ] ++ lib.optional (name == "iota") rust;
} ''
python ${../lib/prepare-source.py} ${name} ${inputs.${name}} ${inputs.mtp} \
${inputs.mtp-type-maps} ${inputs.iota} "$out"
${lib.optionalString (name == "iota") ''
rustfmt --edition 2024 "$out/iota-paths/src/lib.rs" "$out/iota-installer/src/lib.rs" "$out/iota-daemon/src/main.rs"
''}
'');
sources = lib.genAttrs projects (name:
let lock = ./locks + "/${name}.lock";
in if builtins.pathExists lock then pkgs.runCommand "${name}-locked-source" {} ''
cp -r ${prepared.${name}} "$out"
chmod -R u+w "$out"
cp ${lock} "$out/Cargo.lock"
'' else prepared.${name});
vendors = lib.genAttrs projects (name: rustPlatform.fetchCargoVendor {
pname = "${name}-vendor";
version = "0.1.0";
src = sources.${name};
hash = hashes.${name};
});
mkRust = name: flags: rustPlatform.buildRustPackage {
pname = name;
version = "0.1.0";
src = sources.${name};
cargoDeps = vendors.${name};
cargoBuildFlags = flags;
nativeBuildInputs = [ pkgs.cmake pkgs.perl pkgs.pkg-config ];
buildInputs = [ pkgs.openssl pkgs.sqlite ] ++ lib.optional (name == "omega") pkgs.libmysqlclient;
dontUseCmakeConfigure = true;
MTP_TYPE_MAPS = "${inputs.mtp-type-maps}/type-maps.yaml";
doCheck = false;
postInstall = lib.optionalString (name == "iota") ''
mkdir -p "$out/share/iota"
cp -r static/web "$out/share/iota/web"
'';
passthru = {
source = inputs.${name};
transformedSource = sources.${name};
inherit (inputs) mtp mtp-type-maps;
};
meta = { platforms = [ "x86_64-linux" "aarch64-linux" ]; mainProgram = name; };
};
iota = mkRust "iota" [ "-p" "iota" "-p" "iota-daemon" "-p" "iota-updater" "-p" "iota-installer" ];
staticPkgs = pkgs.pkgsStatic;
iotaPortable = rustPlatform.buildRustPackage {
pname = "iota-portable";
inherit (iota) version src cargoDeps cargoBuildFlags postInstall;
nativeBuildInputs = [ pkgs.cmake pkgs.perl pkgs.pkg-config pkgs.binutils pkgs.removeReferencesTo staticPkgs.stdenv.cc ];
buildInputs = [ staticPkgs.openssl staticPkgs.sqlite staticPkgs.zlib ];
OPENSSL_STATIC = "1";
SQLITE3_STATIC = "1";
env.PKG_CONFIG_ALLOW_CROSS = "1";
# Keep build scripts on the native host and cross-link only the payload.
buildPhase = ''
runHook preBuild
export CARGO_TARGET_${pkgs.stdenv.buildPlatform.rust.cargoEnvVarTarget}_LINKER=${pkgs.stdenv.cc}/bin/cc
export CARGO_TARGET_${staticPkgs.stdenv.hostPlatform.rust.cargoEnvVarTarget}_LINKER=${staticPkgs.stdenv.cc}/bin/${staticPkgs.stdenv.cc.targetPrefix}cc
export CC_${pkgs.stdenv.buildPlatform.rust.cargoEnvVarTarget}=${pkgs.stdenv.cc}/bin/cc
export CC_${staticPkgs.stdenv.hostPlatform.rust.cargoEnvVarTarget}=${staticPkgs.stdenv.cc}/bin/${staticPkgs.stdenv.cc.targetPrefix}cc
export CC_${builtins.replaceStrings [ "-" ] [ "_" ] staticPkgs.stdenv.hostPlatform.rust.rustcTarget}=${staticPkgs.stdenv.cc}/bin/${staticPkgs.stdenv.cc.targetPrefix}cc
export CXX_${builtins.replaceStrings [ "-" ] [ "_" ] staticPkgs.stdenv.hostPlatform.rust.rustcTarget}=${staticPkgs.stdenv.cc}/bin/${staticPkgs.stdenv.cc.targetPrefix}c++
unset NIX_CFLAGS_COMPILE NIX_LDFLAGS
export CARGO_TARGET_${staticPkgs.stdenv.hostPlatform.rust.cargoEnvVarTarget}_RUSTFLAGS="-L native=${lib.getLib staticPkgs.zlib}/lib --remap-path-prefix=/nix/store=/usr/src"
cargo build --locked --offline --release -j "$NIX_BUILD_CORES" \
--target ${staticPkgs.stdenv.hostPlatform.rust.rustcTarget} ${lib.escapeShellArgs iota.cargoBuildFlags}
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p "$out/bin"
for binary in iota iota-daemon iota-updater iota-release iota-bundle; do
cp "target/${staticPkgs.stdenv.hostPlatform.rust.rustcTarget}/release/$binary" "$out/bin/"
done
runHook postInstall
'';
dontUseCmakeConfigure = true;
MTP_TYPE_MAPS = "${inputs.mtp-type-maps}/type-maps.yaml";
doCheck = false;
# Reject accidental dynamic linkage before these binaries reach a release.
postFixup = ''
for binary in "$out"/bin/*; do
# Prebuilt Rust std retains compiler source paths in panic messages.
remove-references-to -t ${rust} "$binary"
if readelf -l "$binary" | grep -q INTERP || readelf -d "$binary" | grep -q NEEDED; then
echo "Non-portable ELF: $binary" >&2
exit 1
fi
done
'';
allowedReferences = [ "out" ];
meta = iota.meta;
};
clientPackages = import ./client.nix {
inherit pkgs rust rustPlatform inputs hashes;
mtpSource = sources.mtp;
mtpVendor = vendors.mtp;
};
packages = {
inherit iota;
iota-portable = iotaPortable;
iota-bundle = pkgs.runCommand "iota-bundle" {} ''
mkdir -p "$out/bin" "$out/share/iota"
cp -r ${iota}/bin/. "$out/bin/"
cp -r ${inputs.iota}/systemd "$out/share/iota/"
cp -r ${sources.iota}/scripts "$out/share/iota/"
cp ${inputs.iota}/iota-updater/artifacts.tsv "$out/share/iota/"
cp -r ${iota}/share/iota/web "$out/share/iota/static-web"
cp ${sources.iota}/iota-installer/bundle-files.txt "$out/share/iota/"
'';
iota-daemon = iota.overrideAttrs {
pname = "iota-daemon";
cargoBuildFlags = [ "-p" "iota-daemon" ];
meta.mainProgram = "iota-daemon";
};
iota-ui = iota.overrideAttrs {
pname = "iota-ui";
cargoBuildFlags = [ "-p" "iota" ];
postInstall = iota.postInstall + ''mv "$out/bin/iota" "$out/bin/iota-ui"'';
meta.mainProgram = "iota-ui";
};
omikron = mkRust "omikron" [];
omega = mkRust "omega" [];
iota-container = pkgs.dockerTools.buildLayeredImage {
name = "tensamin/iota";
tag = "${inputs.iota.shortRev or "local"}";
contents = [ iotaPortable pkgs.cacert pkgs.dockerTools.binSh ];
fakeRootCommands = ''
mkdir -p var/lib/iota/config var/lib/iota/runtime tmp
chmod 1777 tmp
chown -R 1000:1000 var/lib/iota
'';
enableFakechroot = true;
config = {
Entrypoint = [ "${iotaPortable}/bin/iota-daemon" ];
User = "1000:1000";
WorkingDir = "/var/lib/iota";
Volumes = { "/var/lib/iota" = {}; };
ExposedPorts = { "1984/tcp" = {}; "1984/udp" = {}; };
Env = [
"HOME=/var/lib/iota"
"IOTA_DATA_ROOT=/var/lib/iota"
"IOTA_DEPLOYMENT_MODE=user_local"
"IOTA_ASSET_DIR=${iotaPortable}/share/iota/web"
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
];
};
};
omikron-container = pkgs.dockerTools.buildLayeredImage {
name = "tensamin/omikron";
tag = "${inputs.omikron.shortRev or "local"}";
contents = [ packages.omikron pkgs.cacert pkgs.dockerTools.binSh ];
config = {
Entrypoint = [ "${packages.omikron}/bin/omikron" ];
WorkingDir = "/var/lib/omikron";
Env = [ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ];
};
};
omega-container = pkgs.dockerTools.buildLayeredImage {
name = "tensamin/omega";
tag = "${inputs.omega.shortRev or "local"}";
contents = [ packages.omega pkgs.cacert pkgs.dockerTools.binSh ];
config = {
Entrypoint = [ "${packages.omega}/bin/omega" ];
WorkingDir = "/var/lib/omega";
Env = [ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ];
};
};
inherit (clientPackages) client client-web mtp-sdk;
default = packages.client;
update-all = pkgs.writeShellApplication {
name = "update-all";
runtimeInputs = [ pkgs.nix pkgs.git pkgs.python3 rust pkgs.nodejs pkgs.pnpm pkgs.coreutils ];
text = ''exec python ${../scripts/update-all.py} "$@"'';
};
} // lib.mapAttrs' (name: value: lib.nameValuePair "${name}-source" value) prepared
// lib.mapAttrs' (name: value: lib.nameValuePair "${name}-vendor" value) vendors
// { inherit (clientPackages) client-source client-deps sdk-deps; };
in {
inherit packages;
devShells = lib.genAttrs [ "iota" "omikron" "omega" "mtp" "client" ] (name: pkgs.mkShell {
packages = [ rust pkgs.git pkgs.cmake pkgs.perl pkgs.pkg-config pkgs.nodejs pkgs.pnpm pkgs.wasm-pack ];
buildInputs = [ pkgs.openssl pkgs.sqlite ] ++ lib.optional (name == "omega") pkgs.libmysqlclient;
MTP_TYPE_MAPS = "${inputs.mtp-type-maps}/type-maps.yaml";
}) // { default = pkgs.mkShell { packages = [ packages.update-all ]; }; };
}

8
packages/hashes.nix Normal file
View file

@ -0,0 +1,8 @@
{
iota = "sha256-LYr2OqOSUX3AXwoJr15xtswpHRVz50evrrT7ep5FZog=";
omikron = "sha256-HFSZ6fEzMzHWFrjPBY08XaLC+OoNNIURflvO/CVaKZM=";
omega = "sha256-rvz6UoZlKPHvTsURLTBqSjuPvdwlKpmKBQ1+27fzC8I=";
mtp = "sha256-zfucKSWEIx3l1L9lTj1lidD/qE/HwueLBVJoND51MMU=";
client = "sha256-w6onBznxx/7bsIjodQz98tcsudcDNEtTPJgf2bmrXSU=";
sdk = "sha256-24mJCREdij/ha95AdmxOIsE/cHKkiBNoQCPANau1GcU=";
}

10385
packages/locks/client.yaml Normal file

File diff suppressed because it is too large Load diff

6163
packages/locks/iota.lock Normal file

File diff suppressed because it is too large Load diff

5677
packages/locks/mtp.lock Normal file

File diff suppressed because it is too large Load diff

3359
packages/locks/omega.lock Normal file

File diff suppressed because it is too large Load diff

4180
packages/locks/omikron.lock Normal file

File diff suppressed because it is too large Load diff