This commit is contained in:
Ana Hobden 2022-09-02 16:03:57 -07:00
commit bca0549c30
15 changed files with 3186 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

1914
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

26
Cargo.toml Normal file
View file

@ -0,0 +1,26 @@
[package]
name = "harmonic"
description = "A `nix` installer"
version = "0.0.1"
edition = "2021"
resolver = "2"
[dependencies]
async-compression = { version = "0.3.14", features = ["xz", "futures-io"] }
async-tar = "0.4.2"
async-trait = "0.1.57"
atty = "0.2.14"
clap = { version = "3.2.20", features = ["derive", "env"] }
color-eyre = "0.6.2"
crossterm = { version = "0.25.0", features = ["event-stream"] }
eyre = "0.6.8"
futures = "0.3.24"
owo-colors = { version = "3.5.0", features = [ "supports-colors" ] }
reqwest = { version = "0.11.11", features = ["native-tls-vendored", "stream"] }
target-lexicon = "0.12.4"
thiserror = "1.0.33"
tokio = { version = "1.21.0", features = ["time", "process", "fs", "tracing", "rt-multi-thread", "macros", "io-util"] }
tracing = { version = "0.1.36", features = [ "valuable" ] }
tracing-error = "0.2.0"
tracing-subscriber = { version = "0.3.15", features = [ "env-filter", "valuable" ] }
valuable = { version = "0.1.0", features = ["derive"] }

87
flake.lock Normal file
View file

@ -0,0 +1,87 @@
{
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1662123511,
"narHash": "sha256-s7iHG3VZjfpXMVK3cAfBPyXElW1fYlFiwvTW4ykNWEc=",
"owner": "nix-community",
"repo": "fenix",
"rev": "772db8b65364676ba4e89dcbcc29749716a8b300",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1662136632,
"narHash": "sha256-RwW/aA3ueQPsilQLi7NOfUnn8MgM6WMV+oRpW+nkDMI=",
"owner": "nix-community",
"repo": "naersk",
"rev": "8d2f4d00cb24cda8e5bdd802b827a0eaeff34eec",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1662025319,
"narHash": "sha256-ZJlBQ7jXynq4+Jg9+DgOe8FJG8sDIeFFYP3V3K98KUs=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "b82ccafb54163ab9024e893e578d840577785fea",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-22.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"fenix": "fenix",
"naersk": "naersk",
"nixpkgs": "nixpkgs"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1662066014,
"narHash": "sha256-DE4FsE2sxd9nFtG+8+lnv/IBbtf+6rAlKjIdfpWN488=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "93c52e41ec0d297c7512adf5936d8c464c820618",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

141
flake.nix Normal file
View file

@ -0,0 +1,141 @@
{
description = "riff";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05";
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
naersk = {
url = "github:nix-community/naersk";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{ self
, nixpkgs
, fenix
, naersk
, ...
} @ inputs:
let
nameValuePair = name: value: { inherit name value; };
genAttrs = names: f: builtins.listToAttrs (map (n: nameValuePair n (f n)) names);
allSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forAllSystems = f: genAttrs allSystems (system: f rec {
inherit system;
pkgs = import nixpkgs { inherit system; };
lib = pkgs.lib;
});
fenixToolchain = system: with fenix.packages.${system};
combine ([
stable.clippy
stable.rustc
stable.cargo
stable.rustfmt
stable.rust-src
] ++ nixpkgs.lib.optionals (system == "x86_64-linux") [
targets.x86_64-unknown-linux-musl.stable.rust-std
] ++ nixpkgs.lib.optionals (system == "aarch64-linux") [
targets.aarch64-unknown-linux-musl.stable.rust-std
]);
in
{
devShell = forAllSystems ({ system, pkgs, ... }:
let
toolchain = fenixToolchain system;
ci = import ./nix/ci.nix { inherit pkgs; };
eclint = import ./nix/eclint.nix { inherit pkgs; };
spellcheck = pkgs.writeScriptBin "spellcheck" ''
${pkgs.codespell}/bin/codespell \
--ignore-words-list crate,pullrequest,pullrequests,ser \
--skip target \
.
'';
in
pkgs.mkShell {
name = "nix-install-shell";
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
nativeBuildInputs = with pkgs; [
pkg-config
];
buildInputs = with pkgs; [
toolchain
openssl
rust-analyzer
# CI dependencies
jq
codespell
findutils # for xargs
git
nixpkgs-fmt
eclint
]
++ ci
++ lib.optionals (pkgs.stdenv.isDarwin) (with pkgs; [ libiconv darwin.apple_sdk.frameworks.Security ]);
});
packages = forAllSystems
({ system, pkgs, lib, ... }:
let
naerskLib = pkgs.callPackage naersk {
cargo = fenixToolchain system;
rustc = fenixToolchain system;
};
sharedAttrs = {
pname = "harmonic";
version = "0.0.0-unreleased";
src = self;
nativeBuildInputs = with pkgs; [
pkg-config
];
buildInputs = with pkgs; [
openssl
] ++ lib.optionals (pkgs.stdenv.isDarwin) (with pkgs.darwin.apple_sdk.frameworks; [
SystemConfiguration
]);
doCheck = true;
override = { preBuild ? "", ... }: {
preBuild = preBuild + ''
logRun "cargo clippy --all-targets --all-features -- -D warnings"
'';
};
};
in
{
harmonic = naerskLib.buildPackage
(sharedAttrs // { });
} // lib.optionalAttrs (system == "x86_64-linux") {
harmonicStatic = naerskLib.buildPackage
(sharedAttrs // {
CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl";
OPENSSL_LIB_DIR = "${pkgs.pkgsStatic.openssl.out}/lib";
OPENSSL_INCLUDE_DIR = "${pkgs.pkgsStatic.openssl.dev}";
});
} // lib.optionalAttrs (system == "aarch64-linux") {
harmonicStatic = naerskLib.buildPackage
(sharedAttrs // {
CARGO_BUILD_TARGET = "aarch64-unknown-linux-musl";
OPENSSL_LIB_DIR = "${pkgs.pkgsStatic.openssl.out}/lib";
OPENSSL_INCLUDE_DIR = "${pkgs.pkgsStatic.openssl.dev}";
});
});
defaultPackage = forAllSystems ({ system, ... }: self.packages.${system}.harmonic);
};
}

