diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..8a3a2768 --- /dev/null +++ b/.envrc @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +use flake + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..6a4f9078 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,55 @@ +name: Build and test flake outputs +on: + push: + workflow_call: + inputs: + branch: + description: Branch name to build on + default: '' + required: false + type: string + +jobs: + nix: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + system: + - x86_64 + nix-command: + - flake check + - eval .#apps.$_system.nixos-install.program + - eval .#apps.$_system.setup.program + + include: + - nix-command: develop + system: x86_64 + + - nix-command: build --dry-run .#homeConfigurations."demo@non-nixos-vm".activationPackage + system: x86_64 + + - nix-command: build --dry-run .#nixosConfigurations.nixos-vm.config.system.build.toplevel + system: x86_64 + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ inputs.branch }} + + - name: Install nix + uses: cachix/install-nix-action@v16 + + - name: Build command (x86_64) + if: matrix.system == 'x86_64' + env: + _system: ${{ matrix.system }}-linux + run: | + nix \ + --verbose \ + --log-format bar-with-logs \ + --keep-going \ + --show-trace \ + ${{ matrix.nix-command }} + diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml new file mode 100644 index 00000000..78b1cf94 --- /dev/null +++ b/.github/workflows/update.yml @@ -0,0 +1,71 @@ +name: Update inputs +on: + schedule: + - cron: 0 0 * * * + +jobs: + update: + runs-on: ubuntu-latest + + outputs: + branch: ${{ steps.branch.outputs.branch }} + update_available: ${{ steps.commit.outputs.changes_detected }} + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Install nix + uses: cachix/install-nix-action@v16 + + - name: Update inputs + run: nix flake update + + - name: Set branch name output + id: branch + run: echo "::set-output name=branch::ci/automatic-update-$(date +%Y-%m-%d)" + + - name: Create branch locally + run: git switch -c ${{ steps.branch.outputs.branch }} + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v4 + id: commit + with: + commit_author: Christian Harke + commit_message: "flake.inputs: automatic update" + branch: ${{ steps.branch.outputs.branch }} + push_options: --force + + build: + uses: christianharke/nixcfg/.github/workflows/ci.yml@master + needs: update + if: needs.update.outputs.update_available == 'true' + with: + branch: ${{ needs.update.outputs.branch }} + + merge: + runs-on: ubuntu-latest + needs: + - update + - build + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Check if update branch is behind master + run: git diff origin/${{ needs.update.outputs.branch }}...origin/master --exit-code + + - name: Merge update into master + run: git merge origin/${{ needs.update.outputs.branch }} + + - name: Push master + run: git push origin master + + - name: Delete update branch + run: git push --delete origin ${{ needs.update.outputs.branch }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e909ffeb --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +result +.pre-commit-config.yaml + +*.tgz +*.tar +*.gz +*.zip diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..37615661 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Christian Harke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..fa972068 --- /dev/null +++ b/README.md @@ -0,0 +1,248 @@ +# :snowflake: Nix Configuration + +[![NixOS][nixos-badge]][nixos] +[![Build and Test][ci-badge]][ci] +[![Update][update-badge]][update] + +## Features + +* Automation scripts to setup a fresh [NixOS machine from scratch](flake/apps/nixos-install.sh) or + an [arbitrary preinstalled Linux machine](flake/apps/setup.sh) easily +* Secret management in [NixOS][nixos] ([agenix][agenix]) and [Home Manager][home-manager] + ([homeage][homeage]) with [age][age] +* Checks source code with [shellcheck][shellcheck] and [nixpkgs-fmt][nixpkgs-fmt] +* Daily automatic flake input updates committed to master when CI passes + +## Supported configurations + +* [NixOS][nixos]-managed + * `nixos-vm` +* [Home Manager][home-manager]-managed + * `non-nixos-vm` + +See [flake.nix](flake.nix) for more information like `system`. + +## Structure + +``` +📂 . +├──📂 flake -- internal flake library +├──🔒 flake.lock -- flake lockfile +├── ❄ flake.nix -- flake definition +├──📂 home -- Home Manager configuration +│ ├──📂 base -- basic configs +│ ├──📂 programs -- custom program modules +│ ├──📂 roles -- custom roles for bundling configsets +│ └──📂 users -- user-specific config +├──📂 hosts -- NixOS host configs +│ └──📂 nixos-vm +├──📂 nixos -- custom NixOS modules +│ ├──📂 base -- basic configs +│ │ └──📂 users -- user configs +│ ├──📂 containers -- custom container modules +│ ├──📂 programs -- custom program modules +│ └──📂 roles -- custom roles for bundling configsets +``` + +## Usage + +This flake can be either extended/modified directly or be used as a library. + +### Directly + +If you are not planning to use this flake for multiple Nix configurations, feel free to fork this +repo and add your host and user configurations into the folder structure and reference them in the +`flake.nix`: + +```nix +{ + description = "Custom config flake"; + + inputs = { + # ... + }; + + outputs = { self, nixpkgs, ... } @ inputs: + let + # ... + in + { + homeConfigurations = listToAttrs [ + (mkHome "x86_64-linux" "demo@non-nixos-host") + ]; + + nixosConfigurations = listToAttrs [ + (mkNixos "x86_64-linux" "nixos-host") + ]; + } + // eachSystem ({ mkGeneric, mkApp, mkCheck, getDevShell, mkDevShell, ... }: + # ... +} +``` + +### As a Library + +Create a new flake and prepare the folder structure as above, according to your needs. Then, add +this flake to the inputs and define your hosts and users in the `flake.nix`: + +```nix +{ + description = "Custom config flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; + nixcfg.url = "github:christianharke/nixcfg"; + }; + + outputs = { nixpkgs, nixcfg, ... } @ inputs: + let + nixcfgLib = nixcfg.lib."x86_64-linux" { + inherit (inputs.nixcfg) inputs; + rootPath = ./.; + }; + + inherit (nixpkgs.lib) listToAttrs; + inherit (nixcfgLib) mkHome mkNixos; + in + { + homeConfigurations = listToAttrs [ + (mkHome "x86_64-linux" "demo@non-nixos-host") + ]; + + nixosConfigurations = listToAttrs [ + (mkNixos "x86_64-linux" "nixos-host") + ]; + }; +} +``` + +## Initial Setup + +### NixOS + +#### NixOS installation + +To install NixOS from the ISO of [nixos.org][nixos] on a fresh machine, run: + +```bash +# If nix version < 2.4, run: +nix-shell -p nixFlakes + +sudo su # become root +mkdir -p ~/.config/nix +echo "experimental-features = nix-command flakes" > ~/.config/nix/nix.conf + +nix run github:christianharke/nixcfg#nixos-install -- +``` + +Where: + +* `` is your target machine's desired host name. Define it beforehand inside + `nixosConfigurations` of `flake.nix`. +* `` is your target drive to install NixOS on. Accepted values are `/dev/sda`, `/dev/nvme0n1` + or the like. + +This will completely *nuke* all the data on your `` device provided. Make sure to have a +working backup from your data of all drives connected to your target machine. + +**Warning:** Even if the script *should* ask you before committing any changes to your machine, +it can unexpectedly cause great harm! + +After rebooting proceed with the [next section](#nixos-config-setup). + +#### NixOS config setup + +```bash +$ sudo nix run github:christianharke/nixcfg#setup +``` + +### Non-NixOS + +#### Nix installation + +```bash +# install Nix +mkdir -p ~/.config/nix +echo "experimental-features = nix-command flakes" > ~/.config/nix/nix.conf +sh <(curl -L https://nixos.org/nix/install) --no-channel-add --no-modify-profile +. ~/.nix-profile/etc/profile.d/nix.sh +``` + +#### Nix config setup + +```bash +# Set up this Nix configuration +nix run github:christianharke/nixcfg#setup + +# set login shell +chsh -s /bin/zsh +``` + +## Secrets management + +### Make secrets available on new host + +The setup script will create the [age][age] keys needed and put them in the +[.agenix.toml](.agenix.toml) file, where it then needs to be assigned to the appropriate groups. +Push the updated `.agenix.toml` back to the git repository, pull it to an existing host and +re-key all the secrets with the command: + +```bash +$ # On NixOS: +$ sudo agenix -i /root/.age/key.txt -i ~/.age/key.txt -r -vv + +$ # On non-NixOS: +$ agenix -i ~/.age/key.txt -r -vv +``` + +After pushing/pulling the re-keyed secrets, just [run a rebuild](#rebuilding) of the new host's +config for decrypting them. + +### Updating secrets + +```bash +$ # First decrypt current secret +$ age --decrypt -i ~/.age/key.txt -o tmpfile < ./secrets/.age + +$ # Update `tmpfile` contents... +$ vim tmpfile + +$ # Re-encrypt the updated secret +$ age --encrypt -i ~/.age/key.txt -o ./secrets/.age < tmpfile +``` + +## Updating inputs + +This corresponds to the classical software/system update process known from other distros. + +```bash +$ nix flake update +``` + +To apply (install) the updated inputs on the system, just [run a rebuild](#rebuilding) of the +config. + +## Rebuilding + +```bash +$ # On NixOS +$ sudo nixos-rebuild switch + +$ # On non-NixOS +$ hm-switch +``` + +[ci]: https://github.com/christianharke/nixcfg/actions/workflows/ci.yml +[ci-badge]: https://github.com/christianharke/nixcfg/actions/workflows/ci.yml/badge.svg +[update]: https://github.com/christianharke/nixcfg/actions/workflows/update.yml +[update-badge]: https://github.com/christianharke/nixcfg/actions/workflows/update.yml/badge.svg + +[age]: https://age-encryption.org/ +[agenix]: https://github.com/ryantm/agenix +[home-manager]: https://github.com/nix-community/home-manager +[homeage]: https://github.com/jordanisaacs/homeage +[nixos]: https://nixos.org/ +[nixos-badge]: https://img.shields.io/badge/NixOS-22.05-blue.svg?logo=NixOS&logoColor=white +[nixpkgs-fmt]: https://github.com/nix-community/nixpkgs-fmt +[shellcheck]: https://github.com/koalaman/shellcheck + diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..2a84d629 --- /dev/null +++ b/flake.lock @@ -0,0 +1,1026 @@ +{ + "nodes": { + "agenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1664140963, + "narHash": "sha256-pFxDtOLduRFlol0Y4ShE+soRQX4kbhaCNBtDOvx7ykw=", + "owner": "ryantm", + "repo": "agenix", + "rev": "6acb1fe5f8597d5ce63fc82bc7fcac7774b1cdf0", + "type": "github" + }, + "original": { + "owner": "ryantm", + "repo": "agenix", + "type": "github" + } + }, + "agenix-cli": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1641404293, + "narHash": "sha256-0+QVY1sDhGF4hAN6m2FdKZgm9V1cuGGjY4aitRBnvKg=", + "owner": "cole-h", + "repo": "agenix-cli", + "rev": "77fccec4ed922a0f5f55ed964022b0db7d99f07d", + "type": "github" + }, + "original": { + "owner": "cole-h", + "repo": "agenix-cli", + "type": "github" + } + }, + "blame-line": { + "flake": false, + "locked": { + "lastModified": 1627412395, + "narHash": "sha256-jSK/Jo9bh/++DLOV9QhjwUxDtSBljSnY4gLPBfDJ2+o=", + "owner": "tveskag", + "repo": "nvim-blame-line", + "rev": "198d131a3ae8033a98759f43d024b80821974113", + "type": "github" + }, + "original": { + "owner": "tveskag", + "repo": "nvim-blame-line", + "type": "github" + } + }, + "calendar": { + "flake": false, + "locked": { + "lastModified": 1646418494, + "narHash": "sha256-ywu14EPoT5P2J0xUPpXqLOqQyvGEwlUN80P4i4b1Pkw=", + "owner": "hoaxdream", + "repo": "calendar-vim", + "rev": "0532d111f3e8639c8657b4479521bffb80093467", + "type": "github" + }, + "original": { + "owner": "hoaxdream", + "repo": "calendar-vim", + "type": "github" + } + }, + "completion": { + "flake": false, + "locked": { + "lastModified": 1634068787, + "narHash": "sha256-6um9dye0MLY8K8AAsDksCpy1KbMNSdMuj+zKteSAxR4=", + "owner": "nvim-lua", + "repo": "completion-nvim", + "rev": "87b0f86da3dffef63b42845049c648b5d90f1c4d", + "type": "github" + }, + "original": { + "owner": "nvim-lua", + "repo": "completion-nvim", + "type": "github" + } + }, + "cursorword": { + "flake": false, + "locked": { + "lastModified": 1592630023, + "narHash": "sha256-01XykhbFxf3mrT00r8kQ4WW0lG7g34IzxIP1JdCUtPQ=", + "owner": "itchyny", + "repo": "vim-cursorword", + "rev": "cc8114226ceefb5cafe1890e0900d3efb7dab1fd", + "type": "github" + }, + "original": { + "owner": "itchyny", + "repo": "vim-cursorword", + "type": "github" + } + }, + "dap": { + "flake": false, + "locked": { + "lastModified": 1646865198, + "narHash": "sha256-gO9UWFZx0q78eH2lqv1kRvNiKt+HfIcOnk0BfjADmxc=", + "owner": "mfussenegger", + "repo": "nvim-dap", + "rev": "bc7ac4864188079a6122032a75ea914c95461c1f", + "type": "github" + }, + "original": { + "owner": "mfussenegger", + "repo": "nvim-dap", + "type": "github" + } + }, + "dap-telescope": { + "flake": false, + "locked": { + "lastModified": 1616786019, + "narHash": "sha256-aGiBknPDjYL4Vlp0DKbJpsKiEaA5dzgg87FQvgThl7k=", + "owner": "nvim-telescope", + "repo": "telescope-dap.nvim", + "rev": "b4134fff5cbaf3b876e6011212ed60646e56f060", + "type": "github" + }, + "original": { + "owner": "nvim-telescope", + "repo": "telescope-dap.nvim", + "type": "github" + } + }, + "dap-virtual-text": { + "flake": false, + "locked": { + "lastModified": 1645301027, + "narHash": "sha256-mB7TV7/J4fNZ0Y8/3yq8jc6vAjyT/E/3VHxtti05PvU=", + "owner": "theHamsta", + "repo": "nvim-dap-virtual-text", + "rev": "7f82fec9a1c7fce292c9a9a7310e7d121607db93", + "type": "github" + }, + "original": { + "owner": "theHamsta", + "repo": "nvim-dap-virtual-text", + "type": "github" + } + }, + "dashboard-startify": { + "flake": false, + "locked": { + "lastModified": 1620487920, + "narHash": "sha256-//3bzFTe1WKqvQ3uYrDbk5Zu5BKq2hXQGeBhmhKIHvk=", + "owner": "mhinz", + "repo": "vim-startify", + "rev": "81e36c352a8deea54df5ec1e2f4348685569bed2", + "type": "github" + }, + "original": { + "owner": "mhinz", + "repo": "vim-startify", + "type": "github" + } + }, + "dashboard-startup": { + "flake": false, + "locked": { + "lastModified": 1644087093, + "narHash": "sha256-TBkTfIcBX8LdnX5Cww/y5i4VBYOwqdIipSWYWY0JLGo=", + "owner": "startup-nvim", + "repo": "startup.nvim", + "rev": "d64c0599aada57f2d992418709dc4490c17641c4", + "type": "github" + }, + "original": { + "owner": "startup-nvim", + "repo": "startup.nvim", + "type": "github" + } + }, + "devicons": { + "flake": false, + "locked": { + "lastModified": 1645288522, + "narHash": "sha256-0nuv0kBPGV9F6Nmt5GERUG9E8kkXFrbS+cJ1CVQAQ9g=", + "owner": "kyazdani42", + "repo": "nvim-web-devicons", + "rev": "4415d1aaa56f73b9c05795af84d625c610b05d3b", + "type": "github" + }, + "original": { + "owner": "kyazdani42", + "repo": "nvim-web-devicons", + "type": "github" + } + }, + "editorconfig": { + "flake": false, + "locked": { + "lastModified": 1642820146, + "narHash": "sha256-qFLXqhV+ZVhxoq+QoxWSDUrg8W/+qChJuCd4hSJqy28=", + "owner": "editorconfig", + "repo": "editorconfig-vim", + "rev": "a8e3e66deefb6122f476c27cee505aaae93f7109", + "type": "github" + }, + "original": { + "owner": "editorconfig", + "repo": "editorconfig-vim", + "type": "github" + } + }, + "flake-commons": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ], + "pre-commit-hooks": [ + "pre-commit-hooks" + ] + }, + "locked": { + "lastModified": 1645445009, + "narHash": "sha256-bpI0+XVo72C3MaFwBXQDPCj32RYEW1f6IIg+jYPcnxQ=", + "owner": "christianharke", + "repo": "flake-commons", + "rev": "9a6d35e5d978301ea8c6999981458d0d305ce66e", + "type": "github" + }, + "original": { + "owner": "christianharke", + "repo": "flake-commons", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1641205782, + "narHash": "sha256-4jY7RCWUoZ9cKD8co0/4tFARpWB+57+r1bLLvXNJliY=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "b7547d3eed6f32d06102ead8991ec52ab0a4f1a7", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1644229661, + "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "fugitive": { + "flake": false, + "locked": { + "lastModified": 1646841024, + "narHash": "sha256-0p0PloueKoysNvzunhMzhF1sDB8MsB4PjhW9iqmqmJw=", + "owner": "tpope", + "repo": "vim-fugitive", + "rev": "46652a304f0b89f36d70cee954d77e467ec0f6de", + "type": "github" + }, + "original": { + "owner": "tpope", + "repo": "vim-fugitive", + "type": "github" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1656169755, + "narHash": "sha256-Nlnm4jeQWEGjYrE6hxi/7HYHjBSZ/E0RtjCYifnNsWk=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "4a3d01fb53f52ac83194081272795aa4612c2381", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "release-22.05", + "repo": "home-manager", + "type": "github" + } + }, + "homeage": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1661959394, + "narHash": "sha256-nNGZ22LDwD8gEhNr2+uPVS7gA2wFcbYzivzR9GUSOQc=", + "owner": "jordanisaacs", + "repo": "homeage", + "rev": "dea8d375df3aacf3209a8b870425631e2dad0f18", + "type": "github" + }, + "original": { + "owner": "jordanisaacs", + "ref": "release", + "repo": "homeage", + "type": "github" + } + }, + "i3lock-pixeled": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ], + "pre-commit-hooks": [ + "pre-commit-hooks" + ] + }, + "locked": { + "lastModified": 1647291961, + "narHash": "sha256-VSqx+t36K5JH8l93i3iS12AT8eVPf1V3NZEiDmrvHf4=", + "owner": "christianharke", + "repo": "i3lock-pixeled", + "rev": "9cb79ec1e7e5d9860ed27ff89e8fd69dc9ce9777", + "type": "gitlab" + }, + "original": { + "owner": "christianharke", + "repo": "i3lock-pixeled", + "type": "gitlab" + } + }, + "indent-blankline": { + "flake": false, + "locked": { + "lastModified": 1646791761, + "narHash": "sha256-ZDNRTgk5Aqx42T7mBalDMNdkhn7IG5xAcqK+e+Gpjj0=", + "owner": "lukas-reineke", + "repo": "indent-blankline.nvim", + "rev": "9915d46ba9361784c70036bb7259c436249e5b0c", + "type": "github" + }, + "original": { + "owner": "lukas-reineke", + "repo": "indent-blankline.nvim", + "type": "github" + } + }, + "indentline": { + "flake": false, + "locked": { + "lastModified": 1644895654, + "narHash": "sha256-5Qoj6XFH7HPaqwjrahsYrHfvDjIWOKL6kDWHL+loBtI=", + "owner": "Yggdroot", + "repo": "indentLine", + "rev": "7753505f3c500ec88d11e9373d05250f49c1d900", + "type": "github" + }, + "original": { + "owner": "Yggdroot", + "repo": "indentLine", + "type": "github" + } + }, + "kmonad": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "dir": "nix", + "lastModified": 1655105761, + "narHash": "sha256-G1htMq3DlLPeNohkCauRUcOWrwV8DVB7OuTuZ8RFjDQ=", + "owner": "christianharke", + "repo": "kmonad", + "rev": "329cbd44062b38e432fcdfab52f0432a10d03782", + "type": "github" + }, + "original": { + "dir": "nix", + "owner": "christianharke", + "repo": "kmonad", + "type": "github" + } + }, + "lang-nix": { + "flake": false, + "locked": { + "lastModified": 1622231877, + "narHash": "sha256-wQzNXfE7JFalgiCQ2ksPAUyFKacmJV7mNKmKDe9jySI=", + "owner": "LnL7", + "repo": "vim-nix", + "rev": "63b47b39c8d481ebca3092822ca8972e08df769b", + "type": "github" + }, + "original": { + "owner": "LnL7", + "repo": "vim-nix", + "type": "github" + } + }, + "lightbulb": { + "flake": false, + "locked": { + "lastModified": 1645713830, + "narHash": "sha256-LxGA2LX9CASodJnZYXGZfGPOeW38MW7z54gYQSDmhRE=", + "owner": "kosayoda", + "repo": "nvim-lightbulb", + "rev": "29ca81408119ba809d1f922edc941868af97ee86", + "type": "github" + }, + "original": { + "owner": "kosayoda", + "repo": "nvim-lightbulb", + "type": "github" + } + }, + "lspconfig": { + "flake": false, + "locked": { + "lastModified": 1647203499, + "narHash": "sha256-VQTMCiHG8FtpGtflYx+/w3MlaeELG9BP1E7o5P4U7wc=", + "owner": "neovim", + "repo": "nvim-lspconfig", + "rev": "686a00f1defe0144851aa57000cfffe29dc6b219", + "type": "github" + }, + "original": { + "owner": "neovim", + "repo": "nvim-lspconfig", + "type": "github" + } + }, + "mach-nix": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ], + "pypi-deps-db": "pypi-deps-db" + }, + "locked": { + "lastModified": 1654084003, + "narHash": "sha256-j/XrVVistvM+Ua+0tNFvO5z83isL+LBgmBi9XppxuKA=", + "owner": "DavHau", + "repo": "mach-nix", + "rev": "7e14360bde07dcae32e5e24f366c83272f52923f", + "type": "github" + }, + "original": { + "owner": "DavHau", + "ref": "3.5.0", + "repo": "mach-nix", + "type": "github" + } + }, + "naersk": { + "inputs": { + "nixpkgs": [ + "neovim", + "rnix-lsp", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1639947939, + "narHash": "sha256-pGsM8haJadVP80GFq4xhnSpNitYNQpaXk4cnA796Cso=", + "owner": "nix-community", + "repo": "naersk", + "rev": "2fc8ce9d3c025d59fee349c1f80be9785049d653", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, + "neovim": { + "inputs": { + "blame-line": "blame-line", + "calendar": "calendar", + "completion": "completion", + "cursorword": "cursorword", + "dap": "dap", + "dap-telescope": "dap-telescope", + "dap-virtual-text": "dap-virtual-text", + "dashboard-startify": "dashboard-startify", + "dashboard-startup": "dashboard-startup", + "devicons": "devicons", + "editorconfig": "editorconfig", + "flake-utils": [ + "flake-utils" + ], + "fugitive": "fugitive", + "indent-blankline": "indent-blankline", + "indentline": "indentline", + "lang-nix": "lang-nix", + "lightbulb": "lightbulb", + "lspconfig": "lspconfig", + "neovim-nightly-overlay": "neovim-nightly-overlay", + "nixpkgs": [ + "nixpkgs" + ], + "plenary": "plenary", + "popup": "popup", + "pre-commit-hooks": [ + "pre-commit-hooks" + ], + "rnix-lsp": "rnix-lsp", + "statusline-lightline": "statusline-lightline", + "statusline-lightline-onedark": "statusline-lightline-onedark", + "statusline-lualine": "statusline-lualine", + "telescope": "telescope", + "test": "test", + "theme-gruvbox": "theme-gruvbox", + "theme-nord": "theme-nord", + "theme-onedark": "theme-onedark", + "tree": "tree", + "treesitter": "treesitter", + "treesitter-context": "treesitter-context", + "vimagit": "vimagit", + "vimwiki": "vimwiki", + "which-key": "which-key" + }, + "locked": { + "lastModified": 1650721412, + "narHash": "sha256-M40EfKExDhEIyUfB/w1UoPIOsq784i+iZiTmQmPMzxY=", + "owner": "christianharke", + "repo": "neovim-flake", + "rev": "c546ec1cb4e78634096fc9bd76da75fe330df451", + "type": "github" + }, + "original": { + "owner": "christianharke", + "repo": "neovim-flake", + "type": "github" + } + }, + "neovim-flake": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "neovim", + "neovim-nightly-overlay", + "nixpkgs" + ] + }, + "locked": { + "dir": "contrib", + "lastModified": 1647152497, + "narHash": "sha256-KNqAdyM0DQPSqb/low6Py9eUBQjfga0KAHAF6naMd7Y=", + "owner": "neovim", + "repo": "neovim", + "rev": "c9b94188d5f96349566372e8a0ce94e14fd6b549", + "type": "github" + }, + "original": { + "dir": "contrib", + "owner": "neovim", + "repo": "neovim", + "type": "github" + } + }, + "neovim-nightly-overlay": { + "inputs": { + "flake-compat": "flake-compat", + "neovim-flake": "neovim-flake", + "nixpkgs": [ + "neovim", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1647159229, + "narHash": "sha256-aGawpstqvThlV5OWqCAFIFBc8apYvMT/Qwig111coSo=", + "owner": "nix-community", + "repo": "neovim-nightly-overlay", + "rev": "4cf0e4a5738fb247f191a35c7c3900c4f06caca3", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "neovim-nightly-overlay", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1664594436, + "narHash": "sha256-YHowMADGzdi7fKnGlg47qe0PIljq+11VqLarmXDuKxQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "9cac45850280978a21a3eb67b15a18f34cbffa2d", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-22.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1664687381, + "narHash": "sha256-9czSuDzS+OGGwq2kC4KXBLXWfYaup+oLB+AA1Md25U4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "59d2991d4256cdca1c0cda45d876c80a0fe45c31", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nur": { + "locked": { + "lastModified": 1664718272, + "narHash": "sha256-BNnUks1BKzBr8HzoKBFQ8a7/avQhDkKCu0DSgW1ulcY=", + "owner": "nix-community", + "repo": "NUR", + "rev": "392b26288ad1cdebd03eac17adb70491f9f392d3", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "NUR", + "type": "github" + } + }, + "plenary": { + "flake": false, + "locked": { + "lastModified": 1646401868, + "narHash": "sha256-nxB5Gx0xI1oREcFHNKbCWdKGoqnZFqKgHE/EOU/Kgxk=", + "owner": "nvim-lua", + "repo": "plenary.nvim", + "rev": "14dfb4071022b22e08384ee125a5607464b6d397", + "type": "github" + }, + "original": { + "owner": "nvim-lua", + "repo": "plenary.nvim", + "type": "github" + } + }, + "popup": { + "flake": false, + "locked": { + "lastModified": 1637254091, + "narHash": "sha256-dNWz/xovUg55fDZUpVs/2kLphk3lqQyvPtc9ATwbeSQ=", + "owner": "nvim-lua", + "repo": "popup.nvim", + "rev": "b7404d35d5d3548a82149238289fa71f7f6de4ac", + "type": "github" + }, + "original": { + "owner": "nvim-lua", + "repo": "popup.nvim", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1645781104, + "narHash": "sha256-d5iDimTPC4r1hKFHAguCnkY9gPbieAIkApLUuYA+Xb4=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "6799201bec19b753a4ac305a53d34371e497941e", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "6799201bec19b753a4ac305a53d34371e497941e", + "type": "github" + } + }, + "pypi-deps-db": { + "flake": false, + "locked": { + "lastModified": 1643877077, + "narHash": "sha256-jv8pIvRFTP919GybOxXE5TfOkrjTbdo9QiCO1TD3ZaY=", + "owner": "DavHau", + "repo": "pypi-deps-db", + "rev": "da53397f0b782b0b18deb72ef8e0fb5aa7c98aa3", + "type": "github" + }, + "original": { + "owner": "DavHau", + "repo": "pypi-deps-db", + "type": "github" + } + }, + "rnix-lsp": { + "inputs": { + "naersk": "naersk", + "nixpkgs": [ + "neovim", + "nixpkgs" + ], + "utils": [ + "neovim", + "flake-utils" + ] + }, + "locked": { + "lastModified": 1647240246, + "narHash": "sha256-/MLdBWfFUN1C1eNVBYfaVAIcDiZKXpWEbzBC2pqVXj0=", + "owner": "nix-community", + "repo": "rnix-lsp", + "rev": "4d1024ccfe1bc569811769d1ef52a2fc6c1d482d", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "rnix-lsp", + "type": "github" + } + }, + "root": { + "inputs": { + "agenix": "agenix", + "agenix-cli": "agenix-cli", + "flake-commons": "flake-commons", + "flake-utils": "flake-utils", + "home-manager": "home-manager", + "homeage": "homeage", + "i3lock-pixeled": "i3lock-pixeled", + "kmonad": "kmonad", + "mach-nix": "mach-nix", + "neovim": "neovim", + "nixpkgs": "nixpkgs", + "nixpkgs-unstable": "nixpkgs-unstable", + "nur": "nur", + "pre-commit-hooks": "pre-commit-hooks" + } + }, + "statusline-lightline": { + "flake": false, + "locked": { + "lastModified": 1637503010, + "narHash": "sha256-OymzH/JHQZ+oevLr/eQS3niZPID9SvR4ZSVNw5nLfFg=", + "owner": "itchyny", + "repo": "lightline.vim", + "rev": "a29b8331e1bb36b09bafa30c3aa77e89cdd832b2", + "type": "github" + }, + "original": { + "owner": "itchyny", + "repo": "lightline.vim", + "type": "github" + } + }, + "statusline-lightline-onedark": { + "flake": false, + "locked": { + "lastModified": 1582496289, + "narHash": "sha256-HuIUA8ngQvMwgXUfv7KqR4EsuOpuwHGW894vKs5R9ik=", + "owner": "NovaDev94", + "repo": "lightline-onedark", + "rev": "34d93c53493b7b70da86a142c9907223f054bf1c", + "type": "github" + }, + "original": { + "owner": "NovaDev94", + "repo": "lightline-onedark", + "type": "github" + } + }, + "statusline-lualine": { + "flake": false, + "locked": { + "lastModified": 1646668162, + "narHash": "sha256-F7R5V70ta/9s9qn+QkZtk5AFb/PfKtuOqPHP/Y6/WHI=", + "owner": "nvim-lualine", + "repo": "lualine.nvim", + "rev": "88a44ade818f9ee7ba730aa4096250e22b243808", + "type": "github" + }, + "original": { + "owner": "nvim-lualine", + "repo": "lualine.nvim", + "type": "github" + } + }, + "telescope": { + "flake": false, + "locked": { + "lastModified": 1647199274, + "narHash": "sha256-tr/+L3hnOvlQ5PZ5EFzWjRViaxLmOlwLXqJLqR4fJKg=", + "owner": "nvim-telescope", + "repo": "telescope.nvim", + "rev": "df303e12e09b4e6e1fd1e1e184d26827105b318d", + "type": "github" + }, + "original": { + "owner": "nvim-telescope", + "repo": "telescope.nvim", + "type": "github" + } + }, + "test": { + "flake": false, + "locked": { + "lastModified": 1646753517, + "narHash": "sha256-CVSTy/FeBgyzRK8NWDMiIynz7DRlFenruiCOjowYnMI=", + "owner": "vim-test", + "repo": "vim-test", + "rev": "16a3b6da1bab42473d42d7e02d89d549d7a5e138", + "type": "github" + }, + "original": { + "owner": "vim-test", + "repo": "vim-test", + "type": "github" + } + }, + "theme-gruvbox": { + "flake": false, + "locked": { + "lastModified": 1593780126, + "narHash": "sha256-H8WkOC9NMPXFznv2+dwOj3syNd2sLqszmoMqST/W5hQ=", + "owner": "morhetz", + "repo": "gruvbox", + "rev": "bf2885a95efdad7bd5e4794dd0213917770d79b7", + "type": "github" + }, + "original": { + "owner": "morhetz", + "repo": "gruvbox", + "type": "github" + } + }, + "theme-nord": { + "flake": false, + "locked": { + "lastModified": 1645276921, + "narHash": "sha256-G/qXfBawNP+pfOh3UUTMDRXBCdJij7R9jVfQ8Bsfojg=", + "owner": "arcticicestudio", + "repo": "nord-vim", + "rev": "a8256787edbd4569a7f92e4e163308ab8256a6e5", + "type": "github" + }, + "original": { + "owner": "arcticicestudio", + "repo": "nord-vim", + "type": "github" + } + }, + "theme-onedark": { + "flake": false, + "locked": { + "lastModified": 1646651338, + "narHash": "sha256-QcC8SRaCYDRt2r0iBFE13e6Z7A8ToK3F7uzi2tqoQbg=", + "owner": "olimorris", + "repo": "onedarkpro.nvim", + "rev": "f0562da89fecbce7ef335875c70baedfdd34c7d8", + "type": "github" + }, + "original": { + "owner": "olimorris", + "repo": "onedarkpro.nvim", + "type": "github" + } + }, + "tree": { + "flake": false, + "locked": { + "lastModified": 1647093634, + "narHash": "sha256-TPnUUOk4Y6fIY5igMIvRS6tUMKCzc+BLPpPXJP1N+pE=", + "owner": "kyazdani42", + "repo": "nvim-tree.lua", + "rev": "d93a93c9c1a12748f09c5e01867908e9985cfccc", + "type": "github" + }, + "original": { + "owner": "kyazdani42", + "repo": "nvim-tree.lua", + "type": "github" + } + }, + "treesitter": { + "flake": false, + "locked": { + "lastModified": 1647241762, + "narHash": "sha256-ZRaT6pKKulrh5WHUD5GBeldRHA6SYZ+i+giKwiZwxyc=", + "owner": "nvim-treesitter", + "repo": "nvim-treesitter", + "rev": "c6d46504ba72a25b41a74397b1728a3677f8bb89", + "type": "github" + }, + "original": { + "owner": "nvim-treesitter", + "repo": "nvim-treesitter", + "type": "github" + } + }, + "treesitter-context": { + "flake": false, + "locked": { + "lastModified": 1642022511, + "narHash": "sha256-OHDdl5PdTQ/M2sOrZ5C4JUG9FcAy0cTtWu56qyPbe4Q=", + "owner": "romgrk", + "repo": "nvim-treesitter-context", + "rev": "b7d7aba81683c1cd76141e090ff335bb55332cba", + "type": "github" + }, + "original": { + "owner": "romgrk", + "repo": "nvim-treesitter-context", + "type": "github" + } + }, + "vimagit": { + "flake": false, + "locked": { + "lastModified": 1628588042, + "narHash": "sha256-onD/M0MWOzabIm3qboKZYu+CC8nD3ElMVl22pJPbP/o=", + "owner": "jreybert", + "repo": "vimagit", + "rev": "fb71060049f829e48fc392e0be43d1040c271204", + "type": "github" + }, + "original": { + "owner": "jreybert", + "repo": "vimagit", + "type": "github" + } + }, + "vimwiki": { + "flake": false, + "locked": { + "lastModified": 1646877853, + "narHash": "sha256-GxAuvvqfmfQRNkNrHyUVclq11es8LtIGlNzhmdYacbM=", + "owner": "vimwiki", + "repo": "vimwiki", + "rev": "63af6e72dd3fa840bffb3ebcb8c96970c02e0913", + "type": "github" + }, + "original": { + "owner": "vimwiki", + "repo": "vimwiki", + "type": "github" + } + }, + "which-key": { + "flake": false, + "locked": { + "lastModified": 1642619077, + "narHash": "sha256-bz41c/zenRVVIkwtHHIi8wF/LZn2JEkFc6tkPv/BYY4=", + "owner": "folke", + "repo": "which-key.nvim", + "rev": "28d2bd129575b5e9ebddd88506601290bb2bb221", + "type": "github" + }, + "original": { + "owner": "folke", + "repo": "which-key.nvim", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..fd3ce2ce --- /dev/null +++ b/flake.nix @@ -0,0 +1,179 @@ +{ + description = "NixOS & Home-Manager Configuration"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; + nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; + nur.url = "github:nix-community/NUR"; + + flake-utils.url = "github:numtide/flake-utils"; + + home-manager = { + url = "github:nix-community/home-manager/release-22.05"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + pre-commit-hooks = { + url = "github:cachix/pre-commit-hooks.nix?rev=6799201bec19b753a4ac305a53d34371e497941e"; + inputs = { + flake-utils.follows = "flake-utils"; + nixpkgs.follows = "nixpkgs"; + }; + }; + + # Modules + + flake-commons = { + url = "github:christianharke/flake-commons"; + inputs = { + flake-utils.follows = "flake-utils"; + nixpkgs.follows = "nixpkgs"; + pre-commit-hooks.follows = "pre-commit-hooks"; + }; + }; + + agenix = { + url = "github:ryantm/agenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + agenix-cli = { + url = "github:cole-h/agenix-cli"; + inputs = { + flake-utils.follows = "flake-utils"; + nixpkgs.follows = "nixpkgs"; + }; + }; + + homeage = { + url = "github:jordanisaacs/homeage/release"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + i3lock-pixeled = { + url = "gitlab:christianharke/i3lock-pixeled"; + inputs = { + flake-utils.follows = "flake-utils"; + nixpkgs.follows = "nixpkgs"; + pre-commit-hooks.follows = "pre-commit-hooks"; + }; + }; + + kmonad = { + url = "github:christianharke/kmonad?dir=nix"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; + + mach-nix = { + url = "github:DavHau/mach-nix/3.5.0"; + inputs = { + flake-utils.follows = "flake-utils"; + nixpkgs.follows = "nixpkgs"; + }; + }; + + neovim = { + url = "github:christianharke/neovim-flake"; + inputs = { + flake-utils.follows = "flake-utils"; + nixpkgs.follows = "nixpkgs"; + pre-commit-hooks.follows = "pre-commit-hooks"; + }; + }; + }; + + outputs = { self, nixpkgs, ... } @ inputs: + let + flakeLib = import ./flake { + inherit inputs; + rootPath = ./.; + }; + + inherit (nixpkgs.lib) listToAttrs; + inherit (flakeLib) eachSystem mkHome mkNixos; + in + { + lib = import ./flake; + + homeConfigurations = listToAttrs [ + (mkHome "x86_64-linux" "demo@non-nixos-vm") + ]; + + nixosConfigurations = listToAttrs [ + (mkNixos "x86_64-linux" "nixos-vm") + ]; + } + // eachSystem ({ mkGeneric, mkApp, mkCheck, getDevShell, mkDevShell, ... }: + let + mkShellCheck = pkgs: '' + shopt -s globstar + echo 'Running shellcheck...' + ${pkgs.shellcheck}/bin/shellcheck --check-sourced --enable all --external-sources --shell bash ${./.}/**/*.sh + ''; + in + { + apps = listToAttrs [ + (mkApp "setup" { + file = "setup.sh"; + envs = { + _doNotClearPath = true; + flakePath = "/home/\$(logname)/.nix-config"; + }; + path = pkgs: with pkgs; [ + git + hostname + jq + ]; + }) + + (mkApp "nixos-install" { + file = "nixos-install.sh"; + envs = { + _doNotClearPath = true; + }; + path = pkgs: with pkgs; [ + git + hostname + util-linux + parted + cryptsetup + lvm2 + ]; + }) + ]; + + checks = listToAttrs [ + (mkGeneric "pre-commit-check" (system: inputs.pre-commit-hooks.lib."${system}".run { + src = ./.; + hooks = { + nixpkgs-fmt.enable = true; + shellcheck.enable = true; + statix.enable = true; + }; + })) + + (mkCheck "shellcheck" { + script = mkShellCheck; + }) + + (mkCheck "nixpkgs-fmt" { + script = pkgs: '' + shopt -s globstar + ${pkgs.nixpkgs-fmt}/bin/nixpkgs-fmt --check ${./.}/**/*.nix + ''; + }) + ]; + + devShell = getDevShell "nixcfg"; + + devShells = listToAttrs [ + (mkDevShell "nixcfg" { + checksShellHook = system: self.checks."${system}".pre-commit-check.shellHook; + packages = pkgs: with pkgs; [ nixpkgs-fmt shellcheck statix ]; + customShellHook = mkShellCheck; + }) + ]; + }); +} diff --git a/flake/apps/nixos-install.sh b/flake/apps/nixos-install.sh new file mode 100644 index 00000000..e38209b7 --- /dev/null +++ b/flake/apps/nixos-install.sh @@ -0,0 +1,161 @@ +# shellcheck disable=SC1091 +source @bashLib@ + +# Source: https://qfpl.io/posts/installing-nixos/ + +### Gather system info + +readonly HOSTNAME="${1}" +readonly DISK="${2}" + +# Validate arguments + +test "${HOSTNAME}" || { + # shellcheck disable=SC2016 + echo '$HOSTNAME is not given!' + exit 1 +} + +NUM_SUPPORTED_DISKS=$(echo "${DISK}" | grep -P "^/dev/(sd[a-z]|nvme[0-9]n[1-9])$" -c) +readonly NUM_SUPPORTED_DISKS + +[[ ${NUM_SUPPORTED_DISKS} -gt 0 ]] || { + # shellcheck disable=SC2016 + echo '$DISK is not of format "/dev/sda" or "/dev/nvme0n1"!' + exit 1 +} + +NUM_NVME_DISKS=$(echo "${DISK}" | grep "^/dev/nvme" -c) +readonly NUM_NVME_DISKS + +is_nvme_disk() { + [[ ${NUM_NVME_DISKS} -gt 0 ]] +} + +get_partition() { + if is_nvme_disk; then + echo "${DISK}p${1}" + else + echo "${DISK}${1}" + fi +} + +BOOT_PARTITION="$(get_partition 1)" +readonly BOOT_PARTITION +LVM_PARTITION="$(get_partition 2)" +readonly LVM_PARTITION + +get_ram_size() { + local mem_summary + mem_summary="$(lsmem --summary=only)" + local mem_summary_online + mem_summary_online="$(echo "${mem_summary}" | grep "Total online memory:")" + local mem_online_size + mem_online_size="$(echo "${mem_summary_online}" | grep -Po "[0-9]+[kKmMgGtTpPeE]")" + echo "${mem_online_size}" +} + +RAM_SIZE="$(get_ram_size)" +readonly RAM_SIZE + + +### Declare functions + +readonly LVM_PV="nixos-enc" +readonly LVM_VG="nixos-vg" +readonly LVM_LV_ROOT="/dev/${LVM_VG}/root" +readonly LVM_LV_SWAP="/dev/${LVM_VG}/swap" + +partition() { + _log "[partition] Deleting partitions..." + dd if=/dev/zero of="${DISK}" bs=512 count=1 conv=notrunc status=progress + + _log "[partition] Creating partition table..." + parted "${DISK}" mklabel gpt + parted "${DISK}" mkpart "boot" fat32 0% 1GiB + parted "${DISK}" set 1 esp on + parted "${DISK}" mkpart "root" ext4 1GiB 100% + + _log "[partition] Result of partitioning:" + fdisk "${DISK}" -l +} + +create_volumes() { + _log "[create_volumes] Encrypting LVM partition..." + cryptsetup luksFormat "${LVM_PARTITION}" + cryptsetup luksOpen "${LVM_PARTITION}" "${LVM_PV}" + + _log "[create_volumes] Creating LVM volumes..." + pvcreate "/dev/mapper/${LVM_PV}" + vgcreate "${LVM_VG}" "/dev/mapper/${LVM_PV}" + lvcreate -L "${RAM_SIZE}" -n swap "${LVM_VG}" + lvcreate -l 100%FREE -n root "${LVM_VG}" +} + +create_filesystems() { + # TODO: Switch to btrfs (https://github.com/wiltaylor/dotfiles/blob/master/tools/makefs-nixos) + _log "[create_filesystems] Creating filesystems..." + mkfs.vfat -n boot "${BOOT_PARTITION}" + mkfs.ext4 -L nixos "${LVM_LV_ROOT}" + mkswap -L swap "${LVM_LV_SWAP}" + + _log "[create_filesystems] Result of filesystems creation:" + lsblk -f "${DISK}" +} + +decrypt_lvm() { + _log "[decrypt_lvm] Decrypting volumes..." + cryptsetup luksOpen "${LVM_PARTITION}" "${LVM_PV}" + lvscan + vgchange -ay + + _log "[decrypt_lvm] Volumes decrypted:" + lsblk -f "${DISK}" +} + +install() { + local mount_root="/mnt" + local mount_boot="${mount_root}/boot" + + _log "[install] Enabling swap..." + local swap_list + swap_list="$(swapon --noheadings)" + local num_swap + num_swap=$(echo "${swap_list}" | wc -l) + if [[ ${num_swap} -lt 1 ]]; then + swapon -v "${LVM_LV_SWAP}" + fi + + _log "[install] Mounting volumes..." + mount "${LVM_LV_ROOT}" "${mount_root}" + mkdir -p "${mount_boot}" + mount "${BOOT_PARTITION}" "${mount_boot}" + + _log "[install] Installing NixOS..." + nixos-install --root "${mount_root}" --flake "github:christianharke/nixcfg#${HOSTNAME}" --impure + _log "[install] Installing NixOS... finished!" + + _log "[install] Installation finished, please reboot and remove installation media..." +} + + +### Pull the trigger + +if _read_boolean "Do you want to DELETE ALL PARTITIONS?" N; then + partition + create_volumes + create_filesystems +fi + +LVM_PV_STATUS="$(cryptsetup -q status "${LVM_PV}")" +readonly LVM_PV_STATUS +LVM_PV_NUM_ACTIVE=$(echo "${LVM_PV_STATUS}" | grep "^/dev/mapper/${LVM_PV} is active and is in use.$" -c) +readonly LVM_PV_NUM_ACTIVE +if [[ ${LVM_PV_NUM_ACTIVE} -lt 1 ]]; then + decrypt_lvm +fi + +if _read_boolean "Do you want to INSTALL NixOS now?" N; then + install +fi + diff --git a/flake/apps/setup.sh b/flake/apps/setup.sh new file mode 100644 index 00000000..2274506f --- /dev/null +++ b/flake/apps/setup.sh @@ -0,0 +1,104 @@ +# shellcheck disable=SC1091 +source @bashLib@ + +export -f _log + +nix_config="@flakePath@" +export nix_config + +USER_NAME="$(logname)" +readonly USER_NAME + +HOST_NAME="$(hostname)" +readonly HOST_NAME + +_clone() { + local name="${1}" + local url="${2}" + local directory="${3}" + + if [[ -d "${directory}" ]]; then + _log "Not cloning ${name} because ${directory} already exists!" + return + fi + + _log "Clone ${name}..." + su -c "git clone ${url} ${directory}" - "${USER_NAME}" +} + +# clone repos +if ! _is_nixos || _is_root; then + _clone "nix-config" git@github.com:christianharke/nixcfg.git "${nix_config}" +fi + +# generage age key +_generate_age() { + local age_dir="${HOME}/.age" + local age_key_file="${age_dir}/key.txt" + if [[ ! -e "${HOME}/.age" ]]; then + mkdir -p "${age_dir}" + _log "Generating age key at ${age_key_file}" + local generated_age_key + generated_age_key="$(nix shell nixpkgs#age -c age-keygen -o "${age_key_file}" 2>&1)" + local age_pubkey_line + age_pubkey_line="$(sed -e "s,^Public key: \(.*\)\$,${HOST_NAME}-${USER} = \"${generated_age_key}\",")" + echo "${age_pubkey_line}" | tee -a "${nix_config}/.agenix.toml" + else + _log "Target directory ${age_dir} already exists, aborting age key generation!" + fi +} +export -f _generate_age + +# installation +_setup_nixos() { + hostname=$(_read "Enter hostname" "${HOST_NAME}") + + _generate_age + su "${USER_NAME}" -c "bash -c _generate_age" + + _log "Linking flake to system config..." + ln -fs "${nix_config}/flake.nix" /etc/nixos/flake.nix + + _log "Run nixos-rebuild for host ${hostname}..." + nixos-rebuild switch --keep-going --flake "${nix_config}#${hostname}" +} + +_setup_nix() { + local nix_env_pkgs_json + nix_env_pkgs_json="$(nix-env -q --json)" + local nix_env_pkgs_names + nix_env_pkgs_names="$(echo "${nix_env_pkgs_json}" | jq ".[].pname")" + local has_nix_imperative + has_nix_imperative=$(echo "${nix_env_pkgs_names}" | grep '"nix"' > /dev/null) + # preparation for non nixos systems + if ${has_nix_imperative}; then + _log "Set priority of installed nix package..." + nix-env --set-flag priority 1000 nix + fi + + _generate_age + + _log "Build home-manager activationPackage..." + nix build "${nix_config}#homeConfigurations.${USER_NAME}@${HOST_NAME}.activationPackage" + + _log "Run activate script..." + HOME_MANAGER_BACKUP_EXT=hm-bak ./result/activate + + rm -v result + + # clean up + if ${has_nix_imperative}; then + _log "Uninstall manual installed nix package..." + nix-env --uninstall nix + fi +} + +if _is_nixos && _is_root; then + _setup_nixos +elif ! _is_nixos && ! _is_root; then + _setup_nix +else + _log "You need to be root on NixOS or non-root on non-NixOS! Aborting..." +fi + +echo diff --git a/flake/builders/mkApp.nix b/flake/builders/mkApp.nix new file mode 100644 index 00000000..aa141d86 --- /dev/null +++ b/flake/builders/mkApp.nix @@ -0,0 +1,16 @@ +{ inputs, pkgs, customLib, rootPath, name, args, ... }: + +let + + file = rootPath + "/flake/apps/${args.file}"; + mkPath = args.path or (pkgs: [ ]); + +in + +inputs.flake-utils.lib.mkApp { + drv = customLib.mkScript + name + file + (mkPath pkgs) + (args.envs or { }); +} diff --git a/flake/builders/mkCheck.nix b/flake/builders/mkCheck.nix new file mode 100644 index 00000000..dad4d9da --- /dev/null +++ b/flake/builders/mkCheck.nix @@ -0,0 +1,6 @@ +{ pkgs, name, args, ... }: + +pkgs.runCommand name { } '' + ${args.script pkgs} + touch ${placeholder "out"} +'' diff --git a/flake/builders/mkDevShell.nix b/flake/builders/mkDevShell.nix new file mode 100644 index 00000000..a7ca0a90 --- /dev/null +++ b/flake/builders/mkDevShell.nix @@ -0,0 +1,23 @@ +{ pkgs, name, system, args, ... }: + +let + + packagesFn = args.packages or (pkgs: [ ]); + checksShellHookFn = args.checksShellHook or (system: ""); + customShellHookFn = args.customShellHook or (pkgs: { }); + +in + +pkgs.mkShell { + inherit name; + buildInputs = with pkgs; [ + # banner printing on enter + figlet + lolcat + ] ++ (packagesFn pkgs); + shellHook = '' + figlet ${name} | lolcat --freq 0.5 + '' + + (checksShellHookFn system) + + (customShellHookFn pkgs); +} diff --git a/flake/builders/mkHome.nix b/flake/builders/mkHome.nix new file mode 100644 index 00000000..dc3c7fec --- /dev/null +++ b/flake/builders/mkHome.nix @@ -0,0 +1,24 @@ +{ inputs, rootPath, machNix, system, pkgs, homeModules, name, ... }: + +let + + # splits "username@hostname" + splittedName = inputs.nixpkgs.lib.splitString "@" name; + + username = builtins.elemAt splittedName 0; + hostname = builtins.elemAt splittedName 1; + +in + +inputs.home-manager.lib.homeManagerConfiguration { + inherit username pkgs system; + + configuration = rootPath + "/hosts/${hostname}/home-${username}.nix"; + homeDirectory = "/home/${username}"; + stateVersion = "22.05"; + + extraModules = homeModules; + extraSpecialArgs = { + inherit rootPath machNix; + }; +} diff --git a/flake/builders/mkNixos.nix b/flake/builders/mkNixos.nix new file mode 100644 index 00000000..4df4ca53 --- /dev/null +++ b/flake/builders/mkNixos.nix @@ -0,0 +1,37 @@ +{ inputs, rootPath, machNix, system, pkgs, customLib, homeModules, name, ... }: + +inputs.nixpkgs.lib.nixosSystem { + inherit system; + + specialArgs = { + inherit homeModules rootPath machNix; + }; + + modules = [ + (rootPath + "/hosts/${name}") + + inputs.agenix.nixosModules.age + inputs.home-manager.nixosModules.home-manager + inputs.kmonad.nixosModules.default + + { + custom.base.hostname = name; + + lib.custom = customLib; + + nixpkgs = { + inherit pkgs; + }; + + nix = { + nixPath = [ "nixpkgs=${inputs.nixpkgs}" ]; + registry = { + nixpkgs.flake = inputs.nixpkgs; + }; + }; + } + ] + ++ customLib.getRecursiveDefaultNixFileList ../../nixos + ++ customLib.getRecursiveDefaultNixFileList (rootPath + "/nixos"); +} + diff --git a/flake/default.nix b/flake/default.nix new file mode 100644 index 00000000..82a4d617 --- /dev/null +++ b/flake/default.nix @@ -0,0 +1,54 @@ +{ inputs, rootPath }: + +let + + homeModulesBuilder = { rootPath, customLib, ... }: + [ + inputs.homeage.homeManagerModules.homeage + + { + lib.custom = customLib; + } + ] + ++ customLib.getRecursiveDefaultNixFileList ../home + ++ customLib.getRecursiveDefaultNixFileList (rootPath + "/home"); + + nameValuePairSystemWrapper = system: name: fn: + inputs.nixpkgs.lib.nameValuePair name (fn system); + + wrapper = builder: system: name: args: + let + flakeArgs = { inherit inputs rootPath system; }; + perSystem = import ./per-system.nix flakeArgs; + + homeModules = homeModulesBuilder (flakeArgs // perSystem); + + builderArgs = flakeArgs // perSystem // { inherit args homeModules name; }; + in + import builder builderArgs; + + nameValuePairWrapper = builder: system: name: args: + inputs.nixpkgs.lib.nameValuePair name (wrapper builder system name args); + + simpleNameValuePairWrapper = builder: system: name: + nameValuePairWrapper builder system name { }; + +in + +{ + mkHome = simpleNameValuePairWrapper ./builders/mkHome.nix; + mkNixos = simpleNameValuePairWrapper ./builders/mkNixos.nix; + + eachSystem = builderPerSystem: + inputs.flake-utils.lib.eachSystem + [ "aarch64-linux" "x86_64-linux" ] + (system: + builderPerSystem { + mkGeneric = nameValuePairSystemWrapper system; + mkApp = nameValuePairWrapper ./builders/mkApp.nix system; + mkCheck = nameValuePairWrapper ./builders/mkCheck.nix system; + getDevShell = name: inputs.self.devShells."${system}"."${name}"; + mkDevShell = nameValuePairWrapper ./builders/mkDevShell.nix system; + } + ); +} diff --git a/flake/per-system.nix b/flake/per-system.nix new file mode 100644 index 00000000..07588c6a --- /dev/null +++ b/flake/per-system.nix @@ -0,0 +1,54 @@ +{ inputs, rootPath, system }: + +let + + config = { + allowAliases = false; + allowUnfree = true; + }; + + unstable = import inputs.nixpkgs-unstable { + inherit config system; + }; + + nur = import inputs.nur { + nurpkgs = inputs.nixpkgs.legacyPackages.x86_64-linux; + pkgs = import inputs.nixpkgs { + inherit config system; + }; + }; + + customOverlays = [ + inputs.i3lock-pixeled.overlay + (final: prev: { + neovim = inputs.neovim.defaultPackage."${system}"; + }) + ]; + + overlays = [ + (final: prev: { + inherit unstable nur; + inherit (inputs.agenix-cli.packages."${system}") agenix-cli; + + custom = prev.lib.composeManyExtensions customOverlays final prev; + }) + ]; + + pkgs = import inputs.nixpkgs { + inherit config overlays system; + }; + + customLib = inputs.flake-commons.lib."${system}" { + inherit (inputs.nixpkgs) lib; + inherit pkgs rootPath; + }; + + machNix = import inputs.mach-nix { + inherit pkgs; + }; + +in + +{ + inherit pkgs customLib machNix; +} diff --git a/home/base/default.nix b/home/base/default.nix new file mode 100644 index 00000000..e5e2d2c9 --- /dev/null +++ b/home/base/default.nix @@ -0,0 +1,15 @@ +{ config, ... }: + +{ + home = { + homeDirectory = "/home/${config.home.username}"; + + sessionPath = [ + "$HOME/bin" + ]; + + enableNixpkgsReleaseCheck = true; + + stateVersion = "22.05"; + }; +} diff --git a/home/base/fonts/default.nix b/home/base/fonts/default.nix new file mode 100644 index 00000000..ec0d73e9 --- /dev/null +++ b/home/base/fonts/default.nix @@ -0,0 +1,11 @@ +{ pkgs, ... }: + +with pkgs; + +{ + fonts.fontconfig.enable = true; + + home.packages = [ + corefonts + ]; +} diff --git a/home/base/nix/default.nix b/home/base/nix/default.nix new file mode 100644 index 00000000..c838854c --- /dev/null +++ b/home/base/nix/default.nix @@ -0,0 +1,4 @@ +{ + nixpkgs.config = import ./nixpkgs-config.nix; + xdg.configFile."nixpkgs/config.nix".source = ./nixpkgs-config.nix; +} diff --git a/home/base/nix/nixpkgs-config.nix b/home/base/nix/nixpkgs-config.nix new file mode 100644 index 00000000..69baf106 --- /dev/null +++ b/home/base/nix/nixpkgs-config.nix @@ -0,0 +1 @@ +{ allowUnfree = true; } diff --git a/home/base/non-nixos/default.nix b/home/base/non-nixos/default.nix new file mode 100644 index 00000000..024387c5 --- /dev/null +++ b/home/base/non-nixos/default.nix @@ -0,0 +1,46 @@ +{ config, lib, pkgs, rootPath, ... }: + +with lib; + +let + + cfg = config.custom.base.non-nixos; + + flakeBaseDir = config.home.homeDirectory + "/.nix-config"; + +in + +{ + + options = { + custom.base.non-nixos = { + enable = mkEnableOption "Config for non NixOS systems"; + + installNix = mkEnableOption "Nix installation" // { default = true; }; + }; + }; + + config = mkIf cfg.enable { + + home = { + packages = with pkgs; [ + unstable.home-manager + nixStatic + ]; + + shellAliases = { + hm-switch = "home-manager switch -b hm-bak --flake '${flakeBaseDir}'"; + }; + }; + + programs.zsh.envExtra = mkAfter '' + hash -f + ''; + + targets.genericLinux.enable = true; + + xdg.configFile."nix/nix.conf".text = '' + experimental-features = nix-command flakes + ''; + }; +} diff --git a/home/base/system/filesystems/default.nix b/home/base/system/filesystems/default.nix new file mode 100644 index 00000000..576dc2cb --- /dev/null +++ b/home/base/system/filesystems/default.nix @@ -0,0 +1,9 @@ +{ pkgs, ... }: + +{ + home.packages = with pkgs; [ + parted + exfat + samba + ]; +} diff --git a/home/base/system/udiskie/default.nix b/home/base/system/udiskie/default.nix new file mode 100644 index 00000000..f3ea3b85 --- /dev/null +++ b/home/base/system/udiskie/default.nix @@ -0,0 +1,3 @@ +{ + services.udiskie.enable = true; +} diff --git a/home/programs/davmail/default.nix b/home/programs/davmail/default.nix new file mode 100644 index 00000000..c1cdb639 --- /dev/null +++ b/home/programs/davmail/default.nix @@ -0,0 +1,107 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.custom.programs.davmail; + + configType = with types; + oneOf [ (attrsOf configType) str int bool ] // { + description = "davmail config type (str, int, bool or attribute set thereof)"; + }; + + toStr = val: if isBool val then boolToString val else toString val; + + linesForAttrs = attrs: concatMap + (name: + let value = attrs."${name}"; in + if isAttrs value + then map (line: name + "." + line) (linesForAttrs value) + else [ "${name}=${toStr value}" ] + ) + (attrNames attrs); + + configFile = pkgs.writeText "davmail.properties" (concatStringsSep "\n" (linesForAttrs cfg.config)); + +in + +{ + options.custom.programs.davmail = { + enable = mkEnableOption "davmail, an MS Exchange gateway"; + + url = mkOption { + type = types.str; + description = "Outlook Web Access URL to access the exchange server, i.e. the base webmail URL."; + example = "https://outlook.office365.com/EWS/Exchange.asmx"; + }; + + config = mkOption { + type = configType; + default = { }; + description = '' + Davmail configuration. Refer to + + and + for details on supported values. + ''; + example = literalExpression '' + { + davmail.allowRemote = true; + davmail.imapPort = 55555; + davmail.bindAddress = "10.0.1.2"; + davmail.smtpSaveInSent = true; + davmail.folderSizeLimit = 10; + davmail.caldavAutoSchedule = false; + log4j.logger.rootLogger = "DEBUG"; + } + ''; + }; + }; + + config = mkIf cfg.enable { + + custom.programs.davmail.config = { + davmail = mapAttrs (name: mkDefault) { + server = true; + disableUpdateCheck = true; + logFilePath = config.xdg.dataHome + "/davmail/davmail.log"; + logFileSize = "1MB"; + mode = "auto"; + inherit (cfg) url; + caldavPort = 1080; + imapPort = 1143; + ldapPort = 1389; + popPort = 1110; + smtpPort = 1025; + }; + log4j = { + logger.davmail = mkDefault "WARN"; + logger.httpclient.wire = mkDefault "WARN"; + logger.org.apache.commons.httpclient = mkDefault "WARN"; + rootLogger = mkDefault "WARN"; + }; + }; + + systemd.user.services.davmail = { + Unit = { + Description = "DavMail POP/IMAP/SMTP Exchange Gateway"; + After = [ "network.target" ]; + }; + + Service = { + Type = "simple"; + Environment = "PATH=${pkgs.davmail}/bin:${pkgs.coreutils}/bin"; + ExecStart = "${pkgs.davmail}/bin/davmail ${configFile}"; + Restart = "on-failure"; + LogsDirectory = "davmail"; + }; + + Install = { + WantedBy = [ "vdirsyncer-oneshot.timer" ]; + }; + }; + + home.packages = [ pkgs.davmail ]; + }; +} diff --git a/home/programs/firefox/default.nix b/home/programs/firefox/default.nix new file mode 100644 index 00000000..6855f344 --- /dev/null +++ b/home/programs/firefox/default.nix @@ -0,0 +1,64 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.custom.programs.firefox; + +in + +{ + options = { + custom.programs.firefox = { + enable = mkEnableOption "Firefox web browser"; + + extensions = mkOption { + type = with types; listOf package; + default = [ ]; + description = '' + List of extension names to be installed. Source: https://gitlab.com/rycee/nur-expressions/-/blob/master/pkgs/firefox-addons/generated-firefox-addons.nix + ''; + }; + + homepage = mkOption { + type = types.str; + default = "https://harke.ch/"; + description = '' + Home page for new tabs and windows. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + programs.firefox = { + inherit (cfg) enable extensions; + + profiles."ztbvdcs8.default" = { + isDefault = true; + settings = { + "browser.startup.homepage" = cfg.homepage; + }; + userChrome = '' + /* Workaround for vim-vixen issue + * https://github.com/ueokande/vim-vixen/issues/1424 + */ + .vimvixen-console-frame { + height: 0px; + color-scheme: light !important; + } + ''; + userContent = '' + /* Workaround for vim-vixen issue + * https://github.com/ueokande/vim-vixen/issues/1424 + */ + .vimvixen-console-frame { + height: 0px; + color-scheme: light !important; + } + ''; + }; + }; + }; +} diff --git a/home/programs/kmonad/default.nix b/home/programs/kmonad/default.nix new file mode 100644 index 00000000..6130de24 --- /dev/null +++ b/home/programs/kmonad/default.nix @@ -0,0 +1,65 @@ +{ config, lib, pkgs, ... }: + +with lib; +with builtins; + +let + + cfg = config.custom.programs.kmonad; + + mkService = kbd-dev: kbd-path: + { + name = "kmonad-${kbd-dev}"; + value = { + Unit = { + Description = "KMonad Instance for: ${kbd-dev}"; + }; + Service = { + Type = "simple"; + Restart = "always"; + RestartSec = 10; + ExecStart = "${cfg.package}/bin/kmonad ${kbd-path}"; + }; + Install = { + WantedBy = [ "default.target" ]; + }; + }; + }; + +in + +{ + options.custom.programs.kmonad = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable KMonad service. + ''; + }; + + configFiles = mkOption { + type = types.attrsOf types.path; + default = { }; + example = '' + { G512 = ./my-config.kbd }; + ''; + description = '' + Input devices mapped to their respective configuration file. + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.kmonad; + description = '' + The KMonad package. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + systemd.user.services = listToAttrs (mapAttrsToList mkService cfg.configFiles); + }; +} diff --git a/home/programs/lazygit/default.nix b/home/programs/lazygit/default.nix new file mode 100644 index 00000000..c5391664 --- /dev/null +++ b/home/programs/lazygit/default.nix @@ -0,0 +1,35 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.custom.programs.lazygit; + +in + +{ + options = { + custom.programs.lazygit = { + enable = mkEnableOption "Lazygit"; + }; + }; + + config = mkIf cfg.enable { + home = { + packages = with pkgs; [ + lazygit + ]; + + shellAliases = { + lg = "lazygit"; + }; + }; + + xdg.configFile."jesseduffield/lazygit/config.yml" = mkIf cfg.enable { + text = '' + reporting: "off" + ''; + }; + }; +} diff --git a/home/programs/ranger/config/commands.py b/home/programs/ranger/config/commands.py new file mode 100644 index 00000000..5defa677 --- /dev/null +++ b/home/programs/ranger/config/commands.py @@ -0,0 +1,1993 @@ +# -*- coding: utf-8 -*- +# This file is part of ranger, the console file manager. +# This configuration file is licensed under the same terms as ranger. +# =================================================================== +# +# NOTE: If you copied this file to /etc/ranger/commands_full.py or +# ~/.config/ranger/commands_full.py, then it will NOT be loaded by ranger, +# and only serve as a reference. +# +# =================================================================== +# This file contains ranger's commands. +# It's all in python; lines beginning with # are comments. +# +# Note that additional commands are automatically generated from the methods +# of the class ranger.core.actions.Actions. +# +# You can customize commands in the files /etc/ranger/commands.py (system-wide) +# and ~/.config/ranger/commands.py (per user). +# They have the same syntax as this file. In fact, you can just copy this +# file to ~/.config/ranger/commands_full.py with +# `ranger --copy-config=commands_full' and make your modifications, don't +# forget to rename it to commands.py. You can also use +# `ranger --copy-config=commands' to copy a short sample commands.py that +# has everything you need to get started. +# But make sure you update your configs when you update ranger. +# +# =================================================================== +# Every class defined here which is a subclass of `Command' will be used as a +# command in ranger. Several methods are defined to interface with ranger: +# execute(): called when the command is executed. +# cancel(): called when closing the console. +# tab(tabnum): called when is pressed. +# quick(): called after each keypress. +# +# tab() argument tabnum is 1 for and -1 for by default +# +# The return values for tab() can be either: +# None: There is no tab completion +# A string: Change the console to this string +# A list/tuple/generator: cycle through every item in it +# +# The return value for quick() can be: +# False: Nothing happens +# True: Execute the command afterwards +# +# The return value for execute() and cancel() doesn't matter. +# +# =================================================================== +# Commands have certain attributes and methods that facilitate parsing of +# the arguments: +# +# self.line: The whole line that was written in the console. +# self.args: A list of all (space-separated) arguments to the command. +# self.quantifier: If this command was mapped to the key "X" and +# the user pressed 6X, self.quantifier will be 6. +# self.arg(n): The n-th argument, or an empty string if it doesn't exist. +# self.rest(n): The n-th argument plus everything that followed. For example, +# if the command was "search foo bar a b c", rest(2) will be "bar a b c" +# self.start(n): Anything before the n-th argument. For example, if the +# command was "search foo bar a b c", start(2) will be "search foo" +# +# =================================================================== +# And this is a little reference for common ranger functions and objects: +# +# self.fm: A reference to the "fm" object which contains most information +# about ranger. +# self.fm.notify(string): Print the given string on the screen. +# self.fm.notify(string, bad=True): Print the given string in RED. +# self.fm.reload_cwd(): Reload the current working directory. +# self.fm.thisdir: The current working directory. (A File object.) +# self.fm.thisfile: The current file. (A File object too.) +# self.fm.thistab.get_selection(): A list of all selected files. +# self.fm.execute_console(string): Execute the string as a ranger command. +# self.fm.open_console(string): Open the console with the given string +# already typed in for you. +# self.fm.move(direction): Moves the cursor in the given direction, which +# can be something like down=3, up=5, right=1, left=1, to=6, ... +# +# File objects (for example self.fm.thisfile) have these useful attributes and +# methods: +# +# tfile.path: The path to the file. +# tfile.basename: The base name only. +# tfile.load_content(): Force a loading of the directories content (which +# obviously works with directories only) +# tfile.is_directory: True/False depending on whether it's a directory. +# +# For advanced commands it is unavoidable to dive a bit into the source code +# of ranger. +# =================================================================== + +from __future__ import (absolute_import, division, print_function) + +from collections import deque +import os +import re + +from ranger.api.commands import Command + + +class alias(Command): + """:alias + + Copies the oldcommand as newcommand. + """ + + context = 'browser' + resolve_macros = False + + def execute(self): + if not self.arg(1) or not self.arg(2): + self.fm.notify('Syntax: alias ', bad=True) + return + + self.fm.commands.alias(self.arg(1), self.rest(2)) + + +class echo(Command): + """:echo + + Display the text in the statusbar. + """ + + def execute(self): + self.fm.notify(self.rest(1)) + + +class cd(Command): + """:cd [-r] + + The cd command changes the directory. + If the path is a file, selects that file. + The command 'cd -' is equivalent to typing ``. + Using the option "-r" will get you to the real path. + """ + + def execute(self): + if self.arg(1) == '-r': + self.shift() + destination = os.path.realpath(self.rest(1)) + if os.path.isfile(destination): + self.fm.select_file(destination) + return + else: + destination = self.rest(1) + + if not destination: + destination = '~' + + if destination == '-': + self.fm.enter_bookmark('`') + else: + self.fm.cd(destination) + + def _tab_args(self): + # dest must be rest because path could contain spaces + if self.arg(1) == '-r': + start = self.start(2) + dest = self.rest(2) + else: + start = self.start(1) + dest = self.rest(1) + + if dest: + head, tail = os.path.split(os.path.expanduser(dest)) + if head: + dest_exp = os.path.join(os.path.normpath(head), tail) + else: + dest_exp = tail + else: + dest_exp = '' + return (start, dest_exp, os.path.join(self.fm.thisdir.path, dest_exp), + dest.endswith(os.path.sep)) + + @staticmethod + def _tab_paths(dest, dest_abs, ends_with_sep): + if not dest: + try: + return next(os.walk(dest_abs))[1], dest_abs + except (OSError, StopIteration): + return [], '' + + if ends_with_sep: + try: + return [os.path.join(dest, path) for path in next(os.walk(dest_abs))[1]], '' + except (OSError, StopIteration): + return [], '' + + return None, None + + def _tab_match(self, path_user, path_file): + if self.fm.settings.cd_tab_case == 'insensitive': + path_user = path_user.lower() + path_file = path_file.lower() + elif self.fm.settings.cd_tab_case == 'smart' and path_user.islower(): + path_file = path_file.lower() + return path_file.startswith(path_user) + + def _tab_normal(self, dest, dest_abs): + dest_dir = os.path.dirname(dest) + dest_base = os.path.basename(dest) + + try: + dirnames = next(os.walk(os.path.dirname(dest_abs)))[1] + except (OSError, StopIteration): + return [], '' + + return [os.path.join(dest_dir, d) for d in dirnames if self._tab_match(dest_base, d)], '' + + def _tab_fuzzy_match(self, basepath, tokens): + """ Find directories matching tokens recursively """ + if not tokens: + tokens = [''] + paths = [basepath] + while True: + token = tokens.pop() + matches = [] + for path in paths: + try: + directories = next(os.walk(path))[1] + except (OSError, StopIteration): + continue + matches += [os.path.join(path, d) for d in directories + if self._tab_match(token, d)] + if not tokens or not matches: + return matches + paths = matches + + return None + + def _tab_fuzzy(self, dest, dest_abs): + tokens = [] + basepath = dest_abs + while True: + basepath_old = basepath + basepath, token = os.path.split(basepath) + if basepath == basepath_old: + break + if os.path.isdir(basepath_old) and not token.startswith('.'): + basepath = basepath_old + break + tokens.append(token) + + paths = self._tab_fuzzy_match(basepath, tokens) + if not os.path.isabs(dest): + paths_rel = self.fm.thisdir.path + paths = [os.path.relpath(os.path.join(basepath, path), paths_rel) + for path in paths] + else: + paths_rel = '' + return paths, paths_rel + + def tab(self, tabnum): + from os.path import sep + + start, dest, dest_abs, ends_with_sep = self._tab_args() + + paths, paths_rel = self._tab_paths(dest, dest_abs, ends_with_sep) + if paths is None: + if self.fm.settings.cd_tab_fuzzy: + paths, paths_rel = self._tab_fuzzy(dest, dest_abs) + else: + paths, paths_rel = self._tab_normal(dest, dest_abs) + + paths.sort() + + if self.fm.settings.cd_bookmarks: + paths[0:0] = [ + os.path.relpath(v.path, paths_rel) if paths_rel else v.path + for v in self.fm.bookmarks.dct.values() for path in paths + if v.path.startswith(os.path.join(paths_rel, path) + sep) + ] + + if not paths: + return None + if len(paths) == 1: + return start + paths[0] + sep + return [start + dirname + sep for dirname in paths] + + +class chain(Command): + """:chain ; ; ... + + Calls multiple commands at once, separated by semicolons. + """ + resolve_macros = False + + def execute(self): + if not self.rest(1).strip(): + self.fm.notify('Syntax: chain ; ; ...', bad=True) + return + for command in [s.strip() for s in self.rest(1).split(";")]: + self.fm.execute_console(command) + + +class shell(Command): + escape_macros_for_shell = True + + def execute(self): + if self.arg(1) and self.arg(1)[0] == '-': + flags = self.arg(1)[1:] + command = self.rest(2) + else: + flags = '' + command = self.rest(1) + + if command: + self.fm.execute_command(command, flags=flags) + + def tab(self, tabnum): + from ranger.ext.get_executables import get_executables + if self.arg(1) and self.arg(1)[0] == '-': + command = self.rest(2) + else: + command = self.rest(1) + start = self.line[0:len(self.line) - len(command)] + + try: + position_of_last_space = command.rindex(" ") + except ValueError: + return (start + program + ' ' for program + in get_executables() if program.startswith(command)) + if position_of_last_space == len(command) - 1: + selection = self.fm.thistab.get_selection() + if len(selection) == 1: + return self.line + selection[0].shell_escaped_basename + ' ' + return self.line + '%s ' + + before_word, start_of_word = self.line.rsplit(' ', 1) + return (before_word + ' ' + file.shell_escaped_basename + for file in self.fm.thisdir.files or [] + if file.shell_escaped_basename.startswith(start_of_word)) + + +class open_with(Command): + + def execute(self): + app, flags, mode = self._get_app_flags_mode(self.rest(1)) + self.fm.execute_file( + files=[f for f in self.fm.thistab.get_selection()], + app=app, + flags=flags, + mode=mode) + + def tab(self, tabnum): + return self._tab_through_executables() + + def _get_app_flags_mode(self, string): # pylint: disable=too-many-branches,too-many-statements + """Extracts the application, flags and mode from a string. + + examples: + "mplayer f 1" => ("mplayer", "f", 1) + "atool 4" => ("atool", "", 4) + "p" => ("", "p", 0) + "" => None + """ + + app = '' + flags = '' + mode = 0 + split = string.split() + + if len(split) == 1: + part = split[0] + if self._is_app(part): + app = part + elif self._is_flags(part): + flags = part + elif self._is_mode(part): + mode = part + + elif len(split) == 2: + part0 = split[0] + part1 = split[1] + + if self._is_app(part0): + app = part0 + if self._is_flags(part1): + flags = part1 + elif self._is_mode(part1): + mode = part1 + elif self._is_flags(part0): + flags = part0 + if self._is_mode(part1): + mode = part1 + elif self._is_mode(part0): + mode = part0 + if self._is_flags(part1): + flags = part1 + + elif len(split) >= 3: + part0 = split[0] + part1 = split[1] + part2 = split[2] + + if self._is_app(part0): + app = part0 + if self._is_flags(part1): + flags = part1 + if self._is_mode(part2): + mode = part2 + elif self._is_mode(part1): + mode = part1 + if self._is_flags(part2): + flags = part2 + elif self._is_flags(part0): + flags = part0 + if self._is_mode(part1): + mode = part1 + elif self._is_mode(part0): + mode = part0 + if self._is_flags(part1): + flags = part1 + + return app, flags, int(mode) + + def _is_app(self, arg): + return not self._is_flags(arg) and not arg.isdigit() + + @staticmethod + def _is_flags(arg): + from ranger.core.runner import ALLOWED_FLAGS + return all(x in ALLOWED_FLAGS for x in arg) + + @staticmethod + def _is_mode(arg): + return all(x in '0123456789' for x in arg) + + +class set_(Command): + """:set