diff --git a/.github/workflows/emulator.yml b/.github/workflows/emulator.yml
index 9ecd277f..9a495229 100644
--- a/.github/workflows/emulator.yml
+++ b/.github/workflows/emulator.yml
@@ -47,7 +47,7 @@ jobs:
rm -rf n-o-d
mkdir -p n-o-d
git -C . archive --format=tar.gz --prefix n-o-d/ HEAD > n-o-d/archive.tar.gz
- ARCHES=x86_64 nix run '.#deploy' -- file:///data/local/tmp/n-o-d/archive.tar.gz n-o-d/
+ ARCHES=x86_64 nix run '.#deploy' -- file:///data/local/tmp/n-o-d/archive.tar.gz --rsync-target n-o-d/
tar cf n-o-d.tar n-o-d
- name: Store zipball and channel tarball to inject (n-o-d)
diff --git a/flake.nix b/flake.nix
index ad92e985..6f6c6c45 100644
--- a/flake.nix
+++ b/flake.nix
@@ -59,7 +59,7 @@
deploy = {
type = "app";
- program = toString (import ./scripts/deploy.nix { inherit nixpkgs system; });
+ program = import ./scripts/deploy.nix { inherit nixpkgs system; };
};
});
diff --git a/modules/environment/login/default.nix b/modules/environment/login/default.nix
index b4f7a936..75d09d2a 100644
--- a/modules/environment/login/default.nix
+++ b/modules/environment/login/default.nix
@@ -37,7 +37,7 @@ in
prootStatic = mkOption {
type = types.package;
- readOnly = true;
+ # not readOnly, this needs to be overridden when building bootstrap zip
internal = true;
description = "proot-static package.";
};
@@ -84,14 +84,27 @@ in
environment.files = {
inherit login loginInner;
+ # Ideally this would build the static proot binary, but doing that on aarch64 is HARD so instead pull it from the bootstrap tarball
prootStatic =
let
- crossCompiledPaths = {
- aarch64-linux = "/nix/store/7qd99m1w65x2vgqg453nd70y60sm3kay-proot-termux-static-aarch64-unknown-linux-android-unstable-2024-05-04";
- x86_64-linux = "/nix/store/pakj3svvw84rhkzdc6211yhc2cgvc21f-proot-termux-static-x86_64-unknown-linux-android-unstable-2024-05-04";
+ attrs = (import ./proot-attrs).${targetSystem};
+ prootFile = pkgs.fetchurl {
+ name = "proot-static-file";
+ inherit (attrs) url hash;
+
+ downloadToTemp = true;
+ executable = true;
+ postFetch = ''
+ ${pkgs.unzip}/bin/unzip -u $downloadedFile bin/proot-static
+ echo $PWD >&2
+ mv bin/proot-static $out
+ '';
};
in
- "${crossCompiledPaths.${targetSystem}}";
+ pkgs.runCommand "proot-static" { } ''
+ mkdir -p $out/bin
+ cp ${prootFile} $out/bin/proot-static
+ '';
};
};
diff --git a/modules/environment/login/proot-attrs/aarch64.nix b/modules/environment/login/proot-attrs/aarch64.nix
new file mode 100644
index 00000000..b1e4e57c
--- /dev/null
+++ b/modules/environment/login/proot-attrs/aarch64.nix
@@ -0,0 +1,5 @@
+# WARNING: This file is autogenerated by the deploy script. Any changes will be overridden
+{
+ url = "https://nix-on-droid.unboiled.info/bootstrap-testing/bootstrap-aarch64.zip";
+ hash = "sha256-fZyqldmHWbv2e6543mwb5UPcKxcODRg+PDvZNcjVyUU=";
+}
diff --git a/modules/environment/login/proot-attrs/default.nix b/modules/environment/login/proot-attrs/default.nix
new file mode 100644
index 00000000..891ce5e8
--- /dev/null
+++ b/modules/environment/login/proot-attrs/default.nix
@@ -0,0 +1,4 @@
+{
+ x86_64-linux = import ./x86_64.nix;
+ aarch64-linux = import ./aarch64.nix;
+}
diff --git a/modules/environment/login/proot-attrs/x86_64.nix b/modules/environment/login/proot-attrs/x86_64.nix
new file mode 100644
index 00000000..8fad9d7a
--- /dev/null
+++ b/modules/environment/login/proot-attrs/x86_64.nix
@@ -0,0 +1,5 @@
+# WARNING: This file is autogenerated by the deploy script. Any changes will be overridden
+{
+ url = "https://nix-on-droid.unboiled.info/bootstrap-testing/bootstrap-x86_64.zip";
+ hash = "sha256-1WZBmFNEmZucOwuzDAFO+Sl+b2XO7Lp1aQr/4egl4wU=";
+}
diff --git a/pkgs/default.nix b/pkgs/default.nix
index 1840faa9..15c26d67 100644
--- a/pkgs/default.nix
+++ b/pkgs/default.nix
@@ -42,6 +42,8 @@ let
# Fix invoking bash after initial build.
user.shell = "${initialPackageInfo.bash}/bin/bash";
+ environment.files.prootStatic = pkgs.lib.mkForce customPkgs.prootTermux;
+
build = {
channel = {
nixpkgs = urlOptionValue nixpkgsChannelURL "NIXPKGS_CHANNEL_URL";
diff --git a/scripts/deploy.nix b/scripts/deploy.nix
index f959b214..e7952d4b 100644
--- a/scripts/deploy.nix
+++ b/scripts/deploy.nix
@@ -4,31 +4,44 @@
let
pkgs = nixpkgs.legacyPackages.${system};
-
- runtimePackages = with pkgs; [
- coreutils
- git
- gnugrep
- gnused
- gnutar
- gzip
- jq
- nix
- openssh
- rsync
+ pypkgs = pkgs.python311Packages;
+ disablePyLints = [
+ "line-too-long"
+ "missing-module-docstring"
+ "wrong-import-position" # import should be at top of file: we purposefully don't import click and such so that users that try to run the script directly get a friendly error
+ "missing-function-docstring"
+ # c'mon, it's a script
+ "too-many-locals"
+ "too-many-branches"
+ "too-many-statements"
];
-in
+ deriv = pypkgs.buildPythonApplication {
+ pname = "deploy";
+ version = "0.0";
+ src = ./.;
+
+ inherit (pkgs) nix git rsync;
-pkgs.runCommand
- "deploy"
-{
- preferLocalBuild = true;
- allowSubstitutes = false;
-}
- ''
- install -D -m755 ${./deploy.sh} $out
+ propagatedBuildInputs = [ pypkgs.click ];
- substituteInPlace $out \
- --subst-var-by bash "${pkgs.bash}" \
- --subst-var-by path "${pkgs.lib.makeBinPath runtimePackages}"
- ''
+ doCheck = true;
+ nativeCheckInputs = with pypkgs; [ mypy pylint black ];
+ checkPhase = ''
+ mypy --strict --no-color deploy.py
+ PYLINTHOME="$PWD/.pylint" pylint \
+ --score=n \
+ --clear-cache-post-run=y \
+ --disable=${pkgs.lib.concatStringsSep "," disablePyLints} \
+ deploy.py
+ black --check --diff deploy.py
+ '';
+
+ patchPhase = ''
+ substituteInPlace deploy.py \
+ --subst-var nix \
+ --subst-var git \
+ --subst-var rsync
+ '';
+ };
+in
+"${deriv}/bin/deploy"
diff --git a/scripts/deploy.py b/scripts/deploy.py
new file mode 100644
index 00000000..a6507867
--- /dev/null
+++ b/scripts/deploy.py
@@ -0,0 +1,181 @@
+import os
+import sys
+
+GIT = "@git@/bin/git"
+NIX = "@nix@/bin/nix"
+NIX_HASH = "@nix@/bin/nix-hash"
+RSYNC = "@rsync@/bin/rsync"
+
+if GIT.startswith("@"):
+ sys.stderr.write(
+ "Do not run this script directly, instead try: nix run .#deploy -- --help"
+ )
+ sys.exit(1)
+
+import subprocess
+import re
+import inspect
+from typing import Never
+from pathlib import Path
+
+import click
+
+
+def err(text: str) -> Never:
+ sys.stderr.write(text)
+ sys.exit(1)
+
+
+def run(*args: str) -> None:
+ subprocess.run(args, check=True)
+
+
+def run_capture(*args: str, env: dict[str, str] | None = None) -> str:
+ proc = subprocess.run(
+ args, check=True, stdout=subprocess.PIPE, stderr=None, env=env
+ )
+ return proc.stdout.decode("utf-8").strip()
+
+
+def log(msg: str) -> None:
+ print(f"> {msg}")
+
+
+@click.command()
+@click.option(
+ "--rsync-target",
+ help="Where bootstrap zipballs and source tarball will be copied to. If given, this is passed directly to rsync so it can be a local folder, ssh path, etc. For production builds this should be a webroot directory that will be served at bootstrap-url",
+)
+@click.option(
+ "--bootstrap-url",
+ help="URL where bootstrap zip files are available. Defaults to folder part of public-url if not given.",
+)
+@click.option(
+ "--arches",
+ default="aarch64,x86_64",
+ help="Which architectures to build for, comma-separated.",
+)
+@click.argument("public-url")
+def go(
+ public_url: str,
+ rsync_target: str | None,
+ bootstrap_url: str | None,
+ arches: str,
+) -> None:
+ """
+ Builds bootstrap zip balls and source code tar ball (for usage as a channel or flake). If rsync_target is specified, uploads it to the directory specified in rsync_target. The contents of this directory should be reachable by the android device with public_url.
+
+ Examples:
+
+ \b
+ $ nix run .#deploy -- \\
+ 'https://example.com/bootstrap/source.tar.gz' \\
+ --rsync-target 'user@host:/path/to/bootstrap'
+
+ \b
+ $ nix run .#deploy -- \\
+ 'github:USER/nix-on-droid/BRANCH' \\
+ --rsync-target 'user@host:/path/to/bootstrap' \\
+ --bootstrap-url 'https://example.com/bootstrap/'
+
+ \b
+ $ nix run .#deploy -- \\
+ 'file:///data/local/tmp/n-o-d/archive.tar.gz'
+
+ ^ useful for testing. Note this is a path on the android device running the APK, not on the build machine
+ """
+ repo_dir = run_capture(GIT, "rev-parse", "--show-toplevel")
+ os.chdir(repo_dir)
+ source_file = "source.tar.gz"
+ if (m := re.search("^github:(.*)/(.*)/(.*)", public_url)) is not None:
+ channel_url = f"https://github.com/{m[1]}/{m[2]}/archive/{m[3]}.tar.gz"
+ if bootstrap_url is None:
+ err("--botstrap-url must be provided for github URLs")
+ elif re.search("^(https?|file)://", public_url):
+ channel_url = public_url
+ else:
+ err(f"unsupported url {public_url}")
+
+ # for CI and local testing
+ if (m := re.search("^file:///(.*)/archive.tar.gz$", public_url)) is not None:
+ flake_url = f"/{m[1]}/unpacked"
+ else:
+ flake_url = public_url
+ base_url = re.sub("/[^/]*$", "", public_url)
+ if bootstrap_url is None:
+ bootstrap_url = base_url
+
+ log(f"channel_url = {channel_url}")
+ log(f"flake_url = {flake_url}")
+ log(f"base_url = {base_url}")
+ log(f"bootstrap_url = {bootstrap_url}")
+
+ uploads: list[str] = []
+
+ for arch in arches.split(","):
+ log(f"building {arch} proot...")
+ proot = run_capture(
+ NIX, "build", "--no-link", "--print-out-paths", f".#prootTermux-{arch}"
+ )
+ proot_hash = run_capture(
+ NIX_HASH, "--type", "sha256", "--sri", f"{proot}/bin/proot-static"
+ )
+ attrs_file = Path(f"modules/environment/login/proot-attrs/{arch}.nix")
+ attrs_text = inspect.cleandoc(
+ f"""
+ # WARNING: This file is autogenerated by the deploy script. Any changes will be overridden
+ {{
+ url = "{bootstrap_url}/bootstrap-{arch}.zip";
+ hash = "{proot_hash}";
+ }}
+ """
+ )
+ # nixpkgs-fmt insists files must end with a newline
+ attrs_text = attrs_text + "\n"
+ write_attrs_file = True
+ if not attrs_file.exists():
+ log(f"warn: {attrs_file} not present; creating")
+ elif (old_attrs_text := attrs_file.read_text(encoding="utf-8")) != attrs_text:
+ log(f"updating contents of {attrs_file}")
+ print("<<<<<<")
+ print(old_attrs_text)
+ print("======")
+ print(attrs_text)
+ print(">>>>>>")
+ else:
+ write_attrs_file = False
+ log(f"no changes needed to {attrs_file}")
+
+ if write_attrs_file:
+ attrs_file.write_text(attrs_text, newline="\n", encoding="utf-8")
+ log(f"adding {attrs_file} to git index")
+ run(GIT, "add", str(attrs_file))
+
+ bootstrap_zip_store_path = run_capture(
+ NIX,
+ "build",
+ "--no-link",
+ "--print-out-paths",
+ "--impure",
+ f".#bootstrapZip-{arch}",
+ env={
+ "NIX_ON_DROID_CHANNEL_URL": channel_url,
+ "NIX_ON_DROID_FLAKE_URL": flake_url,
+ },
+ )
+ uploads.append(bootstrap_zip_store_path + f"/bootstrap-{arch}.zip")
+
+ log("creating tarball of current HEAD")
+ run(GIT, "archive", "--prefix", "nix-on-droid/", "--output", source_file, "HEAD")
+ uploads.append(source_file)
+
+ if rsync_target is not None:
+ log("uploading artifacts...")
+ run(RSYNC, "--progress", *uploads, rsync_target)
+ else:
+ log(f"Would have uploaded {uploads}")
+
+
+if __name__ == "__main__":
+ # pylint: disable = no-value-for-parameter
+ go()
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
deleted file mode 100755
index b54479fa..00000000
--- a/scripts/deploy.sh
+++ /dev/null
@@ -1,89 +0,0 @@
-#!@bash@/bin/bash
-set -euo pipefail
-
-PATH=@path@
-
-if [[ $# -ne 2 ]]; then
- cat >&2 <
-
-Builds bootstrap zip ball and source code tar ball (for usage as a channel or
-flake) and uploads it to the directory specified in . The
-contents of this directory should be reachable by the android device with
-.
-
-Examples:
-$ nix run .#deploy -- 'https://example.com/bootstrap/source.tar.gz' 'user@host:/path/to/bootstrap'
-$ nix run .#deploy -- 'github:USER/nix-on-droid/BRANCH' 'user@host:/path/to/bootstrap'
-
-EOF
- exit 1
-fi
-
-PUBLIC_URL="$1"
-RSYNC_TARGET="$2"
-: ${ARCHES:=aarch64 x86_64}
-
-# this allows to run this script from every place in this git repo
-REPO_DIR="$(git rev-parse --show-toplevel)"
-
-cd "$REPO_DIR"
-
-SOURCE_FILE="source.tar.gz"
-
-function log() {
- echo "> $*"
-}
-
-
-if [[ "$PUBLIC_URL" =~ ^github:(.*)/(.*)/(.*) ]]; then
- export NIX_ON_DROID_CHANNEL_URL="https://github.com/${BASH_REMATCH[1]}/${BASH_REMATCH[2]}/archive/${BASH_REMATCH[3]}.tar.gz"
-else
- [[ "$PUBLIC_URL" =~ ^https?:// ]] || \
- [[ "$PUBLIC_URL" =~ ^file:/// ]] || \
- { echo "unsupported url $PUBLIC_URL" >&2; exit 1; }
- export NIX_ON_DROID_CHANNEL_URL="$PUBLIC_URL"
-fi
-# special case for local / CI testing
-if [[ "$PUBLIC_URL" =~ ^file:///(.*)/archive.tar.gz ]]; then
- export NIX_ON_DROID_FLAKE_URL="/${BASH_REMATCH[1]}/unpacked"
-else
- export NIX_ON_DROID_FLAKE_URL="$PUBLIC_URL"
-fi
-log "NIX_ON_DROID_CHANNEL_URL=$NIX_ON_DROID_CHANNEL_URL"
-log "NIX_ON_DROID_FLAKE_URL=$NIX_ON_DROID_FLAKE_URL"
-
-
-PROOT_HASH_FILE="modules/environment/login/default.nix"
-UPLOADS=()
-for arch in $ARCHES; do
- log "building $arch proot..."
- proot="$(nix build --no-link --print-out-paths ".#prootTermux-${arch}")"
-
- if grep -q "$arch-linux = \"$proot\";" "$PROOT_HASH_FILE"; then
- log "keeping $arch proot path in $PROOT_HASH_FILE"
- elif grep -q "$arch-linux = \"/nix/store/" "$PROOT_HASH_FILE"; then
- log "patching $arch proot path in $PROOT_HASH_FILE..."
- grep "$arch-linux = \"/nix/store/" "$PROOT_HASH_FILE"
- sed -i "s|$arch-linux = \"/nix/store/.*\";|$arch-linux = \"$proot\";|" "$PROOT_HASH_FILE"
- log " ->"
- grep "$arch-linux = \"/nix/store/" "$PROOT_HASH_FILE"
- else
- log "no $arch proot hash found in $PROOT_HASH_FILE!"
- exit 1
- fi
-
- log "building $arch bootstrapZip..."
- BOOTSTRAP_ZIP="$(nix build --no-link --print-out-paths --impure ".#bootstrapZip-${arch}")"
- UPLOADS+=($BOOTSTRAP_ZIP/bootstrap-$arch.zip)
-done
-
-
-log "creating tar ball of current HEAD..."
-git archive --prefix nix-on-droid/ --output "$SOURCE_FILE" HEAD
-UPLOADS+=($SOURCE_FILE)
-
-
-log "uploading artifacts..."
-rsync --progress "${UPLOADS[@]}" "$RSYNC_TARGET"
diff --git a/scripts/local-deploy.sh b/scripts/local-deploy.sh
new file mode 100755
index 00000000..1c9bc3cd
--- /dev/null
+++ b/scripts/local-deploy.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -xevo pipefail
+tmp=`mktemp -d`/n-o-d
+mkdir $tmp
+cd "$(dirname "$0")"/..
+git -C . archive --format=tar.gz --prefix n-o-d/ HEAD > $tmp/archive.tar.gz
+ARCHES=x86_64 nix run .#deploy -- file:///data/local/tmp/n-o-d/archive.tar.gz --rsync-target $tmp/
+adb shell 'rm -rf /data/local/tmp/n-o-d'
+adb push $tmp /data/local/tmp/
+adb shell 'cd /data/local/tmp/n-o-d && tar xzof archive.tar.gz && mv n-o-d unpacked'
diff --git a/scripts/setup.py b/scripts/setup.py
new file mode 100644
index 00000000..11bf1097
--- /dev/null
+++ b/scripts/setup.py
@@ -0,0 +1,13 @@
+from setuptools import setup, find_packages
+
+setup(
+ name='nix-on-droid-deploy-script',
+ version='0.0',
+ packages=[],
+ py_modules=["deploy"],
+ entry_points={
+ 'console_scripts': [
+ 'deploy=deploy:go',
+ ],
+ },
+)