709
nix-install.sh Executable file
View file

@ -0,0 +1,709 @@
#!/bin/sh
# shellcheck shell=dash
# This script is based off https://github.com/rust-lang/rustup/blob/8f6b53628ad996ad86f9c6225fa500cddf860905/rustup-init.sh
# This is just a little script that can be downloaded from the internet to
# install `nix-install`. It just does platform detection, downloads the installer
# and runs it.
# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`
# extension. Note: Most shells limit `local` to 1 var per line, contra bash.
if [ "$KSH_VERSION" = 'Version JM 93t+ 2010-03-05' ]; then
# The version of ksh93 that ships with many illumos systems does not
# support the "local" extension. Print a message rather than fail in
# subtle ways later on:
echo 'rustup does not work with this ksh93 version; please try bash!' >&2
exit 1
fi
set -u
# If NIX_INSTALL_UPDATE_ROOT is unset or empty, default it.
NIX_INSTALL_UPDATE_ROOT="${NIX_INSTALL_UPDATE_ROOT:-https://nix-install.determinate.systems/nix-install}"
#XXX: If you change anything here, please make the same changes in setup_mode.rs
usage() {
cat 1>&2 <<EOF
rustup-init 1.25.1 (48d233f65 2022-07-12)
The installer for rustup
USAGE:
rustup-init [FLAGS] [OPTIONS]
FLAGS:
-v, --verbose Enable verbose output
-q, --quiet Disable progress output
-y Disable confirmation prompt.
--no-modify-path Don't configure the PATH environment variable
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--default-host <default-host> Choose a default host triple
--default-toolchain <default-toolchain> Choose a default toolchain to install
--default-toolchain none Do not install any toolchains
--profile [minimal|default|complete] Choose a profile
-c, --component <components>... Component name to also install
-t, --target <targets>... Target name to also install
EOF
}
main() {
downloader --check
need_cmd uname
need_cmd mktemp
need_cmd chmod
need_cmd mkdir
need_cmd rm
need_cmd rmdir
get_architecture || return 1
local _arch="$RETVAL"
assert_nz "$_arch" "arch"
local _ext=""
case "$_arch" in
*windows*)
_ext=".exe"
;;
esac
local _url="${NIX_INSTALL_UPDATE_ROOT}/dist/${_arch}/nix-install${_ext}"
local _dir
if ! _dir="$(ensure mktemp -d)"; then
# Because the previous command ran in a subshell, we must manually
# propagate exit status.
exit 1
fi
local _file="${_dir}/nix-install${_ext}"
local _ansi_escapes_are_valid=false
if [ -t 2 ]; then
if [ "${TERM+set}" = 'set' ]; then
case "$TERM" in
xterm*|rxvt*|urxvt*|linux*|vt*)
_ansi_escapes_are_valid=true
;;
esac
fi
fi
# check if we have to use /dev/tty to prompt the user
local need_tty=yes
for arg in "$@"; do
case "$arg" in
--help)
usage
exit 0
;;
*)
OPTIND=1
if [ "${arg%%--*}" = "" ]; then
# Long option (other than --help);
# don't attempt to interpret it.
continue
fi
while getopts :hy sub_arg "$arg"; do
case "$sub_arg" in
h)
usage
exit 0
;;
y)
# user wants to skip the prompt --
# we don't need /dev/tty
need_tty=no
;;
*)
;;
esac
done
;;
esac
done
if $_ansi_escapes_are_valid; then
printf "\33[1minfo:\33[0m downloading installer\n" 1>&2
else
printf '%s\n' 'info: downloading installer' 1>&2
fi
ensure mkdir -p "$_dir"
ensure downloader "$_url" "$_file" "$_arch"
ensure chmod u+x "$_file"
if [ ! -x "$_file" ]; then
printf '%s\n' "Cannot execute $_file (likely because of mounting /tmp as noexec)." 1>&2
printf '%s\n' "Please copy the file to a location where you can execute binaries and run ./nix-install${_ext}." 1>&2
exit 1
fi
if [ "$need_tty" = "yes" ] && [ ! -t 0 ]; then
# The installer is going to want to ask for confirmation by
# reading stdin. This script was piped into `sh` though and
# doesn't have stdin to pass to its children. Instead we're going
# to explicitly connect /dev/tty to the installer's stdin.
if [ ! -t 1 ]; then
err "Unable to run interactively. Run with -y to accept defaults, --help for additional options"
fi
ignore "$_file" "$@" < /dev/tty
else
ignore "$_file" "$@"
fi
local _retval=$?
ignore rm "$_file"
ignore rmdir "$_dir"
return "$_retval"
}
check_proc() {
# Check for /proc by looking for the /proc/self/exe link
# This is only run on Linux
if ! test -L /proc/self/exe ; then
err "fatal: Unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc."
fi
}
get_bitness() {
need_cmd head
# Architecture detection without dependencies beyond coreutils.
# ELF files start out "\x7fELF", and the following byte is
# 0x01 for 32-bit and
# 0x02 for 64-bit.
# The printf builtin on some shells like dash only supports octal
# escape sequences, so we use those.
local _current_exe_head
_current_exe_head=$(head -c 5 /proc/self/exe )
if [ "$_current_exe_head" = "$(printf '\177ELF\001')" ]; then
echo 32
elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then
echo 64
else
err "unknown platform bitness"
fi
}
is_host_amd64_elf() {
need_cmd head
need_cmd tail
# ELF e_machine detection without dependencies beyond coreutils.
# Two-byte field at offset 0x12 indicates the CPU,
# but we're interested in it being 0x3E to indicate amd64, or not that.
local _current_exe_machine
_current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1)
[ "$_current_exe_machine" = "$(printf '\076')" ]
}
get_endianness() {
local cputype=$1
local suffix_eb=$2
local suffix_el=$3
# detect endianness without od/hexdump, like get_bitness() does.
need_cmd head
need_cmd tail
local _current_exe_endianness
_current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)"
if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then
echo "${cputype}${suffix_el}"
elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then
echo "${cputype}${suffix_eb}"
else
err "unknown platform endianness"
fi
}
get_architecture() {
local _ostype _cputype _bitness _arch _clibtype
_ostype="$(uname -s)"
_cputype="$(uname -m)"
_clibtype="gnu"
if [ "$_ostype" = Linux ]; then
if [ "$(uname -o)" = Android ]; then
_ostype=Android
fi
if ldd --version 2>&1 | grep -q 'musl'; then
_clibtype="musl"
fi
fi
if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then
# Darwin `uname -m` lies
if sysctl hw.optional.x86_64 | grep -q ': 1'; then
_cputype=x86_64
fi
fi
if [ "$_ostype" = SunOS ]; then
# Both Solaris and illumos presently announce as "SunOS" in "uname -s"
# so use "uname -o" to disambiguate. We use the full path to the
# system uname in case the user has coreutils uname first in PATH,
# which has historically sometimes printed the wrong value here.
if [ "$(/usr/bin/uname -o)" = illumos ]; then
_ostype=illumos
fi
# illumos systems have multi-arch userlands, and "uname -m" reports the
# machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86
# systems. Check for the native (widest) instruction set on the
# running kernel:
if [ "$_cputype" = i86pc ]; then
_cputype="$(isainfo -n)"
fi
fi
case "$_ostype" in
Android)
_ostype=linux-android
;;
Linux)
check_proc
_ostype=unknown-linux-$_clibtype
_bitness=$(get_bitness)
;;
FreeBSD)
_ostype=unknown-freebsd
;;
NetBSD)
_ostype=unknown-netbsd
;;
DragonFly)
_ostype=unknown-dragonfly
;;
Darwin)
_ostype=apple-darwin
;;
illumos)
_ostype=unknown-illumos
;;
MINGW* | MSYS* | CYGWIN* | Windows_NT)
_ostype=pc-windows-gnu
;;
*)
err "unrecognized OS type: $_ostype"
;;
esac
case "$_cputype" in
i386 | i486 | i686 | i786 | x86)
_cputype=i686
;;
xscale | arm)
_cputype=arm
if [ "$_ostype" = "linux-android" ]; then
_ostype=linux-androideabi
fi
;;
armv6l)
_cputype=arm
if [ "$_ostype" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
fi
;;
armv7l | armv8l)
_cputype=armv7
if [ "$_ostype" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
fi
;;
aarch64 | arm64)
_cputype=aarch64
;;
x86_64 | x86-64 | x64 | amd64)
_cputype=x86_64
;;
mips)
_cputype=$(get_endianness mips '' el)
;;
mips64)
if [ "$_bitness" -eq 64 ]; then
# only n64 ABI is supported for now
_ostype="${_ostype}abi64"
_cputype=$(get_endianness mips64 '' el)
fi
;;
ppc)
_cputype=powerpc
;;
ppc64)
_cputype=powerpc64
;;
ppc64le)
_cputype=powerpc64le
;;
s390x)
_cputype=s390x
;;
riscv64)
_cputype=riscv64gc
;;
loongarch64)
_cputype=loongarch64
;;
*)
err "unknown CPU type: $_cputype"
esac
# Detect 64-bit linux with 32-bit userland
if [ "${_ostype}" = unknown-linux-gnu ] && [ "${_bitness}" -eq 32 ]; then
case $_cputype in
x86_64)
if [ -n "${RUSTUP_CPUTYPE:-}" ]; then
_cputype="$RUSTUP_CPUTYPE"
else {
# 32-bit executable for amd64 = x32
if is_host_amd64_elf; then {
echo "This host is running an x32 userland; as it stands, x32 support is poor," 1>&2
echo "and there isn't a native toolchain -- you will have to install" 1>&2
echo "multiarch compatibility with i686 and/or amd64, then select one" 1>&2
echo "by re-running this script with the RUSTUP_CPUTYPE environment variable" 1>&2
echo "set to i686 or x86_64, respectively." 1>&2
echo 1>&2
echo "You will be able to add an x32 target after installation by running" 1>&2
echo " rustup target add x86_64-unknown-linux-gnux32" 1>&2
exit 1
}; else
_cputype=i686
fi
}; fi
;;
mips64)
_cputype=$(get_endianness mips '' el)
;;
powerpc64)
_cputype=powerpc
;;
aarch64)
_cputype=armv7
if [ "$_ostype" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
fi
;;
riscv64gc)
err "riscv64 with 32-bit userland unsupported"
;;
esac
fi
# Detect armv7 but without the CPU features Rust needs in that build,
# and fall back to arm.
# See https://github.com/rust-lang/rustup.rs/issues/587.
if [ "$_ostype" = "unknown-linux-gnueabihf" ] && [ "$_cputype" = armv7 ]; then
if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then
# At least one processor does not have NEON.
_cputype=arm
fi
fi
_arch="${_cputype}-${_ostype}"
RETVAL="$_arch"
}
say() {
printf 'rustup: %s\n' "$1"
}
err() {
say "$1" >&2
exit 1
}
need_cmd() {
if ! check_cmd "$1"; then
err "need '$1' (command not found)"
fi
}
check_cmd() {
command -v "$1" > /dev/null 2>&1
}
assert_nz() {
if [ -z "$1" ]; then err "assert_nz $2"; fi
}
# Run a command that should never fail. If the command fails execution
# will immediately terminate with an error showing the failing
# command.
ensure() {
if ! "$@"; then err "command failed: $*"; fi
}
# This is just for indicating that commands' results are being
# intentionally ignored. Usually, because it's being executed
# as part of error handling.
ignore() {
"$@"
}
# This wraps curl or wget. Try curl first, if not installed,
# use wget instead.
downloader() {
local _dld
local _ciphersuites
local _err
local _status
local _retry
if check_cmd curl; then
_dld=curl
elif check_cmd wget; then
_dld=wget
else
_dld='curl or wget' # to be used in error message of need_cmd
fi
if [ "$1" = --check ]; then
need_cmd "$_dld"
elif [ "$_dld" = curl ]; then
check_curl_for_retry_support
_retry="$RETVAL"
get_ciphersuites_for_curl
_ciphersuites="$RETVAL"
if [ -n "$_ciphersuites" ]; then
_err=$(curl $_retry --proto '=https' --tlsv1.2 --ciphers "$_ciphersuites" --silent --show-error --fail --location "$1" --output "$2" 2>&1)
_status=$?
else
echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure"
if ! check_help_for "$3" curl --proto --tlsv1.2; then
echo "Warning: Not enforcing TLS v1.2, this is potentially less secure"
_err=$(curl $_retry --silent --show-error --fail --location "$1" --output "$2" 2>&1)
_status=$?
else
_err=$(curl $_retry --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2" 2>&1)
_status=$?
fi
fi
if [ -n "$_err" ]; then
echo "$_err" >&2
if echo "$_err" | grep -q 404$; then
err "installer for platform '$3' not found, this may be unsupported"
fi
fi
return $_status
elif [ "$_dld" = wget ]; then
if [ "$(wget -V 2>&1|head -2|tail -1|cut -f1 -d" ")" = "BusyBox" ]; then
echo "Warning: using the BusyBox version of wget. Not enforcing strong cipher suites for TLS or TLS v1.2, this is potentially less secure"
_err=$(wget "$1" -O "$2" 2>&1)
_status=$?
else
get_ciphersuites_for_wget
_ciphersuites="$RETVAL"
if [ -n "$_ciphersuites" ]; then
_err=$(wget --https-only --secure-protocol=TLSv1_2 --ciphers "$_ciphersuites" "$1" -O "$2" 2>&1)
_status=$?
else
echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure"
if ! check_help_for "$3" wget --https-only --secure-protocol; then
echo "Warning: Not enforcing TLS v1.2, this is potentially less secure"
_err=$(wget "$1" -O "$2" 2>&1)
_status=$?
else
_err=$(wget --https-only --secure-protocol=TLSv1_2 "$1" -O "$2" 2>&1)
_status=$?
fi
fi
fi
if [ -n "$_err" ]; then
echo "$_err" >&2
if echo "$_err" | grep -q ' 404 Not Found$'; then
err "installer for platform '$3' not found, this may be unsupported"
fi
fi
return $_status
else
err "Unknown downloader" # should not reach here
fi
}
check_help_for() {
local _arch
local _cmd
local _arg
_arch="$1"
shift
_cmd="$1"
shift
local _category
if "$_cmd" --help | grep -q 'For all options use the manual or "--help all".'; then
_category="all"
else
_category=""
fi
case "$_arch" in
*darwin*)
if check_cmd sw_vers; then
case $(sw_vers -productVersion) in
10.*)
# If we're running on macOS, older than 10.13, then we always
# fail to find these options to force fallback
if [ "$(sw_vers -productVersion | cut -d. -f2)" -lt 13 ]; then
# Older than 10.13
echo "Warning: Detected macOS platform older than 10.13"
return 1
fi
;;
11.*)
# We assume Big Sur will be OK for now
;;
*)
# Unknown product version, warn and continue
echo "Warning: Detected unknown macOS major version: $(sw_vers -productVersion)"
echo "Warning TLS capabilities detection may fail"
;;
esac
fi
;;
esac
for _arg in "$@"; do
if ! "$_cmd" --help $_category | grep -q -- "$_arg"; then
return 1
fi
done
true # not strictly needed
}
# Check if curl supports the --retry flag, then pass it to the curl invocation.
check_curl_for_retry_support() {
local _retry_supported=""
# "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
if check_help_for "notspecified" "curl" "--retry"; then
_retry_supported="--retry 3"
fi
RETVAL="$_retry_supported"
}
# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites
# if support by local tools is detected. Detection currently supports these curl backends:
# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty.
get_ciphersuites_for_curl() {
if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then
# user specified custom cipher suites, assume they know what they're doing
RETVAL="$RUSTUP_TLS_CIPHERSUITES"
return
fi
local _openssl_syntax="no"
local _gnutls_syntax="no"
local _backend_supported="yes"
if curl -V | grep -q ' OpenSSL/'; then
_openssl_syntax="yes"
elif curl -V | grep -iq ' LibreSSL/'; then
_openssl_syntax="yes"
elif curl -V | grep -iq ' BoringSSL/'; then
_openssl_syntax="yes"
elif curl -V | grep -iq ' GnuTLS/'; then
_gnutls_syntax="yes"
else
_backend_supported="no"
fi
local _args_supported="no"
if [ "$_backend_supported" = "yes" ]; then
# "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
if check_help_for "notspecified" "curl" "--tlsv1.2" "--ciphers" "--proto"; then
_args_supported="yes"
fi
fi
local _cs=""
if [ "$_args_supported" = "yes" ]; then
if [ "$_openssl_syntax" = "yes" ]; then
_cs=$(get_strong_ciphersuites_for "openssl")
elif [ "$_gnutls_syntax" = "yes" ]; then
_cs=$(get_strong_ciphersuites_for "gnutls")
fi
fi
RETVAL="$_cs"
}
# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites
# if support by local tools is detected. Detection currently supports these wget backends:
# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty.
get_ciphersuites_for_wget() {
if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then
# user specified custom cipher suites, assume they know what they're doing
RETVAL="$RUSTUP_TLS_CIPHERSUITES"
return
fi
local _cs=""
if wget -V | grep -q '\-DHAVE_LIBSSL'; then
# "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then
_cs=$(get_strong_ciphersuites_for "openssl")
fi
elif wget -V | grep -q '\-DHAVE_LIBGNUTLS'; then
# "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then
_cs=$(get_strong_ciphersuites_for "gnutls")
fi
fi
RETVAL="$_cs"
}
# Return strong TLS 1.2-1.3 cipher suites in OpenSSL or GnuTLS syntax. TLS 1.2
# excludes non-ECDHE and non-AEAD cipher suites. DHE is excluded due to bad
# DH params often found on servers (see RFC 7919). Sequence matches or is
# similar to Firefox 68 ESR with weak cipher suites disabled via about:config.
# $1 must be openssl or gnutls.
get_strong_ciphersuites_for() {
if [ "$1" = "openssl" ]; then
# OpenSSL is forgiving of unknown values, no problems with TLS 1.3 values on versions that don't support it yet.
echo "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
elif [ "$1" = "gnutls" ]; then
# GnuTLS isn't forgiving of unknown values, so this may require a GnuTLS version that supports TLS 1.3 even if wget doesn't.
# Begin with SECURE128 (and higher) then remove/add to build cipher suites. Produces same 9 cipher suites as OpenSSL but in slightly different order.
echo "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-DTLS-ALL:-CIPHER-ALL:-MAC-ALL:-KX-ALL:+AEAD:+ECDHE-ECDSA:+ECDHE-RSA:+AES-128-GCM:+CHACHA20-POLY1305:+AES-256-GCM"
fi
}
main "$@" || exit 1

