├── .github └── workflows │ └── test.yml ├── LICENSE ├── README.md ├── app-shell.bash ├── app-shell.nix ├── flake.lock ├── flake.nix ├── package.nix └── tests ├── include-libs ├── test.c └── test.sh └── test.sh /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | workflow_dispatch: 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v4 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | 21 | - name: Install nix 22 | uses: cachix/install-nix-action@v31 23 | 24 | - name: Test 25 | run: cd tests && ./test.sh 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ivan Mincik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # App shell 2 | 3 | Create a temporary shell environment containing specified applications. 4 | 5 | 6 | ## Quick start 7 | 8 | * Install Nix 9 | [(learn more about this installer)](https://zero-to-nix.com/start/install) 10 | ```bash 11 | curl --proto '=https' --tlsv1.2 -sSf \ 12 | -L https://install.determinate.systems/nix \ 13 | | sh -s -- install 14 | ``` 15 | 16 | * Install app-shell 17 | 18 | * Using Flakes: 19 | 20 | ```bash 21 | nix profile install github:imincik/app-shell#app-shell 22 | ``` 23 | 24 | * Using [npins](https://github.com/andir/npins): 25 | 26 | 1. Add the repository as a dependency: 27 | 28 | ```bash 29 | npins add github -b master imincik app-shell 30 | ``` 31 | 32 | 1. Add the package: 33 | 34 | ```nix 35 | # configuration.nix 36 | let 37 | sources = import ./npins; 38 | app-shell = pkgs.callPackage "${sources.app-shell}/package.nix" { }; 39 | in 40 | { 41 | # add to system packages, using home-manager, ... 42 | environment.systemPackages = [ 43 | app-shell 44 | ]; 45 | } 46 | ``` 47 | 48 | * Create shell containing QGIS and GDAL apps 49 | 50 | ```bash 51 | app-shell --apps qgis,gdal 52 | ``` 53 | 54 | * Add Python with some modules as well 55 | 56 | ```bash 57 | app-shell --apps qgis,gdal,python3 --python-packages python3Packages.numpy,python3Packages.pyproj 58 | ``` 59 | 60 | * Finally, launch QGIS on start 61 | 62 | ```bash 63 | app-shell --apps qgis,gdal,python3 --python-packages python3Packages.numpy,python3Packages.pyproj -- qgis 64 | ``` 65 | 66 | * Or maybe, compile some software 67 | 68 | ```c 69 | // test.c 70 | 71 | #include 72 | #include 73 | 74 | int main() { 75 | CURL *curl; 76 | 77 | curl = curl_easy_init(); 78 | if (curl) { 79 | printf("It works !\n"); 80 | } else { 81 | printf("libcurl failed to initialize.\n"); 82 | } 83 | return 0; 84 | } 85 | ``` 86 | ```bash 87 | app-shell --apps gcc --libs curl --include-libs curl -- gcc test.c -lcurl -o test && ./test 88 | 89 | It works ! 90 | ``` 91 | 92 | 93 | ## Documentation 94 | 95 | * Run `app-shell --help` to learn more. 96 | 97 | 98 | ## app-shell vs. nix-shell 99 | 100 | [nix-shell](https://nix.dev/manual/nix/2.28/command-ref/nix-shell) 101 | was originally designed to provide a debugging environment for Nix 102 | derivations. It does a lot of magic and populates the environment with a lot 103 | of unnecessary content (packages, shell environment variables and functions). 104 | 105 | `app-shell` is much more simple, cleaner and lighter. It only fetches required 106 | packages to the `/nix/store` and propagates them via relevant 107 | environment variables. 108 | -------------------------------------------------------------------------------- /app-shell.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -Eeuo pipefail 4 | 5 | # shellcheck disable=SC2034 6 | script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P) 7 | 8 | usage() { 9 | cat <&2 -e "INFO: ${1-}" 57 | fi 58 | } 59 | 60 | msg() { 61 | echo >&2 -e "${1-}" 62 | } 63 | 64 | die() { 65 | local msg=$1 66 | local code=${2-1} # default exit status 1 67 | msg "$msg" 68 | exit "$code" 69 | } 70 | 71 | parse_params() { 72 | while :; do 73 | case "${1-}" in 74 | -h | --help) usage ;; 75 | -v | --verbose) verbose=1;; 76 | -n| --nixpkgs) 77 | nixpkgs="${2-}" 78 | shift 79 | ;; 80 | -a | --apps) 81 | apps="${2-}" 82 | shift 83 | ;; 84 | -p| --python-packages) 85 | python_packages="${2-}" 86 | shift 87 | ;; 88 | -l| --libs) 89 | libs="${2-}" 90 | shift 91 | ;; 92 | -L| --include-libs) 93 | include_libs="${2-}" 94 | shift 95 | ;; 96 | --) 97 | command=("${@:2}") 98 | ;; 99 | -?*) die "Unknown option: $1" ;; 100 | *) break ;; 101 | esac 102 | shift 103 | done 104 | 105 | return 0 106 | } 107 | parse_params "$@" 108 | 109 | # Create app shell command 110 | cmd="nix build --print-out-paths --no-link" 111 | 112 | if [ -n "${verbose-}" ]; then 113 | cmd+=" --print-build-logs --show-trace" 114 | fi 115 | 116 | cmd+=" --file ${app_shell_nix_dir}/app-shell.nix" 117 | 118 | if [ -n "${nixpkgs-}" ]; then 119 | cmd+=" --argstr nixpkgs $nixpkgs" 120 | fi 121 | 122 | if [ -n "${apps-}" ]; then 123 | cmd+=" --argstr apps $apps" 124 | fi 125 | 126 | if [ -n "${python_packages-}" ]; then 127 | cmd+=" --argstr pythonPackages $python_packages" 128 | fi 129 | 130 | if [ -n "${libs-}" ]; then 131 | cmd+=" --argstr libs $libs" 132 | fi 133 | 134 | if [ -n "${include_libs-}" ]; then 135 | cmd+=" --argstr includeLibs $include_libs" 136 | fi 137 | 138 | if [ -n "${command-}" ]; then 139 | c=(--argstr command \'"${command[*]}"\') 140 | cmd+=" " 141 | cmd+=${c[*]} 142 | fi 143 | 144 | # Activate shell 145 | verbose_msg "nix command: $cmd" 146 | activate=$(eval "$cmd") 147 | verbose_msg "activate script: $activate" 148 | $activate 149 | -------------------------------------------------------------------------------- /app-shell.nix: -------------------------------------------------------------------------------- 1 | # USAGE: 2 | # nix-build --argstr apps "APP,APP,..." && ./result 3 | # or 4 | # $(nix build --print-out-paths --file ./app-shell.nix --argstr apps "APP,APP,...") 5 | # or 6 | # nix build --file ./app-shell.nix --argstr apps "APP,APP,..." && ./result 7 | 8 | { nixpkgs ? "https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz" 9 | , apps ? null 10 | , pythonPackages ? null 11 | , libs ? null 12 | , includeLibs ? null 13 | , command ? null 14 | }: 15 | 16 | let 17 | nixpkgsFetched = if (builtins.substring 0 1 nixpkgs) == "/" then nixpkgs else (fetchTarball nixpkgs); 18 | pkgs = import nixpkgsFetched { }; 19 | 20 | inherit (pkgs) 21 | writeShellScript 22 | ; 23 | 24 | inherit (pkgs.lib) 25 | getAttrFromPath 26 | makeBinPath 27 | makeIncludePath 28 | makeLibraryPath 29 | makeSearchPathOutput 30 | splitString 31 | ; 32 | 33 | inherit (pkgs.python3Packages) 34 | makePythonPath # FIXME: doesn't work for other Python versions 35 | ; 36 | 37 | stringToPackage = str: 38 | getAttrFromPath (splitString "." str) pkgs; 39 | 40 | makePkgConfigPath = packages: 41 | makeSearchPathOutput "dev" "lib/pkgconfig" packages; 42 | 43 | appsList = 44 | if apps != null then 45 | map (x: stringToPackage x) (splitString "," apps) 46 | else [ ]; 47 | appsPath = 48 | if apps != null then 49 | "export PATH=${makeBinPath appsList}:$PATH" 50 | else 51 | ""; 52 | 53 | pythonPackagesList = 54 | if pythonPackages != null then 55 | map (x: stringToPackage x ) (splitString "," pythonPackages) 56 | else [ ]; 57 | pythonPath = 58 | if pythonPackages != null then 59 | "export PYTHONPATH=${makePythonPath pythonPackagesList}:$PYTHONPATH" 60 | else 61 | ""; 62 | 63 | libsList = 64 | if libs != null then 65 | map (x: stringToPackage x) (splitString "," libs) 66 | else [ ]; 67 | libraryPath = 68 | if libs != null then '' 69 | export LIBRARY_PATH=${makeLibraryPath libsList}:$LIBRARY_PATH 70 | export LD_LIBRARY_PATH=${makeLibraryPath libsList}:$LD_LIBRARY_PATH 71 | export CMAKE_LIBRARY_PATH=${makeLibraryPath libsList}:$CMAKE_LIBRARY_PATH 72 | export PKG_CONFIG_PATH=${makePkgConfigPath libsList}:$PKG_CONFIG_PATH 73 | '' 74 | else 75 | ""; 76 | 77 | includeLibsList = 78 | if includeLibs != null then 79 | map (x: stringToPackage x) (splitString "," includeLibs) 80 | else [ ]; 81 | includePath = 82 | if includeLibs != null then '' 83 | export C_INCLUDE_PATH=${makeIncludePath includeLibsList}:$C_INCLUDE_PATH 84 | export CMAKE_INCLUDE_PATH=${makeIncludePath includeLibsList}:$CMAKE_INCLUDE_PATH 85 | '' 86 | else 87 | ""; 88 | 89 | runCommand = if command != null then "-c '${command}'" else ""; 90 | 91 | activate = writeShellScript "app-shell-run" '' 92 | export PS1="\[\033[1m\][app-shell]\[\033[m\]\040\w >\040" 93 | ${appsPath} 94 | ${pythonPath} 95 | ${libraryPath} 96 | ${includePath} 97 | 98 | ${pkgs.lib.getExe pkgs.bash} --norc ${runCommand} 99 | ''; 100 | 101 | in 102 | activate 103 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1745794561, 6 | "narHash": "sha256-T36rUZHUART00h3dW4sV5tv4MrXKT7aWjNfHiZz7OHg=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "5461b7fa65f3ca74cef60be837fd559a8918eaa0", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "app-shell"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | }; 7 | 8 | outputs = { nixpkgs }: 9 | 10 | let 11 | # Flake system 12 | supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; 13 | forAllSystems = nixpkgs.lib.genAttrs supportedSystems; 14 | nixpkgsFor = forAllSystems (system: import nixpkgs { 15 | inherit system; 16 | }); 17 | 18 | in 19 | { 20 | packages = forAllSystems (system: 21 | let 22 | pkgs = nixpkgsFor.${system}; 23 | 24 | in 25 | rec { 26 | app-shell = pkgs.callPackage ./package.nix { }; 27 | default = app-shell; 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /package.nix: -------------------------------------------------------------------------------- 1 | { stdenv, bash, shellcheck, makeWrapper}: 2 | stdenv.mkDerivation { 3 | pname = "app-shell"; 4 | version = "0.1.0"; 5 | 6 | src = builtins.path { path = ./.; name = "app-shell"; }; 7 | 8 | unpackPhase = "true"; 9 | buildPhase = "true"; 10 | 11 | buildInputs = [ bash ]; 12 | checkInputs = [ shellcheck ]; 13 | nativeBuildInputs = [ makeWrapper ]; 14 | 15 | doCheck = true; 16 | checkPhase = ''shellcheck $src/app-shell.bash''; 17 | 18 | installPhase = '' 19 | mkdir -p $out/bin $out/share/nix 20 | 21 | cp -a $src/app-shell.nix $out/share/nix 22 | 23 | cp $src/app-shell.bash $out/bin/app-shell 24 | chmod +x $out/bin/app-shell 25 | 26 | wrapProgram $out/bin/app-shell \ 27 | --set APP_SHELL_NIX_DIR $out/share/nix 28 | ''; 29 | } 30 | -------------------------------------------------------------------------------- /tests/include-libs/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | CURL *curl; 6 | 7 | curl = curl_easy_init(); // Initialize libcurl 8 | if (curl) { 9 | printf("It works !\n"); 10 | // curl_easy_cleanup(curl); // Cleanup 11 | } else { 12 | printf("libcurl failed to initialize.\n"); 13 | } 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /tests/include-libs/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -Eeuo pipefail 4 | 5 | export APP_SHELL_NIX_DIR=../../ 6 | ../../app-shell.bash --apps gcc --libs curl --include-libs curl -- gcc test.c -lcurl -o test && ./test | grep "It works" 7 | -------------------------------------------------------------------------------- /tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -Eeuo pipefail 4 | 5 | export APP_SHELL_NIX_DIR=../ 6 | 7 | echo -e "\nTest --app option ..." 8 | ../app-shell.bash --apps gdal,pdal -- gdalinfo --version | grep "GDAL" 9 | 10 | echo -e "\nTest --python-packages option ..." 11 | ../app-shell.bash --apps python3 --python-packages python3Packages.numpy,python3Packages.pyproj -- 'python3 -c "import pyproj; print(pyproj)"' | grep "module 'pyproj'" 12 | 13 | echo -e "\nTest --libs option ..." 14 | ../app-shell.bash --libs stdenv.cc.cc,libz -- env | grep LD_LIBRARY_PATH | grep "gcc" 15 | ../app-shell.bash --apps pkg-config --libs curl -- pkg-config --libs libcurl | grep "\-lcurl" 16 | 17 | echo -e "\nTest --include-libs option ..." 18 | pushd include-libs 19 | ./test.sh 20 | popd 21 | 22 | echo -e "\nDONE: SUCCESS" 23 | --------------------------------------------------------------------------------