├── .cz.toml ├── .envrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── cachix-push.yml │ ├── flake-check.yml │ ├── flakehub-publish.yml │ └── update-flake-lock.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── flake-parts-bootstrap └── _bootstrap │ ├── .envrc │ ├── .gitignore │ ├── README.md │ ├── flake-parts │ └── _bootstrap.nix │ └── meta.nix ├── flake-parts ├── +github │ └── meta.nix ├── +home-manager │ └── meta.nix ├── +nixos │ └── meta.nix ├── +nixvim │ └── meta.nix ├── agenix │ ├── flake-parts │ │ └── agenix │ │ │ ├── default.nix │ │ │ ├── pubkeys.nix │ │ │ └── secrets │ │ │ └── .gitignore │ └── meta.nix ├── deploy-rs │ ├── flake-parts │ │ └── deploy-rs │ │ │ └── default.nix │ └── meta.nix ├── devenv │ ├── flake-parts │ │ └── devenv │ │ │ ├── default.nix │ │ │ └── dev.nix │ └── meta.nix ├── flake-root │ ├── flake-parts │ │ └── flake-root.nix │ └── meta.nix ├── gh-actions-cachix │ ├── .github │ │ └── workflows │ │ │ └── cachix-push.yml │ └── meta.nix ├── gh-actions-check │ ├── .github │ │ └── workflows │ │ │ └── flake-check.yml │ └── meta.nix ├── gh-actions-flake-update │ ├── .github │ │ └── workflows │ │ │ └── update-flake-lock.yml │ └── meta.nix ├── gh-actions-flakehub │ ├── .github │ │ └── workflows │ │ │ └── flakehub-publish.yml │ └── meta.nix ├── gh-actions-pages │ ├── .github │ │ └── workflows │ │ │ └── pages.yml │ └── meta.nix ├── gh-dependabot │ ├── .github │ │ └── dependabot.yml │ └── meta.nix ├── gh-templates-PR │ ├── .github │ │ └── PULL_REQUEST_TEMPLATE.md │ └── meta.nix ├── gh-templates-issues │ ├── .github │ │ └── ISSUE_TEMPLATE │ │ │ ├── bug_report.md │ │ │ └── feature_request.md │ └── meta.nix ├── gitlab-ci-check │ ├── .gitlab-ci.yml │ └── meta.nix ├── hm-homes │ ├── flake-parts │ │ └── homes │ │ │ └── default.nix │ └── meta.nix ├── hm-modules │ ├── flake-parts │ │ └── modules │ │ │ └── home-manager │ │ │ └── default.nix │ └── meta.nix ├── lib │ ├── flake-parts │ │ └── lib │ │ │ └── default.nix │ └── meta.nix ├── nix-topology │ ├── flake-parts │ │ └── nix-topology │ │ │ ├── default.nix │ │ │ └── topology.nix │ └── meta.nix ├── nixos-hosts │ ├── flake-parts │ │ └── hosts │ │ │ └── default.nix │ └── meta.nix ├── nixos-modules │ ├── flake-parts │ │ └── modules │ │ │ └── nixos │ │ │ └── default.nix │ └── meta.nix ├── nixvim-configurations │ ├── flake-parts │ │ └── nixvim │ │ │ └── default.nix │ └── meta.nix ├── nixvim-modules │ ├── flake-parts │ │ └── modules │ │ │ └── nixvim │ │ │ └── default.nix │ └── meta.nix ├── overlays │ ├── flake-parts │ │ └── overlays │ │ │ └── default.nix │ └── meta.nix ├── pkgs │ ├── flake-parts │ │ └── pkgs │ │ │ └── default.nix │ └── meta.nix ├── pre-commit-hooks │ ├── flake-parts │ │ └── pre-commit-hooks.nix │ └── meta.nix ├── process-compose-flake │ ├── flake-parts │ │ └── process-compose-flake │ │ │ ├── default.nix │ │ │ └── dev.nix │ └── meta.nix ├── shells │ ├── flake-parts │ │ └── shells │ │ │ ├── default.nix │ │ │ └── dev.nix │ └── meta.nix ├── systems │ ├── flake-parts │ │ └── systems.nix │ └── meta.nix └── treefmt │ ├── flake-parts │ └── treefmt.nix │ └── meta.nix ├── flake.lock ├── flake.nix └── src ├── assets ├── flake-inputs.nix.template └── flake.nix.template ├── cmd ├── add.rs ├── init.rs ├── list.rs └── mod.rs ├── config.rs ├── fs_utils.rs ├── main.rs ├── nix.rs ├── parts.rs └── templates.rs /.cz.toml: -------------------------------------------------------------------------------- 1 | [tool.commitizen] 2 | name = "cz_conventional_commits" 3 | tag_format = "$version" 4 | version_scheme = "semver" 5 | version_provider = "cargo" 6 | update_changelog_on_bump = true 7 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then 4 | source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" 5 | fi 6 | 7 | watch_file flake.nix 8 | watch_file flake.lock 9 | 10 | if ! use flake .#dev --accept-flake-config 11 | then 12 | echo "devshell could not be built. Make sure dev.nix is a valid devshell and try again." >&2 13 | fi 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🪲 Bug Report 3 | about: Create a bug report to help us resolve the bug 4 | title: "🪲[Bug]: " 5 | labels: "bug" 6 | assignees: "" 7 | --- 8 | 9 | ## Describe the bug 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | ## Steps To Reproduce 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. ... 18 | 1. ... 19 | 1. ... 20 | 21 | ## Expected behavior 22 | 23 | A clear and concise description of what you expected to happen. 24 | 25 | ## Screenshots 26 | 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | ## Additional context 30 | 31 | Add any other context about the problem here. 32 | 33 | ## Notify maintainers 34 | 35 | 39 | 40 | ## Metadata 41 | 42 | Please run `nix-shell -p nix-info --run "nix-info -m"` and paste the result. 43 | 44 | ```console 45 | [user@system:~]$ nix-shell -p nix-info --run "nix-info -m" 46 | output here 47 | ``` 48 | 49 | --- 50 | 51 | Add a :+1: \[reaction\] to \[issues you find important\]. 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💡 Feature Request 3 | about: Suggest an interesting feature idea for this project 4 | title: '💡[Feature]: ' 5 | labels: "enhancement" 6 | assignees: "" 7 | --- 8 | 9 | ## Is your feature request related to a problem? Please describe 10 | 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated 12 | when \[...\] 13 | 14 | ## Describe the solution you'd like 15 | 16 | A clear and concise description of what you want to happen. 17 | 18 | ## Describe alternatives you've considered 19 | 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | ## Additional context 23 | 24 | Add any other context or screenshots about the feature request here. 25 | 26 | --- 27 | 28 | Add a :+1: \[reaction\] to \[issues you find important\]. 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Overview 4 | 5 | 8 | 9 | ## Testing 10 | 11 | 16 | 17 | ## Dependencies 18 | 19 | 21 | 22 | ## Screenshots 23 | 25 | 26 | ## Checklist 27 | 28 | 29 | 30 | - [ ] I have tested the relevant changes locally. 31 | - [ ] I have checked that `nix flake check` passes. 32 | - [ ] I have ensured my commits follow the project's commits guidelines. 33 | - [ ] I have checked that the changes follow a linear history. 34 | - [ ] (If applicable) I have commented any relevant parts of my code. 35 | - [ ] (If applicable) I have added appropriate unit/feature tests. 36 | - [ ] (If applicable) I have updated the documentation accordingly (in English). 37 | 38 | ## Additional Notes 39 | 40 | 41 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | labels: 13 | - dependencies 14 | # NOTE: For additional ecosystems refer to the documentation 15 | # package-ecosystem: "gitsubmodule" 16 | # directory: "/" 17 | # schedule: 18 | # interval: "weekly" 19 | # labels: 20 | # - dependencies 21 | # - package-ecosystem: "docker" 22 | # directory: "/" 23 | # schedule: 24 | # interval: "weekly" 25 | # labels: 26 | # - dependencies 27 | -------------------------------------------------------------------------------- /.github/workflows/cachix-push.yml: -------------------------------------------------------------------------------- 1 | # --- Push packages & devshells to the cachix binary cache service 2 | name: cachix push 3 | 4 | on: 5 | workflow_dispatch: # allows manual triggering from the Actions UI 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | cachix-push: 12 | runs-on: "ubuntu-latest" 13 | steps: 14 | - name: "Checking out repository..." 15 | uses: actions/checkout@v4 16 | 17 | - name: "Installing and configuring the nix package manager..." 18 | uses: cachix/install-nix-action@v31 19 | with: 20 | extra_nix_config: | 21 | accept-flake-config = true 22 | 23 | # NOTE: Alternatively you can use the DeterminateSystems nix installer 24 | # - name: "Installing and configuring the nix package manager..." 25 | # uses: DeterminateSystems/nix-installer-action@main 26 | # with: 27 | # extra-conf: | 28 | # accept-flake-config = true 29 | 30 | - name: "Settings up cachix binary cache..." 31 | uses: cachix/cachix-action@v16 32 | with: 33 | name: tsandrini 34 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 35 | 36 | # NOTE Install any necessary packages here 37 | - name: "Setting up packages..." 38 | run: | 39 | nix profile install nixpkgs#nix-fast-build # parallel nix builder 40 | 41 | - name: "Building project packages..." 42 | run: nix-fast-build --skip-cached --no-nom --flake ".#packages" 43 | 44 | - name: "Building project devShells..." 45 | run: nix-fast-build --skip-cached --no-nom --flake ".#devShells" 46 | -------------------------------------------------------------------------------- /.github/workflows/flake-check.yml: -------------------------------------------------------------------------------- 1 | # --- Run `nix flake check` 2 | name: nix flake check 3 | 4 | on: 5 | workflow_dispatch: # allows manual triggering from the Actions UI 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | types: [opened, reopened, synchronize] 11 | repository_dispatch: 12 | types: [create-pull-request] 13 | 14 | jobs: 15 | flake-check: 16 | runs-on: "ubuntu-latest" 17 | steps: 18 | - name: "Checking out repository..." 19 | uses: actions/checkout@v4 20 | 21 | - name: "Installing and configuring the nix package manager..." 22 | uses: cachix/install-nix-action@v31 23 | with: 24 | extra_nix_config: | 25 | accept-flake-config = true 26 | 27 | # NOTE: Alternatively you can use the DeterminateSystems nix installer 28 | # - name: "Installing and configuring the nix package manager..." 29 | # uses: DeterminateSystems/nix-installer-action@main 30 | # with: 31 | # extra-conf: | 32 | # accept-flake-config = true 33 | 34 | # NOTE Install any necessary packages here 35 | - name: "Setting up packages..." 36 | run: | 37 | nix profile install nixpkgs#nix-fast-build # parallel nix builder 38 | nix profile install nixpkgs#cargo-audit # Audit Cargo.lock files for crates with security vulnerabilities 39 | 40 | - name: "Running `nix flake check`..." 41 | run: nix-fast-build --skip-cached --no-nom 42 | 43 | - name: "Running `nix build ...`..." 44 | run: nix-fast-build --skip-cached --no-nom --flake ".#packages" 45 | 46 | - name: "Running cargo-audit" 47 | run: cargo-audit audit 48 | 49 | - name: "Checking flake inputs for stale & insecure nixpkgs versions..." 50 | uses: DeterminateSystems/flake-checker-action@main 51 | -------------------------------------------------------------------------------- /.github/workflows/flakehub-publish.yml: -------------------------------------------------------------------------------- 1 | # --- Publish flake to FlakeHub 2 | name: FlakeHub 3 | 4 | on: 5 | workflow_dispatch: # allows manual triggering from the Actions UI 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | flakehub-publish: 12 | runs-on: "ubuntu-latest" 13 | permissions: 14 | id-token: "write" 15 | contents: "read" 16 | steps: 17 | - name: "Checking out repository..." 18 | uses: actions/checkout@v4 19 | 20 | - name: "Installing and configuring the nix package manager..." 21 | uses: cachix/install-nix-action@v31 22 | with: 23 | extra_nix_config: | 24 | accept-flake-config = true 25 | 26 | # NOTE: Alternatively you can use the DeterminateSystems nix installer 27 | # - name: "Installing and configuring the nix package manager..." 28 | # uses: DeterminateSystems/nix-installer-action@main 29 | # with: 30 | # extra-conf: | 31 | # accept-flake-config = true 32 | 33 | - name: "Publishing flake to FlakeHub..." 34 | uses: DeterminateSystems/flakehub-push@main 35 | with: 36 | name: "tsandrini/flake-parts-builder" 37 | rolling: true 38 | visibility: "public" 39 | -------------------------------------------------------------------------------- /.github/workflows/update-flake-lock.yml: -------------------------------------------------------------------------------- 1 | # --- Periodically update flake inputs in flake.lock 2 | name: update-flake-lock 3 | 4 | on: 5 | workflow_dispatch: # allows manual triggering from the Actions UI 6 | schedule: 7 | - cron: "0 0 * * 0" # runs weekly on Sunday at 00:00 8 | 9 | jobs: 10 | update-flake-lock: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: "Checking out repository..." 14 | uses: actions/checkout@v4 15 | 16 | - name: "Installing and configuring the nix package manager..." 17 | uses: cachix/install-nix-action@v31 18 | with: 19 | extra_nix_config: | 20 | accept-flake-config = true 21 | 22 | # NOTE: Alternatively you can use the DeterminateSystems nix installer 23 | # - name: "Installing and configuring the nix package manager..." 24 | # uses: DeterminateSystems/nix-installer-action@main 25 | # with: 26 | # extra-conf: | 27 | # accept-flake-config = true 28 | 29 | - name: "Updating flake.lock..." 30 | uses: DeterminateSystems/update-flake-lock@main 31 | with: 32 | pr-title: "Automated action - Update flake.lock" 33 | pr-labels: | 34 | dependencies 35 | automated 36 | # NOTE You can use a personal access token to identify 37 | # as a concrete user, this may be useful when you want to 38 | # trigger additional CI actions. 39 | # 40 | token: ${{ secrets.GH_TOKEN_FOR_UPDATES }} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ---- 2 | # Rust 3 | # ---- 4 | ## Generated by Cargo 5 | ## will have compiled files and executables 6 | debug/ 7 | target/ 8 | 9 | ## These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | ## MSVC Windows builds of rustc generate these, which store debugging information 13 | *.pdb 14 | 15 | # --------- 16 | # Nix/NixOS 17 | # --------- 18 | 19 | ## Builds 20 | build/ 21 | result 22 | result-* 23 | 24 | ## devshells/devenv/direnv 25 | .direnv 26 | .devenv 27 | 28 | # ----------- 29 | # Editors/IDE 30 | # ----------- 31 | 32 | ## VIM 33 | 34 | ### Swap 35 | [._]*.s[a-v][a-z] 36 | !*.svg # comment out if you don't need vector files 37 | [._]*.sw[a-p] 38 | [._]s[a-rt-v][a-z] 39 | [._]ss[a-gi-z] 40 | [._]sw[a-p] 41 | 42 | ### Session 43 | Session.vim 44 | Sessionx.vim 45 | 46 | ### Temporary 47 | .netrwhist 48 | *~ 49 | ### Auto-generated tag files 50 | tags 51 | ### Persistent undo 52 | [._]*.un~ 53 | 54 | ## VSCode 55 | .vscode/* 56 | !.vscode/settings.json 57 | !.vscode/tasks.json 58 | !.vscode/launch.json 59 | !.vscode/extensions.json 60 | !.vscode/*.code-snippets 61 | 62 | ### Local History for Visual Studio Code 63 | .history/ 64 | 65 | ### Built Visual Studio Code Extensions 66 | *.vsix 67 | 68 | ## Emacs 69 | *~ 70 | \#*\# 71 | /.emacs.desktop 72 | /.emacs.desktop.lock 73 | *.elc 74 | auto-save-list 75 | tramp 76 | .\#* 77 | 78 | ### Org-mode 79 | .org-id-locations 80 | *_archive 81 | 82 | ### flymake-mode 83 | *_flymake.* 84 | 85 | ### eshell files 86 | /eshell/history 87 | /eshell/lastdir 88 | 89 | ### elpa packages 90 | /elpa/ 91 | 92 | ### reftex files 93 | *.rel 94 | 95 | ### AUCTeX auto folder 96 | /auto/ 97 | 98 | ### cask packages 99 | .cask/ 100 | dist/ 101 | 102 | ### Flycheck 103 | flycheck_*.el 104 | 105 | ### server auth directory 106 | /server/ 107 | 108 | ### projectiles files 109 | .projectile 110 | 111 | ### directory configuration 112 | .dir-locals.el 113 | 114 | ### network security 115 | /network-security.data 116 | 117 | ## JetBrains IDE 118 | ### Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 119 | ### Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 120 | 121 | ### User-specific stuff 122 | .idea/**/workspace.xml 123 | .idea/**/tasks.xml 124 | .idea/**/usage.statistics.xml 125 | .idea/**/dictionaries 126 | .idea/**/shelf 127 | 128 | ### AWS User-specific 129 | .idea/**/aws.xml 130 | 131 | ### Generated files 132 | .idea/**/contentModel.xml 133 | 134 | ### Sensitive or high-churn files 135 | .idea/**/dataSources/ 136 | .idea/**/dataSources.ids 137 | .idea/**/dataSources.local.xml 138 | .idea/**/sqlDataSources.xml 139 | .idea/**/dynamic.xml 140 | .idea/**/uiDesigner.xml 141 | .idea/**/dbnavigator.xml 142 | 143 | ### Gradle 144 | .idea/**/gradle.xml 145 | .idea/**/libraries 146 | 147 | ### CMake 148 | cmake-build-*/ 149 | 150 | ### Mongo Explorer plugin 151 | .idea/**/mongoSettings.xml 152 | 153 | ### File-based project format 154 | *.iws 155 | 156 | ### IntelliJ 157 | out/ 158 | 159 | ### mpeltonen/sbt-idea plugin 160 | .idea_modules/ 161 | 162 | ### JIRA plugin 163 | atlassian-ide-plugin.xml 164 | 165 | ### Cursive Clojure plugin 166 | .idea/replstate.xml 167 | 168 | ### SonarLint plugin 169 | .idea/sonarlint/ 170 | 171 | ### Crashlytics plugin (for Android Studio and IntelliJ) 172 | com_crashlytics_export_strings.xml 173 | crashlytics.properties 174 | crashlytics-build.properties 175 | fabric.properties 176 | 177 | ### Editor-based Rest Client 178 | .idea/httpRequests 179 | 180 | ### Android studio 3.1+ serialized cache file 181 | .idea/caches/build_file_checksums.ser 182 | 183 | 184 | # ----------- 185 | # OS specific 186 | # ----------- 187 | 188 | ## Linux 189 | *~ 190 | ### temporary files which can be created if a process still has a handle open of a deleted file 191 | .fuse_hidden* 192 | ### KDE directory preferences 193 | .directory 194 | ### Linux trash folder which might appear on any partition or disk 195 | .Trash-* 196 | ### .nfs files are created when an open file is removed but is still being accessed 197 | .nfs* 198 | 199 | 200 | ## macOS 201 | 202 | ### General 203 | .DS_Store 204 | .AppleDouble 205 | .LSOverride 206 | 207 | ### Icon must end with two \r 208 | Icon 209 | 210 | ### Thumbnails 211 | ._* 212 | 213 | ### Files that might appear in the root of a volume 214 | .DocumentRevisions-V100 215 | .fseventsd 216 | .Spotlight-V100 217 | .TemporaryItems 218 | .Trashes 219 | .VolumeIcon.icns 220 | .com.apple.timemachine.donotpresent 221 | 222 | ### Directories potentially created on remote AFP share 223 | .AppleDB 224 | .AppleDesktop 225 | Network Trash Folder 226 | Temporary Items 227 | .apdisk 228 | 229 | # ----- 230 | # Misc 231 | # ----- 232 | 233 | ## Backups 234 | *.bak 235 | *.gho 236 | *.ori 237 | *.orig 238 | *.tmp 239 | 240 | ## Tags 241 | 242 | ### Ignore tags created by etags, ctags, gtags (GNU global) and cscope 243 | TAGS 244 | .TAGS 245 | !TAGS/ 246 | tags 247 | .tags 248 | !tags/ 249 | gtags.files 250 | GTAGS 251 | GRTAGS 252 | GPATH 253 | GSYMS 254 | cscope.files 255 | cscope.out 256 | cscope.in.out 257 | cscope.po.out 258 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.0-b2 (2025-01-21) 4 | 5 | ### Feat 6 | 7 | - **flake-parts**: add deploy-rs part 8 | - **flake-parts**: update pre-commit-hooks default 9 | - **flake-parts**: update treefmt defaults 10 | - **bootstrap**: add shebang to .envrc 11 | - **flake-parts**: update issue templates 12 | - **flake-parts**: add gh-actions-dependabot 13 | - **flake-parts**: add nix-fast-build system doc 14 | - **gitignore**: add .pre-commit-config.yaml 15 | 16 | ### Fix 17 | 18 | - **builder**: update old builder hash 19 | - **flake**: fix version typo 20 | - **issues**: correct grammar 21 | - **flake-parts**: fix treefmt projectRootFile 22 | - **_bootstrap**: fix mapModules function 23 | - **builder**: use SRI cargo hash instead 24 | 25 | ### Refactor 26 | 27 | - **.envrc**: add shebang to .envrc 28 | - **builder**: resolve clippy warnings 29 | - **project**: rename PR template 30 | - **flake-parts**: rename PR template 31 | - **flake-parts**: rename to gh-dependabot 32 | 33 | ## 1.0.0-b1 (2024-08-01) 34 | 35 | ### Feat 36 | 37 | - **flake-parts**: add parallelism to gh-actions 38 | - **flake**: move builder tests to a flake check 39 | - **flake**: add cargo test to checkPhase 40 | - **flake-parts**: add manual dispatch to gh CI 41 | - **docs**: add cargo doc package 42 | - **flake-parts**: add gh-actions-cachix part 43 | - **builder**: improve perf by making meta pure 44 | - **flake-parts**: add nix-topology 45 | - **flake**: update cargoSha256 46 | - **builder**: implement add command 47 | - **builder**: add unresolved dependencies error 48 | - **buider**: add recursive dependencies resolver 49 | - **builder**: add proper _bootstrap to list cmd 50 | - **builder**: nixfmt, _bootstrap, parts tuples 51 | - **flake**: fill meta attrs, fix builder src 52 | - **builder**: visually distiguish collections 53 | - **flake-parts**: add README.md to bootstrap 54 | - **flake-parts**: add flake-parts collections 55 | - **flake-parts**: add gh-actions-flakehub 56 | - **flake-parts**: add gh-actions-check 57 | - **flake-parts**: add gh-actions-flake-update 58 | - **flake-parts**: add gh-actions-pages 59 | - **flake-parts**: add gh-template-PR 60 | - **flake-parts**: add gh-templates-issues 61 | - **flake-parts**: add gitlab-ci-check 62 | - **builder**: remove itertools requirement 63 | - **builder**: separate nix cli & flake-inputs template 64 | - **builder**: add flake.nix inputs template to init 65 | - **flake-parts**: add process-compose to generic .envrc 66 | - **builder**: separate constant into a config mod 67 | - **builder**: switch anyhow with color-eyre 68 | - **builder**: add fs_utils & init finalization 69 | - **builder**: add regex dep 70 | - **builder**: decouple & modularize src 71 | - **flake-parts**: create one modular unified .envrc 72 | - **builder**: add diff,fs_extra,walkdir crates 73 | - **builder**: move parts parsing into a fun 74 | - **builder**: complete init cmd parts parsing 75 | - **builder**: add itertools dependency 76 | - **flake-parts**: add home-manager related parts 77 | - **flake-parts**: add agenix part 78 | - **flake-parts**: add git pre-commit-hooks part 79 | - **feat-parts**: add numtide binary cache to treefmt 80 | - **flake-parts**: add conditional process-compose to shells 81 | - **flake-parts**: add process-compose-flake part 82 | - **builder**: update parts.rs 83 | - **devshell**: update devshell & add direnv integration 84 | - **flake-parts**: add shells, process-compose and update treefmt 85 | - **flake-parts**: add treefmt,devenv 86 | - **parts**: add flake-root,overlays,nixos-hosts,lib 87 | - **parts**: add _base skeleton, pkgs, nixos parts 88 | - **parts**: add more descriptive errors & update structs 89 | - **builder**: separate CLI logic from parts parsing 90 | - **project**: init new builder & parts & derivations 91 | - **project**: init v1 builder rewrite 92 | - **flake**: update inputs & treefmt settings patch 93 | - **treefmt**: update defaults 94 | - **devenv**: update to v1 & add examples & remove impurities 95 | - **flake**: update flake inputs 96 | - **.envrc**: update nix-direnv to version 3 97 | - **flake**: update inputs & reformat 98 | - **envrc**: add --accept-flake-config flag to templates 99 | - **flake**: update inputs 100 | 101 | ### Fix 102 | 103 | - **version**: add cz & fix version number 104 | - **gh-workflows**: fix nix-fast-build args 105 | - **flake**: update builder cargoSha256 106 | - **cachix-push**: fix version main -> v15 107 | - **builder**: correct SELF_FLAKE_URI 108 | - **builder**: fix cmd.path parsing 109 | - **builder**: fix conflict resolution in init 110 | - **builder**: update temporarily SELF_FLAKE_URI 111 | - **flake**: correct cargoHash 112 | - **builder**: fix merging strats and force flag 113 | - **builder**: tweak template whitespacing 114 | - **builder**: fix flake.nix template rendering 115 | - **flake-parts**: fix process-compose-flake structure 116 | - **flake-parts**: fix minor flake-parts issues 117 | - **flake-parts**: fix small discovered bugs 118 | - **flake-parts**: fix flake-root dir structure 119 | - **flake-parts**: fix conditional treefmt in devenv part 120 | - **envrc**: fix incorrent devshell watch filename 121 | 122 | ### Refactor 123 | 124 | - **flake**: move docs directly to builder drv 125 | - **builder**: fix flake.nix.tmpl formatting 126 | - **builder**: update flake.nix tmpl comment 127 | - **builder**: separate shared cli args 128 | - **builder**: decouple tmpl into templates.rs 129 | - **builder**: move missing,conflicting parts 130 | - **flake**: localize using callPackage pattern 131 | - **flake-parts**: remove redundant .gitignore 132 | - **builder**: decouple init() 133 | - **builder**: remove redundant format_module fun 134 | - **builder**: remove redundant .gitignore 135 | - **flake-parts**: rename _base -> _bootstrap 136 | - **devshell**: remove WIP rust from devshell 137 | - **main.rs**: rename cli options 138 | - **parts.rs**: remove redundant temporary anyhow 139 | - **flake**: remove .code-workspace vscode file 140 | - **flake-parts**: rename parts -> flake-parts 141 | - **parts**: rename parts -> flake-parts 142 | - **formatting**: switch to nixfmt-rfc-style 143 | - **modules**: use module functors to localize inputs instead of global ones 144 | 145 | ## 0.2.0 (2024-01-06) 146 | 147 | ### Feat (0.2.0) 148 | 149 | - **flakes**: remove unnecessary lockfiles 150 | - **.envrc**: nix_direnv_watch_file -> watch_file 151 | - **template-flakes**: remove nixpkgs setup, add debug comment 152 | - **templates**: add additional sensible caches & update .envrc 153 | - **flakehub**: add project to flakehub, update examples in README 154 | - **templates**: add home-manager template 155 | - **templates**: add isolated and isolated-minimal templates 156 | - **templates**: add minimal template 157 | 158 | ### Fix (0.2.0) 159 | 160 | - **exampleFlakes**: fix typo 161 | 162 | ### Refactor (0.2.0) 163 | 164 | - **lib**: reformat files, update examples 165 | - **devshells**: remove unnecessary greeting bloat 166 | 167 | ## 0.1.0 (2023-11-20) 168 | 169 | ### Feat (0.1.0) 170 | 171 | - **README**: add usage note 172 | - **README**: add a proper README 173 | - **main-template**: add other examples, add some documentation 174 | - **CI**: add github and gitlab default config 175 | - **project**: add conventional commits and changelog 176 | - **main-template**: add main template + devenv, treefmt, examples 177 | - **main-template**: init 178 | - **flake**: init dev environment 179 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flake-parts-builder" 3 | version = "1.0.0-b2" 4 | edition = "2021" 5 | authors = ["Tomáš Sandrini"] 6 | repository = "https://github.com/tsandrini/flake-parts-builder" 7 | license = "MIT" 8 | 9 | [dependencies] 10 | clap = { version = "4.5.7", features = ["cargo", "derive"] } 11 | color-eyre = "0.6.3" 12 | diff = "0.1.13" 13 | env_logger = "0.11.5" 14 | fs_extra = "1.3.0" 15 | log = "0.4.22" 16 | minijinja = "2.0.2" 17 | regex = "1.10.5" 18 | serde = { version = "1.0.203", features = ["derive"] } 19 | serde_json = "1.0.117" 20 | tempfile = "3.10.1" 21 | termcolor = "1.4.1" 22 | thiserror = "1.0.61" 23 | walkdir = "2.5.0" 24 | which = "6.0.2" 25 | 26 | [dev-dependencies] 27 | serial_test = "3.1.1" 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tomáš Sandrini 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 | # flake-parts-builder 2 | 3 | [![flake check](https://github.com/tsandrini/flake-parts-builder/actions/workflows/flake-check.yml/badge.svg)](https://github.com/tsandrini/flake-parts-builder/actions/workflows/flake-check.yml) 4 | [![FlakeHub](https://github.com/tsandrini/flake-parts-builder/actions/workflows/flakehub-publish.yml/badge.svg)](https://github.com/tsandrini/flake-parts-builder/actions/workflows/flakehub-publish.yml) 5 | [![cachix](https://github.com/tsandrini/flake-parts-builder/actions/workflows/cachix-push.yml/badge.svg)](https://github.com/tsandrini/flake-parts-builder/actions/workflows/cachix-push.yml) 6 | [![flake.lock update](https://github.com/tsandrini/flake-parts-builder/actions/workflows/update-flake-lock.yml/badge.svg)](https://github.com/tsandrini/flake-parts-builder/actions/workflows/update-flake-lock.yml) 7 | 8 | ## 1. About 📝 9 | 10 | Building a new [flake-parts](https://github.com/hercules-ci/flake-parts) project? 11 | Need a template with all the necessary boilerplate, but none perfectly fits your 12 | needs? Just choose the parts that you need and **build your own**! 13 | 14 | ```bash 15 | nix run github:tsandrini/flake-parts-builder -- init -p +github,+nixos,treefmt myNewProject 16 | ``` 17 | 18 | ----- 19 | 20 | Templates defined to be used with `nix flake init -t` typically suffer from 21 | the case of being **static** and too simple. They usually address only one 22 | specific thing or problem domain (eg. devenv, rust, flake-parts, dotfiles, ...) 23 | which makes the end user quickly start running into issues when trying to 24 | combine said domains since real life flake projects rarely require only one 25 | such domain. 26 | 27 | And this is what `flake-parts-builder` solves! It serves as a 28 | **dynamic extension** to `nix flake init -t`, nothing more, nothing less! 29 | So let's forget about `nix flake init -t` and embrace `nix run` instead :sunglasses: 30 | 31 | ----- 32 | 33 | Okay, but what exactly does it do then? 34 | 35 | - `flake-parts-builder init` - **initialize** a new project with all your 36 | required parts 37 | - `flake-parts-builder add` - **add** new parts to an already existing 38 | flake-parts project 39 | - `flake-parts-builder list` - **list** all currently available flake-parts to 40 | be used with the `list` and `add` subcommands 41 | 42 | ## 2. Installation 🤖 43 | 44 | **Disclaimer**: `flake-parts-builder` is built on top of nix 45 | [flakes](https://wiki.nixos.org/wiki/Flakes) and 46 | [flake-parts](https://github.com/hercules-ci/flake-parts) hence why familiarity 47 | with flakes is a necessity. The builder also currently runs in flakes mode only 48 | and uses flakes to parse flake-parts stores. The following "experimental" 49 | features are then a forced requirement 50 | `--experimental-features 'nix-command flakes'`. 51 | 52 | *NOTE*: if enough people will be using this project I don't have any issues 53 | with pushing it into upstream [nixpkgs](https://github.com/NixOS/nixpkgs). 54 | 55 | ### 2.1. Nix CLI 56 | 57 | ```bash 58 | nix profile install github:tsandrini/flake-parts-builder 59 | ``` 60 | 61 | ### 2.2. NixOS 62 | 63 | ```nix 64 | { 65 | inputs.flake-parts-builder.url = "github:tsandrini/flake-parts-builder"; 66 | 67 | outputs = { self, nixpkgs, flake-parts-builder }: { 68 | # change `yourhostname` to your actual hostname 69 | nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem { 70 | # change to your system: 71 | system = "x86_64-linux"; 72 | modules = [ 73 | ./configuration.nix 74 | ({ system, ... }: { 75 | environment.systemPackages = [ flake-parts-builder.packages.${system}.default ]; 76 | }) 77 | ]; 78 | }; 79 | }; 80 | } 81 | ``` 82 | 83 | ### 3. Binary cache 💾 84 | 85 | `flake-parts-builder` is written in Rust with minimal dependencies to ensure 86 | safety and consistency, however, there is also a binary cache available if you'd 87 | like to skip the build process. 88 | 89 | ```nix 90 | nixConfig = { 91 | extra-substituters = [ 92 | "https://tsandrini.cachix.org" 93 | ]; 94 | extra-trusted-public-keys = [ 95 | "tsandrini.cachix.org-1:t0AzIUglIqwiY+vz/WRWXrOkDZN8TwY3gk+n+UDt4gw=" 96 | ]; 97 | }; 98 | ``` 99 | 100 | ## 4. Available parts 📂 101 | 102 | You can list all of the available parts with the `flake-parts-builder list` 103 | subcommand, which goes through all of the flake-parts stores passed via the `-I` 104 | or `--include` flag. Here is the current output the list subcommand running on 105 | only the base parts provided by this flake (note that you can disable the base 106 | parts using `--disable-base` if you wish so) 107 | 108 | ```bash 109 | flake-parts-builder list 110 | ``` 111 | 112 | ```md 113 | # github:tsandrini/flake-parts-builder#flake-parts 114 | - +github: (Collection) GitHub related parts 115 | - +home-manager: (Collection) Home-manager related parts. 116 | - +nixos: (Collection) NixOS related parts. 117 | - +nixvim: (Collection) All of the nixvim related parts. 118 | - agenix: Bindings for the agenix secrets manager with prepared NixOS/HM modules ready to be used in your configurations. 119 | - deploy-rs: A Simple multi-profile Nix-flake deploy tool. 120 | - devenv: Flake bindings for the `github:cachix/devenv` development environment. 121 | - flake-root: Provides `config.flake-root` variable pointing to the root of the flake project. 122 | - gh-actions-cachix: Adds a simple cachix/cachix-action GitHub action workflow. 123 | - gh-actions-check: Adds a simple `nix flake check` GitHub action workflow. 124 | - gh-actions-flake-update: Adds the periodic `DeterminateSystems/update-flake-lock` GitHub action workflow. 125 | - gh-actions-flakehub: Adds the push to FlakeHub GitHub action workflow. 126 | - gh-actions-pages: Adds a GitHub action that runs `nix build .#pages` and deploys the result to GitHub pages. 127 | - gh-dependabot: A basic GitHub dependabot starting template. 128 | - gh-templates-PR: Adds a basic GitHub pull request template. 129 | - gh-templates-issues: Adds basic bug/feature GitHub issue templates. 130 | - gitlab-ci-check: Adds a simple `nix flake check` to your GitLab CI/CD pipeline. 131 | - hm-homes: Template for your HM homes and a handy generator for you `homeManagerConfiguration` calls. 132 | - hm-modules: Basic template for custom home-manager modules. 133 | - lib: Basic template for custom nix library functions. 134 | - nix-topology: Adds bindings for the `github:oddlama/nix-topology` project to generate graphs of your networks. 135 | - nixos-hosts: Template for your NixOS hosts and a handy generator for `lib.nixosSystem` calls. 136 | - nixos-modules: Basic template for custom NixOS modules. 137 | - nixvim-configurations: Template for Nixvim configurations to handle multiple neovim instances. 138 | - nixvim-modules: Basic template for reusable nixvim modules. 139 | - overlays: Basic template for custom nixpkgs overlays. 140 | - pkgs: Basic template for custom nix packages (ie derivations). 141 | - pre-commit-hooks: Bindings for pre-commit-hooks.nix and a simple pre-commit-hook template. 142 | - process-compose-flake: Bindings for process-compose-flake and a simple process-compose template. 143 | - shells: Basic template for custom nix devshells (ie. `mkShell` calls) with potential bindings to other parts. 144 | - systems: Sets up the default `systems` of flake-parts using `github:nix-systems/default`. 145 | - treefmt: Bindings for the treefmt formatter and a basic treefmt configuration. 146 | 147 | # github:tsandrini/flake-parts-builder#flake-parts-bootstrap 148 | - _bootstrap: (Required) Minimal set of functions used to bootstrap your flake-parts project. 149 | ``` 150 | 151 | ## 6. `flake-parts-builder.lib` API 152 | 153 | If you'd like to remove the `./flake-parts/_bootstrap.nix` file or you'd prefer 154 | using any of the flake-parts functionality in a different set of circumstances 155 | then you can use the `flake-parts-builder.lib` output that this repo exposes. You could 156 | then rewrite your `flake.nix` in the following manner (this, however, adds an 157 | additional dependency to your project) 158 | 159 | ```nix 160 | # --- flake.nix 161 | { 162 | inputs = { 163 | # --- BASE DEPENDENCIES --- 164 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 165 | flake-parts.url = "github:hercules-ci/flake-parts"; 166 | flake-parts-builder.url = "github:tsandrini/flake-parts-builder"; 167 | 168 | # --- (YOUR) EXTRA DEPENDENCIES --- 169 | }; 170 | 171 | outputs = 172 | inputs@{ flake-parts, ... }: 173 | let 174 | inherit (inputs.nixpkgs) lib; 175 | inherit (inputs.flake-parts-builder.lib) loadParts; 176 | in 177 | flake-parts.lib.mkFlake { inherit inputs; } { 178 | imports = loadParts ./flake-parts; 179 | }; 180 | } 181 | ``` 182 | 183 | For more info regarding the API of any of these functions, please refer to the 184 | doccomments of said functions in the `flake.nix` file. 185 | 186 | ## 7. Using your own parts 👨‍💻👩‍💻 187 | 188 | `flake-parts-builder` was designed from the ground up with extensibility in mind. 189 | To be able to use local parts, remote parts and cache parts in an easy manner 190 | the CLI accepts additional flake-parts stores via the `-I` or `--include` flag 191 | as flake derivation outputs. Meaning that you can run 192 | 193 | ```bash 194 | flake-parts-builder init -I ./myDir#flake-parts -p shells,pkgs,my-custom-part myNewProject 195 | ``` 196 | 197 | or even remote parts stores 198 | 199 | ```bash 200 | flake-parts-builder init -I github:org/my-flake-project#flake-parts -p shells,my-custom-remote-part myNewProject 201 | ``` 202 | 203 | Thanks to the wonders of nix, the flake-parts stores will be resolved & fetched 204 | only once and on successive calls, they will be copied directly from you local 205 | `/nix/store` cache. 206 | 207 | ### 7.1. Custom flake-parts-stores 208 | 209 | A **flake-part store** is any derivation that has **flake-parts** located at 210 | `$out/flake-parts`, so for example the following snippet 211 | 212 | ```nix 213 | stdenv.mkDerivation { 214 | name = "my-custom-flake-parts"; 215 | version = "1.0.0"; 216 | src = ./flake-parts; 217 | 218 | dontConfigure = true; 219 | dontBuild = true; 220 | dontCheck = true; 221 | 222 | installPhase = '' 223 | mkdir -p $out/flake-parts 224 | cp -rv $src/* $out/flake-parts 225 | ''; 226 | } 227 | ``` 228 | 229 | This flake also exposes a handy wrapper at `flake-parts-builder.lib.mkFlakeParts`, 230 | which shortens the previous example to 231 | 232 | ```nix 233 | mkFlakeParts { 234 | inherit stdenv; # NOTE: Required 235 | name = "my-custom-flake-parts"; 236 | version = "1.0.0"; 237 | src = ./flake-parts; 238 | } 239 | ``` 240 | 241 | ### 7.2. Custom flake-parts 242 | 243 | A **flake-part** is any folder with a **meta.nix** file at its root containing 244 | an attrset with the following structure. 245 | 246 | ```nix 247 | { 248 | description = "Flake bindings for the cachix/devenv development environment."; 249 | 250 | inputs = { 251 | devenv.url = "github:cachix/devenv" 252 | # .... 253 | }; 254 | dependencies = [ ]; 255 | conflicts = [ "shells" ]; 256 | extraTrustedPublicKeys = [ "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=" ]; 257 | extraSubstituters = [ "https://devenv.cachix.org" ]; 258 | } 259 | ``` 260 | 261 | - `description`: a simple description of the provided parts printed when running 262 | `flake-parts-builder list` 263 | - `inputs`: flake inputs that will be recursively merged from all required parts 264 | and pasted into the final `flake.nix` file 265 | - `dependencies`: with this you can add any additional required parts for the 266 | initialization/addition, this can be either a full flake uri (eg. 267 | `github:org/my-flake-project#flake-parts/my-custom-flake-part`) or a local 268 | reference to some other part **included in the same flake-parts store** 269 | (eg. `my-custom-part`) 270 | - `conflicts`: a specification of other potentially conflicting parts 271 | (for example when handling the same functionality, like `devenv` and `shells`) 272 | that should abort the process in case of found conflict, note that you can 273 | force the initialization/addition even in case of conflict with the 274 | `--ignore-conflicts` 275 | - `extraTrustedPublicKeys`: merged with all of the required parts and pasted into 276 | the final `flake.nix`, for security purposes they are all commented out 277 | - `extraSubstituters`: merged with all of the required parts and pasted into the 278 | final `flake.nix`, for security purposes they are all commented out 279 | 280 | ## 8. Additional questions, issues 🗣️ 281 | 282 | ### 8.1. How can I use a custom version of the `nix` or `nixfmt` binary? 283 | 284 | If installed via the nix package manager, `flake-parts-builder` will use 285 | an isolated version of `pkgs.nixVersions.stable` with 286 | `--extra-experimental-features 'nix-command flakes'` enabled. However, if you'd 287 | like to use a custom version instead, simply pass it via `$NIX_BIN_PATH`, 288 | for example 289 | 290 | ```bash 291 | NIX_BIN_PATH=/bin/patched-nix flake-parts-builder init -p +home-manager,shells myNewProject 292 | ``` 293 | 294 | The same thing works for overriding the `nixfmt` binary using the 295 | `NIXFMT_BIN_PATH` environment variable 296 | 297 | ```bash 298 | NIXFMT_BIN_PATH=/bin/nixfmt-classic flake-parts-builder init -p +home-manager,shells myNewProject 299 | ``` 300 | 301 | ### 8.2. Why not use `flake.templates` instead? 302 | 303 | The `flake.templates` flake output is a static property by design that needs 304 | to point to a fixed path with fixed content known ahead of time, which makes 305 | it heavily impractical for any kind of dynamic evaluation. One could, 306 | given the set of parts, prepare all of the possible combinations of templates 307 | with some patching script and 308 | directly update the source code of `flake.nix`, however ... At the time of 309 | this writing there are currently $27+1$ flake parts provided by this flake in 310 | the base collection of parts, which would result in 311 | 312 | ```math 313 | 2^{28} - 1 = 268435455 314 | ``` 315 | 316 | total combinations of templates and with an average part size of 317 | $8.59 \pm 2.60$ KB this would result in $2.14$ total terabytes of data 318 | with just one part per template. :skull: 319 | 320 | I hope this is enough of an answer. 321 | 322 | ### 8.3. Can't we just stuff this functionality into `flakeModules`? 323 | 324 | I totally agree there is a fine line between a reusable piece of functionality 325 | and boilerplate template code and I personally can't think of a general enough 326 | definition that would discern them and also be somehow useful. However, I do 327 | believe there is a practical, clearly visible difference between them that most 328 | programmers can just simply look and see, let's for example take .... 329 | [devenv/dev.nix](flake-parts/devenv/flake-parts/devenv/dev.nix) or 330 | [nix-topology/topology.nix](flake-parts/nix-topology/flake-parts/nix-topology/topology.nix) 331 | or even 332 | [flake-check.yml](flake-parts/gh-actions-check/.github/workflows/flake-check.yml), 333 | you can clearly **"see"** that this isn't a good candidate for a `flakeModule`, 334 | they are too specific, they typically represent the end user options of some 335 | existing `flakeModule`s. Wrapping this code into another layer of modularity 336 | doesn't make sense, since this is meant to be a piece of configuration code. 337 | 338 | ### 8.4. Help! I'm experiencing an XYZ bug! 339 | 340 | I'm sorry for the inconvenience, please run whatever is producing said bug 341 | with these `RUST_LOG=debug RUST_BACKTRACE=full` environment variables, 342 | for example 343 | 344 | ``` bash 345 | RUST_LOG=debug RUST_BACKTRACE=full flake-parts-builder add shells ./myProject 346 | ``` 347 | 348 | and paste the output into a new bug issue. Thanks! :heart: 349 | -------------------------------------------------------------------------------- /flake-parts-bootstrap/_bootstrap/.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then 4 | source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" 5 | fi 6 | 7 | watch_file flake.nix 8 | watch_file flake.lock 9 | 10 | # Conditionally watch dev files only if they exist 11 | [ -f flake-parts/devenv/dev.nix ] && watch_file flake-parts/devenv/dev.nix 12 | [ -f flake-parts/shells/dev.nix ] && watch_file flake-parts/shells/dev.nix 13 | [ -f flake-parts/process-compose-flake/dev.nix ] && watch_file flake-parts/process-compose-flake/dev.nix 14 | [ -f flake-parts/treefmt.nix ] && watch_file flake-parts/treefmt.nix 15 | [ -f flake-parts/pre-commit-hooks.nix ] && watch_file flake-parts/pre-commit-hooks.nix 16 | 17 | # Check which devshell implementation we are using and load that one 18 | if [ -f flake-parts/devenv/dev.nix ]; then 19 | if ! use flake .#dev --accept-flake-config --override-input devenv-root "file+file://"<(printf %s "$PWD"); then 20 | echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to dev.nix and hit enter to try again." >&2 21 | fi 22 | elif [ -f flake-parts/shells/dev.nix ]; then 23 | if ! use flake .#dev --accept-flake-config; then 24 | echo "devshell could not be built. Make sure dev.nix is a valid devshell and try again." >&2 25 | fi 26 | fi 27 | -------------------------------------------------------------------------------- /flake-parts-bootstrap/_bootstrap/.gitignore: -------------------------------------------------------------------------------- 1 | # --------- 2 | # Nix/NixOS 3 | # --------- 4 | 5 | ## Builds 6 | build/ 7 | result 8 | result-* 9 | 10 | ## devshells/devenv/direnv 11 | .direnv 12 | .devenv 13 | .pre-commit-config.yaml 14 | 15 | # ----------- 16 | # Editors/IDE 17 | # ----------- 18 | 19 | ## VIM 20 | 21 | ### Swap 22 | [._]*.s[a-v][a-z] 23 | !*.svg # comment out if you don't need vector files 24 | [._]*.sw[a-p] 25 | [._]s[a-rt-v][a-z] 26 | [._]ss[a-gi-z] 27 | [._]sw[a-p] 28 | 29 | ### Session 30 | Session.vim 31 | Sessionx.vim 32 | 33 | ### Temporary 34 | .netrwhist 35 | *~ 36 | ### Auto-generated tag files 37 | tags 38 | ### Persistent undo 39 | [._]*.un~ 40 | 41 | ## VSCode 42 | .vscode/* 43 | !.vscode/settings.json 44 | !.vscode/tasks.json 45 | !.vscode/launch.json 46 | !.vscode/extensions.json 47 | !.vscode/*.code-snippets 48 | 49 | ### Local History for Visual Studio Code 50 | .history/ 51 | 52 | ### Built Visual Studio Code Extensions 53 | *.vsix 54 | 55 | ## Emacs 56 | *~ 57 | \#*\# 58 | /.emacs.desktop 59 | /.emacs.desktop.lock 60 | *.elc 61 | auto-save-list 62 | tramp 63 | .\#* 64 | 65 | ### Org-mode 66 | .org-id-locations 67 | *_archive 68 | 69 | ### flymake-mode 70 | *_flymake.* 71 | 72 | ### eshell files 73 | /eshell/history 74 | /eshell/lastdir 75 | 76 | ### elpa packages 77 | /elpa/ 78 | 79 | ### reftex files 80 | *.rel 81 | 82 | ### AUCTeX auto folder 83 | /auto/ 84 | 85 | ### cask packages 86 | .cask/ 87 | dist/ 88 | 89 | ### Flycheck 90 | flycheck_*.el 91 | 92 | ### server auth directory 93 | /server/ 94 | 95 | ### projectiles files 96 | .projectile 97 | 98 | ### directory configuration 99 | .dir-locals.el 100 | 101 | ### network security 102 | /network-security.data 103 | 104 | ## JetBrains IDE 105 | ### Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 106 | ### Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 107 | 108 | ### User-specific stuff 109 | .idea/**/workspace.xml 110 | .idea/**/tasks.xml 111 | .idea/**/usage.statistics.xml 112 | .idea/**/dictionaries 113 | .idea/**/shelf 114 | 115 | ### AWS User-specific 116 | .idea/**/aws.xml 117 | 118 | ### Generated files 119 | .idea/**/contentModel.xml 120 | 121 | ### Sensitive or high-churn files 122 | .idea/**/dataSources/ 123 | .idea/**/dataSources.ids 124 | .idea/**/dataSources.local.xml 125 | .idea/**/sqlDataSources.xml 126 | .idea/**/dynamic.xml 127 | .idea/**/uiDesigner.xml 128 | .idea/**/dbnavigator.xml 129 | 130 | ### Gradle 131 | .idea/**/gradle.xml 132 | .idea/**/libraries 133 | 134 | ### CMake 135 | cmake-build-*/ 136 | 137 | ### Mongo Explorer plugin 138 | .idea/**/mongoSettings.xml 139 | 140 | ### File-based project format 141 | *.iws 142 | 143 | ### IntelliJ 144 | out/ 145 | 146 | ### mpeltonen/sbt-idea plugin 147 | .idea_modules/ 148 | 149 | ### JIRA plugin 150 | atlassian-ide-plugin.xml 151 | 152 | ### Cursive Clojure plugin 153 | .idea/replstate.xml 154 | 155 | ### SonarLint plugin 156 | .idea/sonarlint/ 157 | 158 | ### Crashlytics plugin (for Android Studio and IntelliJ) 159 | com_crashlytics_export_strings.xml 160 | crashlytics.properties 161 | crashlytics-build.properties 162 | fabric.properties 163 | 164 | ### Editor-based Rest Client 165 | .idea/httpRequests 166 | 167 | ### Android studio 3.1+ serialized cache file 168 | .idea/caches/build_file_checksums.ser 169 | 170 | 171 | # ----------- 172 | # OS specific 173 | # ----------- 174 | 175 | ## Linux 176 | *~ 177 | ### temporary files which can be created if a process still has a handle open of a deleted file 178 | .fuse_hidden* 179 | ### KDE directory preferences 180 | .directory 181 | ### Linux trash folder which might appear on any partition or disk 182 | .Trash-* 183 | ### .nfs files are created when an open file is removed but is still being accessed 184 | .nfs* 185 | 186 | 187 | ## macOS 188 | 189 | ### General 190 | .DS_Store 191 | .AppleDouble 192 | .LSOverride 193 | 194 | ### Icon must end with two \r 195 | Icon 196 | 197 | ### Thumbnails 198 | ._* 199 | 200 | ### Files that might appear in the root of a volume 201 | .DocumentRevisions-V100 202 | .fseventsd 203 | .Spotlight-V100 204 | .TemporaryItems 205 | .Trashes 206 | .VolumeIcon.icns 207 | .com.apple.timemachine.donotpresent 208 | 209 | ### Directories potentially created on remote AFP share 210 | .AppleDB 211 | .AppleDesktop 212 | Network Trash Folder 213 | Temporary Items 214 | .apdisk 215 | 216 | # ----- 217 | # Misc 218 | # ----- 219 | 220 | ## Backups 221 | *.bak 222 | *.gho 223 | *.ori 224 | *.orig 225 | *.tmp 226 | 227 | ## Tags 228 | 229 | ### Ignore tags created by etags, ctags, gtags (GNU global) and cscope 230 | TAGS 231 | .TAGS 232 | !TAGS/ 233 | tags 234 | .tags 235 | !tags/ 236 | gtags.files 237 | GTAGS 238 | GRTAGS 239 | GPATH 240 | GSYMS 241 | cscope.files 242 | cscope.out 243 | cscope.in.out 244 | cscope.po.out 245 | -------------------------------------------------------------------------------- /flake-parts-bootstrap/_bootstrap/README.md: -------------------------------------------------------------------------------- 1 | # NAMEPLACEHOLDER 2 | 3 | ## About 4 | 5 | ## References 6 | 7 | 1. This project was built using [tsandrini/flake-parts-builder](https://github.com/tsandrini/flake-parts-builder/) 8 | -------------------------------------------------------------------------------- /flake-parts-bootstrap/_bootstrap/flake-parts/_bootstrap.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/_bootstrap.nix 2 | { lib }: 3 | rec { 4 | # This nix file is used to minimally set up and bootstrap the `loadParts` 5 | # function which is then used to load all the modules in the 6 | # `./flake-parts` directory. The user also has the option to remove 7 | # this file and directly load the `loadParts` function from 8 | # the `lib` attribute of the `github:tsandrini/flake-parts-builder` 9 | # flake, however, that brings an additional dependency to the project, 10 | # which may be undesirable for some and isn't really necessary. 11 | 12 | 13 | /* 14 | Main function for recursively traversing and loading all the modules 15 | in a provided flake-parts directory. 16 | 17 | For more information and specifics on how this function works, see the 18 | doccomment of the `loadModules` function below. 19 | 20 | *Type*: `loadParts :: Path -> { name :: String; value :: AttrSet a; }` 21 | */ 22 | loadParts = dir: flatten (mapModules dir (x: x)); 23 | 24 | /* 25 | Recursively flattens a nested attrset into a list of just its values. 26 | 27 | *Type*: `flatten :: AttrSet a -> [a]` 28 | 29 | Example: 30 | ```nix title="Example" linenums="1" 31 | flatten { 32 | keyA = 10; 33 | keyB = "str20"; 34 | keyC = { 35 | keyD = false; 36 | keyE = { 37 | a = 10; 38 | b = "20"; 39 | c = false; 40 | }; 41 | }; 42 | } 43 | => [ 10 "str20" false 10 "20" false ] 44 | ``` 45 | */ 46 | flatten = attrs: lib.collect (x: !lib.isAttrs x) attrs; 47 | 48 | /* 49 | Apply a map to every attribute of an attrset and then filter the resulting 50 | attrset based on a given predicate function. 51 | 52 | *Type*: `mapFilterAttrs :: (AttrSet b -> Bool) -> (AttrSet a -> AttrSet b) -> AttrSet a -> AttrSet b` 53 | */ 54 | mapFilterAttrs = 55 | pred: f: attrs: 56 | lib.filterAttrs pred (lib.mapAttrs' f attrs); 57 | 58 | /* 59 | Recursively read a directory and apply a provided function to every `.nix` 60 | file. Returns an attrset that reflects the filenames and directory 61 | structure of the root. 62 | 63 | Notes: 64 | 65 | 1. Files and directories starting with the `_` or `.git` prefix will be 66 | completely ignored. 67 | 68 | 2. If a directory with a `myDir/default.nix` file will be encountered, 69 | the function will be applied to the `myDir/default.nix` file 70 | instead of recursively loading `myDir` and applying it to every file. 71 | 72 | *Type*: `mapModules :: Path -> (Path -> AttrSet a) -> { name :: String; value :: AttrSet a; }` 73 | 74 | Example: 75 | ```nix title="Example" linenums="1" 76 | mapModules ./modules import 77 | => { hardware = { moduleA = { ... }; }; system = { moduleB = { ... }; }; } 78 | 79 | mapModules ./hosts (host: mkHostCustomFunction myArg host) 80 | => { hostA = { ... }; hostB = { ... }; } 81 | ``` 82 | */ 83 | mapModules = 84 | dir: fn: 85 | mapFilterAttrs (n: v: v != null && !(lib.hasPrefix "_" n) && !(lib.hasPrefix ".git" n)) ( 86 | n: v: 87 | let 88 | path = "${toString dir}/${n}"; 89 | in 90 | if v == "directory" && builtins.pathExists "${path}/default.nix" then 91 | lib.nameValuePair n (fn path) 92 | else if v == "directory" then 93 | lib.nameValuePair n (mapModules path fn) 94 | else if v == "regular" && n != "default.nix" && lib.hasSuffix ".nix" n then 95 | lib.nameValuePair (lib.removeSuffix ".nix" n) (fn path) 96 | else 97 | lib.nameValuePair "" null 98 | ) (builtins.readDir dir); 99 | } 100 | -------------------------------------------------------------------------------- /flake-parts-bootstrap/_bootstrap/meta.nix: -------------------------------------------------------------------------------- 1 | # --- meta.nix 2 | { 3 | description = "(Required) Minimal set of functions used to bootstrap your flake-parts project."; 4 | 5 | inputs = { }; 6 | extraTrustedPublicKeys = [ ]; 7 | extraSubstituters = [ ]; 8 | } 9 | -------------------------------------------------------------------------------- /flake-parts/+github/meta.nix: -------------------------------------------------------------------------------- 1 | # --- meta.nix 2 | { 3 | description = "(Collection) GitHub related parts"; 4 | 5 | inputs = { }; 6 | dependencies = [ 7 | "gh-actions-check" 8 | "gh-actions-flake-update" 9 | "gh-templates-issues" 10 | "gh-templates-PR" 11 | ]; 12 | extraTrustedPublicKeys = [ ]; 13 | extraSubstituters = [ ]; 14 | } 15 | -------------------------------------------------------------------------------- /flake-parts/+home-manager/meta.nix: -------------------------------------------------------------------------------- 1 | # --- meta.nix 2 | { 3 | description = "(Collection) Home-manager related parts."; 4 | 5 | inputs = { }; 6 | dependencies = [ 7 | "hm-modules" 8 | "hm-homes" 9 | ]; 10 | extraTrustedPublicKeys = [ ]; 11 | extraSubstituters = [ ]; 12 | } 13 | -------------------------------------------------------------------------------- /flake-parts/+nixos/meta.nix: -------------------------------------------------------------------------------- 1 | # --- meta.nix 2 | { 3 | description = "(Collection) NixOS related parts."; 4 | 5 | inputs = { }; 6 | dependencies = [ 7 | "nixos-hosts" 8 | "nixos-modules" 9 | ]; 10 | extraTrustedPublicKeys = [ ]; 11 | extraSubstituters = [ ]; 12 | } 13 | -------------------------------------------------------------------------------- /flake-parts/+nixvim/meta.nix: -------------------------------------------------------------------------------- 1 | # --- meta.nix 2 | { 3 | description = "(Collection) All of the nixvim related parts."; 4 | 5 | inputs = { }; 6 | dependencies = [ 7 | "nixvim-configurations" 8 | "nixvim-modules" 9 | ]; 10 | extraTrustedPublicKeys = [ ]; 11 | extraSubstituters = [ ]; 12 | } 13 | -------------------------------------------------------------------------------- /flake-parts/agenix/flake-parts/agenix/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/agenix/default.nix 2 | { 3 | config, 4 | lib, 5 | inputs, 6 | ... 7 | }: 8 | { 9 | options.secrets = with lib.types; { 10 | secretsPath = lib.mkOption { 11 | type = path; 12 | default = ./secrets; 13 | description = "Path to the actual secrets directory"; 14 | }; 15 | 16 | pubkeys = lib.mkOption { 17 | type = attrsOf (attrsOf anything); 18 | default = { }; 19 | description = '' 20 | The resulting option that will hold the various public keys used around 21 | the flake. 22 | ''; 23 | }; 24 | 25 | pubkeysFile = lib.mkOption { 26 | type = path; 27 | default = ./pubkeys.nix; 28 | description = '' 29 | Path to the pubkeys file that will be used to construct the 30 | `secrets.pubkeys` option. 31 | ''; 32 | }; 33 | 34 | extraPubkeys = lib.mkOption { 35 | type = attrsOf (attrsOf anything); 36 | default = { }; 37 | description = '' 38 | Additional public keys that will be merged into the `secrets.pubkeys` 39 | ''; 40 | }; 41 | }; 42 | 43 | config = { 44 | secrets.pubkeys = (import config.agenix.pubkeysFile) // config.agenix.extraPubkeys; 45 | 46 | flake.nixosModules.security_agenix = 47 | { 48 | config, 49 | lib, 50 | pkgs, 51 | system, 52 | ... 53 | }: 54 | with builtins; 55 | with lib; 56 | let 57 | cfg = config.NAMEPLACEHOLDER.security.agenix; 58 | in 59 | { 60 | options.NAMEPLACEHOLDER.security.agenix = with types; { 61 | enable = mkEnableOption '' 62 | Enables NixOS module that sets up & configures the agenix secrets 63 | backend. 64 | 65 | References 66 | - https://github.com/ryantm/agenix 67 | - https://nixos.wiki/wiki/Agenix 68 | ''; 69 | }; 70 | 71 | imports = with inputs; [ agenix.nixosModules.default ]; 72 | 73 | config = mkIf cfg.enable { 74 | environment.systemPackages = [ 75 | inputs.agenix.packages.${system}.default 76 | pkgs.age 77 | ]; 78 | 79 | age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; 80 | }; 81 | }; 82 | 83 | flake.homeModules.security_agenix = 84 | { config, lib, ... }: 85 | with builtins; 86 | with lib; 87 | let 88 | cfg = config.NAMEPLACEHOLDER.hm.security.agenix; 89 | in 90 | { 91 | options.NAMEPLACEHOLDER.hm.security.agenix = with types; { 92 | enable = mkEnableOption '' 93 | Enable Home Manager module that sets up & configures the agenix 94 | secrets backend. 95 | 96 | References 97 | - https://github.com/ryantm/agenix 98 | - https://nixos.wiki/wiki/Agenix 99 | ''; 100 | }; 101 | 102 | imports = with inputs; [ agenix.homeManagerModules.default ]; 103 | 104 | config = mkIf cfg.enable { 105 | age.identityPaths = [ "${config.home.homeDirectory}/.ssh/id_ed25519" ]; 106 | }; 107 | }; 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /flake-parts/agenix/flake-parts/agenix/pubkeys.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/agenix/pubkeys.nix 2 | let 3 | myUser = "TODO: Add your public key here"; 4 | in 5 | { 6 | common = { }; 7 | hosts = { 8 | myHost = { 9 | users = { 10 | root = { 11 | sshKey = null; 12 | authorizedKeys = [ ]; 13 | }; 14 | myUser = { 15 | sshKey = null; 16 | authorizedKeys = [ myUser ]; 17 | }; 18 | }; 19 | }; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /flake-parts/agenix/flake-parts/agenix/secrets/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /flake-parts/agenix/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Bindings for the agenix secrets manager with prepared NixOS/HM modules ready to be used in your configurations."; 3 | 4 | inputs = { 5 | agenix = { 6 | url = "github:ryantm/agenix"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | }; 10 | conflicts = [ ]; 11 | extraTrustedPublicKeys = [ ]; 12 | extraSubstituters = [ ]; 13 | } 14 | -------------------------------------------------------------------------------- /flake-parts/deploy-rs/flake-parts/deploy-rs/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/deploy-rs/default.nix 2 | { inputs, config, ... }: 3 | let 4 | inherit (inputs) deploy-rs; 5 | in 6 | { 7 | flake.deploy.nodes = { 8 | # NOTE example node configuration 9 | # "myExampleNode" = { 10 | # hostname = "10.0.0.1"; 11 | # 12 | # profiles.system = { 13 | # sshUser = "admin"; 14 | # user = "root"; 15 | # 16 | # autoRollback = true; 17 | # magicRollback = true; 18 | # 19 | # # TODO specify flake-parts attribute of your node configuration 20 | # path = deploy-rs.lib.x86_64-linux.activate.nixos config.flake.nixosConfigurations."myExampleNode"; 21 | # }; 22 | # }; 23 | }; 24 | 25 | # NOTE This way we can automatically enable checks for all nodes 26 | flake.checks = builtins.mapAttrs ( 27 | _system: deployLib: deployLib.deployChecks config.flake.deploy 28 | ) deploy-rs.lib; 29 | } 30 | -------------------------------------------------------------------------------- /flake-parts/deploy-rs/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A Simple multi-profile Nix-flake deploy tool."; 3 | 4 | inputs = { 5 | deploy-rs.url = "github:serokell/deploy-rs"; 6 | }; 7 | conflicts = [ ]; 8 | extraTrustedPublicKeys = [ ]; 9 | extraSubstituters = [ ]; 10 | } 11 | -------------------------------------------------------------------------------- /flake-parts/devenv/flake-parts/devenv/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/devenv/default.nix 2 | { inputs, lib, ... }: 3 | { 4 | imports = with inputs; [ devenv.flakeModule ]; 5 | 6 | perSystem = 7 | { 8 | config, 9 | pkgs, 10 | system, 11 | ... 12 | }: 13 | { 14 | devenv.shells = { 15 | default = config.devenv.shells.dev; 16 | 17 | dev = import ./dev.nix { 18 | inherit pkgs system; 19 | inherit (inputs) devenv-root; 20 | treefmt-wrapper = if (lib.hasAttr "treefmt" config) then config.treefmt.build.wrapper else null; 21 | }; 22 | }; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /flake-parts/devenv/flake-parts/devenv/dev.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/devenv/dev.nix 2 | { 3 | pkgs, 4 | devenv-root, 5 | treefmt-wrapper ? null, 6 | ... 7 | }: 8 | { 9 | # DEVENV: Fast, Declarative, Reproducible, and Composable Developer 10 | # Environments using Nix developed by Cachix. For more information refer to 11 | # 12 | # - https://devenv.sh/ 13 | # - https://github.com/cachix/devenv 14 | 15 | # -------------------------- 16 | # --- ENV & SHELL & PKGS --- 17 | # -------------------------- 18 | packages = 19 | with pkgs; 20 | ( 21 | (lib.optional (treefmt-wrapper != null) treefmt-wrapper) 22 | ++ [ 23 | # -- NIX UTILS -- 24 | nil # Yet another language server for Nix 25 | statix # Lints and suggestions for the nix programming language 26 | deadnix # Find and remove unused code in .nix source files 27 | nix-output-monitor # Processes output of Nix commands to show helpful and pretty information 28 | nixfmt-rfc-style # An opinionated formatter for Nix 29 | # NOTE Choose a different formatter if you'd like to 30 | # nixfmt # An opinionated formatter for Nix 31 | # alejandra # The Uncompromising Nix Code Formatter 32 | 33 | # -- GIT RELATED UTILS -- 34 | # commitizen # Tool to create committing rules for projects, auto bump versions, and generate changelogs 35 | # cz-cli # The commitizen command line utility 36 | # fh # The official FlakeHub CLI 37 | # gh # GitHub CLI tool 38 | # gh-dash # Github Cli extension to display a dashboard with pull requests and issues 39 | 40 | # -- BASE LANG UTILS -- 41 | markdownlint-cli # Command line interface for MarkdownLint 42 | # nodePackages.prettier # Prettier is an opinionated code formatter 43 | # typos # Source code spell checker 44 | 45 | # -- (YOUR) EXTRA PKGS -- 46 | ] 47 | ); 48 | 49 | enterShell = '' 50 | # Welcome splash text 51 | echo ""; echo -e "\e[1;37;42mWelcome to the NAMEPLACEHOLDER devshell!\e[0m"; echo "" 52 | ''; 53 | 54 | # --------------- 55 | # --- SCRIPTS --- 56 | # --------------- 57 | scripts = { 58 | "rename-project".exec = '' 59 | find $1 \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s/NAMEPLACEHOLDER/$2/g" 60 | ''; 61 | }; 62 | 63 | # ----------------- 64 | # --- LANGUAGES --- 65 | # ----------------- 66 | languages.nix.enable = true; 67 | 68 | # ---------------------------- 69 | # --- PROCESSES & SERVICES --- 70 | # ---------------------------- 71 | 72 | # ------------------ 73 | # --- CONTAINERS --- 74 | # ------------------ 75 | # devcontainer.enable = true; 76 | 77 | # ---------------------- 78 | # --- BINARY CACHING --- 79 | # ---------------------- 80 | # cachix.pull = [ "pre-commit-hooks" ]; 81 | # cachix.push = "NAME"; 82 | 83 | # ------------------------ 84 | # --- PRE-COMMIT HOOKS --- 85 | # ------------------------ 86 | # NOTE All available hooks options are listed at 87 | # https://devenv.sh/reference/options/#pre-commithooks 88 | pre-commit = { 89 | hooks = { 90 | treefmt.enable = if (treefmt-wrapper != null) then true else false; 91 | treefmt.package = if (treefmt-wrapper != null) then treefmt-wrapper else pkgs.treefmt; 92 | 93 | nil.enable = true; # Nix Language server, an incremental analysis assistant for writing in Nix. 94 | markdownlint.enable = true; # Markdown lint tool 95 | # typos.enable = true; # Source code spell checker 96 | 97 | # actionlint.enable = true; # GitHub workflows linting 98 | # commitizen.enable = true; # Commitizen is release management tool designed for teams. 99 | editorconfig-checker.enable = true; # A tool to verify that your files are in harmony with your .editorconfig 100 | }; 101 | }; 102 | 103 | # -------------- 104 | # --- FLAKES --- 105 | # -------------- 106 | devenv.flakesIntegration = true; 107 | 108 | # This is currently needed for devenv to properly run in pure hermetic 109 | # mode while still being able to run processes & services and modify 110 | # (some parts) of the active shell. 111 | devenv.root = 112 | let 113 | devenvRootFileContent = builtins.readFile devenv-root.outPath; 114 | in 115 | pkgs.lib.mkIf (devenvRootFileContent != "") devenvRootFileContent; 116 | 117 | # --------------------- 118 | # --- MISCELLANEOUS --- 119 | # --------------------- 120 | difftastic.enable = true; 121 | } 122 | -------------------------------------------------------------------------------- /flake-parts/devenv/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Flake bindings for the `github:cachix/devenv` development environment."; 3 | 4 | inputs = { 5 | devenv.url = "github:cachix/devenv"; 6 | devenv-root = { 7 | url = "file+file:///dev/null"; 8 | flake = false; 9 | }; 10 | mk-shell-bin.url = "github:rrbutani/nix-mk-shell-bin"; 11 | nix2container = { 12 | url = "github:nlewo/nix2container"; 13 | inputs.nixpkgs.follows = "nixpkgs"; 14 | }; 15 | }; 16 | dependencies = [ 17 | "systems" 18 | ]; 19 | conflicts = [ "shells" ]; 20 | extraTrustedPublicKeys = [ "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=" ]; 21 | extraSubstituters = [ "https://devenv.cachix.org" ]; 22 | } 23 | -------------------------------------------------------------------------------- /flake-parts/flake-root/flake-parts/flake-root.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/flake-root.nix 2 | { lib, ... }: 3 | { 4 | # NOTE This is probably conflicting with https://github.com/srid/flake-root/ 5 | # however it essentially fully replaces that functionality with a simple 6 | # option (thanks to the known structure) so it should be probably fine. 7 | options.flake-root = lib.mkOption { 8 | type = lib.types.path; 9 | description = '' 10 | Provides `config.flake-root` with the path to the flake root. 11 | ''; 12 | default = ../.; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /flake-parts/flake-root/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Provides `config.flake-root` variable pointing to the root of the flake project."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/gh-actions-cachix/.github/workflows/cachix-push.yml: -------------------------------------------------------------------------------- 1 | # --- Push packages & devshells to the cachix binary cache service 2 | name: cachix push 3 | 4 | on: 5 | workflow_dispatch: # allows manual triggering from the Actions UI 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | cachix-push: 12 | runs-on: "ubuntu-latest" 13 | steps: 14 | - name: "Checking out repository..." 15 | uses: actions/checkout@v4 16 | 17 | - name: "Installing and configuring the nix package manager..." 18 | uses: cachix/install-nix-action@v31 19 | with: 20 | extra_nix_config: | 21 | accept-flake-config = true 22 | 23 | # NOTE: Alternatively you can use the DeterminateSystems nix installer 24 | # - name: "Installing and configuring the nix package manager..." 25 | # uses: DeterminateSystems/nix-installer-action@main 26 | # with: 27 | # extra-conf: | 28 | # accept-flake-config = true 29 | 30 | - name: "Settings up cachix binary cache..." 31 | uses: cachix/cachix-action@v15 32 | with: 33 | name: mycache 34 | # If you chose signing key for write access 35 | signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' 36 | # If you chose API tokens for write access OR if you have a private cache 37 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 38 | 39 | # NOTE Install any necessary packages here 40 | - name: "Setting up packages..." 41 | run: | 42 | nix profile install nixpkgs#nix-fast-build # parallel nix builder 43 | 44 | - name: "Building project packages..." 45 | run: nix-fast-build --skip-cached --no-nom --flake ".#packages" 46 | # NOTE: You can also limit the build only to the currentSystem if needed 47 | # run: nix-fast-build --skip-cached --no-nom --flake ".#packages.$(nix eval --raw --impure --expr builtins.currentSystem)" 48 | 49 | - name: "Building project devShells..." 50 | run: nix-fast-build --skip-cached --no-nom --flake ".#devShells" 51 | # NOTE: You can also limit the build only to the currentSystem if needed 52 | # run: nix-fast-build --skip-cached --no-nom --flake ".#devShells.$(nix eval --raw --impure --expr builtins.currentSystem)" 53 | -------------------------------------------------------------------------------- /flake-parts/gh-actions-cachix/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Adds a simple cachix/cachix-action GitHub action workflow."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/gh-actions-check/.github/workflows/flake-check.yml: -------------------------------------------------------------------------------- 1 | # --- Run `nix flake check` 2 | name: nix flake check 3 | 4 | on: 5 | workflow_dispatch: # allows manual triggering from the Actions UI 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | types: [opened, reopened, synchronize] 11 | repository_dispatch: 12 | types: [create-pull-request] 13 | 14 | jobs: 15 | flake-check: 16 | runs-on: "ubuntu-latest" 17 | steps: 18 | - name: "Checking out repository..." 19 | uses: actions/checkout@v4 20 | 21 | - name: "Installing and configuring the nix package manager..." 22 | uses: cachix/install-nix-action@v31 23 | with: 24 | extra_nix_config: | 25 | accept-flake-config = true 26 | 27 | # NOTE: Alternatively you can use the DeterminateSystems nix installer 28 | # - name: "Installing and configuring the nix package manager..." 29 | # uses: DeterminateSystems/nix-installer-action@main 30 | # with: 31 | # extra-conf: | 32 | # accept-flake-config = true 33 | 34 | # NOTE Install any necessary packages here 35 | - name: "Setting up packages..." 36 | run: | 37 | nix profile install nixpkgs#nix-fast-build # parallel nix builder 38 | 39 | - name: "Running `nix flake check`..." 40 | run: nix-fast-build --skip-cached --no-nom 41 | # NOTE: You can also limit the build only to the currentSystem if needed 42 | # run: nix-fast-build --skip-cached --no-nom --flake ".#checks.$(nix eval --raw --impure --expr builtins.currentSystem)" 43 | 44 | - name: "Running `nix build ...`..." 45 | run: nix-fast-build --skip-cached --no-nom --flake ".#packages" 46 | # NOTE: You can also limit the build only to the currentSystem if needed 47 | # run: nix-fast-build --skip-cached --no-nom --flake ".#packages.$(nix eval --raw --impure --expr builtins.currentSystem)" 48 | 49 | - name: "Running `nix develop...`..." 50 | run: nix-fast-build --skip-cached --no-nom --flake ".#devShells" 51 | # NOTE: You can also limit the build only to the currentSystem if needed 52 | # run: nix-fast-build --skip-cached --no-nom --flake ".#devShells.$(nix eval --raw --impure --expr builtins.currentSystem)" 53 | 54 | - name: "Checking flake inputs for stale & insecure nixpkgs versions..." 55 | uses: DeterminateSystems/flake-checker-action@main 56 | -------------------------------------------------------------------------------- /flake-parts/gh-actions-check/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Adds a simple `nix flake check` GitHub action workflow."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/gh-actions-flake-update/.github/workflows/update-flake-lock.yml: -------------------------------------------------------------------------------- 1 | # --- Periodically update flake inputs in flake.lock 2 | name: update-flake-lock 3 | 4 | on: 5 | workflow_dispatch: # allows manual triggering from the Actions UI 6 | schedule: 7 | - cron: "0 0 * * 0" # runs weekly on Sunday at 00:00 8 | 9 | jobs: 10 | update-flake-lock: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: "Checking out repository..." 14 | uses: actions/checkout@v4 15 | 16 | - name: "Installing and configuring the nix package manager..." 17 | uses: cachix/install-nix-action@v31 18 | with: 19 | extra_nix_config: | 20 | accept-flake-config = true 21 | 22 | # NOTE: Alternatively you can use the DeterminateSystems nix installer 23 | # - name: "Installing and configuring the nix package manager..." 24 | # uses: DeterminateSystems/nix-installer-action@main 25 | # with: 26 | # extra-conf: | 27 | # accept-flake-config = true 28 | 29 | - name: "Updating flake.lock..." 30 | uses: DeterminateSystems/update-flake-lock@main 31 | with: 32 | pr-title: "Automated action - Update flake.lock" 33 | pr-labels: | 34 | dependencies 35 | automated 36 | 37 | # NOTE You can use a personal access token to identify 38 | # as a concrete user, this may be useful when you want to 39 | # trigger additional CI actions. 40 | # 41 | # token: ${{ secrets.GH_TOKEN_FOR_UPDATES }} 42 | -------------------------------------------------------------------------------- /flake-parts/gh-actions-flake-update/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Adds the periodic `DeterminateSystems/update-flake-lock` GitHub action workflow."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/gh-actions-flakehub/.github/workflows/flakehub-publish.yml: -------------------------------------------------------------------------------- 1 | # --- Publish flake to FlakeHub 2 | name: FlakeHub 3 | 4 | on: 5 | workflow_dispatch: # allows manual triggering from the Actions UI 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | flakehub-publish: 12 | runs-on: "ubuntu-latest" 13 | permissions: 14 | id-token: "write" 15 | contents: "read" 16 | steps: 17 | - name: "Checking out repository..." 18 | uses: actions/checkout@v4 19 | 20 | - name: "Installing and configuring the nix package manager..." 21 | uses: cachix/install-nix-action@v31 22 | with: 23 | extra_nix_config: | 24 | accept-flake-config = true 25 | 26 | # NOTE: Alternatively you can use the DeterminateSystems nix installer 27 | # - name: "Installing and configuring the nix package manager..." 28 | # uses: DeterminateSystems/nix-installer-action@main 29 | # with: 30 | # extra-conf: | 31 | # accept-flake-config = true 32 | 33 | - name: "Publishing flake to FlakeHub..." 34 | uses: DeterminateSystems/flakehub-push@main 35 | with: 36 | name: "ORG/NAMEPLACEHOLDER" # TODO: fill in the details 37 | rolling: true 38 | visibility: "public" 39 | -------------------------------------------------------------------------------- /flake-parts/gh-actions-flakehub/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Adds the push to FlakeHub GitHub action workflow."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/gh-actions-pages/.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: "Deploy static content to GitHub Pages" 2 | 3 | on: 4 | workflow_dispatch: # allows manual triggering from the Actions UI 5 | push: 6 | branches: 7 | - main 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # Allow only one concurrent deployment, skipping runs queued between the run 16 | # in-progress and latest queued. However, do NOT cancel in-progress runs as 17 | # we want to allow these production deployments to complete. 18 | concurrency: 19 | group: "pages" 20 | cancel-in-progress: false 21 | 22 | jobs: 23 | pages: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: "Checking out repository..." 27 | uses: actions/checkout@v4 28 | 29 | - name: "Installing and configuring the nix package manager..." 30 | uses: cachix/install-nix-action@v31 31 | with: 32 | extra_nix_config: | 33 | accept-flake-config = true 34 | 35 | # NOTE: Alternatively you can use the DeterminateSystems nix installer 36 | # - name: "Installing and configuring the nix package manager..." 37 | # uses: DeterminateSystems/nix-installer-action@main 38 | # with: 39 | # extra-conf: | 40 | # accept-flake-config = true 41 | 42 | - name: "Building static content using `nix build .#pages`..." 43 | run: nix build .#pages 44 | 45 | - name: "Setting up GitHub Pages..." 46 | uses: actions/configure-pages@v3 47 | 48 | - name: "Uploading static content..." 49 | uses: actions/upload-pages-artifact@v2 50 | with: 51 | path: "result" 52 | 53 | - name: "Deploying static content to GitHub Pages..." 54 | id: deployment 55 | uses: actions/deploy-pages@v2 56 | -------------------------------------------------------------------------------- /flake-parts/gh-actions-pages/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Adds a GitHub action that runs `nix build .#pages` and deploys the result to GitHub pages."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/gh-dependabot/.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | labels: 13 | - dependencies 14 | 15 | # NOTE: For additional ecosystems refer to the documentation 16 | # package-ecosystem: "gitsubmodule" 17 | # directory: "/" 18 | # schedule: 19 | # interval: "weekly" 20 | # labels: 21 | # - dependencies 22 | 23 | # - package-ecosystem: "docker" 24 | # directory: "/" 25 | # schedule: 26 | # interval: "weekly" 27 | # labels: 28 | # - dependencies 29 | -------------------------------------------------------------------------------- /flake-parts/gh-dependabot/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A basic GitHub dependabot starting template."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/gh-templates-PR/.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Overview 4 | 5 | 8 | 9 | ## Testing 10 | 11 | 16 | 17 | ## Dependencies 18 | 19 | 21 | 22 | ## Screenshots 23 | 25 | 26 | ## Checklist 27 | 28 | 29 | 30 | - [ ] I have tested the relevant changes locally. 31 | - [ ] I have checked that `nix flake check` passes. 32 | - [ ] I have ensured my commits follow the project's commits guidelines. 33 | - [ ] I have checked that the changes follow a linear history. 34 | - [ ] (If applicable) I have commented any relevant parts of my code. 35 | - [ ] (If applicable) I have added appropriate unit/feature tests. 36 | - [ ] (If applicable) I have updated the documentation accordingly (in English). 37 | 38 | ## Additional Notes 39 | 40 | 41 | -------------------------------------------------------------------------------- /flake-parts/gh-templates-PR/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Adds a basic GitHub pull request template."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/gh-templates-issues/.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🪲 Bug Report 3 | about: Create a bug report to help us resolve the bug 4 | title: "🪲[Bug]: " 5 | labels: "bug" 6 | assignees: "" 7 | --- 8 | 9 | ## Describe the bug 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | ## Steps To Reproduce 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. ... 18 | 1. ... 19 | 1. ... 20 | 21 | ## Expected behavior 22 | 23 | A clear and concise description of what you expected to happen. 24 | 25 | ## Screenshots 26 | 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | ## Additional context 30 | 31 | Add any other context about the problem here. 32 | 33 | ## Notify maintainers 34 | 35 | 39 | 40 | ## Metadata 41 | 42 | Please run `nix-shell -p nix-info --run "nix-info -m"` and paste the result. 43 | 44 | ```console 45 | [user@system:~]$ nix-shell -p nix-info --run "nix-info -m" 46 | output here 47 | ``` 48 | 49 | --- 50 | 51 | Add a :+1: \[reaction\] to \[issues you find important\]. 52 | -------------------------------------------------------------------------------- /flake-parts/gh-templates-issues/.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💡 Feature Request 3 | about: Suggest an interesting feature idea for this project 4 | title: '💡[Feature]: ' 5 | labels: "enhancement" 6 | assignees: "" 7 | --- 8 | 9 | ## Is your feature request related to a problem? Please describe 10 | 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated 12 | when \[...\] 13 | 14 | ## Describe the solution you'd like 15 | 16 | A clear and concise description of what you want to happen. 17 | 18 | ## Describe alternatives you've considered 19 | 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | ## Additional context 23 | 24 | Add any other context or screenshots about the feature request here. 25 | 26 | --- 27 | 28 | Add a :+1: \[reaction\] to \[issues you find important\]. 29 | -------------------------------------------------------------------------------- /flake-parts/gh-templates-issues/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Adds basic bug/feature GitHub issue templates."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/gitlab-ci-check/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: nixos/nix:latest 2 | 3 | variables: 4 | NIX_CONF_DIR: "/etc/nix" 5 | 6 | before_script: 7 | - echo 'experimental-features = nix-command flakes' > $NIX_CONF_DIR/nix.conf 8 | 9 | flake-check: 10 | script: 11 | - nix flake check --show-trace --accept-flake-config 12 | rules: 13 | - if: '$CI_COMMIT_BRANCH == "main" || $CI_PIPELINE_SOURCE == "merge_request_event"' 14 | -------------------------------------------------------------------------------- /flake-parts/gitlab-ci-check/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Adds a simple `nix flake check` to your GitLab CI/CD pipeline."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/hm-homes/flake-parts/homes/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/homes/default.nix 2 | { 3 | lib, 4 | inputs, 5 | withSystem, 6 | config, 7 | ... 8 | }: 9 | let 10 | mkHome = 11 | args: home: 12 | { 13 | extraSpecialArgs ? { }, 14 | extraModules ? [ ], 15 | extraOverlays ? [ ], 16 | ... 17 | }: 18 | inputs.home-manager.lib.homeManagerConfiguration { 19 | inherit (args) pkgs; 20 | extraSpecialArgs = { 21 | inherit (args) system; 22 | inherit inputs home; 23 | } // extraSpecialArgs; 24 | modules = [ 25 | { 26 | nixpkgs.overlays = extraOverlays; 27 | nixpkgs.config.allowUnfree = true; 28 | } 29 | ./${home} 30 | ] ++ extraModules; 31 | # NOTE You can also load all of your defined modules in the 32 | # following manner 33 | # 34 | # ++ (lib.attrValues config.flake.homeModules); 35 | }; 36 | in 37 | { 38 | options.flake.homeConfigurations = lib.mkOption { 39 | type = with lib.types; lazyAttrsOf unspecified; 40 | default = { }; 41 | }; 42 | 43 | config = { 44 | flake.homeConfigurations = { 45 | # "myUser@myHost" = withSystem "x86_64-linux" ( 46 | # args: 47 | # mkHome args "myUser@myHost" { 48 | # extraOverlays = with inputs; [ 49 | # neovim-nightly-overlay.overlays.default 50 | # (final: _prev: { nur = import inputs.nur { pkgs = final; }; }) 51 | # ]; 52 | # } 53 | # ); 54 | }; 55 | 56 | flake.checks."x86_64-linux" = { 57 | # "home-myUser@myHost" = config.flake.homeConfigurations."myUser@myHost".config.home.path; 58 | }; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /flake-parts/hm-homes/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Template for your HM homes and a handy generator for you `homeManagerConfiguration` calls."; 3 | 4 | inputs = { 5 | home-manager = { 6 | url = "github:nix-community/home-manager"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | }; 10 | extraTrustedPublicKeys = [ ]; 11 | extraSubstituters = [ ]; 12 | } 13 | -------------------------------------------------------------------------------- /flake-parts/hm-modules/flake-parts/modules/home-manager/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/modules/home-manager/default.nix 2 | { 3 | lib, 4 | inputs, 5 | self, 6 | ... 7 | }: 8 | let 9 | inherit (inputs.flake-parts.lib) importApply; 10 | localFlake = self; 11 | in 12 | { 13 | options.flake.homeModules = lib.mkOption { 14 | type = with lib.types; lazyAttrsOf unspecified; 15 | default = { }; 16 | }; 17 | 18 | config.flake.homeModules = { 19 | # NOTE Dogfooding your modules with `importApply` will make them more 20 | # reusable even outside of your flake. For more info see 21 | # https://flake.parts/dogfood-a-reusable-module#example-with-importapply 22 | 23 | # programs_myProgram = importApply ./programs/myProgram { inherit localFlake; }; 24 | # services_myService = importApply ./services/myService { inherit localFlake inputs; }; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /flake-parts/hm-modules/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Basic template for custom home-manager modules."; 3 | 4 | inputs = { 5 | home-manager = { 6 | url = "github:nix-community/home-manager"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | }; 10 | extraTrustedPublicKeys = [ ]; 11 | extraSubstituters = [ ]; 12 | } 13 | -------------------------------------------------------------------------------- /flake-parts/lib/flake-parts/lib/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/lib/default.nix 2 | { 3 | inputs, 4 | lib, 5 | self, 6 | ... 7 | }: 8 | let 9 | localFlake = self; 10 | in 11 | { 12 | flake.lib = { 13 | # modules = import ./modules { inherit localFlake lib inputs; }; 14 | # functions = import ./functions { inherit localFlake lib; }; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /flake-parts/lib/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Basic template for custom nix library functions."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/nix-topology/flake-parts/nix-topology/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/nix-topology/default.nix 2 | { 3 | inputs, 4 | lib, 5 | self, 6 | ... 7 | }: 8 | let 9 | inherit (inputs.flake-parts.lib) importApply; 10 | localFlake = self; 11 | in 12 | { 13 | imports = with inputs; [ nix-topology.flakeModule ]; 14 | 15 | perSystem = 16 | { ... }: 17 | { 18 | topology.modules = [ 19 | { inherit (localFlake) nixosConfigurations; } 20 | (importApply ./topology.nix { inherit localFlake; }) 21 | ]; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /flake-parts/nix-topology/flake-parts/nix-topology/topology.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/nix-topology/topology.nix 2 | { localFlake }: 3 | { config, ... }: 4 | let 5 | inherit (config.lib.topology) 6 | mkInternet 7 | mkRouter 8 | mkSwitch 9 | mkConnection 10 | ; 11 | in 12 | { 13 | # Add a node for the internet 14 | nodes.internet = mkInternet { connections = mkConnection "router" "wan1"; }; 15 | 16 | # Add a router that we use to access the internet 17 | nodes.router = mkRouter "Example Router" { 18 | info = "Example Router"; 19 | # image = ./images/fritzbox.png; 20 | interfaceGroups = [ 21 | [ 22 | "eth1" 23 | "eth2" 24 | "eth3" 25 | "eth4" 26 | "wifi" 27 | ] 28 | [ "wan1" ] 29 | ]; 30 | connections.eth1 = mkConnection "exampleHost1" "eth0"; 31 | connections.wifi = mkConnection "exampleHost2" "wlp3s0"; 32 | interfaces.eth1 = { 33 | addresses = [ "192.168.0.1" ]; 34 | network = "home"; 35 | }; 36 | }; 37 | 38 | networks.home = { 39 | name = "Home"; 40 | cidrv4 = "192.168.0.0/24"; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /flake-parts/nix-topology/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Adds bindings for the `github:oddlama/nix-topology` project to generate graphs of your networks."; 3 | 4 | inputs = { 5 | nix-topology.url = "github:oddlama/nix-topology"; 6 | }; 7 | conflicts = [ ]; 8 | extraTrustedPublicKeys = [ ]; 9 | extraSubstituters = [ ]; 10 | } 11 | -------------------------------------------------------------------------------- /flake-parts/nixos-hosts/flake-parts/hosts/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/hosts/default.nix 2 | { 3 | lib, 4 | inputs, 5 | withSystem, 6 | config, 7 | ... 8 | }: 9 | let 10 | mkHost = 11 | args: hostName: 12 | { 13 | extraSpecialArgs ? { }, 14 | extraModules ? [ ], 15 | extraOverlays ? [ ], 16 | withHomeManager ? false, 17 | ... 18 | }: 19 | let 20 | baseSpecialArgs = { 21 | inherit (args) system; 22 | inherit inputs hostName; 23 | } // extraSpecialArgs; 24 | in 25 | inputs.nixpkgs.lib.nixosSystem { 26 | inherit (args) system; 27 | specialArgs = baseSpecialArgs // { 28 | inherit lib hostName; 29 | host.hostName = hostName; 30 | }; 31 | modules = 32 | [ 33 | { 34 | nixpkgs.overlays = extraOverlays; 35 | nixpkgs.config.allowUnfree = true; 36 | networking.hostName = hostName; 37 | } 38 | ./${hostName} 39 | ] 40 | ++ extraModules 41 | # NOTE You can also load all of your defined modules in the 42 | # following manner 43 | # 44 | # ++ (lib.attrValues config.flake.nixosModules) 45 | ++ ( 46 | if (withHomeManager && (lib.hasAttr "home-manager" inputs)) then 47 | [ 48 | inputs.home-manager.nixosModules.home-manager 49 | { 50 | home-manager = { 51 | useGlobalPkgs = true; 52 | useUserPackages = true; 53 | extraSpecialArgs = baseSpecialArgs; 54 | 55 | # NOTE You can also load all of your defined modules in the 56 | # following manner 57 | # 58 | # sharedModules = lib.attrValues config.flake.homeModules; 59 | }; 60 | } 61 | ] 62 | else 63 | [ ] 64 | ); 65 | }; 66 | in 67 | { 68 | flake.nixosConfigurations = { 69 | # myExampleHost = withSystem "x86_64-linux" ( 70 | # args: 71 | # mkHost args "myExampleHost" { 72 | # withHomeManager = true; 73 | # extraOverlays = with inputs; [ 74 | # neovim-nightly-overlay.overlays.default 75 | # (final: _prev: { nur = import inputs.nur { pkgs = final; }; }) 76 | # ]; 77 | # } 78 | # ); 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /flake-parts/nixos-hosts/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Template for your NixOS hosts and a handy generator for `lib.nixosSystem` calls."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/nixos-modules/flake-parts/modules/nixos/default.nix: -------------------------------------------------------------------------------- 1 | { inputs, self, ... }: 2 | let 3 | inherit (inputs.flake-parts.lib) importApply; 4 | localFlake = self; 5 | in 6 | { 7 | flake.nixosModules = { 8 | 9 | # NOTE Dogfooding your modules with `importApply` will make them more 10 | # reusable even outside of your flake. For more info see 11 | # https://flake.parts/dogfood-a-reusable-module#example-with-importapply 12 | 13 | # programs_myProgram = importApply ./programs/myProgram.nix { inherit localFlake; }; 14 | # services_myService = importApply ./services/myService.nix { inherit localFlake inputs; }; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /flake-parts/nixos-modules/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Basic template for custom NixOS modules."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/nixvim-configurations/flake-parts/nixvim/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/nixvim/default.nix 2 | { 3 | lib, 4 | inputs, 5 | config, 6 | ... 7 | }: 8 | let 9 | inherit (lib) mkOption types; 10 | inherit (inputs.flake-parts.lib) importApply mkPerSystemOption; 11 | 12 | mkNixvimConfiguration = 13 | name: pkgs: 14 | { 15 | extraSpecialArgs ? { }, 16 | extraModules ? [ ], 17 | configImportArgs ? { }, 18 | }: 19 | { 20 | inherit pkgs extraSpecialArgs; 21 | module = 22 | { ... }: 23 | { 24 | imports = 25 | [ 26 | (importApply ./${name} configImportArgs) 27 | ] 28 | # NOTE You can also load all of your defined modules in the 29 | # following manner 30 | # 31 | # ++ (lib.attrValues config.flake.nixvimModules) 32 | ++ extraModules; 33 | }; 34 | }; 35 | in 36 | { 37 | options.perSystem = mkPerSystemOption (_: { 38 | options.nixvimConfigurations = mkOption { 39 | type = types.lazyAttrsOf types.unspecified; 40 | default = { }; 41 | }; 42 | }); 43 | 44 | config = { 45 | perSystem = 46 | { 47 | pkgs, 48 | config, 49 | system, 50 | ... 51 | }: 52 | let 53 | inherit (inputs.nixvim.lib.${system}.check) mkTestDerivationFromNixvimModule; 54 | inherit (inputs.nixvim.legacyPackages.${system}) makeNixvimWithModule; 55 | in 56 | { 57 | # NOTE Here you can define your nixvim configurations, for more 58 | # specific informations and examples see 59 | # https://nix-community.github.io/nixvim/ 60 | nixvimConfigurations = { 61 | # example-config = mkNixvimConfiguration "example-config" pkgs { }; 62 | }; 63 | 64 | packages = { 65 | nvim = config.packages.nvim-ide-config; 66 | 67 | # nvim-example-config = makeNixvimWithModule config.nixvimConfigurations."example-config"; 68 | }; 69 | 70 | checks = { 71 | # nvim-example-config = mkTestDerivationFromNixvimModule config.nixvimConfigurations."example-config"; 72 | }; 73 | }; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /flake-parts/nixvim-configurations/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Template for Nixvim configurations to handle multiple neovim instances."; 3 | 4 | inputs = { 5 | nixvim = { 6 | url = "github:nix-community/nixvim"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | }; 10 | extraTrustedPublicKeys = [ ]; 11 | extraSubstituters = [ ]; 12 | } 13 | -------------------------------------------------------------------------------- /flake-parts/nixvim-modules/flake-parts/modules/nixvim/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/modules/nixvim/default.nix 2 | { 3 | lib, 4 | self, 5 | inputs, 6 | ... 7 | }: 8 | let 9 | inherit (lib) mkOption types; 10 | inherit (inputs.flake-parts.lib) importApply; 11 | localFlake = self; 12 | in 13 | { 14 | options.flake.nixvimModules = mkOption { 15 | type = types.lazyAttrsOf types.unspecified; 16 | default = { }; 17 | }; 18 | 19 | config.flake.nixvimModules = { 20 | # NOTE Dogfooding your modules with `importApply` will make them more 21 | # reusable even outside of your flake. For more info see 22 | # https://flake.parts/dogfood-a-reusable-module#example-with-importapply 23 | 24 | # auto_cmds = importApply ./auto_cmds.nix { inherit localFlake; }; 25 | # keymaps = importApply ./keymaps.nix { inherit localFlake; }; 26 | # settings = importApply ./settings.nix { inherit localFlake; }; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /flake-parts/nixvim-modules/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Basic template for reusable nixvim modules."; 3 | 4 | inputs = { 5 | nixvim = { 6 | url = "github:nix-community/nixvim"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | }; 10 | extraTrustedPublicKeys = [ ]; 11 | extraSubstituters = [ ]; 12 | } 13 | -------------------------------------------------------------------------------- /flake-parts/overlays/flake-parts/overlays/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/overlays/default.nix 2 | { inputs, self, ... }: 3 | let 4 | localFlake = self; 5 | in 6 | { 7 | flake.overlays = { 8 | # myOverlay = final: prev: { 9 | # myCustomSet = {}; 10 | # }; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /flake-parts/overlays/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Basic template for custom nixpkgs overlays."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/pkgs/flake-parts/pkgs/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/pkgs/default.nix 2 | { ... }: 3 | { 4 | perSystem = 5 | { pkgs, ... }: 6 | { 7 | packages = { 8 | # NOTE For more info on the nix `callPackage` pattern see 9 | # https://nixos.org/guides/nix-pills/13-callpackage-design-pattern.html 10 | 11 | # my-custom-package = pkgs.callPackage ./my-custom-package.nix { }; 12 | }; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /flake-parts/pkgs/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Basic template for custom nix packages (ie derivations)."; 3 | 4 | inputs = { }; 5 | extraTrustedPublicKeys = [ ]; 6 | extraSubstituters = [ ]; 7 | } 8 | -------------------------------------------------------------------------------- /flake-parts/pre-commit-hooks/flake-parts/pre-commit-hooks.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/pre-commit-hooks.nix 2 | { inputs, lib, ... }: 3 | { 4 | imports = with inputs; [ pre-commit-hooks.flakeModule ]; 5 | 6 | perSystem = 7 | { config, pkgs, ... }: 8 | { 9 | pre-commit.settings = 10 | let 11 | treefmt-wrapper = if (lib.hasAttr "treefmt" config) then config.treefmt.build.wrapper else null; 12 | in 13 | { 14 | excludes = [ "flake.lock" ]; 15 | 16 | hooks = { 17 | treefmt.enable = if (treefmt-wrapper != null) then true else false; 18 | treefmt.package = if (treefmt-wrapper != null) then treefmt-wrapper else pkgs.treefmt; 19 | 20 | nil.enable = true; # Nix Language server, an incremental analysis assistant for writing in Nix. 21 | markdownlint.enable = true; # Markdown lint tool 22 | 23 | commitizen.enable = true; # Commitizen is release management tool designed for teams. 24 | editorconfig-checker.enable = true; # A tool to verify that your files are in harmony with your .editorconfig 25 | # actionlint.enable = true; # GitHub workflows linting 26 | # typos.enable = true; # Source code spell checker 27 | 28 | # General use pre-commit hooks 29 | trim-trailing-whitespace.enable = true; 30 | mixed-line-endings.enable = true; 31 | end-of-file-fixer.enable = true; 32 | check-executables-have-shebangs.enable = true; 33 | check-added-large-files.enable = true; 34 | 35 | gitleaks = { 36 | enable = true; 37 | name = "gitleaks"; 38 | entry = "${pkgs.gitleaks}/bin/gitleaks protect --verbose --redact --staged"; 39 | pass_filenames = false; 40 | }; 41 | }; 42 | }; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /flake-parts/pre-commit-hooks/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Bindings for pre-commit-hooks.nix and a simple pre-commit-hook template."; 3 | 4 | inputs = { 5 | pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix"; 6 | }; 7 | conflicts = [ "devenv" ]; 8 | extraTrustedPublicKeys = [ 9 | "pre-commit-hooks.cachix.org-1:Pkk3Panw5AW24TOv6kz3PvLhlH8puAsJTBbOPmBo7Rc=" 10 | ]; 11 | extraSubstituters = [ "https://pre-commit-hooks.cachix.org/" ]; 12 | } 13 | -------------------------------------------------------------------------------- /flake-parts/process-compose-flake/flake-parts/process-compose-flake/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/process-compose-flake/default.nix 2 | { inputs, lib, ... }: 3 | { 4 | imports = with inputs; [ process-compose-flake.flakeModule ]; 5 | 6 | perSystem = 7 | { config, pkgs, ... }: 8 | { 9 | process-compose = { 10 | default = config.process-compose.dev-process; 11 | 12 | dev-process = import ./dev.nix { inherit pkgs lib; }; 13 | }; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /flake-parts/process-compose-flake/flake-parts/process-compose-flake/dev.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/process-compose-flake/dev.nix 2 | { pkgs, lib }: 3 | { 4 | # For more info about the process-compose format seek 5 | # https://github.com/Platonic-Systems/process-compose-flake 6 | settings = { 7 | environment = { 8 | MY_ENV_VAR = "Hello from process-compose!"; 9 | }; 10 | 11 | processes = { 12 | hello.command = '' 13 | ${lib.getExe pkgs.hello} -g $MY_ENV_VAR 14 | ''; 15 | }; 16 | }; 17 | 18 | preHook = '' 19 | echo "Running preHook" 20 | ''; 21 | 22 | postHook = '' 23 | echo "Running postHook" 24 | ''; 25 | } 26 | -------------------------------------------------------------------------------- /flake-parts/process-compose-flake/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Bindings for process-compose-flake and a simple process-compose template."; 3 | 4 | inputs = { 5 | process-compose-flake.url = "github:Platonic-Systems/process-compose-flake"; 6 | }; 7 | 8 | conflicts = [ "devenv" ]; 9 | extraTrustedPublicKeys = [ ]; 10 | extraSubstituters = [ ]; 11 | } 12 | -------------------------------------------------------------------------------- /flake-parts/shells/flake-parts/shells/default.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/shells/default.nix 2 | { lib, ... }: 3 | { 4 | perSystem = 5 | { pkgs, config, ... }: 6 | { 7 | devShells = { 8 | default = config.devShells.dev; 9 | 10 | dev = pkgs.callPackage ./dev.nix { 11 | inherit lib; 12 | treefmt-wrapper = if (lib.hasAttr "treefmt" config) then config.treefmt.build.wrapper else null; 13 | dev-process = if (lib.hasAttr "process-compose" config) then config.packages.dev-process else null; 14 | pre-commit = if (lib.hasAttr "pre-commit" config) then config.pre-commit else null; 15 | }; 16 | }; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /flake-parts/shells/flake-parts/shells/dev.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/shells/dev.nix 2 | { 3 | lib, 4 | mkShell, 5 | nil, 6 | statix, 7 | deadnix, 8 | nix-output-monitor, 9 | nixfmt-rfc-style, 10 | markdownlint-cli, 11 | writeShellScriptBin, 12 | treefmt-wrapper ? null, 13 | dev-process ? null, 14 | pre-commit ? null, 15 | }: 16 | let 17 | scripts = { 18 | rename-project = writeShellScriptBin "rename-project" '' 19 | find $1 \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s/NAMEPLACEHOLDER/$2/g" 20 | ''; 21 | }; 22 | 23 | env = { 24 | # MY_ENV_VAR = "Hello, World!"; 25 | # MY_OTHER_ENV_VAR = "Goodbye, World!"; 26 | }; 27 | in 28 | mkShell { 29 | 30 | packages = 31 | (lib.attrValues scripts) 32 | ++ (lib.optional (treefmt-wrapper != null) treefmt-wrapper) 33 | ++ (lib.optional (dev-process != null) dev-process) 34 | ++ [ 35 | # -- NIX UTILS -- 36 | nil # Yet another language server for Nix 37 | statix # Lints and suggestions for the nix programming language 38 | deadnix # Find and remove unused code in .nix source files 39 | nix-output-monitor # Processes output of Nix commands to show helpful and pretty information 40 | nixfmt-rfc-style # An opinionated formatter for Nix 41 | 42 | # -- GIT RELATED UTILS -- 43 | # commitizen # Tool to create committing rules for projects, auto bump versions, and generate changelogs 44 | # cz-cli # The commitizen command line utility 45 | # fh # The official FlakeHub CLI 46 | # gh # GitHub CLI tool 47 | # gh-dash # Github Cli extension to display a dashboard with pull requests and issues 48 | 49 | # -- BASE LANG UTILS -- 50 | markdownlint-cli # Command line interface for MarkdownLint 51 | # nodePackages.prettier # Prettier is an opinionated code formatter 52 | # typos # Source code spell checker 53 | 54 | # -- (YOUR) EXTRA PKGS -- 55 | ]; 56 | 57 | shellHook = '' 58 | ${lib.concatLines (lib.mapAttrsToList (name: value: "export ${name}=${value}") env)} 59 | ${lib.optionalString (pre-commit != null) pre-commit.installationScript} 60 | 61 | # Welcome splash text 62 | echo ""; echo -e "\e[1;37;42mWelcome to the NAMEPLACEHOLDER devshell!\e[0m"; echo "" 63 | ''; 64 | } 65 | -------------------------------------------------------------------------------- /flake-parts/shells/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Basic template for custom nix devshells (ie. `mkShell` calls) with potential bindings to other parts."; 3 | 4 | inputs = { }; 5 | 6 | conflicts = [ "devenv" ]; 7 | extraTrustedPublicKeys = [ ]; 8 | extraSubstituters = [ ]; 9 | } 10 | -------------------------------------------------------------------------------- /flake-parts/systems/flake-parts/systems.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/systems.nix 2 | { inputs, ... }: 3 | { 4 | # NOTE We use the default `systems` defined by the `nix-systems` flake, if 5 | # you need any additional systems, simply add them in the following manner 6 | # 7 | # `systems = (import inputs.systems) ++ [ "armv7l-linux" ];` 8 | systems = import inputs.systems; 9 | } 10 | -------------------------------------------------------------------------------- /flake-parts/systems/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Sets up the default `systems` of flake-parts using `github:nix-systems/default`."; 3 | 4 | inputs = { 5 | systems.url = "github:nix-systems/default"; 6 | }; 7 | 8 | extraTrustedPublicKeys = [ ]; 9 | extraSubstituters = [ ]; 10 | } 11 | -------------------------------------------------------------------------------- /flake-parts/treefmt/flake-parts/treefmt.nix: -------------------------------------------------------------------------------- 1 | # --- flake-parts/treefmt.nix 2 | { inputs, ... }: 3 | { 4 | imports = with inputs; [ treefmt-nix.flakeModule ]; 5 | 6 | perSystem = 7 | { pkgs, ... }: 8 | { 9 | treefmt = { 10 | # treefmt is a formatting tool that saves you time: it provides 11 | # developers with a universal way to trigger all formatters needed for the 12 | # project in one place. 13 | # For more information refer to 14 | # 15 | # - https://numtide.github.io/treefmt/ 16 | # - https://github.com/numtide/treefmt-nix 17 | package = pkgs.treefmt; 18 | flakeCheck = true; 19 | flakeFormatter = true; 20 | projectRootFile = "flake.nix"; 21 | 22 | settings = { 23 | global.excludes = [ 24 | "*.age" # Age encrypted files 25 | ]; 26 | shellcheck.includes = [ 27 | "*.sh" 28 | ".envrc" 29 | ]; 30 | prettier.editorconfig = true; 31 | }; 32 | 33 | programs = { 34 | deadnix.enable = true; # Find and remove unused code in .nix source files 35 | statix.enable = true; # Lints and suggestions for the nix programming language 36 | nixfmt.enable = true; # An opinionated formatter for Nix 37 | 38 | prettier.enable = true; # Prettier is an opinionated code formatter 39 | yamlfmt.enable = true; # An extensible command line tool or library to format yaml files. 40 | jsonfmt.enable = true; # Formatter for JSON files 41 | # mdformat.enable = true; # CommonMark compliant Markdown formatter 42 | 43 | # shellcheck.enable = true; # Shell script analysis tool 44 | # shfmt.enable = true; # Shell parser and formatter 45 | 46 | # actionlint.enable = true; # Static checker for GitHub Actions workflow files 47 | # mdsh.enable = true; # Markdown shell pre-processor 48 | }; 49 | }; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /flake-parts/treefmt/meta.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Bindings for the treefmt formatter and a basic treefmt configuration."; 3 | 4 | inputs = { 5 | treefmt-nix.url = "github:numtide/treefmt-nix"; 6 | }; 7 | 8 | dependencies = [ "flake-root" ]; 9 | extraTrustedPublicKeys = [ "numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE=" ]; 10 | extraSubstituters = [ "https://numtide.cachix.org" ]; 11 | } 12 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": "nixpkgs-lib" 6 | }, 7 | "locked": { 8 | "lastModified": 1743550720, 9 | "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", 10 | "owner": "hercules-ci", 11 | "repo": "flake-parts", 12 | "rev": "c621e8422220273271f52058f618c94e405bb0f5", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "hercules-ci", 17 | "repo": "flake-parts", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1746663147, 24 | "narHash": "sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ+TCkTRpRc=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "dda3dcd3fe03e991015e9a74b22d35950f264a54", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs-lib": { 38 | "locked": { 39 | "lastModified": 1743296961, 40 | "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", 41 | "owner": "nix-community", 42 | "repo": "nixpkgs.lib", 43 | "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "nix-community", 48 | "repo": "nixpkgs.lib", 49 | "type": "github" 50 | } 51 | }, 52 | "root": { 53 | "inputs": { 54 | "flake-parts": "flake-parts", 55 | "nixpkgs": "nixpkgs", 56 | "systems": "systems" 57 | } 58 | }, 59 | "systems": { 60 | "locked": { 61 | "lastModified": 1681028828, 62 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 63 | "owner": "nix-systems", 64 | "repo": "default", 65 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 66 | "type": "github" 67 | }, 68 | "original": { 69 | "owner": "nix-systems", 70 | "repo": "default", 71 | "type": "github" 72 | } 73 | } 74 | }, 75 | "root": "root", 76 | "version": 7 77 | } 78 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | # --- flake.nix 2 | { 3 | description = "Nix flakes interactive template builder based on flake-parts written in Rust."; 4 | 5 | inputs = { 6 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 7 | flake-parts.url = "github:hercules-ci/flake-parts"; 8 | systems.url = "github:nix-systems/default"; 9 | }; 10 | 11 | outputs = 12 | inputs@{ flake-parts, ... }: 13 | let 14 | inherit (inputs.nixpkgs) lib; 15 | 16 | tsandrini = { 17 | email = "t@tsandrini.sh"; 18 | name = "Tomáš Sandrini"; 19 | github = "tsandrini"; 20 | githubId = 21975189; 21 | }; 22 | 23 | /* 24 | Simple wrapper around `stdenv.mkDerivation` that sets up a basic 25 | derivation that holds user defined flake-parts => a flake-parts store. 26 | 27 | NOTE: It is required to pass an instance of your `stdenv` to this 28 | function. 29 | 30 | *Type*: `mkFlakeParts :: Attrset a -> Package a` 31 | */ 32 | mkFlakeParts = 33 | args@{ stdenv, ... }: 34 | let 35 | finalArgs = { 36 | name = "flake-parts"; 37 | version = "1.0.0-b2"; 38 | 39 | dontConfigure = true; 40 | dontBuild = true; 41 | dontCheck = true; 42 | 43 | installPhase = '' 44 | mkdir -p $out/flake-parts 45 | cp -rv $src/* $out/flake-parts 46 | ''; 47 | } // args; 48 | in 49 | stdenv.mkDerivation finalArgs; 50 | in 51 | flake-parts.lib.mkFlake { inherit inputs; } { 52 | 53 | systems = import inputs.systems; 54 | 55 | flake.lib = rec { 56 | inherit mkFlakeParts; 57 | 58 | /* 59 | Main function for recursively traversing and loading all modules 60 | in a provided flake-parts directory. 61 | 62 | For more information and specifics on how this function works, see the 63 | doccomment of the `loadModules` function below. 64 | 65 | *Type*: `loadParts :: Path -> { name :: String; value :: AttrSet a; }` 66 | */ 67 | loadParts = dir: flatten (mapModules dir (x: x)); 68 | 69 | /* 70 | Recursively flattens a nested attrset into a list of just its values. 71 | 72 | *Type*: `flatten :: AttrSet a -> [a]` 73 | 74 | Example: 75 | ```nix title="Example" linenums="1" 76 | flatten { 77 | keyA = 10; 78 | keyB = "str20"; 79 | keyC = { 80 | keyD = false; 81 | keyE = { 82 | a = 10; 83 | b = "20"; 84 | c = false; 85 | }; 86 | }; 87 | } 88 | => [ 10 "str20" false 10 "20" false ] 89 | ``` 90 | */ 91 | flatten = attrs: lib.collect (x: !lib.isAttrs x) attrs; 92 | 93 | /* 94 | Apply a map to every attribute of an attrset and then filter the resulting 95 | attrset based on a given predicate function. 96 | 97 | *Type*: `mapFilterAttrs :: (AttrSet b -> Bool) -> (AttrSet a -> AttrSet b) -> AttrSet a -> AttrSet b` 98 | */ 99 | mapFilterAttrs = 100 | pred: f: attrs: 101 | lib.filterAttrs pred (lib.mapAttrs' f attrs); 102 | 103 | /* 104 | Recursively read a directory and apply a provided function to every `.nix` 105 | file. Returns an attrset that reflects the filenames and directory 106 | structure of the root. 107 | 108 | Notes: 109 | 110 | 1. Files and directories starting with the `_` or `.git` prefix will be 111 | completely ignored. 112 | 113 | 2. If a directory with a `myDir/default.nix` file will be encountered, 114 | the function will be applied to the `myDir/default.nix` file 115 | instead of recursively loading `myDir` and applying it to every file. 116 | 117 | *Type*: `mapModules :: Path -> (Path -> AttrSet a) -> { name :: String; value :: AttrSet a; }` 118 | 119 | Example: 120 | ```nix title="Example" linenums="1" 121 | mapModules ./modules import 122 | => { hardware = { moduleA = { ... }; }; system = { moduleB = { ... }; }; } 123 | 124 | mapModules ./hosts (host: mkHostCustomFunction myArg host) 125 | => { hostA = { ... }; hostB = { ... }; } 126 | ``` 127 | */ 128 | mapModules = 129 | dir: fn: 130 | mapFilterAttrs (n: v: v != null && !(lib.hasPrefix "_" n) && !(lib.hasPrefix ".git" n)) ( 131 | n: v: 132 | let 133 | path = "${toString dir}/${n}"; 134 | in 135 | if v == "directory" && builtins.pathExists "${path}/default.nix" then 136 | lib.nameValuePair n (fn path) 137 | else if v == "directory" then 138 | lib.nameValuePair n (mapModules path fn) 139 | else if v == "regular" && n != "default.nix" && lib.hasSuffix ".nix" n then 140 | lib.nameValuePair (lib.removeSuffix ".nix" n) (fn path) 141 | else 142 | lib.nameValuePair "" null 143 | ) (builtins.readDir dir); 144 | }; 145 | 146 | perSystem = 147 | { 148 | config, 149 | pkgs, 150 | system, 151 | ... 152 | }: 153 | { 154 | packages = { 155 | default = config.packages.builder; 156 | 157 | builder = 158 | let 159 | package = 160 | { 161 | lib, 162 | rustPlatform, 163 | nixfmt-rfc-style, 164 | nix, 165 | makeWrapper, 166 | tsandrini, 167 | }: 168 | rustPlatform.buildRustPackage { 169 | name = "flake-parts-builder"; 170 | version = "1.0.0-b2"; 171 | 172 | src = [ 173 | ./src 174 | ./Cargo.toml 175 | ./Cargo.lock 176 | ]; 177 | 178 | unpackPhase = '' 179 | runHook preUnpack 180 | for srcItem in $src; do 181 | if [ -d "$srcItem" ]; then 182 | cp -r "$srcItem" $(stripHash "$srcItem") 183 | else 184 | cp "$srcItem" $(stripHash "$srcItem") 185 | fi 186 | done 187 | runHook postUnpack 188 | ''; 189 | 190 | preCheck = '' 191 | dirs=(store var var/nix var/log/nix etc home) 192 | 193 | for dir in $dirs; do 194 | mkdir -p "$TMPDIR/$dir" 195 | done 196 | 197 | export NIX_STORE_DIR=$TMPDIR/store 198 | export NIX_LOCALSTATE_DIR=$TMPDIR/var 199 | export NIX_STATE_DIR=$TMPDIR/var/nix 200 | export NIX_LOG_DIR=$TMPDIR/var/log/nix 201 | export NIX_CONF_DIR=$TMPDIR/etc 202 | export HOME=$TMPDIR/home 203 | ''; 204 | 205 | cargoHash = "sha256-Vb5QPw8inbG3oLLsaMe53jiMTqTWrHsPf/HdeduE2YE="; 206 | 207 | postBuild = '' 208 | cargo doc --no-deps --release 209 | ''; 210 | 211 | NIX_BIN_PATH = lib.getExe nix; 212 | NIXFMT_BIN_PATH = lib.getExe nixfmt-rfc-style; 213 | 214 | postInstall = '' 215 | mkdir -p $out/doc 216 | cp -r target/doc $out/ 217 | ''; 218 | 219 | nativeBuildInputs = [ 220 | makeWrapper 221 | ]; 222 | 223 | buildInputs = [ 224 | nixfmt-rfc-style 225 | nix 226 | ]; 227 | 228 | # Just add required binaries to PATH, letting the Rust 229 | # program's which::which handle discovery 230 | postFixup = '' 231 | wrapProgram $out/bin/flake-parts-builder \ 232 | --prefix PATH : ${lib.makeBinPath [ nix nixfmt-rfc-style ]} 233 | ''; 234 | 235 | meta = with lib; { 236 | homepage = "https://github.com/tsandrini/flake-parts-builder"; 237 | description = "Nix flakes interactive template builder based on flake-parts written in Rust."; 238 | license = licenses.mit; 239 | platforms = [ system ]; 240 | maintainers = [ tsandrini ]; 241 | mainProgram = "flake-parts-builder"; 242 | }; 243 | }; 244 | in 245 | pkgs.callPackage package { 246 | inherit tsandrini; 247 | nix = pkgs.nixVersions.stable; 248 | }; 249 | 250 | flake-parts = 251 | let 252 | package = 253 | { 254 | lib, 255 | stdenv, 256 | tsandrini, 257 | mkFlakeParts, 258 | }: 259 | mkFlakeParts { 260 | inherit stdenv; 261 | name = "flake-parts"; 262 | version = "1.0.0-b2"; 263 | src = ./flake-parts; 264 | 265 | meta = with lib; { 266 | homepage = "https://github.com/tsandrini/flake-parts-builder"; 267 | description = "The base collection of flake-parts for the flake-parts-builder."; 268 | license = licenses.mit; 269 | platforms = [ system ]; 270 | maintainers = [ tsandrini ]; 271 | }; 272 | }; 273 | in 274 | pkgs.callPackage package { inherit tsandrini mkFlakeParts; }; 275 | 276 | flake-parts-bootstrap = 277 | let 278 | package = 279 | { 280 | lib, 281 | stdenv, 282 | tsandrini, 283 | mkFlakeParts, 284 | }: 285 | mkFlakeParts { 286 | inherit stdenv; 287 | name = "flake-parts-bootstrap"; 288 | version = "1.0.0-b2"; 289 | src = ./flake-parts-bootstrap; 290 | 291 | meta = with lib; { 292 | homepage = "https://github.com/tsandrini/flake-parts-builder"; 293 | description = "The base collection of flake-parts for the flake-parts-builder."; 294 | license = licenses.mit; 295 | platforms = [ system ]; 296 | maintainers = [ tsandrini ]; 297 | }; 298 | }; 299 | in 300 | pkgs.callPackage package { inherit tsandrini mkFlakeParts; }; 301 | }; 302 | 303 | devShells = { 304 | default = config.devShells.dev; 305 | 306 | dev = 307 | let 308 | package = 309 | { 310 | mkShell, 311 | nil, 312 | statix, 313 | deadnix, 314 | nix-output-monitor, 315 | nixfmt-rfc-style, 316 | commitizen, 317 | cz-cli, 318 | gh, 319 | gh-dash, 320 | markdownlint-cli, 321 | rustc, 322 | pkg-config, 323 | cargo, 324 | openssl, 325 | cargo-audit 326 | }: 327 | mkShell { 328 | buildInputs = [ 329 | # -- NIX UTILS -- 330 | nil # Yet another language server for Nix 331 | statix # Lints and suggestions for the nix programming language 332 | deadnix # Find and remove unused code in .nix source files 333 | nix-output-monitor # Processes output of Nix commands to show helpful and pretty information 334 | nixfmt-rfc-style # An opinionated formatter for Nix 335 | 336 | # -- GIT RELATED UTILS -- 337 | commitizen # Tool to create committing rules for projects, auto bump versions, and generate changelogs 338 | cz-cli # The commitizen command line utility 339 | # fh # The official FlakeHub CLI 340 | gh # GitHub CLI tool 341 | gh-dash # Github Cli extension to display a dashboard with pull requests and issues 342 | 343 | # -- BASE LANG UTILS -- 344 | markdownlint-cli # Command line interface for MarkdownLint 345 | # nodePackages.prettier # Prettier is an opinionated code formatter 346 | # typos # Source code spell checker 347 | 348 | # -- (YOUR) EXTRA PKGS -- 349 | rustc 350 | cargo 351 | pkg-config 352 | openssl 353 | cargo-audit 354 | ]; 355 | 356 | shellHook = '' 357 | # Welcome splash text 358 | echo ""; echo -e "\e[1;37;42mWelcome to the flake-parts-builder devshell!\e[0m"; echo "" 359 | ''; 360 | }; 361 | in 362 | pkgs.callPackage package { }; 363 | }; 364 | }; 365 | }; 366 | } 367 | -------------------------------------------------------------------------------- /src/assets/flake-inputs.nix.template: -------------------------------------------------------------------------------- 1 | {# NOTE over time the community has developed a certain formatting 2 | style for flake.nix inputs that tends to be shared among most 3 | of us, which is not a simple "JSON"-like dump. This is why I 4 | opted to manually parse the JSON dump. #} 5 | {%- if context.inputs is defined %} 6 | {%- for input_name in context.inputs %} 7 | {%- set input = context.inputs[input_name] -%} 8 | {%- if not input.inputs is defined and not input.flake is defined %} 9 | {{ input_name }}.url = "{{ input.url }}"; 10 | {%- else %} 11 | {{ input_name }} = { 12 | url = "{{ input.url }}"; 13 | {%- if input.inputs is defined %} 14 | {%- for follow_name in input.inputs %} 15 | inputs.{{ follow_name }}.follows = "{{ input.inputs[follow_name].follows }}"; 16 | {%- endfor %} 17 | {%- endif %} 18 | {%- if input.flake is defined %} 19 | flake = {{ input.flake }}; 20 | {%- endif %} 21 | }; 22 | {%- endif %} 23 | {%- endfor %} 24 | {% endif %} -------------------------------------------------------------------------------- /src/assets/flake.nix.template: -------------------------------------------------------------------------------- 1 | # --- flake.nix 2 | { 3 | description = "TODO Add description of your new project"; 4 | 5 | inputs = { 6 | # --- BASE DEPENDENCIES --- 7 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 8 | flake-parts.url = "github:hercules-ci/flake-parts"; 9 | 10 | # --- YOUR DEPENDENCIES --- 11 | {%- with context = context.flake_inputs_context %} 12 | {%- include "flake-inputs.nix" ignore missing -%} 13 | {% endwith -%} 14 | }; 15 | 16 | # NOTE Here you can add additional binary cache substituers that you trust. 17 | # There are also some sensible default caches commented out that you 18 | # might consider using, however, you are advised to doublecheck the keys. 19 | nixConfig = { 20 | extra-trusted-public-keys = [ 21 | # "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" 22 | # "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" 23 | {% if context.extra_trusted_public_keys is defined -%} 24 | {% for key in context.extra_trusted_public_keys -%} 25 | # "{{ key }}" 26 | {% endfor -%} 27 | {% endif -%} 28 | ]; 29 | extra-substituters = [ 30 | # "https://cache.nixos.org" 31 | # "https://nix-community.cachix.org/" 32 | {% if context.extra_substituters is defined -%} 33 | {% for substituter in context.extra_substituters -%} 34 | # "{{ substituter }}" 35 | {% endfor -%} 36 | {% endif -%} 37 | ]; 38 | }; 39 | 40 | outputs = 41 | inputs@{ flake-parts, ... }: 42 | let 43 | inherit (inputs.nixpkgs) lib; 44 | inherit (import ./flake-parts/_bootstrap.nix { inherit lib; }) loadParts; 45 | in 46 | flake-parts.lib.mkFlake { inherit inputs; } { 47 | 48 | # We recursively traverse all of the flakeModules in ./flake-parts and 49 | # import only the final modules, meaning that you can have an arbitrary 50 | # nested structure that suffices your needs. For example 51 | # 52 | # - ./flake-parts 53 | # - modules/ 54 | # - nixos/ 55 | # - myNixosModule1.nix 56 | # - myNixosModule2.nix 57 | # - default.nix 58 | # - home-manager/ 59 | # - myHomeModule1.nix 60 | # - myHomeModule2.nix 61 | # - default.nix 62 | # - sharedModules.nix 63 | # - pkgs/ 64 | # - myPackage1.nix 65 | # - myPackage2.nix 66 | # - default.nix 67 | # - mySimpleModule.nix 68 | # - _not_a_module.nix 69 | imports = loadParts ./flake-parts; 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /src/cmd/add.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use color_eyre::eyre::Result; 3 | use fs_extra::dir::{self, CopyOptions}; 4 | use tempfile::tempdir; 5 | 6 | use crate::cmd::init::{parse_required_parts_tuples, prepare_tmpdir, InitCommand}; 7 | use crate::config::{BASE_DERIVATION_NAME, SELF_FLAKE_URI}; 8 | use crate::nix::NixCmdInterface; 9 | use crate::parts::FlakePartsStore; 10 | use crate::templates::FlakeInputsContext; 11 | 12 | // TODO: I might figure out a way to do this automatically in the future, but 13 | // it's way too complicated for now. 14 | // TODO for some reason broken formatting 15 | /// Add additional flake-parts to an already initialized project. 16 | /// 17 | /// This is similar to the init command, but differs in two significant ways: 18 | /// 19 | /// 1. No `_bootstrap` part is added, as the project is already bootstrapped. 20 | /// 21 | /// 2. `flake.nix` is left untouched as the user may have already made manual changes. 22 | /// Additional inputs will be printed to the console and the user is 23 | /// advised to add them manually. 24 | #[derive(Debug, Args)] 25 | pub struct AddCommand { 26 | #[clap(flatten)] 27 | pub init: InitCommand, 28 | } 29 | 30 | pub fn add(mut cmd: AddCommand, nix_cmd: impl NixCmdInterface) -> Result<()> { 31 | if !cmd.init.shared_args.disable_base_parts { 32 | log::info!("Adding base parts store to `cmd.shared_args.parts_stores`"); 33 | 34 | cmd.init 35 | .shared_args 36 | .parts_stores 37 | .push(format!("{}#{}", SELF_FLAKE_URI, BASE_DERIVATION_NAME)); 38 | } 39 | 40 | // NOTE we init stores here to have sensible ownerships of FlakePartTuples 41 | let stores = cmd 42 | .init 43 | .shared_args 44 | .parts_stores 45 | .iter() 46 | .map(|store| FlakePartsStore::from_flake_uri(store, &nix_cmd)) 47 | .collect::>>()?; 48 | 49 | log::debug!( 50 | "All parts stores: {:?}", 51 | stores 52 | .iter() 53 | .map(|store| store.flake_uri.clone()) 54 | .collect::>() 55 | ); 56 | 57 | let parts_tuples = parse_required_parts_tuples(&cmd.init, &stores)?; 58 | 59 | let path = cmd 60 | .init 61 | .path 62 | .canonicalize() 63 | .unwrap_or_else(|_| cmd.init.path.clone()); 64 | 65 | log::debug!("Full user provided path: {:?}", path); 66 | 67 | if !path.exists() { 68 | Err(std::io::Error::new( 69 | std::io::ErrorKind::NotFound, 70 | format!("Path {:?} does not exist", path), 71 | ))? 72 | } 73 | 74 | let tmpdir = tempdir()?; 75 | log::info!("Preparing new additions in a tmpdir at {:?}", tmpdir.path()); 76 | prepare_tmpdir( 77 | &nix_cmd, 78 | &tmpdir, 79 | &parts_tuples, 80 | path.file_name().map(|osstr| osstr.to_str().unwrap()), 81 | &cmd.init.strategy, 82 | false, 83 | )?; 84 | 85 | // NOTE the flake.nix file shouldn't be present due to the strucutre of 86 | // flake-parts, but I am way tooo paranoid. 87 | if tmpdir.path().join("flake.nix").exists() { 88 | log::warn!("Unexpected flake.nix file found in tmpdir, removing it."); 89 | std::fs::remove_file(tmpdir.path().join("flake.nix"))?; 90 | } 91 | 92 | let metadata = parts_tuples 93 | .iter() 94 | .map(|part_tuple| &part_tuple.part.metadata) 95 | .collect::>(); 96 | 97 | log::info!("Rendering `flake-inputs.nix.template` inputs"); 98 | let flake_context = FlakeInputsContext::from_merged_metadata(&metadata); 99 | 100 | let rendered = flake_context.render()?; 101 | println!("Please add the following snippet to your `flake.nix` inputs:"); 102 | println!("{}", rendered); 103 | 104 | log::info!("Addition succesfully prepared in tmpdir, now copying to target directory"); 105 | dir::copy( 106 | &tmpdir, 107 | &cmd.init.path, 108 | &CopyOptions::new() 109 | .content_only(true) 110 | .skip_exist(!cmd.init.force) 111 | .overwrite(cmd.init.force), 112 | )?; 113 | 114 | Ok(()) 115 | } 116 | -------------------------------------------------------------------------------- /src/cmd/init.rs: -------------------------------------------------------------------------------- 1 | use clap::{Args, ValueEnum}; 2 | use color_eyre::eyre::Result; 3 | use fs_extra::dir::{self, CopyOptions}; 4 | use std::fs; 5 | use std::path::PathBuf; 6 | use tempfile::{tempdir, TempDir}; 7 | use thiserror::Error; 8 | 9 | use crate::cmd::SharedArgs; 10 | use crate::config::{ 11 | BASE_DERIVATION_NAME, BOOTSTRAP_DERIVATION_NAME, META_FILE, NAMEPLACEHOLDER, SELF_FLAKE_URI, 12 | }; 13 | use crate::fs_utils::{regex_in_dir_recursive, reset_permissions}; 14 | use crate::nix::NixCmdInterface; 15 | use crate::parts::{FlakePartTuple, FlakePartsStore}; 16 | use crate::templates::FlakeContext; 17 | 18 | /// Initialize a new flake-parts projects using the builder. 19 | #[derive(Debug, Args)] 20 | pub struct InitCommand { 21 | #[clap(flatten)] 22 | pub shared_args: SharedArgs, 23 | 24 | /// Path (relative or absolute) for the desired flake-parts project. 25 | /// You can either pass an empty or non-existing directory, in which case 26 | /// all content will be new or you can pass an existing directory already 27 | /// populated with files. In such case the directories will be merged 28 | /// according to the strategy specified in `--strategy`. 29 | #[clap(verbatim_doc_comment)] 30 | pub path: PathBuf, 31 | 32 | /// Which parts to include in the project separated by commas. To see 33 | /// which ones are available use the `list` subcommand. 34 | #[arg( 35 | short = 'p', 36 | long = "parts", 37 | required = true, 38 | value_delimiter = ',', 39 | verbatim_doc_comment 40 | )] 41 | pub parts: Vec, 42 | 43 | /// Strategy to use when encountering already existing files 44 | #[arg(value_enum, short, long, default_value = "skip", verbatim_doc_comment)] 45 | pub strategy: InitStrategy, 46 | 47 | /// Force initialization in case of conflicting parts. Note that in such 48 | /// cases you should probably also pass a merging strategy that fits your 49 | /// specific needs. 50 | #[arg( 51 | long = "ignore-conflicts", 52 | default_value_t = false, 53 | verbatim_doc_comment 54 | )] 55 | pub ignore_conflicts: bool, 56 | 57 | /// Force initialization in case of unresolved dependencies. This can happen 58 | /// if you request parts that have 3rd party dependencies on parts stores 59 | /// that aren't included via the `--include` or `-I` flag. 60 | #[arg( 61 | long = "ignore-unresolved-deps", 62 | default_value_t = false, 63 | verbatim_doc_comment 64 | )] 65 | pub ignore_unresolved_deps: bool, 66 | 67 | /// Force overwriting of local files in case of initialization in 68 | /// a non-empty directory 69 | #[arg(long = "force", default_value_t = false, verbatim_doc_comment)] 70 | pub force: bool, 71 | } 72 | 73 | #[derive(Debug, Copy, Clone, PartialEq, ValueEnum)] 74 | pub enum InitStrategy { 75 | /// Skip file if already present in the filesystem 76 | #[clap(verbatim_doc_comment)] 77 | Skip, 78 | 79 | /// Overwrite file if already present in the filesystem 80 | #[clap(verbatim_doc_comment)] 81 | Overwrite, 82 | 83 | /// Try to merge file if already present in the filesystem. 84 | /// This will use a diff like patching algorithm and may fail 85 | /// in case of conflicts. (TODO not public yet) 86 | #[clap(verbatim_doc_comment)] 87 | Merge, 88 | } 89 | 90 | #[derive(Error, Debug)] 91 | pub enum PartsTuplesParsingError { 92 | #[error("The following user required parts couldn't be resolved: {0:?}")] 93 | MissingPartsError(Vec), 94 | 95 | #[error("You have requested parts that conflict with each other: {0:?} If you wish to ignore these conflicts use the `--ignore-conflicts` flag")] 96 | ConflictingPartsError(Vec), 97 | 98 | #[error("The following dependencies were required but couldn't be resolved: {0:?} Please include the necessary flake-parts stores using the `-I` flag or pass the `--ignore-unresolved-deps` flag to ignore this error and force initialization.")] 99 | UnresolvedDependenciesError(Vec), 100 | } 101 | 102 | pub fn parse_required_parts_tuples<'a>( 103 | cmd: &InitCommand, 104 | stores: &'a [FlakePartsStore], 105 | ) -> Result>, PartsTuplesParsingError> { 106 | let all_parts_tuples = stores 107 | .iter() 108 | .flat_map(|store| { 109 | store 110 | .parts 111 | .iter() 112 | .map(move |part| FlakePartTuple::new(store, part.to_owned())) 113 | }) 114 | .collect::>(); 115 | 116 | let user_req_flake_strings = cmd.parts.clone(); 117 | 118 | log::debug!("User requested parts: {:?}", user_req_flake_strings); 119 | 120 | let (resolved_deps, unresolved_deps) = { 121 | let start_indices: Vec = all_parts_tuples 122 | .iter() 123 | .enumerate() 124 | .filter(|&(_, part_tuple)| { 125 | let flake_uri = part_tuple.to_flake_uri(None); 126 | user_req_flake_strings 127 | .iter() 128 | .any(|req| req == &flake_uri || req == &part_tuple.part.name) 129 | }) 130 | .map(|(index, _)| index) 131 | .collect(); 132 | 133 | FlakePartTuple::resolve_dependencies_of(&all_parts_tuples, start_indices) 134 | }; 135 | 136 | if !unresolved_deps.is_empty() { 137 | return Err(PartsTuplesParsingError::UnresolvedDependenciesError( 138 | unresolved_deps, 139 | )); 140 | } 141 | 142 | let all_req_flake_strings = user_req_flake_strings 143 | .iter() 144 | .chain(resolved_deps.iter()) 145 | .collect::>(); 146 | 147 | log::debug!("Resolved dependencies: {:?}", resolved_deps); 148 | log::debug!("Unresolved dependencies: {:?}", unresolved_deps); 149 | log::debug!("All required parts: {:?}", all_req_flake_strings); 150 | 151 | let final_parts_tuples = all_parts_tuples 152 | .into_iter() 153 | .filter(|part_tuple| { 154 | let flake_uri = part_tuple.to_flake_uri(None); 155 | all_req_flake_strings 156 | .iter() 157 | .any(|&req| req == &flake_uri || req == &part_tuple.part.name) 158 | }) 159 | .collect::>(); 160 | 161 | let missing_parts = 162 | FlakePartTuple::find_missing_parts_in(&final_parts_tuples, &user_req_flake_strings); 163 | 164 | if !missing_parts.is_empty() { 165 | log::error!("Missing parts: {:?}", missing_parts); 166 | return Err(PartsTuplesParsingError::MissingPartsError( 167 | missing_parts.into_iter().cloned().collect::>(), 168 | )); 169 | } 170 | 171 | if !cmd.ignore_conflicts { 172 | // check_for_conflicts(&final_parts_tuples)?; 173 | let conflicts = FlakePartTuple::find_conflicting_parts_in(&final_parts_tuples); 174 | 175 | if !conflicts.is_empty() { 176 | log::error!("Conflicting parts: {:?}", conflicts); 177 | return Err(PartsTuplesParsingError::ConflictingPartsError( 178 | conflicts 179 | .into_iter() 180 | .map(|flake_part| flake_part.to_flake_uri(None)) 181 | .collect::>(), 182 | )); 183 | } 184 | } else { 185 | log::warn!("Ignoring conflicts"); 186 | } 187 | 188 | Ok(final_parts_tuples) 189 | } 190 | 191 | pub fn prepare_tmpdir( 192 | nix_cmd: &impl NixCmdInterface, 193 | tmpdir: &TempDir, 194 | parts_tuples: &Vec, 195 | target_name: Option<&str>, 196 | init_strategy: &InitStrategy, 197 | render_flake_nix: bool, 198 | ) -> Result<()> { 199 | // TODO MERGE STRATEGY 200 | let tmp_path = tmpdir.path(); 201 | for part_tuple in parts_tuples { 202 | log::debug!( 203 | "Copying the following part into tmpdir: {:?}", 204 | part_tuple.part.name 205 | ); 206 | dir::copy( 207 | &part_tuple.part.nix_store_path, 208 | tmp_path, 209 | &CopyOptions::new() 210 | .content_only(true) 211 | .skip_exist(init_strategy == &InitStrategy::Skip) 212 | .overwrite(init_strategy == &InitStrategy::Overwrite), 213 | )?; 214 | } 215 | 216 | log::debug!("Removing meta file from tmpdir"); 217 | std::fs::remove_file(tmp_path.join(META_FILE))?; 218 | 219 | log::info!("Resetting permissions in tmpdir"); 220 | reset_permissions(tmp_path.to_str().unwrap())?; 221 | 222 | if render_flake_nix { 223 | log::info!("Rendering `flake.nix.template` in tmpdir"); 224 | 225 | let metadata = parts_tuples 226 | .iter() 227 | .map(|part_tuple| &part_tuple.part.metadata) 228 | .collect::>(); 229 | 230 | let flake_context = FlakeContext::from_merged_metadata(&metadata); 231 | 232 | let rendered = flake_context.render()?; 233 | fs::write(tmp_path.join("flake.nix"), rendered)?; 234 | log::info!("Running nixfmt on flake.nix in tmpdir"); 235 | nix_cmd.nixfmt_file(&tmp_path.join("flake.nix"))?; 236 | // nixfmt_file(&tmp_path.join("flake.nix"))?; 237 | } else { 238 | log::info!("Skipping rendering of `flake.nix.template`"); 239 | } 240 | 241 | // This becomes None when `.`, `../`,etc... is passed 242 | if let Some(name) = target_name { 243 | log::info!( 244 | "Globally replacing NAMEPLACEHOLDER in tmpdir to name: {}", 245 | name 246 | ); 247 | regex_in_dir_recursive(tmp_path.to_str().unwrap(), NAMEPLACEHOLDER, name)?; 248 | } 249 | 250 | Ok(()) 251 | } 252 | 253 | pub fn init(mut cmd: InitCommand, nix_cmd: impl NixCmdInterface) -> Result<()> { 254 | if !cmd.shared_args.disable_base_parts { 255 | log::info!("Adding base parts store to `cmd.shared_args.parts_stores`"); 256 | 257 | cmd.shared_args 258 | .parts_stores 259 | .push(format!("{}#{}", SELF_FLAKE_URI, BASE_DERIVATION_NAME)); 260 | } 261 | 262 | log::info!("Adding bootstrap parts store to `cmd.shared_args.parts_stores`"); 263 | cmd.shared_args 264 | .parts_stores 265 | .push(format!("{}#{}", SELF_FLAKE_URI, BOOTSTRAP_DERIVATION_NAME)); 266 | 267 | log::info!("Adding _bootstrap to required `cmd.parts`"); 268 | cmd.parts.push(format!( 269 | "{}#{}/_bootstrap", 270 | SELF_FLAKE_URI, BOOTSTRAP_DERIVATION_NAME 271 | )); 272 | 273 | // NOTE we init stores here to have sensible ownerships of FlakePartTuples 274 | let stores = cmd 275 | .shared_args 276 | .parts_stores 277 | .iter() 278 | .map(|store| FlakePartsStore::from_flake_uri(store, &nix_cmd)) 279 | .collect::>>()?; 280 | 281 | log::debug!( 282 | "All parts stores: {:?}", 283 | stores 284 | .iter() 285 | .map(|store| store.flake_uri.clone()) 286 | .collect::>() 287 | ); 288 | 289 | let parts_tuples = parse_required_parts_tuples(&cmd, &stores)?; 290 | 291 | let path = cmd.path.canonicalize().unwrap_or_else(|_| cmd.path.clone()); 292 | log::debug!("Full user provided path: {:?}", path); 293 | 294 | if !path.exists() { 295 | log::info!("Provided path doesn't exist, creating it"); 296 | dir::create_all(&path, false)?; 297 | } 298 | 299 | let tmpdir = tempdir()?; 300 | log::info!("Preparing new project in a tmpdir at {:?}", tmpdir.path()); 301 | prepare_tmpdir( 302 | &nix_cmd, 303 | &tmpdir, 304 | &parts_tuples, 305 | path.file_name().map(|osstr| osstr.to_str().unwrap()), 306 | &cmd.strategy, 307 | true, 308 | )?; 309 | 310 | log::info!("Project successfully prepared in tmpdir, now copying to target directory"); 311 | dir::copy( 312 | &tmpdir, 313 | &cmd.path, 314 | &CopyOptions::new() 315 | .content_only(true) 316 | .skip_exist(!cmd.force) 317 | .overwrite(cmd.force), 318 | )?; 319 | 320 | Ok(()) 321 | } 322 | -------------------------------------------------------------------------------- /src/cmd/list.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use color_eyre::eyre::Result; 3 | use std::io::Write; 4 | use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; 5 | 6 | use crate::cmd::SharedArgs; 7 | use crate::config::{BASE_DERIVATION_NAME, BOOTSTRAP_DERIVATION_NAME, SELF_FLAKE_URI}; 8 | use crate::nix::NixCmdInterface; 9 | use crate::parts::FlakePartsStore; 10 | 11 | /// List all available flake-parts in all parts stores provided by the user. 12 | #[derive(Debug, Args)] 13 | pub struct ListCommand { 14 | #[clap(flatten)] 15 | pub shared_args: SharedArgs, 16 | } 17 | 18 | pub fn list(mut cmd: ListCommand, nix_cmd: impl NixCmdInterface) -> Result<()> { 19 | if !cmd.shared_args.disable_base_parts { 20 | log::info!("Adding base parts store to `cmd.shared_args.parts_stores`"); 21 | cmd.shared_args 22 | .parts_stores 23 | .push(format!("{}#{}", SELF_FLAKE_URI, BASE_DERIVATION_NAME)); 24 | } 25 | 26 | // NOTE this one is required even if you disable base store parts 27 | log::info!("Adding bootstrap parts store to `cmd.shared_args.parts_stores`"); 28 | cmd.shared_args 29 | .parts_stores 30 | .push(format!("{}#{}", SELF_FLAKE_URI, BOOTSTRAP_DERIVATION_NAME)); 31 | 32 | let mut stdout = StandardStream::stdout(ColorChoice::Auto); 33 | 34 | cmd.shared_args 35 | .parts_stores 36 | .iter() 37 | .try_for_each(|flake_uri| { 38 | stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?; 39 | writeln!(&mut stdout, " # {}", flake_uri)?; 40 | 41 | // TODO maybe some error message instead of unwrap? 42 | FlakePartsStore::from_flake_uri(flake_uri, &nix_cmd) 43 | .unwrap() 44 | .parts 45 | .iter() 46 | .try_for_each(|part| { 47 | // Visually distinguish collections 48 | let color = if part.name.contains('+') { 49 | Color::Cyan 50 | } else { 51 | Color::Red 52 | }; 53 | 54 | stdout.set_color(ColorSpec::new().set_fg(Some(color)))?; 55 | 56 | write!(&mut stdout, " - {}: ", part.name)?; 57 | 58 | stdout.set_color(ColorSpec::new().set_fg(Some(Color::White)))?; 59 | 60 | writeln!(&mut stdout, "{}", part.metadata.description)?; 61 | 62 | Ok(()) as Result<()> 63 | })?; 64 | 65 | println!(); 66 | Ok(()) 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /src/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | 3 | pub mod add; 4 | pub mod init; 5 | pub mod list; 6 | 7 | #[derive(Debug, Args)] 8 | pub struct SharedArgs { 9 | /// Additional parts templates stores to load. This currently accepts any 10 | /// valid flakes derivation URI. For example: 11 | /// 12 | /// - `github:tsandrini/flake-parts-builder#flake-parts` 13 | /// - `../myDir#flake-parts` 14 | /// - `.#different-flake-parts` 15 | /// 16 | /// NOTE: the derivation needs to have the parts stored at 17 | /// `$out/flake-parts`. You can also use `lib.mkFlakeParts` defined 18 | /// in `flake.nix` to make this easier. 19 | #[arg( 20 | short = 'I', 21 | long = "include", 22 | value_delimiter = ',', 23 | verbatim_doc_comment 24 | )] 25 | pub parts_stores: Vec, 26 | 27 | /// Disable base parts provided by this flake, that is, 28 | /// `github:tsandrini/flake-parts-builder#flake-parts`. Useful in case 29 | /// you'd like to override certain parts or simply not use the one provided 30 | /// by this repo. 31 | /// 32 | /// NOTE: _bootstrap part is always included for the project to 33 | /// properly function (if you really need to you can override the files 34 | /// with your own versions) 35 | #[arg(long = "disable-base", default_value_t = false, verbatim_doc_comment)] 36 | pub disable_base_parts: bool, 37 | 38 | /// Enable verbose logging 39 | #[arg(short, long, default_value_t = false)] 40 | pub verbose: bool, 41 | } 42 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | pub static FLAKE_TEMPLATE: &str = include_str!("assets/flake.nix.template"); 2 | pub static FLAKE_INPUTS_TEMPLATE: &str = include_str!("assets/flake-inputs.nix.template"); 3 | pub static META_FILE: &str = "meta.nix"; 4 | pub static NAMEPLACEHOLDER: &str = "NAMEPLACEHOLDER"; 5 | pub static BASE_DERIVATION_NAME: &str = "flake-parts"; 6 | pub static BOOTSTRAP_DERIVATION_NAME: &str = "flake-parts-bootstrap"; 7 | pub static SELF_FLAKE_URI: &str = "github:tsandrini/flake-parts-builder"; 8 | -------------------------------------------------------------------------------- /src/fs_utils.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::eyre::Result; 2 | use diff::{slice, Result as DiffResult}; 3 | use fs_extra::dir::{self, CopyOptions}; 4 | use regex::Regex; 5 | use std::fs::{self, File, Permissions}; 6 | use std::io::{self, Read, Write}; 7 | use std::os::unix::fs::PermissionsExt; 8 | use std::path::Path; 9 | use walkdir::WalkDir; 10 | 11 | use crate::config::META_FILE; 12 | 13 | pub fn reset_permissions(path: &str) -> std::io::Result<()> { 14 | for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) { 15 | let path = entry.path(); 16 | let metadata = fs::metadata(path)?; 17 | 18 | if metadata.is_dir() { 19 | fs::set_permissions(path, Permissions::from_mode(0o755))?; 20 | } else if metadata.is_file() { 21 | fs::set_permissions(path, Permissions::from_mode(0o644))?; 22 | } 23 | } 24 | Ok(()) 25 | } 26 | 27 | pub fn regex_in_dir_recursive(dir: &str, pattern: &str, replacement: &str) -> io::Result<()> { 28 | let re = Regex::new(pattern).unwrap(); 29 | 30 | for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) { 31 | if entry.file_type().is_file() { 32 | let path = entry.path(); 33 | let mut contents = String::new(); 34 | { 35 | let mut file = File::open(path)?; 36 | file.read_to_string(&mut contents)?; 37 | } 38 | let new_contents = re.replace_all(&contents, replacement).to_string(); 39 | if new_contents != contents { 40 | let mut file = File::create(path)?; 41 | file.write_all(new_contents.as_bytes())?; 42 | } 43 | } 44 | } 45 | Ok(()) 46 | } 47 | 48 | // TODO might implement a "merging" strategy instead of skipping/overwriting 49 | // but currently not entirely sure about its use case 50 | #[allow(dead_code)] 51 | pub fn merge_files(base: &Path, theirs: &Path, ours: &Path) -> Result<()> { 52 | let base_content = fs::read_to_string(base)?; 53 | let their_content = fs::read_to_string(theirs)?; 54 | let our_content = fs::read_to_string(ours)?; 55 | 56 | let base_lines = base_content.lines().collect::>(); 57 | let their_lines = their_content.lines().collect::>(); 58 | let our_lines = our_content.lines().collect::>(); 59 | 60 | let diffs = slice(&base_lines, &their_lines); 61 | 62 | let mut merged_lines = Vec::new(); 63 | let mut our_iter = our_lines.iter(); 64 | for diff in diffs { 65 | match diff { 66 | DiffResult::Both(_, line) => { 67 | // Both versions are the same 68 | merged_lines.push(*line); 69 | our_iter.next(); // Move along ours as well 70 | } 71 | DiffResult::Left(_) => { 72 | // Line is only in base, so we take from ours 73 | if let Some(line) = our_iter.next() { 74 | merged_lines.push(*line); 75 | } 76 | } 77 | DiffResult::Right(line) => { 78 | // Line is only in theirs 79 | merged_lines.push(*line); 80 | } 81 | } 82 | } 83 | // Append remaining lines from ours 84 | for line in our_iter { 85 | merged_lines.push(*line); 86 | } 87 | 88 | fs::write(ours, merged_lines.join("\n"))?; 89 | Ok(()) 90 | } 91 | 92 | // TODO 93 | #[allow(dead_code)] 94 | pub fn merge_dirs(src: &Path, dst: &Path, options: &CopyOptions) -> Result<()> { 95 | for entry in WalkDir::new(src) 96 | .into_iter() 97 | .filter_entry(|e| e.file_name() != META_FILE) 98 | { 99 | let entry = entry?; 100 | let target_path = dst.join(entry.path().strip_prefix(src)?); 101 | 102 | if target_path.exists() { 103 | if entry.path().is_file() { 104 | // Attempt to merge files 105 | merge_files(entry.path(), &target_path, entry.path())?; 106 | } 107 | } else if entry.path().is_dir() { 108 | dir::create_all(&target_path, false)?; 109 | } else { 110 | // TODO copy_with_progress? 111 | dir::copy(entry.path(), &target_path, options)?; 112 | } 113 | } 114 | Ok(()) 115 | } 116 | 117 | #[cfg(test)] 118 | mod tests { 119 | use super::*; 120 | use std::fs::{create_dir_all, File}; 121 | use tempfile::TempDir; 122 | 123 | #[test] 124 | fn test_reset_permissions() -> io::Result<()> { 125 | let temp_dir = TempDir::new()?; 126 | let test_dir = temp_dir.path().join("test_dir"); 127 | create_dir_all(&test_dir)?; 128 | 129 | // Create a test file with different permissions 130 | let test_file = test_dir.join("test_file.txt"); 131 | File::create(&test_file)?; 132 | fs::set_permissions(&test_file, Permissions::from_mode(0o777))?; 133 | 134 | reset_permissions(test_dir.to_str().unwrap())?; 135 | 136 | let metadata = fs::metadata(&test_file)?; 137 | assert_eq!(metadata.permissions().mode() & 0o777, 0o644); 138 | 139 | let dir_metadata = fs::metadata(&test_dir)?; 140 | assert_eq!(dir_metadata.permissions().mode() & 0o777, 0o755); 141 | 142 | Ok(()) 143 | } 144 | 145 | #[test] 146 | fn test_reset_permissions_recursive() -> io::Result<()> { 147 | let temp_dir = TempDir::new()?; 148 | let root_dir = temp_dir.path().join("root"); 149 | let nested_dir = root_dir.join("level1").join("level2"); 150 | create_dir_all(&nested_dir)?; 151 | 152 | // Create files at different levels with different permissions 153 | let root_file = root_dir.join("root_file.txt"); 154 | let nested_file = nested_dir.join("nested_file.txt"); 155 | File::create(&root_file)?; 156 | File::create(&nested_file)?; 157 | 158 | fs::set_permissions(&root_file, Permissions::from_mode(0o777))?; 159 | fs::set_permissions(&nested_file, Permissions::from_mode(0o600))?; 160 | fs::set_permissions(&nested_dir, Permissions::from_mode(0o700))?; 161 | 162 | reset_permissions(root_dir.to_str().unwrap())?; 163 | 164 | // Check permissions 165 | assert_eq!( 166 | fs::metadata(&root_file)?.permissions().mode() & 0o777, 167 | 0o644 168 | ); 169 | assert_eq!( 170 | fs::metadata(&nested_file)?.permissions().mode() & 0o777, 171 | 0o644 172 | ); 173 | assert_eq!( 174 | fs::metadata(&nested_dir)?.permissions().mode() & 0o777, 175 | 0o755 176 | ); 177 | assert_eq!(fs::metadata(&root_dir)?.permissions().mode() & 0o777, 0o755); 178 | 179 | Ok(()) 180 | } 181 | 182 | #[test] 183 | fn test_regex_in_dir_recursive() -> io::Result<()> { 184 | let temp_dir = TempDir::new()?; 185 | let test_file = temp_dir.path().join("test_file.txt"); 186 | 187 | let initial_content = "Hello, world! This is a test."; 188 | fs::write(&test_file, initial_content)?; 189 | 190 | regex_in_dir_recursive(temp_dir.path().to_str().unwrap(), r"world", "universe")?; 191 | 192 | let new_content = fs::read_to_string(&test_file)?; 193 | assert_eq!(new_content, "Hello, universe! This is a test."); 194 | 195 | Ok(()) 196 | } 197 | 198 | #[test] 199 | fn test_regex_in_dir_recursive_nested() -> io::Result<()> { 200 | let temp_dir = TempDir::new()?; 201 | let root_dir = temp_dir.path().join("root"); 202 | let nested_dir = root_dir.join("level1").join("level2"); 203 | create_dir_all(&nested_dir)?; 204 | 205 | // Create files at different levels with test content 206 | let root_file = root_dir.join("root_file.txt"); 207 | let nested_file = nested_dir.join("nested_file.txt"); 208 | 209 | fs::write(&root_file, "Hello, world! This is the root file.")?; 210 | fs::write(&nested_file, "Goodbye, world! This is the nested file.")?; 211 | 212 | regex_in_dir_recursive(root_dir.to_str().unwrap(), r"world", "universe")?; 213 | 214 | // Check content of both files 215 | let root_content = fs::read_to_string(&root_file)?; 216 | let nested_content = fs::read_to_string(&nested_file)?; 217 | 218 | assert_eq!(root_content, "Hello, universe! This is the root file."); 219 | assert_eq!( 220 | nested_content, 221 | "Goodbye, universe! This is the nested file." 222 | ); 223 | 224 | Ok(()) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! Nix flakes interactive template builder based on flake-parts written 2 | //! in Rust. 3 | //! 4 | //! TODO lorem ipsum 5 | use clap::{Parser, Subcommand}; 6 | use color_eyre::eyre::Result; 7 | 8 | pub mod cmd; 9 | pub mod config; 10 | pub mod fs_utils; 11 | pub mod nix; 12 | pub mod parts; 13 | pub mod templates; 14 | 15 | use crate::cmd::add::{add, AddCommand}; 16 | use crate::cmd::init::{init, InitCommand}; 17 | use crate::cmd::list::{list, ListCommand}; 18 | use crate::nix::NixExecutor; 19 | 20 | /// Nix flakes interactive template builder based on flake-parts written 21 | /// in Rust. 22 | #[derive(Debug, Parser)] 23 | #[command(author, version, about, long_about = None, verbatim_doc_comment)] 24 | struct Cli { 25 | #[command(subcommand)] 26 | command: Commands, 27 | } 28 | 29 | #[derive(Debug, Subcommand)] 30 | enum Commands { 31 | Init(InitCommand), 32 | List(ListCommand), 33 | Add(AddCommand), 34 | } 35 | 36 | // TODO add logging 37 | // TODO better docs 38 | fn main() -> Result<()> { 39 | color_eyre::install()?; 40 | env_logger::init(); 41 | log::debug!("color-eyre installed and logger initialized"); 42 | 43 | let cli = Cli::parse(); 44 | let nix_cmd = NixExecutor::from_env()?; 45 | 46 | match cli.command { 47 | Commands::List(cmd) => { 48 | log::info!("Executing list command"); 49 | list(cmd, nix_cmd) 50 | } 51 | Commands::Init(cmd) => { 52 | log::info!("Executing init command"); 53 | init(cmd, nix_cmd) 54 | } 55 | Commands::Add(cmd) => { 56 | log::info!("Executing add command"); 57 | add(cmd, nix_cmd) 58 | } 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::*; 65 | 66 | #[test] 67 | fn verify_cli() { 68 | use clap::CommandFactory; 69 | Cli::command().debug_assert(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/nix.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::eyre::Result; 2 | use std::path::PathBuf; 3 | use std::process::Command; 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug, Clone)] 7 | pub enum NixCmdInterfaceError { 8 | #[error("failed to run cmd due to invalid path: {0}")] 9 | InvalidPath(PathBuf), 10 | 11 | #[error("failed to convert output to UTF-8: {0}")] 12 | UTF8ConversionError(String), 13 | 14 | #[error("failed to run nix command: {0}")] 15 | NixCommandError(String), 16 | 17 | #[error("failed to run nixfmt command: {0}")] 18 | NixfmtCommandError(String), 19 | } 20 | 21 | pub trait NixCmdInterface { 22 | // TODO figure out how to remove the static lifetime 23 | type Error: From + std::error::Error + Send + Sync + 'static; 24 | 25 | fn eval_nix_file(&self, path: &PathBuf, to_json: bool) -> Result; 26 | fn store_path_of_flake(&self, flake_uri: &str) -> Result; 27 | fn nixfmt_file(&self, path: &PathBuf) -> Result<(), Self::Error>; 28 | } 29 | 30 | pub struct NixExecutor { 31 | nix_binary: PathBuf, 32 | nixfmt_binary: PathBuf, 33 | } 34 | 35 | #[derive(Error, Debug)] 36 | pub enum NixExecutorError { 37 | #[error("{0}")] 38 | NixCmdInterfaceError(#[from] NixCmdInterfaceError), 39 | 40 | #[error("nix binary not found")] 41 | NixBinaryNotFound, 42 | 43 | #[error("nixfmt binary not found")] 44 | NixfmtBinaryNotFound, 45 | 46 | #[error("nix command failed with nonzero status: {0}")] 47 | NonzeroStatusError(String), 48 | } 49 | 50 | impl NixExecutor { 51 | pub fn new(nix_binary: PathBuf, nixfmt_binary: PathBuf) -> Self { 52 | Self { 53 | nix_binary, 54 | nixfmt_binary, 55 | } 56 | } 57 | 58 | pub fn from_env() -> Result { 59 | let nix_binary = std::env::var_os("NIX_BIN_PATH") 60 | .map(PathBuf::from) 61 | .or_else(|| which::which("nix").ok()) 62 | .ok_or(NixExecutorError::NixBinaryNotFound)?; 63 | 64 | let nixfmt_binary = std::env::var_os("NIXFMT_BIN_PATH") 65 | .map(PathBuf::from) 66 | .or_else(|| which::which("nixfmt").ok()) 67 | .ok_or(NixExecutorError::NixfmtBinaryNotFound)?; 68 | 69 | Ok(Self::new(nix_binary, nixfmt_binary)) 70 | } 71 | 72 | fn nix_command(&self) -> Command { 73 | let mut cmd = Command::new(&self.nix_binary); 74 | cmd.args([ 75 | "--extra-experimental-features", 76 | "nix-command", 77 | "--extra-experimental-features", 78 | "flakes", 79 | ]); 80 | cmd 81 | } 82 | 83 | fn nixfmt_command(&self) -> Command { 84 | Command::new(&self.nixfmt_binary) 85 | } 86 | } 87 | 88 | impl NixCmdInterface for NixExecutor { 89 | type Error = NixExecutorError; 90 | 91 | fn eval_nix_file(&self, path: &PathBuf, to_json: bool) -> Result { 92 | let path = path.to_str().ok_or(NixExecutorError::NixCmdInterfaceError( 93 | NixCmdInterfaceError::InvalidPath(path.clone()), 94 | ))?; 95 | 96 | let mut command = self.nix_command(); 97 | command.arg("eval"); 98 | command.arg("--file").arg(path); 99 | if to_json { 100 | command.arg("--json"); 101 | } 102 | 103 | let output = command.output().map_err(|e| { 104 | NixExecutorError::NixCmdInterfaceError(NixCmdInterfaceError::NixCommandError( 105 | e.to_string(), 106 | )) 107 | })?; 108 | 109 | if !output.status.success() { 110 | return Err(NixExecutorError::NonzeroStatusError( 111 | String::from_utf8_lossy(&output.stderr).to_string(), 112 | )); 113 | } 114 | 115 | let stdout = String::from_utf8(output.stdout).map_err(|e| { 116 | NixExecutorError::NixCmdInterfaceError(NixCmdInterfaceError::UTF8ConversionError( 117 | e.to_string(), 118 | )) 119 | })?; 120 | 121 | Ok(stdout.trim().to_string()) 122 | } 123 | 124 | fn store_path_of_flake(&self, flake_uri: &str) -> Result { 125 | let mut command = self.nix_command(); 126 | command.args(["build", "--no-link", "--print-out-paths", flake_uri]); 127 | 128 | let output = command.output().map_err(|e| { 129 | NixExecutorError::NixCmdInterfaceError(NixCmdInterfaceError::NixCommandError( 130 | e.to_string(), 131 | )) 132 | })?; 133 | 134 | if !output.status.success() { 135 | return Err(NixExecutorError::NonzeroStatusError( 136 | String::from_utf8_lossy(&output.stderr).to_string(), 137 | )); 138 | } 139 | 140 | let stdout = String::from_utf8(output.stdout).map_err(|e| { 141 | NixExecutorError::NixCmdInterfaceError(NixCmdInterfaceError::UTF8ConversionError( 142 | e.to_string(), 143 | )) 144 | })?; 145 | 146 | Ok(PathBuf::from(stdout.trim())) 147 | } 148 | 149 | fn nixfmt_file(&self, path: &PathBuf) -> Result<(), Self::Error> { 150 | let path = path.to_str().ok_or(NixExecutorError::NixCmdInterfaceError( 151 | NixCmdInterfaceError::InvalidPath(path.clone()), 152 | ))?; 153 | 154 | let output = self.nixfmt_command().arg(path).output().map_err(|e| { 155 | NixExecutorError::NixCmdInterfaceError(NixCmdInterfaceError::NixfmtCommandError( 156 | e.to_string(), 157 | )) 158 | })?; 159 | 160 | if !output.status.success() { 161 | return Err(NixExecutorError::NonzeroStatusError( 162 | String::from_utf8_lossy(&output.stderr).to_string(), 163 | )); 164 | } 165 | 166 | Ok(()) 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | mod tests { 172 | use super::*; 173 | use serial_test::serial; 174 | use std::fs::File; 175 | use std::io::Write; 176 | use tempfile::TempDir; 177 | 178 | // Helper function to clean strings for comparison 179 | fn clean_string(s: &str) -> String { 180 | s.split_whitespace().collect::() 181 | } 182 | 183 | mod mock_tests { 184 | use crate::nix::{NixCmdInterface, NixCmdInterfaceError}; 185 | use std::collections::HashMap; 186 | use std::fs::File; 187 | use std::io::Write; 188 | use std::path::{Path, PathBuf}; 189 | use std::thread::sleep; 190 | use std::time::Duration; 191 | use std::time::SystemTime; 192 | use tempfile::{tempdir, TempDir}; 193 | 194 | pub struct MockExecutor { 195 | eval_results: HashMap>, 196 | mocked_store: TempDir, 197 | store_paths: HashMap, 198 | } 199 | 200 | impl MockExecutor { 201 | pub fn new() -> Self { 202 | Self { 203 | eval_results: HashMap::new(), 204 | mocked_store: tempdir().expect("Failed to create temporary directory"), 205 | store_paths: HashMap::new(), 206 | } 207 | } 208 | 209 | pub fn mock_eval>( 210 | &mut self, 211 | path: P, 212 | result: Result, 213 | ) { 214 | self.eval_results 215 | .insert(path.as_ref().to_path_buf(), result); 216 | } 217 | 218 | pub fn mock_store_path( 219 | &mut self, 220 | flake_uri: String, 221 | ) -> Result { 222 | let mock_path = self 223 | .mocked_store 224 | .path() 225 | .join(format!("mock-{}", flake_uri.replace(':', "-"))); 226 | std::fs::create_dir_all(&mock_path) 227 | .map_err(|e| NixCmdInterfaceError::NixCommandError(e.to_string()))?; 228 | self.store_paths.insert(flake_uri, mock_path.clone()); 229 | Ok(mock_path) 230 | } 231 | } 232 | 233 | impl NixCmdInterface for MockExecutor { 234 | type Error = NixCmdInterfaceError; 235 | 236 | fn eval_nix_file(&self, path: &PathBuf, _to_json: bool) -> Result { 237 | self.eval_results 238 | .get(path) 239 | .cloned() 240 | .unwrap_or(Err(NixCmdInterfaceError::InvalidPath(path.clone()))) 241 | } 242 | 243 | fn store_path_of_flake(&self, flake_uri: &str) -> Result { 244 | self.store_paths.get(flake_uri).cloned().ok_or_else(|| { 245 | NixCmdInterfaceError::NixCommandError(format!( 246 | "Flake URI not mocked: {}", 247 | flake_uri 248 | )) 249 | }) 250 | } 251 | 252 | fn nixfmt_file(&self, path: &PathBuf) -> Result<(), Self::Error> { 253 | if path.exists() { 254 | // Touch the file by updating its modification time 255 | File::open(path) 256 | .and_then(|file| file.set_modified(SystemTime::now())) 257 | .map_err(|e| NixCmdInterfaceError::NixCommandError(e.to_string())) 258 | } else { 259 | Err(NixCmdInterfaceError::InvalidPath(path.clone())) 260 | } 261 | } 262 | } 263 | 264 | #[test] 265 | fn test_mock_eval_nix_file_valid() { 266 | let mut mock = MockExecutor::new(); 267 | let path = PathBuf::from("/test/valid.nix"); 268 | let expected_output = r#"{"description":"Test description","inputs":{"test":{"url":"github:test/repo"}}}"#.to_string(); 269 | 270 | mock.mock_eval(&path, Ok(expected_output.clone())); 271 | 272 | let result = mock.eval_nix_file(&path, true).unwrap(); 273 | assert_eq!(result, expected_output); 274 | } 275 | 276 | #[test] 277 | fn test_mock_eval_nix_file_error() { 278 | let mut mock = MockExecutor::new(); 279 | let path = PathBuf::from("/test/error.nix"); 280 | let error_message = "Nix evaluation error".to_string(); 281 | 282 | mock.mock_eval( 283 | &path, 284 | Err(NixCmdInterfaceError::NixCommandError(error_message.clone())), 285 | ); 286 | 287 | let result = mock.eval_nix_file(&path, true); 288 | assert!( 289 | matches!(result, Err(NixCmdInterfaceError::NixCommandError(msg)) if msg == error_message) 290 | ); 291 | } 292 | 293 | #[test] 294 | fn test_mock_eval_nix_file_utf8_error() { 295 | let mut mock = MockExecutor::new(); 296 | let path = PathBuf::from("/test/utf8_error.nix"); 297 | let error_message = "UTF-8 conversion error".to_string(); 298 | 299 | mock.mock_eval( 300 | &path, 301 | Err(NixCmdInterfaceError::UTF8ConversionError( 302 | error_message.clone(), 303 | )), 304 | ); 305 | 306 | let result = mock.eval_nix_file(&path, true); 307 | assert!( 308 | matches!(result, Err(NixCmdInterfaceError::UTF8ConversionError(msg)) if msg == error_message) 309 | ); 310 | } 311 | 312 | #[test] 313 | fn test_mock_eval_nix_file_not_mocked() { 314 | let mock = MockExecutor::new(); 315 | let path = PathBuf::from("/test/not_mocked.nix"); 316 | 317 | let result = mock.eval_nix_file(&path, true); 318 | assert!(matches!(result, Err(NixCmdInterfaceError::InvalidPath(p)) if p == path)); 319 | } 320 | 321 | #[test] 322 | fn test_mock_eval_nix_file_multiple_calls() { 323 | let mut mock = MockExecutor::new(); 324 | let path = PathBuf::from("/test/multiple_calls.nix"); 325 | let expected_output = "Test output".to_string(); 326 | 327 | mock.mock_eval(&path, Ok(expected_output.clone())); 328 | 329 | // First call should succeed 330 | let result1 = mock.eval_nix_file(&path, true).unwrap(); 331 | assert_eq!(result1, expected_output); 332 | 333 | // Second call should also succeed with the same result 334 | let result2 = mock.eval_nix_file(&path, true).unwrap(); 335 | assert_eq!(result2, expected_output); 336 | } 337 | 338 | #[test] 339 | fn test_mock_eval_nix_file_different_paths() { 340 | let mut mock = MockExecutor::new(); 341 | let path1 = PathBuf::from("/test/path1.nix"); 342 | let path2 = PathBuf::from("/test/path2.nix"); 343 | let output1 = "Output 1".to_string(); 344 | let output2 = "Output 2".to_string(); 345 | 346 | mock.mock_eval(&path1, Ok(output1.clone())); 347 | mock.mock_eval(&path2, Ok(output2.clone())); 348 | 349 | let result1 = mock.eval_nix_file(&path1, true).unwrap(); 350 | let result2 = mock.eval_nix_file(&path2, true).unwrap(); 351 | 352 | assert_eq!(result1, output1); 353 | assert_eq!(result2, output2); 354 | } 355 | 356 | #[test] 357 | fn test_mock_eval_nix_file_to_json_ignored() { 358 | let mut mock = MockExecutor::new(); 359 | let path = PathBuf::from("/test/json_ignored.nix"); 360 | let expected_output = r#"{"key": "value"}"#.to_string(); 361 | 362 | mock.mock_eval(&path, Ok(expected_output.clone())); 363 | 364 | // The to_json parameter should be ignored in the mock 365 | let result_true = mock.eval_nix_file(&path, true).unwrap(); 366 | let result_false = mock.eval_nix_file(&path, false).unwrap(); 367 | 368 | assert_eq!(result_true, expected_output); 369 | assert_eq!(result_false, expected_output); 370 | } 371 | 372 | #[test] 373 | fn test_mock_store_path_of_flake_valid() { 374 | let mut mock = MockExecutor::new(); 375 | let flake_uri = "github:user/repo"; 376 | let mock_path = mock.mock_store_path(flake_uri.to_string()).unwrap(); 377 | 378 | let result = mock.store_path_of_flake(flake_uri).unwrap(); 379 | assert_eq!(result, mock_path); 380 | assert!(result.exists()); 381 | assert!(result.is_dir()); 382 | } 383 | 384 | #[test] 385 | fn test_mock_store_path_of_flake_not_mocked() { 386 | let mock = MockExecutor::new(); 387 | let flake_uri = "github:user/not-mocked-repo"; 388 | 389 | let result = mock.store_path_of_flake(flake_uri); 390 | assert!(matches!( 391 | result, 392 | Err(NixCmdInterfaceError::NixCommandError(_)) 393 | )); 394 | } 395 | 396 | #[test] 397 | fn test_mock_store_path_of_flake_multiple_flakes() { 398 | let mut mock = MockExecutor::new(); 399 | let flake_uri1 = "github:user/repo1"; 400 | let flake_uri2 = "github:user/repo2"; 401 | let mock_path1 = mock.mock_store_path(flake_uri1.to_string()).unwrap(); 402 | let mock_path2 = mock.mock_store_path(flake_uri2.to_string()).unwrap(); 403 | 404 | let result1 = mock.store_path_of_flake(flake_uri1).unwrap(); 405 | let result2 = mock.store_path_of_flake(flake_uri2).unwrap(); 406 | 407 | assert_eq!(result1, mock_path1); 408 | assert_eq!(result2, mock_path2); 409 | assert_ne!(result1, result2); 410 | } 411 | 412 | #[test] 413 | fn test_mock_store_path_of_flake_overwrite() { 414 | let mut mock = MockExecutor::new(); 415 | let flake_uri = "github:user/repo"; 416 | let mock_path1 = mock.mock_store_path(flake_uri.to_string()).unwrap(); 417 | let mock_path2 = mock.mock_store_path(flake_uri.to_string()).unwrap(); 418 | 419 | assert_eq!(mock_path1, mock_path2); 420 | 421 | let result = mock.store_path_of_flake(flake_uri).unwrap(); 422 | assert_eq!(result, mock_path2); 423 | } 424 | 425 | #[test] 426 | fn test_mock_store_path_of_flake_different_uris() { 427 | let mut mock = MockExecutor::new(); 428 | let flake_uri1 = "github:user/repo"; 429 | let flake_uri2 = "gitlab:user/repo"; 430 | let mock_path1 = mock.mock_store_path(flake_uri1.to_string()).unwrap(); 431 | let mock_path2 = mock.mock_store_path(flake_uri2.to_string()).unwrap(); 432 | 433 | let result1 = mock.store_path_of_flake(flake_uri1).unwrap(); 434 | let result2 = mock.store_path_of_flake(flake_uri2).unwrap(); 435 | 436 | assert_ne!(result1, result2); 437 | assert_eq!(result1, mock_path1); 438 | assert_eq!(result2, mock_path2); 439 | } 440 | 441 | #[test] 442 | fn test_mock_nixfmt_file_success() { 443 | let mock = MockExecutor::new(); 444 | let temp_dir = tempdir().unwrap(); 445 | let file_path = temp_dir.path().join("test.nix"); 446 | File::create(&file_path) 447 | .unwrap() 448 | .write_all(b"# Test Nix file") 449 | .unwrap(); 450 | 451 | let original_modified = file_path.metadata().unwrap().modified().unwrap(); 452 | sleep(Duration::from_secs(1)); // Ensure some time passes 453 | 454 | let result = mock.nixfmt_file(&file_path); 455 | assert!(result.is_ok()); 456 | 457 | let new_modified = file_path.metadata().unwrap().modified().unwrap(); 458 | assert!(new_modified > original_modified); 459 | } 460 | 461 | #[test] 462 | fn test_mock_nixfmt_file_not_exist() { 463 | let mock = MockExecutor::new(); 464 | let non_existent_path = PathBuf::from("/path/to/non/existent/file.nix"); 465 | 466 | let result = mock.nixfmt_file(&non_existent_path); 467 | assert!(matches!(result, Err(NixCmdInterfaceError::InvalidPath(_)))); 468 | } 469 | } 470 | 471 | mod nix_executor_tests { 472 | use super::*; 473 | 474 | #[test] 475 | #[serial(nix_transaction)] 476 | fn test_valid_nix_file() -> Result<()> { 477 | let nix_cmd = NixExecutor::from_env()?; 478 | let temp_dir = TempDir::new()?; 479 | let file_path = temp_dir.path().join("test.nix"); 480 | let mut file = File::create(&file_path)?; 481 | write!( 482 | file, 483 | r#" 484 | {{ 485 | description = "Test description"; 486 | inputs = {{ 487 | test.url = "github:test/repo"; 488 | }}; 489 | }} 490 | "# 491 | )?; 492 | 493 | let result = nix_cmd.eval_nix_file(&file_path, true)?; 494 | let expected = r#"{"description":"Test description","inputs":{"test":{"url":"github:test/repo"}}}"#; 495 | 496 | assert_eq!(clean_string(&result), clean_string(expected)); 497 | 498 | Ok(()) 499 | } 500 | 501 | #[test] 502 | #[serial(nix_transaction)] 503 | fn test_nonexistent_path() -> Result<()> { 504 | let nix_cmd = NixExecutor::from_env()?; 505 | let invalid_path = PathBuf::from("/nonexistent/path"); 506 | let result = nix_cmd.eval_nix_file(&invalid_path, true); 507 | assert!(matches!( 508 | result, 509 | Err(NixExecutorError::NonzeroStatusError(_)) 510 | )); 511 | Ok(()) 512 | } 513 | 514 | #[test] 515 | #[serial(nix_transaction)] 516 | fn test_invalid_path() -> Result<()> { 517 | let nix_cmd = NixExecutor::from_env()?; 518 | let invalid_path = PathBuf::from(""); 519 | let result = nix_cmd.eval_nix_file(&invalid_path, true); 520 | assert!(matches!( 521 | result, 522 | Err(NixExecutorError::NonzeroStatusError(_)) 523 | )); 524 | Ok(()) 525 | } 526 | 527 | #[test] 528 | #[serial(nix_transaction)] 529 | fn test_non_json_output() -> Result<()> { 530 | let nix_cmd = NixExecutor::from_env()?; 531 | let temp_dir = TempDir::new()?; 532 | let file_path = temp_dir.path().join("test.nix"); 533 | let mut file = File::create(&file_path)?; 534 | write!(file, r#""Hello, World!""#)?; 535 | 536 | let result = nix_cmd.eval_nix_file(&file_path, false)?; 537 | assert_eq!(clean_string(&result), clean_string("\"Hello, World!\"")); 538 | 539 | Ok(()) 540 | } 541 | 542 | #[test] 543 | #[serial(nix_transaction)] 544 | fn test_complex_nix_file() -> Result<()> { 545 | let nix_cmd = NixExecutor::from_env()?; 546 | let temp_dir = TempDir::new()?; 547 | let file_path = temp_dir.path().join("test.nix"); 548 | let mut file = File::create(&file_path)?; 549 | write!( 550 | file, 551 | r#" 552 | {{ 553 | description = "Flake bindings for the `github:cachix/devenv` development environment."; 554 | inputs = {{ 555 | devenv.url = "github:cachix/devenv"; 556 | devenv-root = {{ 557 | url = "file+file:///dev/null"; 558 | flake = false; 559 | }}; 560 | mk-shell-bin.url = "github:rrbutani/nix-mk-shell-bin"; 561 | nix2container = {{ 562 | url = "github:nlewo/nix2container"; 563 | inputs.nixpkgs.follows = "nixpkgs"; 564 | }}; 565 | }}; 566 | conflicts = [ "shells" ]; 567 | extraTrustedPublicKeys = [ "https://devenv.cachix.org" ]; 568 | extraSubstituters = [ "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=" ]; 569 | }} 570 | "# 571 | )?; 572 | 573 | let result = nix_cmd.eval_nix_file(&file_path, true)?; 574 | let expected = r#" 575 | { 576 | "conflicts":["shells"], 577 | "description":"Flake bindings for the `github:cachix/devenv` development environment.", 578 | "extraSubstituters":["devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="], 579 | "extraTrustedPublicKeys":["https://devenv.cachix.org"], 580 | "inputs":{ 581 | "devenv":{"url":"github:cachix/devenv"}, 582 | "devenv-root":{"flake":false,"url":"file+file:///dev/null"}, 583 | "mk-shell-bin":{"url":"github:rrbutani/nix-mk-shell-bin"}, 584 | "nix2container":{ 585 | "inputs":{"nixpkgs":{"follows":"nixpkgs"}}, 586 | "url":"github:nlewo/nix2container" 587 | } 588 | } 589 | } 590 | "#; 591 | 592 | assert_eq!(clean_string(&result), clean_string(expected)); 593 | 594 | Ok(()) 595 | } 596 | 597 | #[test] 598 | #[serial(nix_transaction)] 599 | fn test_nix_command_error() -> Result<()> { 600 | let nix_cmd = NixExecutor::from_env()?; 601 | let temp_dir = TempDir::new()?; 602 | let file_path = temp_dir.path().join("invalid.nix"); 603 | let mut file = File::create(&file_path)?; 604 | write!(file, "this is not a valid nix expression")?; 605 | 606 | let result = nix_cmd.eval_nix_file(&file_path, true); 607 | assert!(matches!( 608 | result, 609 | Err(NixExecutorError::NonzeroStatusError(_)) 610 | )); 611 | 612 | Ok(()) 613 | } 614 | } 615 | } 616 | -------------------------------------------------------------------------------- /src/parts.rs: -------------------------------------------------------------------------------- 1 | //! Provides a way to parse and store flake parts metadata 2 | use color_eyre::eyre::Result; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_json::Value as JsonValue; 5 | use std::fs; 6 | use std::path::PathBuf; 7 | use thiserror::Error; 8 | 9 | use crate::config::META_FILE; 10 | use crate::nix::NixCmdInterface; 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct FlakePart { 14 | pub name: String, 15 | pub nix_store_path: PathBuf, 16 | pub metadata: FlakePartMetadata, 17 | } 18 | 19 | // TODO helper struct to evade overcomplicated lifetimes 20 | // beware that the store references will live only as long 21 | // as the original function call 22 | #[derive(Debug)] 23 | pub struct FlakePartTuple<'a> { 24 | pub store: &'a FlakePartsStore, 25 | pub part: FlakePart, 26 | } 27 | 28 | pub fn normalize_flake_string(target: &str, flake: &str, derivation: Option<&str>) -> String { 29 | if target.contains('#') { 30 | target.to_string() // OK 31 | } else if flake.contains('#') { 32 | format!("{}/{}", flake, target) 33 | } else if let Some(derivation) = derivation { 34 | format!("{}#{}/{}", flake, derivation, target) 35 | } else { 36 | format!("{}#default/{}", flake, target) 37 | } 38 | } 39 | 40 | impl<'a> FlakePartTuple<'a> { 41 | pub fn new(store: &'a FlakePartsStore, part: FlakePart) -> Self { 42 | Self { store, part } 43 | } 44 | 45 | pub fn to_flake_uri(&self, derivation: Option<&str>) -> String { 46 | normalize_flake_string(&self.part.name, &self.store.flake_uri, derivation) 47 | } 48 | 49 | pub fn resolve_dependencies_of( 50 | parts_tuples_pool: &[FlakePartTuple], 51 | start_indices: Vec, 52 | ) -> (Vec, Vec) { 53 | use std::collections::{HashSet, VecDeque}; 54 | let mut resolved_dependencies = HashSet::new(); 55 | let mut unresolved_dependencies = Vec::new(); 56 | let mut to_process = VecDeque::from(start_indices); 57 | 58 | while let Some(index) = to_process.pop_front() { 59 | let part_tuple = &parts_tuples_pool[index]; 60 | for dep in &part_tuple.part.metadata.dependencies { 61 | let normalized_dep = normalize_flake_string(dep, &part_tuple.store.flake_uri, None); 62 | if resolved_dependencies.insert(normalized_dep.clone()) { 63 | // If this is a new dependency, try to find the corresponding PartTuple 64 | if let Some(dep_index) = parts_tuples_pool 65 | .iter() 66 | .position(|p| p.to_flake_uri(None) == normalized_dep) 67 | { 68 | to_process.push_back(dep_index); 69 | } else { 70 | // This dependency couldn't be resolved 71 | unresolved_dependencies.push(normalized_dep); 72 | } 73 | } 74 | } 75 | } 76 | 77 | ( 78 | resolved_dependencies.into_iter().collect(), 79 | unresolved_dependencies, 80 | ) 81 | } 82 | 83 | pub fn find_conflicting_parts_in( 84 | parts_tuples: &'a [FlakePartTuple], 85 | ) -> Vec<&'a FlakePartTuple<'a>> { 86 | let conflicting_parts_uris = parts_tuples 87 | .iter() 88 | .flat_map(|part_tuple| { 89 | part_tuple.part.metadata.conflicts.iter().map(|conflict| { 90 | normalize_flake_string(conflict, &part_tuple.store.flake_uri, None) 91 | }) 92 | }) 93 | .collect::>(); 94 | 95 | let conflicting_parts: Vec<&'a FlakePartTuple> = parts_tuples 96 | .iter() 97 | .filter(|&uri| conflicting_parts_uris.contains(&uri.to_flake_uri(None))) 98 | .collect::>(); 99 | 100 | conflicting_parts 101 | } 102 | 103 | pub fn find_missing_parts_in<'b>( 104 | parts_tuples: &[FlakePartTuple], 105 | required_parts: &'b [String], 106 | ) -> Vec<&'b String> { 107 | required_parts 108 | .iter() 109 | .filter(|&uri| { 110 | !parts_tuples.iter().any(|part_tuple| { 111 | uri == &part_tuple.to_flake_uri(None) || uri == &part_tuple.part.name 112 | }) 113 | }) 114 | .collect::>() 115 | } 116 | } 117 | 118 | #[derive(Debug, Serialize, Deserialize, Clone)] 119 | pub struct FlakePartMetadata { 120 | #[serde(default)] 121 | pub description: String, 122 | 123 | #[serde(default)] 124 | pub inputs: JsonValue, 125 | 126 | #[serde(default)] 127 | pub dependencies: Vec, 128 | 129 | #[serde(default)] 130 | pub conflicts: Vec, 131 | 132 | #[serde(rename = "extraTrustedPublicKeys", default)] 133 | pub extra_trusted_public_keys: Vec, 134 | 135 | #[serde(rename = "extraSubstituters", default)] 136 | pub extra_substituters: Vec, 137 | } 138 | 139 | #[derive(Debug)] 140 | pub struct FlakePartsStore { 141 | pub flake_uri: String, 142 | pub nix_store_path: PathBuf, 143 | pub parts: Vec, 144 | } 145 | 146 | #[derive(Error, Debug)] 147 | pub enum FlakePartParseError { 148 | #[error("provided flake part path is invalid")] 149 | InvalidPathError(), 150 | 151 | #[error("failed to evaluate flake part metadata")] 152 | NixEvalError(#[from] std::io::Error), 153 | 154 | #[error("failed to parse the UTF-8 output of nix eval")] 155 | NixEvalUTF8Error(#[from] std::string::FromUtf8Error), 156 | 157 | #[error("failed to convert flake parts metadata to JSON")] 158 | MetadataConversionError(#[from] serde_json::Error), 159 | } 160 | 161 | impl FlakePart { 162 | fn new(name: String, nix_store_path: PathBuf, metadata: FlakePartMetadata) -> Self { 163 | Self { 164 | name, 165 | nix_store_path, 166 | metadata, 167 | } 168 | } 169 | 170 | pub fn from_path(nix_store_path: PathBuf, nix_cmd: &impl NixCmdInterface) -> Result { 171 | let name = nix_store_path 172 | .file_name() 173 | .ok_or(FlakePartParseError::InvalidPathError())? 174 | .to_str() 175 | .ok_or(FlakePartParseError::InvalidPathError())?; 176 | 177 | let eval_output = nix_cmd.eval_nix_file(&nix_store_path.join(META_FILE), true)?; 178 | 179 | let metadata: FlakePartMetadata = serde_json::from_str(&eval_output) 180 | .map_err(FlakePartParseError::MetadataConversionError)?; 181 | 182 | Ok(Self::new(name.to_string(), nix_store_path, metadata)) 183 | } 184 | } 185 | 186 | #[derive(Error, Debug)] 187 | pub enum FlakePartsStoreParseError { 188 | #[error("failed to realize flake uri to a valid store path")] 189 | StoreRealizationError(#[from] std::io::Error), 190 | 191 | #[error("failed to parse the UTF-8 output of nix build command")] 192 | NixBuildUTF8Error(#[from] std::string::FromUtf8Error), 193 | 194 | #[error("failed to parse flake part")] 195 | FlakePartParseError(#[from] FlakePartParseError), 196 | } 197 | 198 | impl FlakePartsStore { 199 | fn new(flake_uri: String, nix_store_path: PathBuf, parts: Vec) -> Self { 200 | Self { 201 | flake_uri, 202 | nix_store_path, 203 | parts, 204 | } 205 | } 206 | 207 | // TODO handle errors 208 | pub fn from_flake_uri(flake_uri: &str, nix_cmd: &impl NixCmdInterface) -> Result { 209 | let nix_store_path = nix_cmd.store_path_of_flake(flake_uri)?; 210 | 211 | let parts = fs::read_dir(nix_store_path.join("flake-parts"))? 212 | .map(|entry| { 213 | let entry = entry?; 214 | 215 | FlakePart::from_path(entry.path(), nix_cmd) 216 | }) 217 | .collect::>()?; 218 | 219 | Ok(Self::new(flake_uri.to_string(), nix_store_path, parts)) 220 | } 221 | } 222 | 223 | #[cfg(test)] 224 | mod tests { 225 | use super::*; 226 | 227 | #[test] 228 | fn test_normalize_flake_string_with_hash_in_target() { 229 | let result = normalize_flake_string("github:user/repo#output", "unused", None); 230 | assert_eq!(result, "github:user/repo#output"); 231 | } 232 | 233 | #[test] 234 | fn test_normalize_flake_string_with_hash_in_flake() { 235 | let result = normalize_flake_string("output", "github:user/repo#derivation", None); 236 | assert_eq!(result, "github:user/repo#derivation/output"); 237 | } 238 | 239 | #[test] 240 | fn test_normalize_flake_string_with_derivation() { 241 | let result = normalize_flake_string("target", "github:user/repo", Some("derivation")); 242 | assert_eq!(result, "github:user/repo#derivation/target"); 243 | } 244 | 245 | #[test] 246 | fn test_normalize_flake_string_without_derivation() { 247 | let result = normalize_flake_string("target", "github:user/repo", None); 248 | assert_eq!(result, "github:user/repo#default/target"); 249 | } 250 | 251 | #[test] 252 | fn test_normalize_flake_string_with_empty_target() { 253 | let result = normalize_flake_string("", "github:user/repo", Some("derivation")); 254 | assert_eq!(result, "github:user/repo#derivation/"); 255 | } 256 | 257 | #[test] 258 | fn test_normalize_flake_string_with_empty_target_and_no_derivation() { 259 | let result = normalize_flake_string("", "github:user/repo", None); 260 | assert_eq!(result, "github:user/repo#default/"); 261 | } 262 | 263 | #[test] 264 | fn test_normalize_flake_string_with_complex_target() { 265 | let result = 266 | normalize_flake_string("path/to/target", "github:user/repo", Some("derivation")); 267 | assert_eq!(result, "github:user/repo#derivation/path/to/target"); 268 | } 269 | 270 | #[test] 271 | fn test_normalize_flake_string_with_hash_in_flake_and_derivation() { 272 | let result = 273 | normalize_flake_string("output", "github:user/repo#derivation", Some("unused")); 274 | assert_eq!(result, "github:user/repo#derivation/output"); 275 | } 276 | 277 | #[test] 278 | fn test_normalize_flake_string_with_local_path() { 279 | let result = normalize_flake_string("target", "./local/path", Some("derivation")); 280 | assert_eq!(result, "./local/path#derivation/target"); 281 | } 282 | 283 | #[test] 284 | fn test_normalize_flake_string_with_hash_in_flake_and_target() { 285 | let result = normalize_flake_string("output#extra", "github:user/repo#branch", None); 286 | assert_eq!(result, "output#extra"); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/templates.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::eyre::Result; 2 | use serde::Serialize; 3 | use serde_json::Value as JsonValue; 4 | 5 | use minijinja::{context, Environment}; 6 | 7 | use crate::config::{FLAKE_INPUTS_TEMPLATE, FLAKE_TEMPLATE}; 8 | use crate::parts::FlakePartMetadata; 9 | 10 | #[derive(Debug, Serialize)] 11 | pub struct FlakeInputsContext { 12 | pub inputs: JsonValue, 13 | } 14 | 15 | impl FlakeInputsContext { 16 | fn new(inputs: JsonValue) -> Self { 17 | Self { inputs } 18 | } 19 | 20 | pub fn from_merged_metadata(metadata: &[&FlakePartMetadata]) -> Self { 21 | let inputs = metadata 22 | .iter() 23 | .fold(JsonValue::Object(Default::default()), |mut acc, m| { 24 | if let (JsonValue::Object(acc_obj), JsonValue::Object(inputs_obj)) = 25 | (&mut acc, &m.inputs) 26 | { 27 | for (k, v) in inputs_obj.iter() { 28 | acc_obj.insert(k.clone(), v.clone()); 29 | } 30 | } 31 | acc 32 | }); 33 | 34 | Self::new(inputs) 35 | } 36 | 37 | pub fn render(&self) -> Result { 38 | let mut env = Environment::new(); 39 | env.add_template("flake-inputs.nix", FLAKE_INPUTS_TEMPLATE) 40 | .unwrap(); 41 | let tmpl = env.get_template("flake-inputs.nix").unwrap(); 42 | let rendered = tmpl.render(context! ( context => self))?; 43 | Ok(rendered) 44 | } 45 | } 46 | 47 | #[derive(Debug, Serialize)] 48 | pub struct FlakeContext { 49 | pub flake_inputs_context: FlakeInputsContext, 50 | pub extra_trusted_public_keys: Vec, 51 | pub extra_substituters: Vec, 52 | } 53 | 54 | impl FlakeContext { 55 | fn new( 56 | flake_inputs_context: FlakeInputsContext, 57 | extra_trusted_public_keys: Vec, 58 | extra_substituters: Vec, 59 | ) -> Self { 60 | Self { 61 | flake_inputs_context, 62 | extra_trusted_public_keys, 63 | extra_substituters, 64 | } 65 | } 66 | 67 | pub fn from_merged_metadata(metadata: &[&FlakePartMetadata]) -> Self { 68 | let flake_inputs_context = FlakeInputsContext::from_merged_metadata(metadata); 69 | 70 | let extra_trusted_public_keys = metadata 71 | .iter() 72 | .flat_map(|m| m.extra_trusted_public_keys.iter().cloned()) 73 | .collect::>(); 74 | 75 | let extra_substituters = metadata 76 | .iter() 77 | .flat_map(|m| m.extra_substituters.iter().cloned()) 78 | .collect::>(); 79 | 80 | Self::new( 81 | flake_inputs_context, 82 | extra_trusted_public_keys, 83 | extra_substituters, 84 | ) 85 | } 86 | 87 | pub fn render(&self) -> Result { 88 | let mut env = Environment::new(); 89 | env.add_template("flake.nix", FLAKE_TEMPLATE).unwrap(); 90 | env.add_template("flake-inputs.nix", FLAKE_INPUTS_TEMPLATE) 91 | .unwrap(); 92 | let tmpl = env.get_template("flake.nix").unwrap(); 93 | let rendered = tmpl.render(context! ( context => self))?; 94 | Ok(rendered) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | use serde_json::json; 102 | 103 | #[test] 104 | fn test_flake_inputs_context_new() { 105 | let inputs = json!({"input1": "value1", "input2": "value2"}); 106 | let context = FlakeInputsContext::new(inputs.clone()); 107 | assert_eq!(context.inputs, inputs); 108 | } 109 | 110 | #[test] 111 | fn test_flake_inputs_context_from_merged_metadata() { 112 | let metadata1 = FlakePartMetadata { 113 | description: "Metadata 1".to_string(), 114 | inputs: json!({"input1": "value1"}), 115 | dependencies: vec![], 116 | conflicts: vec![], 117 | extra_trusted_public_keys: vec![], 118 | extra_substituters: vec![], 119 | }; 120 | let metadata2 = FlakePartMetadata { 121 | description: "Metadata 2".to_string(), 122 | inputs: json!({"input2": "value2"}), 123 | dependencies: vec![], 124 | conflicts: vec![], 125 | extra_trusted_public_keys: vec![], 126 | extra_substituters: vec![], 127 | }; 128 | let metadata = vec![&metadata1, &metadata2]; 129 | 130 | let context = FlakeInputsContext::from_merged_metadata(&metadata); 131 | assert_eq!( 132 | context.inputs, 133 | json!({"input1": "value1", "input2": "value2"}) 134 | ); 135 | } 136 | 137 | #[test] 138 | fn test_flake_inputs_context_render_with_simple_inputs() -> Result<()> { 139 | let inputs = json!({"input1": { 140 | "url": "github:org1/repo1", 141 | }, "input2": { 142 | "url": "github:org2/repo2", 143 | }}); 144 | let context = FlakeInputsContext::new(inputs); 145 | let rendered = context.render()?; 146 | let cleaned_rendered = rendered.split_whitespace().collect::(); 147 | 148 | let expected = r#" 149 | input1.url = "github:org1/repo1"; 150 | input2.url = "github:org2/repo2"; 151 | "#; 152 | 153 | let cleaned_expected = expected.split_whitespace().collect::(); 154 | 155 | assert_eq!(cleaned_rendered, cleaned_expected); 156 | Ok(()) 157 | } 158 | 159 | #[test] 160 | fn test_flake_inputs_context_render_with_complex_inputs() -> Result<()> { 161 | let inputs = json!({"input1": { 162 | "url": "github:org1/repo1", 163 | "flake": false 164 | }, "input2": { 165 | "url": "github:org2/repo2", 166 | "inputs": { 167 | "input1": { 168 | "follows": "input1" 169 | } 170 | } 171 | }}); 172 | let context = FlakeInputsContext::new(inputs); 173 | let rendered = context.render()?; 174 | let cleaned_rendered = rendered.split_whitespace().collect::(); 175 | 176 | let expected = r#" 177 | input1 = { 178 | url = "github:org1/repo1"; 179 | flake = false; 180 | }; 181 | input2 = { 182 | url = "github:org2/repo2"; 183 | inputs.input1.follows = "input1"; 184 | }; 185 | "#; 186 | 187 | let cleaned_expected = expected.split_whitespace().collect::(); 188 | 189 | assert_eq!(cleaned_rendered, cleaned_expected); 190 | Ok(()) 191 | } 192 | 193 | #[test] 194 | fn test_flake_context_new() { 195 | let inputs_context = FlakeInputsContext::new(json!({})); 196 | let trusted_keys = vec!["key1".to_string(), "key2".to_string()]; 197 | let substituters = vec!["sub1".to_string(), "sub2".to_string()]; 198 | let context = FlakeContext::new(inputs_context, trusted_keys.clone(), substituters.clone()); 199 | 200 | assert_eq!(context.extra_trusted_public_keys, trusted_keys); 201 | assert_eq!(context.extra_substituters, substituters); 202 | } 203 | 204 | #[test] 205 | fn test_flake_context_from_merged_metadata() { 206 | let metadata1 = FlakePartMetadata { 207 | description: "Metadata 1".to_string(), 208 | inputs: json!({"input1": "value1"}), 209 | dependencies: vec![], 210 | conflicts: vec![], 211 | extra_trusted_public_keys: vec!["key1".to_string()], 212 | extra_substituters: vec!["sub1".to_string()], 213 | }; 214 | let metadata2 = FlakePartMetadata { 215 | description: "Metadata 2".to_string(), 216 | inputs: json!({"input2": "value2"}), 217 | dependencies: vec![], 218 | conflicts: vec![], 219 | extra_trusted_public_keys: vec!["key2".to_string()], 220 | extra_substituters: vec!["sub2".to_string()], 221 | }; 222 | let metadata = vec![&metadata1, &metadata2]; 223 | 224 | let context = FlakeContext::from_merged_metadata(&metadata); 225 | assert_eq!( 226 | context.flake_inputs_context.inputs, 227 | json!({"input1": "value1", "input2": "value2"}) 228 | ); 229 | assert_eq!( 230 | context.extra_trusted_public_keys, 231 | vec!["key1".to_string(), "key2".to_string()] 232 | ); 233 | assert_eq!( 234 | context.extra_substituters, 235 | vec!["sub1".to_string(), "sub2".to_string()] 236 | ); 237 | } 238 | 239 | #[test] 240 | fn test_flake_context_render() -> Result<()> { 241 | let inputs_context = FlakeInputsContext::new(json!({"input1": { 242 | "url": "github:org1/repo1", 243 | }, "input2": { 244 | "url": "github:org2/repo2", 245 | }})); 246 | let trusted_keys = vec!["key1".to_string(), "key2".to_string()]; 247 | let substituters = vec!["sub1".to_string(), "sub2".to_string()]; 248 | let context = FlakeContext::new(inputs_context, trusted_keys, substituters); 249 | 250 | let rendered = context.render()?; 251 | let cleaned_rendered = rendered.split_whitespace().collect::(); 252 | 253 | let expected = r#" 254 | # --- flake.nix 255 | { 256 | description = "TODO Add description of your new project"; 257 | 258 | inputs = { 259 | # --- BASE DEPENDENCIES --- 260 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 261 | flake-parts.url = "github:hercules-ci/flake-parts"; 262 | 263 | # --- YOUR DEPENDENCIES --- 264 | input1.url = "github:org1/repo1"; 265 | input2.url = "github:org2/repo2"; 266 | }; 267 | 268 | # NOTE Here you can add additional binary cache substituers that you trust. 269 | # There are also some sensible default caches commented out that you 270 | # might consider using, however, you are advised to doublecheck the keys. 271 | nixConfig = { 272 | extra-trusted-public-keys = [ 273 | # "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" 274 | # "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" 275 | # "key1" 276 | # "key2" 277 | ]; 278 | extra-substituters = [ 279 | # "https://cache.nixos.org" 280 | # "https://nix-community.cachix.org/" 281 | # "sub1" 282 | # "sub2" 283 | ]; 284 | }; 285 | 286 | outputs = 287 | inputs@{ flake-parts, ... }: 288 | let 289 | inherit (inputs.nixpkgs) lib; 290 | inherit (import ./flake-parts/_bootstrap.nix { inherit lib; }) loadParts; 291 | in 292 | flake-parts.lib.mkFlake { inherit inputs; } { 293 | 294 | # We recursively traverse all of the flakeModules in ./flake-parts and 295 | # import only the final modules, meaning that you can have an arbitrary 296 | # nested structure that suffices your needs. For example 297 | # 298 | # - ./flake-parts 299 | # - modules/ 300 | # - nixos/ 301 | # - myNixosModule1.nix 302 | # - myNixosModule2.nix 303 | # - default.nix 304 | # - home-manager/ 305 | # - myHomeModule1.nix 306 | # - myHomeModule2.nix 307 | # - default.nix 308 | # - sharedModules.nix 309 | # - pkgs/ 310 | # - myPackage1.nix 311 | # - myPackage2.nix 312 | # - default.nix 313 | # - mySimpleModule.nix 314 | # - _not_a_module.nix 315 | imports = loadParts ./flake-parts; 316 | }; 317 | } 318 | "#; 319 | 320 | let cleaned_expected = expected.split_whitespace().collect::(); 321 | 322 | assert_eq!(cleaned_rendered, cleaned_expected); 323 | Ok(()) 324 | } 325 | } 326 | --------------------------------------------------------------------------------