45
nix/ci.nix Normal file
View file

@ -0,0 +1,45 @@
{ pkgs }:
let
inherit (pkgs) writeScriptBin;
in
[
# Format
(writeScriptBin "ci-check-rustfmt" "cargo fmt --check")
# Test
(writeScriptBin "ci-test-rust" "cargo test")
# Spelling
(writeScriptBin "ci-check-spelling" ''
codespell \
--ignore-words-list crate,pullrequest,pullrequests,ser \
--skip target \
.
'')
# NixFormatting
(writeScriptBin "ci-check-nixpkgs-fmt" ''
git ls-files '*.nix' | xargs | nixpkgs-fmt --check
'')
# RegistryFormatting
(writeScriptBin "ci-check-registry-format" ''
./registry/format.sh && git diff --exit-code
'')
# EditorConfig
(writeScriptBin "ci-check-editorconfig" ''
eclint
'')
(writeScriptBin "ci-all" ''
ci-check-rustfmt
ci-test-rust
ci-check-spelling
ci-check-nixpkgs-fmt
ci-check-registry-format
ci-check-editorconfig
'')
]

16
nix/eclint.nix Normal file
View file

@ -0,0 +1,16 @@
{ pkgs }:
pkgs.buildGoModule
rec {
pname = "eclint";
version = "0.3.3";
src = pkgs.fetchFromGitHub {
owner = "greut";
repo = pname;
rev = "v${version}";
sha256 = "sha256-9i2oAqFXflWGeBumE/5njaafBRhuRQSbA/ggUS72fwk=";
};
vendorSha256 = "sha256-XAyHy7UAb2LgwhsxaJgj0Qy6ukw9szeRC9JkRb+zc0Y=";
}

View file

@ -0,0 +1,71 @@
use atty::Stream;
use eyre::WrapErr;
use std::error::Error;
use tracing_error::ErrorLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
use valuable::Valuable;
#[derive(clap::Args, Debug, Valuable)]
pub(crate) struct Instrumentation {
/// Enable debug logs, -vv for trace
#[clap(
short = 'v',
long,
parse(from_occurrences),
global = true,
group = "verbosity"
)]
pub(crate) verbose: usize,
}
impl<'a> Instrumentation {
pub(crate) fn log_level(&self) -> String {
match self.verbose {
0 => "info",
1 => "debug",
_ => "trace",
}
.to_string()
}
pub(crate) fn setup<'b: 'a>(&'b self) -> eyre::Result<()> {
let fmt_layer = self.fmt_layer();
let filter_layer = self.filter_layer()?;
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default())
.try_init()?;
Ok(())
}
pub(crate) fn fmt_layer<S>(&self) -> impl tracing_subscriber::layer::Layer<S>
where
S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
{
tracing_subscriber::fmt::Layer::new()
.with_ansi(atty::is(Stream::Stderr))
.with_writer(std::io::stderr)
.pretty()
}
pub(crate) fn filter_layer(&self) -> eyre::Result<EnvFilter> {
let filter_layer = match EnvFilter::try_from_default_env() {
Ok(layer) => layer,
Err(e) => {
// Catch a parse error and report it, ignore a missing env.
if let Some(source) = e.source() {
match source.downcast_ref::<std::env::VarError>() {
Some(std::env::VarError::NotPresent) => (),
_ => return Err(e).wrap_err_with(|| "parsing RUST_LOG directives"),
}
}
EnvFilter::try_new(&format!("{}={}", env!("CARGO_PKG_NAME"), self.log_level()))?
}
};
Ok(filter_layer)
}
}

2
src/cli/arg/mod.rs Normal file
View file

@ -0,0 +1,2 @@
mod instrumentation;
pub(crate) use instrumentation::Instrumentation;

51
src/cli/mod.rs Normal file
View file

@ -0,0 +1,51 @@
pub(crate) mod arg;
use clap::Parser;
use harmonic::Harmonic;
use reqwest::Url;
use std::process::ExitCode;
#[async_trait::async_trait]
pub(crate) trait CommandExecute {
async fn execute(self) -> eyre::Result<ExitCode>;
}
#[derive(Debug, Parser)]
#[clap(version)]
pub(crate) struct HarmonicCli {
#[clap(flatten)]
pub(crate) instrumentation: arg::Instrumentation,
#[clap(long, default_value = "https://nixos.org/channels/nixpkgs-unstable")]
pub(crate) channels: Vec<Url>,
#[clap(long)]
pub(crate) no_modify_profile: bool,
#[clap(long, default_value = "32")]
pub(crate) daemon_user_count: usize,
}
#[async_trait::async_trait]
impl CommandExecute for HarmonicCli {
#[tracing::instrument(skip_all, fields(
channels = %self.channels.iter().map(ToString::to_string).collect::<Vec<_>>().join(", "),
daemon_user_count = %self.daemon_user_count,
no_modify_profile = %self.no_modify_profile,
))]
async fn execute(self) -> eyre::Result<ExitCode> {
let Self {
instrumentation: _,
daemon_user_count,
channels,
no_modify_profile,
} = self;
let mut harmonic = Harmonic::default();
harmonic.daemon_user_count(daemon_user_count);
harmonic.channels(channels);
harmonic.modify_profile(!no_modify_profile);
harmonic.install().await?;
Ok(ExitCode::SUCCESS)
}
}

7
src/error.rs Normal file
View file

@ -0,0 +1,7 @@
#[derive(thiserror::Error, Debug)]
pub enum HarmonicError {
#[error("Downloading Nix: {0}")]
DownloadingNix(#[from] reqwest::Error),
#[error("Unpacking Nix: {0}")]
UnpackingNix(#[from] std::io::Error),
}

92
src/lib.rs Normal file
View file

@ -0,0 +1,92 @@
mod error;
use error::HarmonicError;
use futures::stream::TryStreamExt;
use reqwest::Url;
// This uses a Rust builder pattern
#[derive(Debug)]
pub struct Harmonic {
daemon_user_count: usize,
channels: Vec<Url>,
modify_profile: bool,
}
impl Harmonic {
pub fn daemon_user_count(&mut self, count: usize) -> &mut Self {
self.daemon_user_count = count;
self
}
pub fn channels(&mut self, channels: impl IntoIterator<Item = Url>) -> &mut Self {
self.channels = channels.into_iter().collect();
self
}
pub fn modify_profile(&mut self, toggle: bool) -> &mut Self {
self.modify_profile = toggle;
self
}
}
#[cfg(target_os = "linux")]
impl Harmonic {
#[tracing::instrument(skip_all, fields(
channels = %self.channels.iter().map(ToString::to_string).collect::<Vec<_>>().join(", "),
daemon_user_count = %self.daemon_user_count,
modify_profile = %self.modify_profile
))]
pub async fn install(&self) -> Result<(), HarmonicError> {
self.download_nix().await?;
Ok(())
}
pub async fn download_nix(&self) -> Result<(), HarmonicError> {
// TODO(@hoverbear): architecture specific download
// TODO(@hoverbear): hash check
let res = reqwest::get(
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz",
)
.await
.map_err(HarmonicError::DownloadingNix)?;
let stream = res.bytes_stream();
let async_read = stream
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
.into_async_read();
let buffered = futures::io::BufReader::new(async_read);
let decoder = async_compression::futures::bufread::XzDecoder::new(buffered);
let archive = async_tar::Archive::new(decoder);
archive
.unpack("boop")
.await
.map_err(HarmonicError::UnpackingNix)?;
tracing::info!("Jobs done!!!");
Ok(())
}
}
#[cfg(target_os = "macos")]
impl Harmonic {
#[tracing::instrument]
pub async fn install(&self) -> Result<(), HarmonicError> {
// TODO(@hoverbear): Check MacOS version
todo!();
Ok(())
}
pub async fn download_nix(&self) -> Result<(), HarmonicError> {
Ok(())
}
}
impl Default for Harmonic {
fn default() -> Self {
Self {
channels: vec!["https://nixos.org/channels/nixpkgs-unstable"
.parse::<Url>()
.unwrap()],
daemon_user_count: 32,
modify_profile: true,
}
}
}

23
src/main.rs Normal file
View file

@ -0,0 +1,23 @@
pub(crate) mod cli;
use std::process::ExitCode;
use clap::Parser;
use cli::CommandExecute;
#[tokio::main]
async fn main() -> color_eyre::Result<ExitCode> {
color_eyre::config::HookBuilder::default()
.theme(if !atty::is(atty::Stream::Stderr) {
color_eyre::config::Theme::new()
} else {
color_eyre::config::Theme::dark()
})
.install()?;
let cli = cli::HarmonicCli::parse();
cli.instrumentation.setup()?;
cli.execute().await
}