Your one-stop-cake-shop for everything Freshly Baked has to offer

import: add sprinkles to patisserie

Sprinkles is widgets for our Niri desktop. Currently we're working on a
notification daemon, and we plan to move our clock out from PacketMix
into sprinkles as well...

a.starrysky.fyi c05a78db 5cce7ee7

verified
+10918
+90
README.md
··· 1 + # Welcome to the *patisserie* 2 + 3 + *~ your one-stop-cake-shop for everything Freshly Baked has to offer ~* 4 + 5 + ## Structure 6 + 7 + *Patisserie* is a [monorepo](https://en.wikipedia.org/wiki/Monorepo), which means 8 + there are multiple projects hosted here. Here's a list! 9 + 10 + | Project | Description | 11 + | ----------- | ------------------------------------------------------------------------------------------------- | 12 + | *packetmix* | Our [*NixOS*](https://nixos.org) configurations ("All you need to bake a delicious system") | 13 + | *sprinkles* | Our [*Niri*](https://github.com/YaLTeR/niri) widgets ("Add some decoration to your Niri desktop") | 14 + 15 + ## Cloning a single project 16 + 17 + You may clone and push to *patisserie* via [*tangled*](https://tangled.sh) as 18 + you usually would. If you'd like to clone only a single project, however, we 19 + provide a public [*josh* proxy](https://josh-project.github.io/josh/) which can 20 + be used to filter your clone: 21 + 22 + ```bash 23 + git clone https://git.freshlybakedca.ke/patisserie.git:/packetmix.git 24 + # Swap out "packetmix" at the end of the URL for whatever project you want to clone 25 + ``` 26 + 27 + If you need to push then, as with *tangled* normally, you are required to use 28 + SSH. 29 + 30 + *Josh* can push via SSH, but requires you to forward your SSH agent to 31 + authenticate your push: 32 + 33 + ```ssh-config 34 + # In ~/.ssh/config 35 + Host git.freshlybakedca.ke 36 + User git 37 + ForwardAgent yes 38 + ``` 39 + 40 + When you've added this section to your ssh config, you can add a custom push URL 41 + to use SSH. 42 + 43 + ```bash 44 + git remote set-url --push ssh://git@git.freshlybakedca.ke/patisserie.git:/packetmix.git 45 + # Swap out "packetmix" at the end of the URL for whatever project you have cloned 46 + ``` 47 + 48 + Except for when creating branches, pushing will work as-normal for SSH clones. 49 + 50 + ### Creating new branches 51 + 52 + When pushing to *josh*, creating branches won't work on a regular `git push`. 53 + This is because *josh* doesn't know what state you want the rest of the 54 + repository to be for your branch 55 + 56 + You can tell *josh* by providing the `base=` push option like so: 57 + 58 + ```bash 59 + git push origin HEAD:my-new-branch -o base=main 60 + ``` 61 + 62 + If you want to always pick `main` by default you can set this in your 63 + repository-specific *git* config 64 + 65 + ```bash 66 + git config push.pushOption 'base=main' 67 + ``` 68 + 69 + Setting this in your *git* config may also be useful if 70 + you're using an alternative *git* frontend, for example 71 + [*Jujutsu*](https://jj-vcs.github.io/jj/latest/), which does not provide the 72 + ability to set push-options when pushing to git remotes 73 + 74 + ### Signing commits 75 + 76 + As *josh* rewrites commits, they will not be validly signed everywhere. 77 + We therefore recommend you turn off commit signing for *patisserie* or any 78 + subprojects which you clone down 79 + 80 + For example, 81 + 82 + ```bash 83 + git config commit.gpgsign false 84 + ``` 85 + 86 + or 87 + 88 + ```bash 89 + jj config set --repo git.sign-on-push false 90 + ```
+51
packetmix/.github/workflows/eval.yml
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + name: Evaluate NixOS systems and Homes 6 + 7 + on: 8 + push: 9 + branches: [ "main" ] 10 + pull_request: 11 + branches: [ "main" ] 12 + 13 + workflow_dispatch: 14 + 15 + jobs: 16 + evaluate: 17 + runs-on: ubuntu-latest 18 + 19 + steps: 20 + - uses: actions/checkout@v4 21 + 22 + - name: Install Lix 23 + uses: samueldr/lix-gha-installer-action@8dc19fbd6451fa106a68ecb2dafeeeb90dff3a29 24 + with: 25 + extra_nix_config: "experimental-features = nix-command" 26 + 27 + - run: cd $GITHUB_WORKSPACE 28 + 29 + - name: Evaluate all systems 30 + run: | 31 + eval_out=$(nix-instantiate ./ci.nix -A packages.allNixOSSystems.result.x86_64-linux --add-root ./system-root 2>&1 | tee /dev/stderr) 32 + eval_warns=$(echo "$eval_out" | grep "evaluation warning:" || true) 33 + 34 + if [ -n "$eval_warns" ]; then 35 + echo "There were some warnings while evaluating your systems:" 36 + echo "$eval_warns" 37 + echo "Please fix these and squash into your existing commits" 38 + exit 1 39 + fi 40 + 41 + - name: Evaluate all homes 42 + run: | 43 + eval_out=$(nix-instantiate ./ci.nix -A packages.allNixOSSystems.result.x86_64-linux --add-root ./home-root 2>&1 | tee /dev/stderr) 44 + eval_warns=$(echo "$eval_out" | grep "evaluation warning:" || true) 45 + 46 + if [ -n "$eval_warns" ]; then 47 + echo "There were some warnings while evaluating your homes:" 48 + echo "$eval_warns" 49 + echo "Please fix these and squash into your existing commits" 50 + exit 1 51 + fi
+69
packetmix/.github/workflows/nixos.yml
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # SPDX-FileCopyrightText: 2022 Markus Dobel 4 + # 5 + # SPDX-License-Identifier: MIT 6 + 7 + name: Build and cache NixOS systems and Homes 8 + 9 + on: 10 + push: 11 + branches: [ "main" ] 12 + pull_request: 13 + branches: [ "main" ] 14 + 15 + workflow_dispatch: 16 + 17 + concurrency: # Since as this check is expensive, it's a bad idea to keep running it when we push new commits... 18 + group: ${{ github.workflow }}-${{ github.ref }} 19 + cancel-in-progress: true 20 + 21 + jobs: 22 + build: 23 + runs-on: ubuntu-latest 24 + 25 + steps: 26 + - name: Clean up runner 27 + uses: easimon/maximize-build-space@c28619d8999a147d5e09c1199f84ff6af6ad5794 28 + with: 29 + overprovision-lvm: true # needed for our mount path to be /nix - we'll cope with the weird errors this option suggests may arise as it'd be untenable to constantly readjust root-reserve-mb 30 + remove-dotnet: true 31 + remove-android: true 32 + remove-haskell: true 33 + remove-codeql: true 34 + remove-docker-images: true 35 + build-mount-path: /nix 36 + build-mount-path-ownership: root:root 37 + 38 + - uses: actions/checkout@v4 39 + 40 + - name: Install Lix 41 + uses: samueldr/lix-gha-installer-action@8dc19fbd6451fa106a68ecb2dafeeeb90dff3a29 42 + with: 43 + extra_nix_config: "experimental-features = nix-command" 44 + 45 + - run: cd $GITHUB_WORKSPACE 46 + 47 + - name: Set up a cachix cache 48 + uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad 49 + with: 50 + # Name of a cachix cache to push and pull/substitute 51 + name: "freshlybakedcake" 52 + authToken: "${{ secrets.CACHIX_TOKEN }}" 53 + 54 + # see https://git.lix.systems/lix-project/lix/issues/545 55 + - name: Fix apparmor configuration for building Lix 56 + run: | 57 + sudo sysctl -w kernel.apparmor_restrict_unprivileged_unconfined=0 58 + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 59 + 60 + - name: Build all systems 61 + run: nix build -f ./ci.nix packages.allNixOSSystems.result.x86_64-linux --show-trace 62 + 63 + - name: Build all homes 64 + run: nix build -f ./ci.nix packages.allHomes.result.x86_64-linux --show-trace 65 + 66 + - if: github.event_name == 'push' 67 + name: Push to release branch 68 + run: | 69 + git push -f origin HEAD:release
+38
packetmix/.github/workflows/npins-duplicate-check.yml
··· 1 + # SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. <https://fsfe.org> 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: CC0-1.0 5 + 6 + name: Check for npins duplicate keys 7 + # As it's possible to specify duplicate keys in npins, we need to route them 8 + # out... Duplicated npins keys cause the earlier definition of a pin to be 9 + # silently ignored, potentially causing confusion about what version is being 10 + # used 11 + 12 + on: 13 + push: 14 + branches: [ "main" ] 15 + pull_request: 16 + branches: [ "main" ] 17 + workflow_dispatch: 18 + 19 + permissions: 20 + contents: read 21 + 22 + jobs: 23 + npins-duplicate-checker: 24 + runs-on: ubuntu-latest 25 + steps: 26 + - name: Checkout 27 + uses: actions/checkout@v4 28 + 29 + - name: Check for duplicate npins keys 30 + run: | 31 + dupes=$(jq --stream 'select((.[0] | length == 3) and (.[0][2] == "type")) | .[0][1]' $GITHUB_WORKSPACE/npins/sources.json | sort | uniq -d) 32 + # We have to use the stream parser else jq will get rid of the duplicates 33 + 34 + if [ ! -z "$dupes" ]; then 35 + echo "The following keys are duplicated in your npins pins. By default, npins will take *the later definition*:" 36 + echo "$dupes" 37 + exit 1 38 + fi
+24
packetmix/.github/workflows/reuse.yml
··· 1 + # SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. <https://fsfe.org> 2 + # 3 + # SPDX-License-Identifier: CC0-1.0 4 + 5 + name: Check REUSE compliance 6 + 7 + on: 8 + push: 9 + branches: [ "main" ] 10 + pull_request: 11 + branches: [ "main" ] 12 + 13 + permissions: 14 + contents: read 15 + 16 + jobs: 17 + reuse: 18 + runs-on: ubuntu-latest 19 + steps: 20 + - name: Checkout 21 + uses: actions/checkout@v4 22 + 23 + - name: Check REUSE compliance 24 + uses: fsfe/reuse-action@a46482ca367aef4454a87620aa37c2be4b2f8106
+33
packetmix/.github/workflows/tangled.yml
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + name: Mirror to tangled.sh 6 + 7 + on: 8 + push: 9 + branches: [ "main", "release" ] 10 + 11 + jobs: 12 + tangled-push: 13 + runs-on: ubuntu-latest 14 + 15 + steps: 16 + - uses: actions/checkout@v4 17 + 18 + - run: cd $GITHUB_WORKSPACE 19 + 20 + - name: Write out SSH key 21 + env: 22 + TANGLED_SSH_KEY: ${{ secrets.TANGLED_SSH_KEY }} 23 + run: | 24 + echo "$TANGLED_SSH_KEY" > ../tangled_ssh_key 25 + chmod 600 ../tangled_ssh_key 26 + 27 + - name: Push to tangled 28 + run: | 29 + git remote add tangled git@tangled.sh:freshlybakedca.ke/packetmix 30 + export GIT_SSH_COMMAND="ssh -i $(realpath ../tangled_ssh_key) -o StrictHostKeyChecking=no" 31 + git fetch --unshallow origin 32 + git fetch tangled 33 + git push tangled HEAD
+51
packetmix/.github/workflows/treefmt.yaml
··· 1 + # SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. <https://fsfe.org> 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: CC0-1.0 5 + 6 + name: Ensure files are formatted with treefmt 7 + 8 + on: 9 + push: 10 + branches: [ "main" ] 11 + pull_request: 12 + branches: [ "main" ] 13 + workflow_dispatch: 14 + 15 + permissions: 16 + contents: read 17 + 18 + jobs: 19 + treefmt-check: 20 + runs-on: ubuntu-latest 21 + steps: 22 + - name: Checkout 23 + uses: actions/checkout@v4 24 + 25 + - name: Install Nix 26 + uses: cachix/install-nix-action@526118121621777ccd86f79b04685a9319637641 27 + with: 28 + extra_nix_config: "experimental-features = nix-command" 29 + 30 + - run: cd $GITHUB_WORKSPACE 31 + 32 + - name: Set up a cachix cache 33 + uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad 34 + with: 35 + # Name of a cachix cache to push and pull/substitute 36 + name: "freshlybakedcake" 37 + authToken: "${{ secrets.CACHIX_TOKEN }}" 38 + 39 + - name: Ensure all files are formatted 40 + run: | 41 + set +e 42 + 43 + nix-shell ./ci.nix -A shells.default.result.x86_64-linux --run 'treefmt --ci' 44 + 45 + exitCode=$? 46 + 47 + if [ $exitCode -ne 0 ]; then 48 + echo "Your code isn't formatted correctly, please run 'nilla fmt' and squash it into each commit" 49 + fi 50 + 51 + exit $exitCode
+63
packetmix/.github/workflows/update-npins.yml
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + name: Update npins dependencies 6 + # This is inspired by https://github.com/getchoo/update-npins - though I wrote it from scratch 7 + # I'm not using it as I need to run the npins command in a shell 8 + 9 + on: 10 + schedule: 11 + - cron: "48 02 * * *" # Time is pretty arbitrary - the only important thing is that it's unlikely to be a peak time... 12 + 13 + workflow_dispatch: 14 + 15 + jobs: 16 + update-npins: 17 + runs-on: ubuntu-latest 18 + 19 + steps: 20 + - name: Generate GitHub token for RoboPâtissière 21 + uses: actions/create-github-app-token@v1 22 + id: generate-token 23 + with: 24 + app-id: ${{ secrets.CUSTOM_GITHUB_APP_ID }} 25 + private-key: ${{ secrets.CUSTOM_GITHUB_APP_PRIVATE_KEY }} 26 + 27 + - uses: actions/checkout@v4 28 + 29 + - name: Install Nix 30 + uses: cachix/install-nix-action@526118121621777ccd86f79b04685a9319637641 31 + with: 32 + extra_nix_config: "experimental-features = nix-command" 33 + 34 + - run: cd $GITHUB_WORKSPACE 35 + 36 + - name: Set up a cachix cache 37 + uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad 38 + with: 39 + # Name of a cachix cache to push and pull/substitute 40 + name: "freshlybakedcake" 41 + authToken: "${{ secrets.CACHIX_TOKEN }}" 42 + 43 + - name: Run npins update 44 + run: nix-shell ./ci.nix -A shells.default.result.x86_64-linux --run 'npins update' 45 + 46 + - name: Create a pull request 47 + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e 48 + id: pull-request 49 + with: 50 + branch: auto/update-npins 51 + commit-message: "chore: bump npins dependencies" 52 + token: ${{ steps.generate-token.outputs.token }} 53 + title: "chore: bump npins dependencies" 54 + author: "RoboPâtissière[bot] <213641064+robopatissiere[bot]@users.noreply.github.com>" 55 + committer: "RoboPâtissière[bot] <213641064+robopatissiere[bot]@users.noreply.github.com>" 56 + body: | 57 + This is an automated npins dependency bump 58 + 59 + - if: steps.pull-request.outputs.pull-request-operation == 'created' 60 + name: Automerge pull request 61 + run: gh pr merge --rebase --auto "${{ steps.pull-request.outputs.pull-request-number }}" 62 + env: 63 + GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+5
packetmix/.gitignore
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + result
+73
packetmix/LICENSES/Apache-2.0.txt
··· 1 + Apache License 2 + Version 2.0, January 2004 3 + http://www.apache.org/licenses/ 4 + 5 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 + 7 + 1. Definitions. 8 + 9 + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 10 + 11 + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 12 + 13 + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 14 + 15 + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 16 + 17 + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 18 + 19 + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 20 + 21 + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 22 + 23 + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 24 + 25 + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 26 + 27 + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 28 + 29 + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 30 + 31 + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 32 + 33 + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 34 + 35 + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and 36 + 37 + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and 38 + 39 + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 + 41 + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 42 + 43 + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 44 + 45 + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 46 + 47 + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 48 + 49 + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 50 + 51 + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 52 + 53 + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 54 + 55 + END OF TERMS AND CONDITIONS 56 + 57 + APPENDIX: How to apply the Apache License to your work. 58 + 59 + To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 60 + 61 + Copyright [yyyy] [name of copyright owner] 62 + 63 + Licensed under the Apache License, Version 2.0 (the "License"); 64 + you may not use this file except in compliance with the License. 65 + You may obtain a copy of the License at 66 + 67 + http://www.apache.org/licenses/LICENSE-2.0 68 + 69 + Unless required by applicable law or agreed to in writing, software 70 + distributed under the License is distributed on an "AS IS" BASIS, 71 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 72 + See the License for the specific language governing permissions and 73 + limitations under the License.
+121
packetmix/LICENSES/CC0-1.0.txt
··· 1 + Creative Commons Legal Code 2 + 3 + CC0 1.0 Universal 4 + 5 + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 + HEREUNDER. 13 + 14 + Statement of Purpose 15 + 16 + The laws of most jurisdictions throughout the world automatically confer 17 + exclusive Copyright and Related Rights (defined below) upon the creator 18 + and subsequent owner(s) (each and all, an "owner") of an original work of 19 + authorship and/or a database (each, a "Work"). 20 + 21 + Certain owners wish to permanently relinquish those rights to a Work for 22 + the purpose of contributing to a commons of creative, cultural and 23 + scientific works ("Commons") that the public can reliably and without fear 24 + of later claims of infringement build upon, modify, incorporate in other 25 + works, reuse and redistribute as freely as possible in any form whatsoever 26 + and for any purposes, including without limitation commercial purposes. 27 + These owners may contribute to the Commons to promote the ideal of a free 28 + culture and the further production of creative, cultural and scientific 29 + works, or to gain reputation or greater distribution for their Work in 30 + part through the use and efforts of others. 31 + 32 + For these and/or other purposes and motivations, and without any 33 + expectation of additional consideration or compensation, the person 34 + associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 + is an owner of Copyright and Related Rights in the Work, voluntarily 36 + elects to apply CC0 to the Work and publicly distribute the Work under its 37 + terms, with knowledge of his or her Copyright and Related Rights in the 38 + Work and the meaning and intended legal effect of CC0 on those rights. 39 + 40 + 1. Copyright and Related Rights. A Work made available under CC0 may be 41 + protected by copyright and related or neighboring rights ("Copyright and 42 + Related Rights"). Copyright and Related Rights include, but are not 43 + limited to, the following: 44 + 45 + i. the right to reproduce, adapt, distribute, perform, display, 46 + communicate, and translate a Work; 47 + ii. moral rights retained by the original author(s) and/or performer(s); 48 + iii. publicity and privacy rights pertaining to a person's image or 49 + likeness depicted in a Work; 50 + iv. rights protecting against unfair competition in regards to a Work, 51 + subject to the limitations in paragraph 4(a), below; 52 + v. rights protecting the extraction, dissemination, use and reuse of data 53 + in a Work; 54 + vi. database rights (such as those arising under Directive 96/9/EC of the 55 + European Parliament and of the Council of 11 March 1996 on the legal 56 + protection of databases, and under any national implementation 57 + thereof, including any amended or successor version of such 58 + directive); and 59 + vii. other similar, equivalent or corresponding rights throughout the 60 + world based on applicable law or treaty, and any national 61 + implementations thereof. 62 + 63 + 2. Waiver. To the greatest extent permitted by, but not in contravention 64 + of, applicable law, Affirmer hereby overtly, fully, permanently, 65 + irrevocably and unconditionally waives, abandons, and surrenders all of 66 + Affirmer's Copyright and Related Rights and associated claims and causes 67 + of action, whether now known or unknown (including existing as well as 68 + future claims and causes of action), in the Work (i) in all territories 69 + worldwide, (ii) for the maximum duration provided by applicable law or 70 + treaty (including future time extensions), (iii) in any current or future 71 + medium and for any number of copies, and (iv) for any purpose whatsoever, 72 + including without limitation commercial, advertising or promotional 73 + purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 + member of the public at large and to the detriment of Affirmer's heirs and 75 + successors, fully intending that such Waiver shall not be subject to 76 + revocation, rescission, cancellation, termination, or any other legal or 77 + equitable action to disrupt the quiet enjoyment of the Work by the public 78 + as contemplated by Affirmer's express Statement of Purpose. 79 + 80 + 3. Public License Fallback. Should any part of the Waiver for any reason 81 + be judged legally invalid or ineffective under applicable law, then the 82 + Waiver shall be preserved to the maximum extent permitted taking into 83 + account Affirmer's express Statement of Purpose. In addition, to the 84 + extent the Waiver is so judged Affirmer hereby grants to each affected 85 + person a royalty-free, non transferable, non sublicensable, non exclusive, 86 + irrevocable and unconditional license to exercise Affirmer's Copyright and 87 + Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 + maximum duration provided by applicable law or treaty (including future 89 + time extensions), (iii) in any current or future medium and for any number 90 + of copies, and (iv) for any purpose whatsoever, including without 91 + limitation commercial, advertising or promotional purposes (the 92 + "License"). The License shall be deemed effective as of the date CC0 was 93 + applied by Affirmer to the Work. Should any part of the License for any 94 + reason be judged legally invalid or ineffective under applicable law, such 95 + partial invalidity or ineffectiveness shall not invalidate the remainder 96 + of the License, and in such case Affirmer hereby affirms that he or she 97 + will not (i) exercise any of his or her remaining Copyright and Related 98 + Rights in the Work or (ii) assert any associated claims and causes of 99 + action with respect to the Work, in either case contrary to Affirmer's 100 + express Statement of Purpose. 101 + 102 + 4. Limitations and Disclaimers. 103 + 104 + a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 + surrendered, licensed or otherwise affected by this document. 106 + b. Affirmer offers the Work as-is and makes no representations or 107 + warranties of any kind concerning the Work, express, implied, 108 + statutory or otherwise, including without limitation warranties of 109 + title, merchantability, fitness for a particular purpose, non 110 + infringement, or the absence of latent or other defects, accuracy, or 111 + the present or absence of errors, whether or not discoverable, all to 112 + the greatest extent permissible under applicable law. 113 + c. Affirmer disclaims responsibility for clearing rights of other persons 114 + that may apply to the Work or any use thereof, including without 115 + limitation any person's Copyright and Related Rights in the Work. 116 + Further, Affirmer disclaims responsibility for obtaining any necessary 117 + consents, permissions or other rights required for any use of the 118 + Work. 119 + d. Affirmer understands and acknowledges that Creative Commons is not a 120 + party to this document and has no duty or obligation with respect to 121 + this CC0 or use of the Work.
+232
packetmix/LICENSES/GPL-3.0-or-later.txt
··· 1 + GNU GENERAL PUBLIC LICENSE 2 + Version 3, 29 June 2007 3 + 4 + Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/> 5 + 6 + Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 7 + 8 + Preamble 9 + 10 + The GNU General Public License is a free, copyleft license for software and other kinds of works. 11 + 12 + The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. 13 + 14 + When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. 15 + 16 + To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. 17 + 18 + For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. 19 + 20 + Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. 21 + 22 + For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. 23 + 24 + Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. 25 + 26 + Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. 27 + 28 + The precise terms and conditions for copying, distribution and modification follow. 29 + 30 + TERMS AND CONDITIONS 31 + 32 + 0. Definitions. 33 + 34 + “This License” refers to version 3 of the GNU General Public License. 35 + 36 + “Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. 37 + 38 + “The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. 39 + 40 + To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. 41 + 42 + A “covered work” means either the unmodified Program or a work based on the Program. 43 + 44 + To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. 45 + 46 + To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. 47 + 48 + An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 49 + 50 + 1. Source Code. 51 + The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. 52 + 53 + A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. 54 + 55 + The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. 56 + 57 + The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. 58 + 59 + The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. 60 + 61 + The Corresponding Source for a work in source code form is that same work. 62 + 63 + 2. Basic Permissions. 64 + All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. 65 + 66 + You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. 67 + 68 + Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 69 + 70 + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 71 + No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. 72 + 73 + When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 74 + 75 + 4. Conveying Verbatim Copies. 76 + You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. 77 + 78 + You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 79 + 80 + 5. Conveying Modified Source Versions. 81 + You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: 82 + 83 + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. 84 + 85 + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. 86 + 87 + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. 88 + 89 + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. 90 + 91 + A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 92 + 93 + 6. Conveying Non-Source Forms. 94 + You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: 95 + 96 + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. 97 + 98 + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. 99 + 100 + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. 101 + 102 + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. 103 + 104 + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. 105 + 106 + A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. 107 + 108 + A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. 109 + 110 + “Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. 111 + 112 + If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). 113 + 114 + The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. 115 + 116 + Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 117 + 118 + 7. Additional Terms. 119 + “Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. 120 + 121 + When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. 122 + 123 + Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: 124 + 125 + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or 126 + 127 + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or 128 + 129 + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or 130 + 131 + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or 132 + 133 + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or 134 + 135 + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. 136 + 137 + All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. 138 + 139 + If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. 140 + 141 + Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 142 + 143 + 8. Termination. 144 + You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). 145 + 146 + However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. 147 + 148 + Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. 149 + 150 + Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 151 + 152 + 9. Acceptance Not Required for Having Copies. 153 + You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 154 + 155 + 10. Automatic Licensing of Downstream Recipients. 156 + Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. 157 + 158 + An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. 159 + 160 + You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 161 + 162 + 11. Patents. 163 + A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. 164 + 165 + A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. 166 + 167 + Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. 168 + 169 + In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. 170 + 171 + If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. 172 + 173 + If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. 174 + 175 + A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. 176 + 177 + Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 178 + 179 + 12. No Surrender of Others' Freedom. 180 + If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 181 + 182 + 13. Use with the GNU Affero General Public License. 183 + Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 184 + 185 + 14. Revised Versions of this License. 186 + The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 187 + 188 + Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. 189 + 190 + If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. 191 + 192 + Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 193 + 194 + 15. Disclaimer of Warranty. 195 + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 196 + 197 + 16. Limitation of Liability. 198 + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 199 + 200 + 17. Interpretation of Sections 15 and 16. 201 + If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. 202 + 203 + END OF TERMS AND CONDITIONS 204 + 205 + How to Apply These Terms to Your New Programs 206 + 207 + If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. 208 + 209 + To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. 210 + 211 + <one line to give the program's name and a brief idea of what it does.> 212 + Copyright (C) <year> <name of author> 213 + 214 + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 215 + 216 + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 217 + 218 + You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. 219 + 220 + Also add information on how to contact you by electronic and paper mail. 221 + 222 + If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: 223 + 224 + <program> Copyright (C) <year> <name of author> 225 + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 226 + This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. 227 + 228 + The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. 229 + 230 + You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <https://www.gnu.org/licenses/>. 231 + 232 + The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <https://www.gnu.org/philosophy/why-not-lgpl.html>.
+18
packetmix/LICENSES/MIT.txt
··· 1 + MIT License 2 + 3 + Copyright (c) <year> <copyright holders> 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 + associated documentation files (the "Software"), to deal in the Software without restriction, including 7 + without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 + copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 9 + following conditions: 10 + 11 + The above copyright notice and this permission notice shall be included in all copies or substantial 12 + portions of the Software. 13 + 14 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 15 + LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 16 + EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 18 + USE OR OTHER DEALINGS IN THE SOFTWARE.
+144
packetmix/LICENSES/MPL-2.0.txt
··· 1 + Mozilla Public License Version 2.0 2 + 3 + 1. Definitions 4 + 5 + 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 6 + 7 + 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 8 + 9 + 1.3. "Contribution" means Covered Software of a particular Contributor. 10 + 11 + 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 12 + 13 + 1.5. "Incompatible With Secondary Licenses" means 14 + 15 + (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or 16 + 17 + (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 18 + 19 + 1.6. "Executable Form" means any form of the work other than Source Code Form. 20 + 21 + 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 22 + 23 + 1.8. "License" means this document. 24 + 25 + 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 26 + 27 + 1.10. "Modifications" means any of the following: 28 + 29 + (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or 30 + 31 + (b) any new file in Source Code Form that contains any Covered Software. 32 + 33 + 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 34 + 35 + 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 36 + 37 + 1.13. "Source Code Form" means the form of the work preferred for making modifications. 38 + 39 + 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 40 + 41 + 2. License Grants and Conditions 42 + 43 + 2.1. Grants 44 + Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: 45 + 46 + (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and 47 + 48 + (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 49 + 50 + 2.2. Effective Date 51 + The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 52 + 53 + 2.3. Limitations on Grant Scope 54 + The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: 55 + 56 + (a) for any code that a Contributor has removed from Covered Software; or 57 + 58 + (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or 59 + 60 + (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. 61 + 62 + This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 63 + 64 + 2.4. Subsequent Licenses 65 + No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 66 + 67 + 2.5. Representation 68 + Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 69 + 70 + 2.6. Fair Use 71 + This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 72 + 73 + 2.7. Conditions 74 + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 75 + 76 + 3. Responsibilities 77 + 78 + 3.1. Distribution of Source Form 79 + All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 80 + 81 + 3.2. Distribution of Executable Form 82 + If You distribute Covered Software in Executable Form then: 83 + 84 + (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and 85 + 86 + (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 87 + 88 + 3.3. Distribution of a Larger Work 89 + You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 90 + 91 + 3.4. Notices 92 + You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 93 + 94 + 3.5. Application of Additional Terms 95 + You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 96 + 97 + 4. Inability to Comply Due to Statute or Regulation 98 + If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 99 + 100 + 5. Termination 101 + 102 + 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 103 + 104 + 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 105 + 106 + 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. 107 + 108 + 6. Disclaimer of Warranty 109 + Covered Software is provided under this License on an "as is" basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer. 110 + 111 + 7. Limitation of Liability 112 + Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party's negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. 113 + 114 + 8. Litigation 115 + Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 116 + 117 + 9. Miscellaneous 118 + This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 119 + 120 + 10. Versions of the License 121 + 122 + 10.1. New Versions 123 + Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 124 + 125 + 10.2. Effect of New Versions 126 + You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 127 + 128 + 10.3. Modified Versions 129 + If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 130 + 131 + 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 132 + If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. 133 + 134 + Exhibit A - Source Code Form License Notice 135 + 136 + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 137 + 138 + If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. 139 + 140 + You may add additional accurate notices of copyright ownership. 141 + 142 + Exhibit B - "Incompatible With Secondary Licenses" Notice 143 + 144 + This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
+47
packetmix/README.md
··· 1 + <!-- 2 + SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + 4 + SPDX-License-Identifier: MIT 5 + --> 6 + 7 + # PacketMix 8 + 9 + # Principles 10 + 11 + ## Automatic updates 12 + 13 + Your system should always be up-to-date. Therefore, we'll update your system 14 + each day ready for you to reboot into. 15 + 16 + ## Uptime is an antipattern 17 + 18 + Not everything can be updated out-from-under-your-feet and even when it can be 19 + traces are often left behind (think: old environment variables, version 20 + mismatches between running and new programs, missing kernel updates, etc.). 21 + 22 + Therefore, `nilla nixos switch` (& `nixos-rebuild switch`) are officially 23 + unsupported. Use them if you must, but report bugs only if you've tried 24 + rebooting into your newly-updated system. 25 + 26 + Our auto-updates will always require a reboot to finish installing. 27 + 28 + ## Opinionated 29 + 30 + We build this because we want this. Any configuration that is no longer used 31 + will be removed. We will ruthlessly set defaults (e.g. `--smart-case` for 32 + `ripgrep`) to align with our preferences. If you disagree with our choices, 33 + override our config or pull a module out of our tree and maintain it yourself. 34 + 35 + <!-- ## Composition of configs 36 + 37 + PacketMix is built with [Mixins](./docs/mixins.md) so you can pull in 38 + pre-defined sets of configuration for different purposes. Want a gaming 39 + machine? Install Steam and Itch off the bat. Choosing catppuccin as your theme? 40 + Make sure all your apps start with configurations for it. 41 + 42 + Mixins are additive, so by default you'll only start with the required 43 + PacketMix base. 44 + 45 + Of course, if you want more configurability - say you want to install Steam 46 + without any other games launchers - you can break out into your own modules and 47 + get the full power of nix alongside your PacketMix. -->
+36
packetmix/ci.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + let 6 + base = import ./nilla.nix; 7 + project = base.extend { 8 + modules = [ 9 + ( 10 + { config, lib, ... }: 11 + { 12 + config.inputs = ( 13 + builtins.mapAttrs ( 14 + name: value: 15 + if value ? settings.configuration.allowUnfree then 16 + { 17 + settings.configuration = { 18 + allowUnfree = false; 19 + allowUnfreePredicate = ( 20 + x: (x ? meta.license) && (x.meta.license.shortName == "unfreeRedistributable") 21 + ); # As we push to a public cachix, we can't use non-redistributable unfree software in CI 22 + }; 23 + } 24 + else 25 + { } 26 + ) base.inputs 27 + ); 28 + } 29 + ) 30 + { 31 + config.lib.ci = true; 32 + } 33 + ]; 34 + }; 35 + in 36 + project.config // { inherit (project) extend; }
+12
packetmix/homes/catppuccin/catppuccin.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { project, lib, ... }: 6 + { 7 + imports = [ project.inputs.catppuccin.result.homeModules.catppuccin ]; 8 + config.catppuccin.enable = true; 9 + 10 + config.catppuccin.cursors.enable = true; 11 + config.home.pointerCursor.enable = true; 12 + }
+8
packetmix/homes/coded/bitwarden.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + home.packages = [ pkgs.bitwarden-cli ]; 8 + }
+39
packetmix/homes/coded/calendar.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.vdirsyncer.enable = true; 7 + programs.khal.enable = true; 8 + 9 + accounts.calendar.basePath = ".calendar"; 10 + 11 + accounts.calendar.accounts."nextcloud" = { 12 + primary = true; 13 + primaryCollection = "personal"; 14 + 15 + khal.enable = true; 16 + khal.type = "discover"; 17 + 18 + remote = { 19 + type = "caldav"; 20 + url = "https://nextcloud.clicks.codes/remote.php/dav"; 21 + userName = "clicks-coded"; 22 + passwordCommand = [ 23 + "bw" 24 + "get" 25 + "password" 26 + "nextcloud calendar app password" 27 + ]; 28 + }; 29 + 30 + vdirsyncer.enable = true; 31 + vdirsyncer.collections = [ 32 + "personal" 33 + "personal_shared_by_clicks-minion" 34 + "skyler-ist41_shared_by_clicks-minion" 35 + "infra_shared_by_clicks-minion" 36 + "oh-god-what-is-happening_shared_by_clicks-minion" 37 + ]; 38 + }; 39 + }
+11
packetmix/homes/coded/catppuccin.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { project, ... }: 6 + { 7 + config.catppuccin = { 8 + flavor = "macchiato"; 9 + accent = "blue"; 10 + }; 11 + }
+47
packetmix/homes/coded/email.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.aerc.enable = true; 7 + 8 + accounts.email.accounts."gmail" = { 9 + primary = true; 10 + address = "samuel.shuert@gmail.com"; 11 + passwordCommand = "bw get password 'samuel.shuert@gmail.com shorthair app password'"; 12 + imap = { 13 + host = "imap.gmail.com"; 14 + port = 993; 15 + }; 16 + smtp = { 17 + host = "smtp.gmail.com"; 18 + port = 465; 19 + }; 20 + realName = "Samuel Shuert"; 21 + signature = { 22 + showSignature = "append"; 23 + text = "Samuel Shuert"; 24 + }; 25 + }; 26 + 27 + accounts.email.accounts."clicks" = { 28 + address = "coded@clicks.codes"; 29 + aliases = [ 30 + "me@thecoded.prof" 31 + ]; 32 + passwordCommand = "bw get notes 'mail.clicks.codes App Password'"; 33 + imap = { 34 + host = "mail.clicks.codes"; 35 + port = 993; 36 + }; 37 + smtp = { 38 + host = "mail.clicks.codes"; 39 + port = 465; 40 + }; 41 + realName = "Samuel Shuert"; 42 + signature = { 43 + showSignature = "append"; 44 + text = "TheCodedProf | Samuel Shuert"; 45 + }; 46 + }; 47 + }
+22
packetmix/homes/coded/jujutsu.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + programs.jujutsu.settings = { 8 + git.sign-on-push = true; 9 + include = [ 10 + { 11 + path-regex = "~/Code/FreshlyBaked"; 12 + file = pkgs.writers.writeTOML "config-freshly.toml" { 13 + user.email = "coded@freshlybakedca.ke"; 14 + }; 15 + } 16 + ]; 17 + user = { 18 + name = "Samuel Shuert"; 19 + email = "me@thecoded.prof"; 20 + }; 21 + }; 22 + }
+70
packetmix/homes/coded/niri.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + ingredient.niri.enable = true; 7 + 8 + ingredient.niri.niri.timers = { 9 + lock = 900; 10 + sleep = 1800; 11 + }; 12 + ingredient.niri.niri.wallpaper = ./wallpaper.png; 13 + programs.niri.settings = { 14 + input.mouse.natural-scroll = false; 15 + outputs = { 16 + "eDP-1" = { 17 + position = { 18 + x = 0; 19 + y = 0; 20 + }; 21 + }; 22 + "DP-1" = { 23 + position = { 24 + x = 5760; 25 + y = 2160; 26 + }; 27 + mode = { 28 + width = 3840; 29 + height = 2160; 30 + refresh = 144.; 31 + }; 32 + scale = 1; 33 + }; 34 + "DP-2" = { 35 + position = { 36 + x = 1920; 37 + y = 2160; 38 + }; 39 + mode = { 40 + width = 3840; 41 + height = 2160; 42 + refresh = 144.; 43 + }; 44 + scale = 1; 45 + }; 46 + "LG Electronics LG TV SSCR2 0x01010101" = { 47 + position = { 48 + x = 1920; 49 + y = 0; 50 + }; 51 + mode = { 52 + width = 3840; 53 + height = 2160; 54 + refresh = 60.; 55 + }; 56 + }; 57 + "Dell Inc. DELL S2422HG BTTCK83" = { 58 + position = { 59 + x = 0; 60 + y = 2700; 61 + }; 62 + mode = { 63 + width = 1920; 64 + height = 1080; 65 + refresh = 164.997; 66 + }; 67 + }; 68 + }; 69 + }; 70 + }
+8
packetmix/homes/coded/sesh.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + programs.sesh.settings = lib.importTOML ./sesh.toml; 8 + }
+6
packetmix/homes/coded/sesh.toml
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + [default_session] 6 + startup_command = "jj"
packetmix/homes/coded/wallpaper.png

This is a binary file and will not be displayed.

+3
packetmix/homes/coded/wallpaper.png.license
··· 1 + SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + 3 + SPDX-License-Identifier: MIT
+15
packetmix/homes/collabora/gtimelog.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { project, system, ... }: 7 + { 8 + home.packages = [ 9 + project.packages.collabora-gtimelog.result.${system} 10 + ]; 11 + 12 + clicks.storage.impermanence.persist.directories = [ 13 + ".gtimelog" 14 + ]; 15 + }
+14
packetmix/homes/common/bash.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.bash = { 7 + enable = true; 8 + enableCompletion = true; 9 + }; 10 + 11 + clicks.storage.impermanence.persist.files = [ 12 + ".bash_history" 13 + ]; 14 + }
+11
packetmix/homes/common/chromium.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.chromium.enable = true; 7 + 8 + clicks.storage.impermanence.persist.directories = [ 9 + ".config/chromium" 10 + ]; 11 + }
+10
packetmix/homes/common/files.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + home.packages = [ 8 + pkgs.nautilus 9 + ]; 10 + }
+11
packetmix/homes/common/firefox.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.firefox.enable = true; 7 + 8 + clicks.storage.impermanence.persist.directories = [ 9 + ".mozilla/firefox" 10 + ]; 11 + }
+7
packetmix/homes/common/ghostty.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.ghostty.enable = true; 7 + }
+8
packetmix/homes/common/gtk.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + gtk.enable = true; 8 + }
+41
packetmix/homes/common/helix.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + config, 7 + pkgs, 8 + project, 9 + system, 10 + ... 11 + }: 12 + { 13 + programs.helix = { 14 + enable = true; 15 + 16 + package = project.packages.helix.result.${system}; 17 + 18 + settings = { 19 + editor = { 20 + bufferline = "multiple"; 21 + line-number = "relative"; 22 + 23 + auto-save.focus-lost = true; 24 + 25 + whitespace.render = { 26 + space = "none"; 27 + tab = "all"; 28 + nbsp = "all"; 29 + nnbsp = "all"; 30 + newline = "none"; 31 + }; 32 + whitespace.characters = { 33 + tabpad = "-"; 34 + tab = "-"; 35 + }; 36 + }; 37 + }; 38 + }; 39 + 40 + home.sessionVariables.EDITOR = "${config.programs.helix.package}/bin/hx"; 41 + }
+50
packetmix/homes/common/impermanence.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, lib, ... }: 6 + let 7 + cfg = config.clicks.storage.impermanence; 8 + in 9 + { 10 + options.clicks.storage.impermanence = { 11 + enable = lib.mkEnableOption "Enable impermanent home files, this requires you to be using the NixOS to home connection"; 12 + 13 + volumes.persistent_data = lib.mkOption { 14 + type = lib.types.str; 15 + description = "Path on persist device to store persistent data on. This should be identical to your path in NixOS"; 16 + default = "data"; 17 + }; 18 + persist = { 19 + directories = lib.mkOption { 20 + type = lib.types.listOf ( 21 + lib.types.oneOf [ 22 + lib.types.str 23 + (lib.types.attrsOf ( 24 + lib.types.oneOf [ 25 + lib.types.str 26 + (lib.types.attrsOf lib.types.str) 27 + ] 28 + )) 29 + ] 30 + ); 31 + description = "List of directories to store between boots"; 32 + default = [ ]; 33 + }; 34 + files = lib.mkOption { 35 + type = lib.types.listOf lib.types.str; 36 + description = "List of files to store between boots"; 37 + default = [ ]; 38 + }; 39 + }; 40 + }; 41 + 42 + config = { 43 + home = lib.optionalAttrs cfg.enable { 44 + persistence."/persist/${cfg.volumes.persistent_data}/${config.home.homeDirectory}" = { 45 + allowOther = true; 46 + inherit (cfg.persist) directories files; 47 + }; 48 + }; 49 + }; 50 + }
+18
packetmix/homes/common/thunderbird.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + pkgs, 7 + ... 8 + }: 9 + { 10 + home.packages = [ 11 + pkgs.thunderbird 12 + ]; 13 + 14 + clicks.storage.impermanence.persist.directories = [ 15 + ".mozilla/thunderbird" 16 + ".thunderbird" 17 + ]; 18 + }
+17
packetmix/homes/common/zoxide.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.zoxide = { 7 + enable = true; 8 + options = [ 9 + "--cmd=cd" 10 + "--hook=prompt" 11 + ]; 12 + }; 13 + 14 + clicks.storage.impermanence.persist.directories = [ 15 + ".local/share/zoxide" 16 + ]; 17 + }
+113
packetmix/homes/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { config, ... }: 7 + let 8 + nixpkgs = config.inputs.nixpkgs.result; 9 + in 10 + { 11 + config.homes."maya:x86_64-linux" = { 12 + modules = [ 13 + { 14 + home.stateVersion = "24.11"; 15 + home.homeDirectory = "/home/maya"; 16 + } 17 + ]; 18 + ingredients = [ 19 + "catppuccin" 20 + "collabora" 21 + "development" 22 + "espanso" 23 + "freshlybakedcake" 24 + "gaming" 25 + "nix-index" 26 + "remote" 27 + ]; 28 + args = { 29 + system = "x86_64-linux"; 30 + }; 31 + }; 32 + config.homes."minion:x86_64-linux" = { 33 + modules = [ 34 + { 35 + home.stateVersion = "24.11"; 36 + home.homeDirectory = "/home/minion"; 37 + } 38 + ]; 39 + ingredients = [ 40 + "catppuccin" 41 + "development" 42 + "espanso" 43 + "freshlybakedcake" 44 + "gaming" 45 + "nix-index" 46 + "remote" 47 + ]; 48 + args = { 49 + system = "x86_64-linux"; 50 + }; 51 + }; 52 + config.homes."minion@redhead:x86_64-linux" = { 53 + modules = [ 54 + { 55 + home.stateVersion = "24.11"; 56 + home.homeDirectory = "/home/minion"; 57 + } 58 + ]; 59 + ingredients = [ 60 + "catppuccin" 61 + "collabora" 62 + "development" 63 + "espanso" 64 + "freshlybakedcake" 65 + "gaming" 66 + "nix-index" 67 + "remote" 68 + ]; 69 + args = { 70 + system = "x86_64-linux"; 71 + }; 72 + }; 73 + config.homes."coded:x86_64-linux" = { 74 + modules = [ 75 + { 76 + home.stateVersion = "25.05"; 77 + home.homeDirectory = "/home/coded"; 78 + } 79 + ]; 80 + ingredients = [ 81 + "catppuccin" 82 + "development" 83 + "espanso" 84 + "freshlybakedcake" 85 + "gaming" 86 + "nix-index" 87 + "remote" 88 + ]; 89 + args = { 90 + system = "x86_64-linux"; 91 + }; 92 + }; 93 + config.homes."pinea:x86_64-linux" = { 94 + modules = [ 95 + { 96 + home.stateVersion = "25.05"; 97 + home.homeDirectory = "/home/pinea"; 98 + } 99 + ]; 100 + ingredients = [ 101 + "catppuccin" 102 + "development" 103 + "espanso" 104 + "freshlybakedcake" 105 + "gaming" 106 + "nix-index" 107 + "remote" 108 + ]; 109 + args = { 110 + system = "x86_64-linux"; 111 + }; 112 + }; 113 + }
+26
packetmix/homes/development/collaboration.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { project, system, ... }: 6 + { 7 + programs.zed-editor = { 8 + enable = true; 9 + package = project.inputs.nixos-unstable.result.${system}.zed-editor; 10 + extensions = [ 11 + "catppuccin" 12 + "catppuccin-icons" 13 + "nix" 14 + ]; 15 + userSettings = { 16 + edit_predictions.mode = "subtle"; 17 + git.git_gutter = "hide"; 18 + minimap.show = "auto"; 19 + helix_mode = true; 20 + }; 21 + }; 22 + 23 + clicks.storage.impermanence.persist.directories = [ 24 + ".local/share/zed" 25 + ]; 26 + }
+17
packetmix/homes/development/direnv.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs = { 7 + direnv = { 8 + enable = true; 9 + enableBashIntegration = true; 10 + nix-direnv.enable = true; 11 + }; 12 + }; 13 + 14 + clicks.storage.impermanence.persist.directories = [ 15 + ".local/share/direnv" 16 + ]; 17 + }
+23
packetmix/homes/development/gpg.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { pkgs, ... }: 5 + { 6 + programs.gpg = { 7 + enable = true; 8 + scdaemonSettings = { 9 + reader-port = "Yubico Yubi"; 10 + disable-ccid = true; 11 + }; 12 + }; 13 + 14 + services.gpg-agent = { 15 + enable = true; 16 + enableScDaemon = true; 17 + pinentry.package = pkgs.pinentry-gnome3; 18 + }; 19 + 20 + clicks.storage.impermanence.persist.directories = [ 21 + ".gnupg" 22 + ]; 23 + }
+57
packetmix/homes/development/helix.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + pkgs, 7 + project, 8 + system, 9 + ... 10 + }: 11 + { 12 + programs.helix = { 13 + settings = { 14 + editor = { 15 + lsp.display-inlay-hints = true; 16 + 17 + end-of-line-diagnostics = "hint"; 18 + inline-diagnostics = { 19 + cursor-line = "warning"; 20 + other-lines = "error"; 21 + }; 22 + 23 + indent-guides.render = true; 24 + }; 25 + 26 + keys.normal = { 27 + space.E = { 28 + command = "@mip<space>e"; 29 + label = "Hard-wrap (rEflow) current paragraph"; 30 + }; 31 + space.e = { 32 + command = ":reflow"; 33 + label = "Hard-wrap (rEflow) selected text"; 34 + }; 35 + }; 36 + }; 37 + 38 + languages = { 39 + language = [ 40 + { 41 + name = "nix"; 42 + formatter = { 43 + command = "${pkgs.nixfmt-rfc-style}/bin/nixfmt"; 44 + }; 45 + } 46 + { 47 + name = "qml"; 48 + language-servers = [ "qmlls" ]; 49 + } 50 + ]; 51 + language-server.qmlls = { 52 + args = [ "-E" ]; # Read the QML Path from the environment - this way it can be provided in nix shells 53 + command = "qmlls"; # Again, many people won't need qmlls so there's no point in installing it - this lets it stay picked up from shell paths 54 + }; 55 + }; 56 + }; 57 + }
+13
packetmix/homes/development/hoppscotch.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + home.packages = [ pkgs.hoppscotch ]; 8 + 9 + clicks.storage.impermanence.persist.directories = [ 10 + ".config/io.hoppscotch.desktop/" 11 + ".local/share/hoppscotch-desktop" 12 + ]; 13 + }
+473
packetmix/homes/development/jujutsu.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { 7 + config, 8 + lib, 9 + project, 10 + pkgs, 11 + system, 12 + ... 13 + }: 14 + { 15 + options.ingredient.development.jujutsu = { 16 + allowedSSHSigners = lib.mkOption { 17 + type = lib.types.attrsOf (lib.types.listOf lib.types.str); 18 + description = "A mapping of SSH keys to emails they are valid for"; 19 + default = { }; 20 + }; 21 + }; 22 + 23 + config = { 24 + ingredient.scriptfs.enable = true; # used for signing configuration 25 + programs.jujutsu = { 26 + enable = true; 27 + package = project.packages.jujutsu.result.${system}; 28 + settings = { 29 + aliases = { 30 + init = [ 31 + "git" 32 + "init" 33 + "--colocate" 34 + ]; # TODO: remove when git is no longer the only backend 35 + 36 + clean = [ 37 + "abandon" 38 + "-r" 39 + "empty() & ~::immutable_heads() & ~bookmarks()" 40 + ]; # Delete commits that are no longer useful - less important after jujutsu includes its change IDs in commits 41 + 42 + tug = 43 + let 44 + tug-script = pkgs.writeScript "tug.sh" '' 45 + #!${pkgs.bash}/bin/bash 46 + 47 + set -e 48 + 49 + if [[ -z "$1" ]]; then 50 + ${config.programs.jujutsu.package}/bin/jj bookmark move --from "closest_bookmark(@)" --to "closest_pushable_allow_empty_desc(@)" 51 + else 52 + ${config.programs.jujutsu.package}/bin/jj bookmark move --to "closest_pushable_allow_empty_desc(@)" "$@" 53 + fi 54 + ''; 55 + in 56 + [ 57 + "util" 58 + "exec" 59 + "--" 60 + tug-script 61 + ]; # Move the nearest bookmark up to a commit 62 + 63 + t = [ 64 + "log" 65 + "-r" 66 + "touched()" 67 + ]; 68 + touched = [ 69 + "log" 70 + "-r" 71 + "touched()" 72 + ]; 73 + 74 + evol = [ "evolution-log" ]; # `evol` is what you get to when you try to tab complete from `ev`, as you stop between `evolog` and `evolution-log` 75 + 76 + ia = [ 77 + "new" 78 + "--no-edit" 79 + "--after" 80 + ]; # short for jj insertafter, good for megamerges 81 + 82 + here = [ 83 + "log" 84 + "-r" 85 + "here" 86 + ]; 87 + hereish = [ 88 + "log" 89 + "-r" 90 + "here | trunk() | here-" 91 + ]; # Show commits that are generally around @ - a less noisy alternative to the default log revset 92 + 93 + stat = [ 94 + "show" 95 + "--stat" 96 + ]; 97 + 98 + copy = [ 99 + "util" 100 + "exec" 101 + "--" 102 + "${pkgs.bash}/bin/bash" 103 + "-c" 104 + "${config.programs.jujutsu.package}/bin/jj show --git $@ | ${pkgs.wl-clipboard}/bin/wl-copy" 105 + "--" 106 + ]; # Copy a git-compatible patch to your clipboard 107 + 108 + rangediff = 109 + let 110 + rangediff-script = pkgs.writeScript "rangediff.sh" '' 111 + #!${pkgs.bash}/bin/bash 112 + 113 + set -e 114 + 115 + BEFORE_REVSET="$1" 116 + shift || (echo "Usage !!BEFORE_REVSET!! AFTER_REVSET BEFORE_LENGTH [AFTER_LENGTH] [...ARGS]"; exit 1) 117 + AFTER_REVSET="$1" 118 + shift || (echo "Usage BEFORE_REVSET !!AFTER_REVSET!! BEFORE_LENGTH [AFTER_LENGTH] [...ARGS]"; exit 1) 119 + BEFORE_LENGTH="$1" 120 + shift || (echo "Usage BEFORE_REVSET AFTER_REVSET !!BEFORE_LENGTH!! [AFTER_LENGTH] [...ARGS]"; exit 1) 121 + 122 + if [[ $1 =~ ^[0-9]+$ ]]; then 123 + AFTER_LENGTH="$1" 124 + shift 125 + else 126 + AFTER_LENGTH="$BEFORE_LENGTH" 127 + fi 128 + 129 + BEFORE_TOP=$(${config.programs.jujutsu.package}/bin/jj show -T "self.commit_id()" --no-patch --quiet $BEFORE_REVSET) 130 + BEFORE_BOTTOM=$(${config.programs.jujutsu.package}/bin/jj show -T "self.commit_id()" --no-patch --quiet "back($BEFORE_REVSET, $BEFORE_LENGTH)") 131 + 132 + AFTER_TOP=$(${config.programs.jujutsu.package}/bin/jj show -T "self.commit_id()" --no-patch --quiet $AFTER_REVSET) 133 + AFTER_BOTTOM=$(${config.programs.jujutsu.package}/bin/jj show -T "self.commit_id()" --no-patch --quiet "back($AFTER_REVSET, $AFTER_LENGTH)") 134 + 135 + ${pkgs.git}/bin/git range-diff $BEFORE_BOTTOM~..$BEFORE_TOP $AFTER_BOTTOM~..$AFTER_TOP "$@" 136 + ''; 137 + in 138 + [ 139 + "util" 140 + "exec" 141 + "--" 142 + rangediff-script 143 + ]; # Use git range-diff to see the difference between some revsets, usage 'jj rangediff before-revset after-refset before-length [after-length=before-length]' 144 + 145 + reverthere = [ 146 + "revert" 147 + "-B" 148 + "@" 149 + "-r" 150 + ]; 151 + rh = [ "reverthere" ]; 152 + 153 + resetcid = 154 + let 155 + resetcid-script = pkgs.writeScript "resetcid.sh" '' 156 + #!${pkgs.bash}/bin/bash 157 + 158 + set -e 159 + 160 + CID_BEFORE=$(jj show "$1" -T "self.change_id().shortest(8)" --no-patch --color=always) 161 + 162 + jj new --before "$1" --no-edit --quiet 163 + CID_AFTER=$(jj show "$1-" -T "self.change_id().shortest(8)" --no-patch --color=always) 164 + 165 + jj squash --from "$1" --to "($1)-" --quiet 166 + 167 + echo "$CID_BEFORE is now known as $CID_AFTER" 168 + ''; 169 + in 170 + [ 171 + "util" 172 + "exec" 173 + "--" 174 + resetcid-script 175 + ]; # Reset a change ID, useful if you are using jj change IDs in scripts/etc. - uses new/squash though duplicate with some fancy flags and abandon could probably also work 176 + }; 177 + fix.tools = { 178 + clang-format = { 179 + command = [ 180 + "clang-format" 181 + "--assume-filename=$path" 182 + ]; # Not in nix because some projects want specific versions - use a shell 183 + patterns = [ 184 + "glob:'**/*.cxx'" 185 + "glob:'**/*.hxx'" 186 + "glob:'**/*.c'" 187 + "glob:'**/*.h'" 188 + ]; 189 + }; 190 + prettier = { 191 + command = [ 192 + "prettierd" 193 + "--stdin-filepath=$path" 194 + ]; # Not in nix because some projects want specific versions - use a shell 195 + patterns = [ 196 + "glob:'**/*.js'" 197 + "glob:'**/*._js'" 198 + "glob:'**/*.bones'" 199 + "glob:'**/*.es'" 200 + "glob:'**/*.es6'" 201 + "glob:'**/*.frag'" 202 + "glob:'**/*.gs'" 203 + "glob:'**/*.jake'" 204 + "glob:'**/*.jsb'" 205 + "glob:'**/*.jscad'" 206 + "glob:'**/*.jsfl'" 207 + "glob:'**/*.jsm'" 208 + "glob:'**/*.jss'" 209 + "glob:'**/*.mjs'" 210 + "glob:'**/*.njs'" 211 + "glob:'**/*.pac'" 212 + "glob:'**/*.sjs'" 213 + "glob:'**/*.ssjs'" 214 + "glob:'**/*.xsjs'" 215 + "glob:'**/*.xsjslib'" 216 + "glob:'**/Jakefile'" 217 + 218 + "glob:'**/*.js.flow'" 219 + 220 + "glob:'**/*.jsx'" 221 + 222 + "glob:'**/*.ts'" 223 + "glob:'**/*.tsx'" 224 + 225 + "glob:'**/package.json'" 226 + "glob:'**/package-lock.json'" 227 + "glob:'**/composer.json'" 228 + 229 + "glob:'**/*.json'" 230 + "glob:'**/*.avsc'" 231 + "glob:'**/*.geojson'" 232 + "glob:'**/*.gltf'" 233 + "glob:'**/*.JSON-tmLanguage'" 234 + "glob:'**/*.jsonl'" 235 + "glob:'**/*.tfstate'" 236 + "glob:'**/*.tfstate.backup'" 237 + "glob:'**/*.topojson'" 238 + "glob:'**/*.webapp'" 239 + "glob:'**/*.webmanifest'" 240 + "glob:'**/.arcconfig'" 241 + "glob:'**/.htmlhintrc'" 242 + "glob:'**/.tern-config'" 243 + "glob:'**/.tern-project'" 244 + "glob:'**/composer.lock'" 245 + "glob:'**/mcmod.info'" 246 + "glob:'**/.prettierrc'" 247 + 248 + "glob:'**/*.jsonc'" 249 + "glob:'**/*.sublime-build'" 250 + "glob:'**/*.sublime-commands'" 251 + "glob:'**/*.sublime-completions'" 252 + "glob:'**/*.sublime-keymap'" 253 + "glob:'**/*.sublime-macro'" 254 + "glob:'**/*.sublime-menu'" 255 + "glob:'**/*.sublime-mousemap'" 256 + "glob:'**/*.sublime-project'" 257 + "glob:'**/*.sublime-settings'" 258 + "glob:'**/*.sublime-theme'" 259 + "glob:'**/*.sublime-workspace'" 260 + "glob:'**/*.sublime_metrics'" 261 + "glob:'**/*.sublime_session'" 262 + "glob:'**/.babelrc'" 263 + "glob:'**/.eslintrc.json'" 264 + "glob:'**/.jscsrc'" 265 + "glob:'**/.jshintrc'" 266 + "glob:'**/.jslintrc'" 267 + "glob:'**/tsconfig.json'" 268 + "glob:'**/.eslintrc'" 269 + 270 + "glob:'**/*.json5'" 271 + 272 + "glob:'**/*.css'" 273 + 274 + "glob:'**/*.pcss'" 275 + "glob:'**/*.postcss'" 276 + 277 + "glob:'**/*.less'" 278 + 279 + "glob:'**/*.scss'" 280 + 281 + "glob:'**/*.graphql'" 282 + "glob:'**/*.gql'" 283 + 284 + "glob:'**/*.md'" 285 + "glob:'**/*.markdown'" 286 + "glob:'**/*.mdown'" 287 + "glob:'**/*.mdwn'" 288 + "glob:'**/*.mkd'" 289 + "glob:'**/*.mkdn'" 290 + "glob:'**/*.mkdown'" 291 + "glob:'**/*.ronn'" 292 + "glob:'**/*.workbook'" 293 + "glob:'**/README'" 294 + 295 + "glob:'**/*.mdx'" 296 + 297 + "glob:'**/*.xhtml'" 298 + "glob:'**/*.component.html'" 299 + 300 + "glob:'**/*.html'" 301 + "glob:'**/*.htm'" 302 + "glob:'**/*.html.hl'" 303 + "glob:'**/*.inc'" 304 + "glob:'**/*.st'" 305 + "glob:'**/*.xht'" 306 + "glob:'**/*.xhtml'" 307 + "glob:'**/*.mjml'" 308 + 309 + "glob:'**/*.vue'" 310 + 311 + "glob:'**/*.yml'" 312 + "glob:'**/*.mir'" 313 + "glob:'**/*.reek'" 314 + "glob:'**/*.rviz'" 315 + "glob:'**/*.sublime-syntax'" 316 + "glob:'**/*.syntax'" 317 + "glob:'**/*.yaml'" 318 + "glob:'**/*.yaml-tmlanguage'" 319 + "glob:'**/*.yml.mysql'" 320 + "glob:'**/.clang-format'" 321 + "glob:'**/.clang-tidy'" 322 + "glob:'**/.gemrc'" 323 + "glob:'**/glide.lock'" 324 + ]; 325 + }; 326 + }; 327 + merge-tools = { 328 + kdiff3 = { 329 + edit-args = [ 330 + "--merge" 331 + "--cs" 332 + "CreateBakFiles=0" 333 + "--cs" 334 + "WhiteSpaceEqual=0" 335 + "--cs" 336 + "UnfoldSubdirs=1" 337 + "--cs" 338 + "EncodingForA=iso 8859-1" 339 + "--cs" 340 + "EncodingForB=iso 8859-1" 341 + "--cs" 342 + "EncodingForC=iso 8859-1" 343 + "--cs" 344 + "EncodingForOutput=iso 8859-1" 345 + "--cs" 346 + "EncodingForPP=iso 8859-1" 347 + "$left" 348 + "$right" 349 + ]; 350 + program = "${pkgs.kdiff3}/bin/kdiff3"; 351 + }; 352 + mergiraf.program = "${pkgs.mergiraf}/bin/mergiraf"; 353 + }; 354 + revset-aliases = { 355 + "touched()" = "reachable(mine(), immutable_heads()..)"; 356 + "stack()" = "(immutable_heads()..@ | @::) & mine()::"; 357 + "base()" = "trunk()"; 358 + "here" = "reachable(@, trunk()..)"; 359 + "in(branch, matching)" = "matching & ::branch"; 360 + 361 + "back(revision, distance)" = "roots(ancestors(revision, distance))"; 362 + "fwd(revision, distance)" = "heads(decendants(revision, distance))"; 363 + 364 + "closest_bookmark(to)" = "heads(::to & bookmarks())"; 365 + "closest_pushable_allow_empty_desc(to)" = "heads(::to & mutable() & (~empty() | merges()))"; 366 + 367 + "main" = "coalesce(bookmarks(exact:'main'), bookmarks(exact:'master'))"; 368 + 369 + "series(tip, length)" = "back(tip, length)::tip"; 370 + }; 371 + signing = { 372 + backend = "ssh"; 373 + backends.ssh.allowed-signers = 374 + let 375 + allowedSigners = lib.mapAttrsToList ( 376 + key: emails: "${builtins.concatStringsSep "," emails} ${key}" 377 + ) config.ingredient.development.jujutsu.allowedSSHSigners; 378 + allowedSignersContent = builtins.concatStringsSep "\n" allowedSigners; 379 + allowedSignersFile = builtins.toFile "allowed-signers" allowedSignersContent; 380 + in 381 + allowedSignersFile; 382 + behavior = "drop"; # override or set git.sign-on-push in your own config to auto-sign stuff... 383 + key = 384 + let 385 + signingScript = pkgs.writeShellScript "first-ssh-key" '' 386 + set -euo pipefail 387 + 388 + KEYS="$(${pkgs.openssh}/bin/ssh-add -L)" 389 + 390 + SECURITY_KEY="$(echo "$KEYS" | ${pkgs.gnugrep}/bin/grep '^sk-' || echo "")" 391 + 392 + if [[ -z "$SECURITY_KEY" ]]; then 393 + echo "$KEYS" | ${pkgs.coreutils}/bin/head -n 1 394 + exit 0 395 + fi 396 + 397 + echo "$SECURITY_KEY" | ${pkgs.coreutils}/bin/head -n 1 398 + ''; 399 + signingScriptPath = builtins.toString signingScript; 400 + signingScriptScriptFSPath = 401 + "~/.local/state/run/scriptfs" + (lib.removePrefix "/nix/store" signingScriptPath); 402 + in 403 + signingScriptScriptFSPath; 404 + }; 405 + snapshot.auto-track = "~(root-glob:'**/.envrc' | root-glob:'**/*.env' | root-glob:'**/.direnv/**/*')"; 406 + template-aliases.series_log = '' 407 + if(root, 408 + format_root_commit(self), 409 + label(if(current_working_copy, "working_copy"), 410 + concat( 411 + if(current_working_copy, label("op_log current_operation id", "@"), 412 + if(self.contained_in("..@"), label("diff added", "-"), 413 + label("diff removed", "+") 414 + )), 415 + " ", 416 + separate(" ", 417 + format_short_change_id_with_hidden_and_divergent_info(self), 418 + format_short_commit_id(commit_id), 419 + git_head, 420 + if(conflict, label("conflict", "conflict")), 421 + ) ++ " ", 422 + separate(" ", 423 + if(self.contained_in("@.."), 424 + label("rest", separate(" ", 425 + if(empty, "(empty)"), 426 + if(description, 427 + description.first_line(), 428 + "(no description set)", 429 + ), 430 + )), 431 + separate(" ", 432 + if(empty, label("empty", "(empty)")), 433 + if(description, 434 + description.first_line(), 435 + label(if(empty, "empty"), description_placeholder), 436 + ), 437 + ) 438 + ), 439 + if(!(current_working_copy || parents), "\033[22m") 440 + ) ++ "\n", 441 + ), 442 + ) 443 + ) 444 + ''; 445 + templates = { 446 + git_push_bookmark = "'private/${config.home.username}/push-' ++ change_id.short()"; 447 + commit_trailers = '' 448 + if(config("ui.should-sign-off").as_boolean(), format_signed_off_by_trailer(self)) 449 + ++ if(config("ui.should-add-gerrit-change-id").as_boolean() && !trailers.contains_key("Change-Id"), format_gerrit_change_id_trailer(self)) 450 + ''; 451 + }; 452 + ui = { 453 + default-command = "hereish"; 454 + diff-editor = "kdiff3"; 455 + diff-formatter = [ 456 + "${pkgs.difftastic}/bin/difft" 457 + "--color=always" 458 + "$left" 459 + "$right" 460 + ]; 461 + merge-editor = "mergiraf"; 462 + pager = [ 463 + "${pkgs.less}/bin/less" 464 + "-FRX" 465 + ]; 466 + should-sign-off = false; # See templates.commit_trailers, override in individual repos 467 + should-add-gerrit-change-id = false; # See templates.commit_trailers, override in individual repos 468 + show-cryptographic-signatures = true; 469 + }; 470 + }; 471 + }; 472 + }; 473 + }
+19
packetmix/homes/development/simplified-utilities.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + programs.ripgrep = { 8 + enable = true; 9 + 10 + arguments = [ 11 + "--smart-case" 12 + ]; 13 + }; 14 + 15 + home.packages = [ 16 + pkgs.sd 17 + pkgs.fd 18 + ]; 19 + }
+10
packetmix/homes/development/tmux.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + programs.tmux.enable = true; 8 + programs.fzf.tmux.enableShellIntegration = true; # Needed for using sesh - which relies on fzf+tmux 9 + programs.sesh.enable = true; 10 + }
+40
packetmix/homes/espanso/espanso.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { config, lib, ... }: 7 + { 8 + home.file = { 9 + ".cache/espanso/kvs/has_completed_wizard" = { 10 + enable = true; 11 + text = "true"; 12 + }; 13 + ".cache/espanso/kvs/has_displayed_welcome" = { 14 + enable = true; 15 + text = "true"; 16 + }; 17 + }; 18 + xdg.configFile."espanso/config/default.yml".text = builtins.toJSON ( 19 + { 20 + search_trigger = ":search"; 21 + show_notifications = false; 22 + } 23 + // ( 24 + if (config.home.keyboard != null) then 25 + { 26 + keyboard_layout = { 27 + inherit (config.home.keyboard) layout model variant; 28 + 29 + options = builtins.concatStringsSep "," config.home.keyboard.options; 30 + }; 31 + } 32 + else 33 + { } 34 + ) 35 + ); 36 + 37 + clicks.storage.impermanence.persist.directories = [ 38 + ".config/espanso/match" 39 + ]; 40 + }
+80
packetmix/homes/freshlybakedcake+development/ssh.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + # Non-packetmixers can be included in this list, but you should have a comment stating where you got the key/email(s) from 7 + ingredient.development.jujutsu.allowedSSHSigners = { 8 + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKpBNIHk/kRhQL7Nl3Fd+UBVRoS2bTpbeerA//vwL2D4 coded" = [ 9 + "me@thecoded.prof" 10 + 11 + "coded@clicks.codes" 12 + "coded@coded.codes" 13 + "coded@freshlybakedca.ke" 14 + "samuel.shuert@gmail.com" 15 + ]; 16 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOMSUqXuH1bQZJc9rLV0H7/UY0c2BlkzAKWkwrXFWbQ7AAAABHNzaDo= ShorthairNanoResident" = 17 + [ 18 + "me@thecoded.prof" 19 + 20 + "coded@clicks.codes" 21 + "coded@coded.codes" 22 + "coded@freshlybakedca.ke" 23 + "samuel.shuert@gmail.com" 24 + ]; 25 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIIteIdlZv52nUDxW2SUsoJ2NZi/w9j1NZwuHanQ/o/DuAAAAHnNzaDpjb2xsYWJvcmFfeXViaWtleV9yZXNpZGVudA== ssh:collabora_yubikey_resident" = 26 + [ 27 + "sky@a.starrysky.fyi" 28 + 29 + "minion@clicks.codes" 30 + "minion@freshlybakedca.ke" 31 + "minion@libreoffice.org" 32 + "minion@trans.gg" 33 + "skyler.grey@collabora.com" 34 + "skyler3665@gmail.com" 35 + "skyler3665@gmail.com" 36 + "skyler@clicks.codes" 37 + ]; 38 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIJRzQbQjXFpHKtt8lpNKmoNx57+EJ/z3wnKOn3/LjM6cAAAAFXNzaDppeXViaWtleV9yZXNpZGVudA== ssh:iyubikey_resident" = 39 + [ 40 + "sky@a.starrysky.fyi" 41 + 42 + "minion@clicks.codes" 43 + "minion@freshlybakedca.ke" 44 + "minion@libreoffice.org" 45 + "minion@trans.gg" 46 + "skyler.grey@collabora.com" 47 + "skyler3665@gmail.com" 48 + "skyler3665@gmail.com" 49 + "skyler@clicks.codes" 50 + ]; 51 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOhzJ0p9bFRSURUjV05rrt5jCbxPXke7juNbEC9ZJXS/AAAAGXNzaDp0aW55X3l1YmlrZXlfcmVzaWRlbnQ= ssh:tiny_yubikey_resident" = 52 + [ 53 + "sky@a.starrysky.fyi" 54 + 55 + "minion@clicks.codes" 56 + "minion@freshlybakedca.ke" 57 + "minion@libreoffice.org" 58 + "minion@trans.gg" 59 + "skyler.grey@collabora.com" 60 + "skyler3665@gmail.com" 61 + "skyler3665@gmail.com" 62 + "skyler@clicks.codes" 63 + ]; 64 + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICf5rx2r0w98r5lOpxr+/hScABDnk1UfgTH8T2WzeNp4 icy@kvothe" = [ 65 + "anirudh@tangled.sh" # Sourced by Coded from Tangled 66 + ]; 67 + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICJPYX06+qKr9IHWfkgCtHbExoBOOwS/+iAWbog9bAdk icy@wyndle" = [ 68 + "anirudh@tangled.sh" # Sourced by Coded from Tangled 69 + ]; 70 + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMj1Dn9YuFo2BNr993ymBa6nzzyIKAURIqMbUtfI8+4X op@mantis" = [ 71 + "me@oppi.li" # Sourced by Coded from Tangled 72 + ]; 73 + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKrWYk05vDYhqw4laH4nh2lT1/0L+9dMx1nqDxQJbfPg isabelroses" = [ 74 + "isabel@isabelroses.com" # Sourced by Coded from Tangled 75 + ]; 76 + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHYUFynzvO9dfLqn7hssLIeHBp/y5V1lKLyCnDmThgDg boltless.me" = [ 77 + "boltlessengineer@proton.me" # Sourced by Coded from Tangled 78 + ]; 79 + }; 80 + }
packetmix/homes/freshlybakedcake/.gitkeep

This is a binary file and will not be displayed.

+8
packetmix/homes/gaming/itch.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + home.packages = [ pkgs.itch ]; 8 + }
+12
packetmix/homes/gaming/minecraft.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + home.packages = [ pkgs.prismlauncher ]; 8 + 9 + clicks.storage.impermanence.persist.directories = [ 10 + ".local/share/PrismLauncher" 11 + ]; 12 + }
+9
packetmix/homes/gaming/steam.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + clicks.storage.impermanence.persist.directories = [ 7 + ".local/share/Steam" 8 + ]; 9 + }
+10
packetmix/homes/maya/catppuccin.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + { 6 + catppuccin = { 7 + flavor = "mocha"; 8 + accent = "mauve"; 9 + }; 10 + }
+87
packetmix/homes/maya/espanso.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { 7 + xdg.configFile."espanso/match/collabora.yml".text = builtins.toJSON { 8 + matches = [ 9 + { 10 + trigger = ":co:work"; 11 + replace = "Co-Authored-By: Skyler Grey <maya.stephens@collabora.com>"; 12 + } 13 + { 14 + trigger = ":syst"; 15 + replace = "sudo mount --bind /nix systemplate/nix"; 16 + } 17 + { 18 + trigger = ":work"; 19 + replace = "Skyler Grey <maya.stephens@collabora.com>"; 20 + } 21 + { 22 + trigger = "SJIC"; 23 + replace = "St John's Innovation Center"; 24 + } 25 + ]; 26 + }; 27 + xdg.configFile."espanso/match/collabora-timesheets.yml".text = builtins.toJSON { 28 + matches = [ 29 + { 30 + regex = '':t(?P<name>[^\s-]+)-?meet''; 31 + replace = "productivity: r&d-productivity: project-admin: internal-meeting: {{name}}-meeting"; 32 + } 33 + { 34 + trigger = ":tevent"; 35 + replace = "collabora: business-development: event-attendance: event-attendee: "; 36 + } 37 + { 38 + trigger = ":tmail"; 39 + replace = "collabora: internal: communications: e-mails: read emails"; 40 + } 41 + { 42 + trigger = ":tmeet"; 43 + replace = "productivity: r&d-productivity: project-admin: internal-meeting: "; 44 + } 45 + { 46 + trigger = ":tmentor"; 47 + replace = "productivity: r&d-productivity: up-stream: mentoring: "; 48 + } 49 + { 50 + trigger = ":tmobile"; 51 + replace = "productivity: r&d-productivity: product: collabora-online-25-04: mobile release"; 52 + } 53 + { 54 + trigger = ":trnd"; 55 + replace = "productivity: r&d-productivity: product: collabora-online-25-04: "; 56 + } 57 + { 58 + trigger = ":tstat"; 59 + replace = "collabora: internal: communications: e-mails: status report"; 60 + } 61 + { 62 + trigger = ":ttrain"; 63 + replace = "collabora: internal-training: training: attendance: "; 64 + } 65 + { 66 + trigger = ":tttt"; 67 + replace = "productivity: r&d-productivity: tea-time-training: tea-time-training: "; 68 + } 69 + { 70 + trigger = '':tah''; 71 + replace = "productivity: r&d-productivity: project-admin: internal-meeting: all hands meeting"; 72 + } 73 + { 74 + trigger = '':tcwm''; 75 + replace = "productivity: r&d-productivity: project-admin: internal-meeting: cool-weekly-meeting"; 76 + } 77 + ]; 78 + }; 79 + xdg.configFile."espanso/match/javascript.yml".text = builtins.toJSON { 80 + matches = [ 81 + { 82 + trigger = "//es"; 83 + replace = "// eslint-disable-next-line"; 84 + } 85 + ]; 86 + }; 87 + }
+19
packetmix/homes/maya/helix.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + programs.helix = { 8 + settings = { 9 + theme = lib.mkForce "catppuccin_latte_packetmix"; 10 + }; 11 + 12 + themes = { 13 + catppuccin_latte_packetmix = { 14 + inherits = "catppuccin_latte"; 15 + "ui.virtual.whitespace" = "surface0"; # The default catppuccin_latte theme displays rendered whitespace way too harshly... 16 + }; 17 + }; 18 + }; 19 + }
+10
packetmix/homes/maya/jujutsu.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.jujutsu.settings.user = { 7 + name = "Maya Stephens"; 8 + email = "maya.stephens@collabora.com"; 9 + }; 10 + }
+10
packetmix/homes/maya/keyboard.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + home.keyboard = { 7 + layout = "us"; 8 + options = [ "compose:ralt" ]; 9 + }; 10 + }
+14
packetmix/homes/maya/misc.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { pkgs, ... }: 7 + { 8 + # Miscellaneous package installs that aren't really big enough to get their own folder 9 + # Don't place any config that isn't directly adding lines to home.packages here... 10 + home.packages = [ 11 + pkgs.libreoffice 12 + pkgs.obs-studio 13 + ]; 14 + }
+8
packetmix/homes/maya/nano.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + home.sessionVariables = lib.mkForce { EDITOR = "nano"; }; 8 + }
+34
packetmix/homes/maya/niri.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { 7 + project, 8 + config, 9 + pkgs, 10 + lib, 11 + ... 12 + }: 13 + { 14 + ingredient.niri.enable = true; 15 + 16 + programs.niri = { 17 + settings = { 18 + input.mouse.natural-scroll = true; 19 + layout.gaps = lib.mkForce 0; 20 + }; 21 + }; 22 + 23 + ingredient.niri.niri.wallpaper = ../minion/wallpaper.png; 24 + ingredient.niri.niri.overviewBackground = pkgs.stdenv.mkDerivation { 25 + name = "niri-overview-background"; 26 + 27 + src = ../minion/overviewBackground.png; 28 + dontUnpack = true; 29 + 30 + buildPhase = '' 31 + ${pkgs.imagemagick}/bin/magick $src -blur 0x4 -fill black -colorize 40% $out 32 + ''; 33 + }; 34 + }
+32
packetmix/homes/maya/ssh.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + systemd.user.services.ssh-agent-add = { 8 + Unit = { 9 + Description = "Automatically add ssh keys to the agent"; 10 + After = [ "ssh-agent.service" ]; 11 + }; 12 + 13 + Service = { 14 + Type = "oneshot"; 15 + Environment = "SSH_AUTH_SOCK=/run/user/%U/ssh-agent"; 16 + WorkingDirectory = "%h"; 17 + ExecStart = pkgs.writeShellScript "ssh-agent-add.sh" '' 18 + SSH_KEYS=$(ls .ssh/id_* | grep -v '.pub$') 19 + 20 + if [ ! -z "$SSH_KEYS" ]; then 21 + ${pkgs.openssh}/bin/ssh-add $SSH_KEYS 22 + else 23 + ${pkgs.coreutils}/bin/echo "Didn't find any ssh keys - please make sure you have some id_* files in ~/.ssh" 24 + fi 25 + ''; 26 + }; 27 + 28 + Install = { 29 + WantedBy = [ "ssh-agent.service" ]; 30 + }; 31 + }; 32 + }
+13
packetmix/homes/maya/zed.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + programs.zed-editor = { 8 + userSettings = { 9 + helix_mode = lib.mkForce false; 10 + format_on_save = "off"; 11 + }; 12 + }; 13 + }
+9
packetmix/homes/minion/catppuccin.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { 5 + catppuccin = { 6 + flavor = "latte"; 7 + accent = "maroon"; 8 + }; 9 + }
+110
packetmix/homes/minion/espanso.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + xdg.configFile."espanso/match/collabora.yml".text = builtins.toJSON { 7 + matches = [ 8 + { 9 + trigger = ":co:work"; 10 + replace = "Co-Authored-By: Skyler Grey <skyler.grey@collabora.com>"; 11 + } 12 + { 13 + trigger = ":syst"; 14 + replace = "sudo mount --bind /nix systemplate/nix"; 15 + } 16 + { 17 + trigger = ":work"; 18 + replace = "Skyler Grey <skyler.grey@collabora.com>"; 19 + } 20 + { 21 + trigger = "SJIC"; 22 + replace = "St John's Innovation Center"; 23 + } 24 + ]; 25 + }; 26 + xdg.configFile."espanso/match/collabora-timesheets.yml".text = builtins.toJSON { 27 + matches = [ 28 + { 29 + regex = '':t(?P<name>[^\s-]+)-?meet''; 30 + replace = "productivity: r&d-productivity: project-admin: internal-meeting: {{name}}-meeting"; 31 + } 32 + { 33 + trigger = ":tevent"; 34 + replace = "collabora: business-development: event-attendance: event-attendee: "; 35 + } 36 + { 37 + trigger = ":tmail"; 38 + replace = "collabora: internal: communications: e-mails: read emails"; 39 + } 40 + { 41 + trigger = ":tmeet"; 42 + replace = "productivity: r&d-productivity: project-admin: internal-meeting: "; 43 + } 44 + { 45 + trigger = ":tmentor"; 46 + replace = "productivity: r&d-productivity: up-stream: mentoring: "; 47 + } 48 + { 49 + trigger = ":tmobile"; 50 + replace = "productivity: r&d-productivity: product: collabora-online-25-04: mobile release"; 51 + } 52 + { 53 + trigger = ":trnd"; 54 + replace = "productivity: r&d-productivity: product: collabora-online-25-04: "; 55 + } 56 + { 57 + trigger = ":tstat"; 58 + replace = "collabora: internal: communications: e-mails: status report"; 59 + } 60 + { 61 + trigger = ":ttrain"; 62 + replace = "collabora: internal-training: training: attendance: "; 63 + } 64 + { 65 + trigger = ":tttt"; 66 + replace = "productivity: r&d-productivity: tea-time-training: tea-time-training: "; 67 + } 68 + { 69 + trigger = '':tah''; 70 + replace = "productivity: r&d-productivity: project-admin: internal-meeting: all hands meeting"; 71 + } 72 + { 73 + trigger = '':tcwm''; 74 + replace = "productivity: r&d-productivity: project-admin: internal-meeting: cool-weekly-meeting"; 75 + } 76 + ]; 77 + }; 78 + xdg.configFile."espanso/match/javascript.yml".text = builtins.toJSON { 79 + matches = [ 80 + { 81 + trigger = "//es"; 82 + replace = "// eslint-disable-next-line"; 83 + } 84 + ]; 85 + }; 86 + xdg.configFile."espanso/match/personal.yml".text = builtins.toJSON { 87 + matches = [ 88 + { 89 + regex = ''@(c\.|companies)''; 90 + replace = "@companies.starrysky.fyi"; 91 + } 92 + { 93 + regex = ''sky@a(?P<whitespace>\s)''; 94 + replace = "sky@a.starrysky.fyi{{whitespace}}"; 95 + } 96 + { 97 + trigger = ":co:me"; 98 + replace = "Co-Authored-By: Skyler Grey <sky@a.starrysky.fyi>"; 99 + } 100 + { 101 + trigger = ":me"; 102 + replace = "Skyler Grey <sky@a.starrysky.fyi>"; 103 + } 104 + { 105 + trigger = "sky@a."; 106 + replace = "sky@a.starrysky.fyi"; 107 + } 108 + ]; 109 + }; 110 + }
+19
packetmix/homes/minion/helix.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + programs.helix = { 8 + settings = { 9 + theme = lib.mkForce "catppuccin_latte_packetmix"; 10 + }; 11 + 12 + themes = { 13 + catppuccin_latte_packetmix = { 14 + inherits = "catppuccin_latte"; 15 + "ui.virtual.whitespace" = "surface0"; # The default catppuccin_latte theme displays rendered whitespace way too harshly... 16 + }; 17 + }; 18 + }; 19 + }
+11
packetmix/homes/minion/impermanence.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + clicks.storage.impermanence.persist.directories = [ 7 + ".config/jj" 8 + ".ssh" 9 + "Code" 10 + ]; 11 + }
+44
packetmix/homes/minion/jujutsu.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.jujutsu.settings = { 7 + aliases = { 8 + collabora = [ 9 + "util" 10 + "exec" 11 + "--" 12 + "sh" 13 + "-c" 14 + "jj config set --repo user.email skyler.grey@collabora.com 2>/dev/null && jj describe --reset-author --no-edit" 15 + ]; 16 + clicks = [ 17 + "util" 18 + "exec" 19 + "--" 20 + "sh" 21 + "-c" 22 + "jj config set --repo user.email minion@clicks.codes 2>/dev/null && jj describe --reset-author --no-edit" 23 + ]; 24 + personal = [ 25 + "util" 26 + "exec" 27 + "--" 28 + "sh" 29 + "-c" 30 + "jj config set --repo user.email sky@a.starrysky.fyi 2>/dev/null && jj describe --reset-author --no-edit" 31 + ]; 32 + freshly = [ 33 + "util" 34 + "exec" 35 + "--" 36 + "sh" 37 + "-c" 38 + "jj config set --repo user.email minion@freshlybakedca.ke 2>/dev/null && jj describe --reset-author --no-edit" 39 + ]; 40 + }; 41 + git.sign-on-push = true; 42 + user.name = "Skyler Grey"; 43 + }; 44 + }
+11
packetmix/homes/minion/keyboard.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + home.keyboard = { 7 + layout = "us"; 8 + variant = "dvorak"; 9 + options = [ "compose:ralt" ]; 10 + }; 11 + }
+16
packetmix/homes/minion/misc.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + # Miscellaneous package installs that aren't really big enough to get their own folder 8 + # Don't place any config that isn't directly adding lines to home.packages here... 9 + home.packages = [ 10 + pkgs.obs-studio 11 + ]; 12 + 13 + clicks.storage.impermanence.persist.directories = [ 14 + ".config/obs-studio" 15 + ]; 16 + }
+79
packetmix/homes/minion/niri.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + project, 7 + config, 8 + pkgs, 9 + lib, 10 + ... 11 + }: 12 + { 13 + ingredient.niri.enable = true; 14 + 15 + programs.niri = { 16 + settings = { 17 + input.mouse.natural-scroll = true; 18 + outputs = { 19 + "eDP-1" = { 20 + # frame.work laptop internal monitor 21 + position = { 22 + x = 1200; 23 + y = 1560; 24 + }; 25 + }; 26 + "Hewlett Packard LA2405 CN40370NRF" = { 27 + # work left monitor 28 + position = { 29 + x = 0; 30 + y = 0; 31 + }; 32 + transform.rotation = 270; 33 + }; 34 + "Hewlett Packard LA2405 CN40500PYR" = { 35 + # work right monitor 36 + position = { 37 + x = 1200; 38 + y = 360; 39 + }; 40 + }; 41 + "Dell Inc. DELL P2715Q V7WP95AV914L" = { 42 + # emden mid-monitor 43 + position = { 44 + x = 1080; 45 + y = 120; 46 + }; 47 + scale = 1.5; 48 + }; 49 + "PNP(AOC) 2460G5 0x00014634" = { 50 + # emden left monitor 51 + position = { 52 + x = 0; 53 + y = 0; 54 + }; 55 + transform.rotation = 270; 56 + }; 57 + "PNP(AOC) 2460G5 0x00023C3F" = { 58 + # emden right monitor 59 + position = { 60 + x = 3640; 61 + y = 300; 62 + }; 63 + }; 64 + }; 65 + }; 66 + }; 67 + 68 + ingredient.niri.niri.wallpaper = ./wallpaper.png; 69 + ingredient.niri.niri.overviewBackground = pkgs.stdenv.mkDerivation { 70 + name = "niri-overview-background"; 71 + 72 + src = ./overviewBackground.png; 73 + dontUnpack = true; 74 + 75 + buildPhase = '' 76 + ${pkgs.imagemagick}/bin/magick $src -blur 0x4 -fill black -colorize 40% $out 77 + ''; 78 + }; 79 + }
packetmix/homes/minion/overviewBackground.png

This is a binary file and will not be displayed.

+3
packetmix/homes/minion/overviewBackground.png.license
··· 1 + SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + 3 + SPDX-License-Identifier: MIT
+9
packetmix/homes/minion/prompt.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.bash.bashrcExtra = '' 7 + export PS1="\n\[\033[1;35m\][\[\e]0;\u@\h: \w\a\]\u@\h:\w]\$\[\033[0m\] " 8 + ''; 9 + }
+32
packetmix/homes/minion/ssh.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + systemd.user.services.ssh-agent-add = { 8 + Unit = { 9 + Description = "Automatically add ssh keys to the agent"; 10 + After = [ "ssh-agent.service" ]; 11 + }; 12 + 13 + Service = { 14 + Type = "oneshot"; 15 + Environment = "SSH_AUTH_SOCK=/run/user/%U/ssh-agent"; 16 + WorkingDirectory = "%h"; 17 + ExecStart = pkgs.writeShellScript "ssh-agent-add.sh" '' 18 + SSH_KEYS=$(ls .ssh/id_* | grep -v '.pub$') 19 + 20 + if [ ! -z "$SSH_KEYS" ]; then 21 + ${pkgs.openssh}/bin/ssh-add $SSH_KEYS 22 + else 23 + ${pkgs.coreutils}/bin/echo "Didn't find any ssh keys - please make sure you have some id_* files in ~/.ssh" 24 + fi 25 + ''; 26 + }; 27 + 28 + Install = { 29 + WantedBy = [ "ssh-agent.service" ]; 30 + }; 31 + }; 32 + }
packetmix/homes/minion/wallpaper.png

This is a binary file and will not be displayed.

+3
packetmix/homes/minion/wallpaper.png.license
··· 1 + SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + 3 + SPDX-License-Identifier: MIT
+17
packetmix/homes/minion/xdg.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + xdg.mimeApps = { 7 + enable = true; 8 + defaultApplications = { 9 + "default-web-browser" = [ "firefox.desktop" ]; 10 + "text/html" = [ "firefox.desktop" ]; 11 + "x-scheme-handler/http" = [ "firefox.desktop" ]; 12 + "x-scheme-handler/https" = [ "firefox.desktop" ]; 13 + "x-scheme-handler/about" = [ "firefox.desktop" ]; 14 + "x-scheme-handler/unknown" = [ "firefox.desktop" ]; 15 + }; 16 + }; 17 + }
+388
packetmix/homes/niri/niri.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + project, 7 + config, 8 + pkgs, 9 + lib, 10 + ... 11 + }: 12 + { 13 + imports = [ 14 + project.inputs.niri.result.homeModules.niri 15 + project.inputs.walker.result.homeManagerModules.walker 16 + ]; 17 + 18 + options.ingredient.niri.niri = { 19 + wallpaper = lib.mkOption { 20 + type = lib.types.path; 21 + description = "Path to the desktop wallpaper you'd like to use"; 22 + }; 23 + lockscreen = lib.mkOption { 24 + type = lib.types.path; 25 + description = "Path to the lockscreen background you'd like to use, defaults to a greyscale version of your desktop wallpaper"; 26 + default = pkgs.stdenv.mkDerivation { 27 + name = "niri-lock-background"; 28 + 29 + src = config.ingredient.niri.niri.wallpaper; 30 + dontUnpack = true; 31 + 32 + buildPhase = '' 33 + ${pkgs.imagemagick}/bin/magick $src -colorspace Gray $out 34 + ''; 35 + }; 36 + }; 37 + timers = { 38 + lock = lib.mkOption { 39 + type = lib.types.int; 40 + description = "How long while idling before locking the device (in seconds)"; 41 + default = 300; 42 + }; 43 + sleep = lib.mkOption { 44 + type = lib.types.addCheck lib.types.int (x: x >= config.ingredient.niri.niri.timers.lock); 45 + description = "How long while idling before sleeping the device (in seconds)"; 46 + default = 450; 47 + }; 48 + }; 49 + overviewBackground = lib.mkOption { 50 + type = lib.types.path; 51 + description = "Path to the overview background you'd like to use, defaults to a darkened, blurred version of your desktop wallpaper"; 52 + default = pkgs.stdenv.mkDerivation { 53 + name = "niri-overview-background"; 54 + 55 + src = config.ingredient.niri.niri.wallpaper; 56 + dontUnpack = true; 57 + 58 + buildPhase = '' 59 + ${pkgs.imagemagick}/bin/magick $src -blur 0x16 -fill black -colorize 40% $out 60 + ''; 61 + }; 62 + }; 63 + }; 64 + 65 + config = { 66 + programs.niri = 67 + let 68 + lock = ''${config.programs.niri.package}/bin/niri msg action do-screen-transition && ${pkgs.swaylock}/bin/swaylock -i ${config.ingredient.niri.niri.lockscreen} -s fill -f''; 69 + in 70 + { 71 + enable = true; 72 + 73 + package = pkgs.niri; 74 + 75 + settings = { 76 + environment = { 77 + NIXOS_OZONE_WL = "1"; 78 + DISPLAY = ":0"; 79 + }; 80 + 81 + input.keyboard = { 82 + track-layout = "window"; 83 + repeat-delay = 200; 84 + repeat-rate = 25; 85 + 86 + xkb = lib.mkIf (config.home.keyboard != null) { 87 + layout = if config.home.keyboard.layout == null then "" else config.home.keyboard.layout; 88 + model = if config.home.keyboard.model == null then "" else config.home.keyboard.model; 89 + options = builtins.concatStringsSep "," config.home.keyboard.options; 90 + variant = if config.home.keyboard.variant == null then "" else config.home.keyboard.variant; 91 + }; 92 + }; 93 + 94 + input.touchpad.natural-scroll = true; 95 + input.touchpad.click-method = "clickfinger"; 96 + 97 + input.warp-mouse-to-focus.enable = true; 98 + input.focus-follows-mouse = { 99 + enable = true; 100 + max-scroll-amount = "0%"; 101 + }; 102 + 103 + input.power-key-handling.enable = false; 104 + 105 + binds = 106 + let 107 + inherit (config.lib.niri) actions; 108 + 109 + mod = "Super"; 110 + mod1 = "Alt"; 111 + 112 + generateWorkspaceBindings = workspaceNumber: { 113 + "${mod}+${builtins.toString (lib.mod workspaceNumber 10)}".action.focus-workspace = [ 114 + workspaceNumber 115 + ]; 116 + "${mod}+Shift+${builtins.toString (lib.mod workspaceNumber 10)}".action.move-column-to-workspace = [ 117 + workspaceNumber 118 + ]; 119 + }; 120 + joinAttrsetList = listOfAttrsets: lib.fold (a: b: a // b) { } listOfAttrsets; 121 + in 122 + { 123 + # General Keybinds 124 + "${mod}+Q".action.close-window = [ ]; 125 + "${mod}+Shift+Q".action.quit = [ ]; 126 + "${mod}+Return".action.spawn = "${pkgs.ghostty}/bin/ghostty"; 127 + "${mod}+L".action.spawn = [ 128 + "sh" 129 + "-c" 130 + lock 131 + ]; 132 + "${mod}+P".action.power-off-monitors = [ ]; 133 + 134 + "${mod}+R".action.screenshot = [ ]; 135 + "${mod}+Ctrl+R".action.screenshot-screen = [ ]; 136 + "${mod}+Shift+R".action.screenshot-window = [ ]; 137 + "Print".action.screenshot = [ ]; 138 + "Ctrl+Print".action.screenshot-screen = [ ]; 139 + "Shift+Print".action.screenshot-window = [ ]; 140 + 141 + "${mod}+Space".action.switch-layout = [ "next" ]; 142 + "${mod}+Shift+Space".action.switch-layout = [ "prev" ]; 143 + 144 + "${mod}+D".action.spawn = "${config.programs.walker.package}/bin/walker"; 145 + 146 + "${mod}+Shift+Slash".action.show-hotkey-overlay = [ ]; 147 + 148 + "${mod}+V".action.set-dynamic-cast-monitor = [ ]; 149 + "${mod}+W".action.set-dynamic-cast-window = [ ]; 150 + "${mod}+Shift+V".action.clear-dynamic-cast-target = [ ]; 151 + "${mod}+Shift+W".action.clear-dynamic-cast-target = [ ]; 152 + 153 + "${mod}+N".action.spawn = [ 154 + "sh" 155 + "-c" 156 + "${pkgs.systemd}/bin/systemctl --user start swaync && ${pkgs.swaynotificationcenter}/bin/swaync-client -t" 157 + ]; 158 + # We need to ensure swaync is started, since as it isn't usually until we get a notification 159 + } 160 + // 161 + # Workspace Keybinds 162 + (lib.pipe (lib.range 1 10) [ 163 + (map generateWorkspaceBindings) 164 + joinAttrsetList 165 + ]) 166 + // 167 + # Window Manipulation Bindings 168 + ({ 169 + "${mod}+BracketLeft".action.consume-or-expel-window-left = [ ]; 170 + "${mod}+BracketRight".action.consume-or-expel-window-right = [ ]; 171 + "${mod}+Shift+BracketLeft".action.consume-window-into-column = [ ]; 172 + "${mod}+Shift+BracketRight".action.expel-window-from-column = [ ]; 173 + "${mod}+Slash".action.switch-preset-column-width = [ ]; 174 + "${mod}+${mod1}+F".action.fullscreen-window = [ ]; 175 + "${mod}+${mod1}+Shift+F".action.toggle-windowed-fullscreen = [ ]; 176 + 177 + # Focus 178 + "${mod}+Up".action.focus-window-or-workspace-up = [ ]; 179 + "${mod}+Down".action.focus-window-or-workspace-down = [ ]; 180 + 181 + # Non Jump Movement 182 + "${mod}+Shift+Up".action.move-window-up-or-to-workspace-up = [ ]; 183 + "${mod}+Shift+Down".action.move-window-down-or-to-workspace-down = [ ]; 184 + "${mod}+Shift+Left".action.consume-or-expel-window-left = [ ]; 185 + "${mod}+Shift+Right".action.consume-or-expel-window-right = [ ]; 186 + 187 + # To Monitor 188 + "${mod}+Shift+Ctrl+Up".action.move-window-to-monitor-up = [ ]; 189 + "${mod}+Shift+Ctrl+Down".action.move-window-to-monitor-down = [ ]; 190 + "${mod}+Shift+Ctrl+Left".action.move-window-to-monitor-left = [ ]; 191 + "${mod}+Shift+Ctrl+Right".action.move-window-to-monitor-right = [ ]; 192 + 193 + # To Workspace 194 + "${mod}+Ctrl+Up".action.move-window-to-workspace-up = [ ]; 195 + "${mod}+Ctrl+Down".action.move-window-to-workspace-down = [ ]; 196 + 197 + # Sizing 198 + "${mod}+Equal".action.set-window-height = [ "+5%" ]; 199 + "${mod}+Minus".action.set-window-height = [ "-5%" ]; 200 + }) 201 + // 202 + # Column Manipulation Bindings 203 + ({ 204 + # Focus 205 + "${mod}+Left".action.focus-column-left = [ ]; 206 + "${mod}+Right".action.focus-column-right = [ ]; 207 + "${mod}+${mod1}+C".action.center-column = [ ]; 208 + "${mod}+F".action.maximize-column = [ ]; 209 + 210 + # Non Monitor Movement 211 + "${mod}+${mod1}+Shift+Up".action.move-column-to-workspace-up = [ ]; 212 + "${mod}+${mod1}+Shift+Down".action.move-column-to-workspace-down = [ ]; 213 + "${mod}+${mod1}+Shift+Left".action.move-column-left = [ ]; 214 + "${mod}+${mod1}+Shift+Right".action.move-column-right = [ ]; 215 + 216 + # To Monitor 217 + "${mod}+${mod1}+Shift+Ctrl+Up".action.move-column-to-monitor-up = [ ]; 218 + "${mod}+${mod1}+Shift+Ctrl+Down".action.move-column-to-monitor-down = [ ]; 219 + "${mod}+${mod1}+Shift+Ctrl+Left".action.move-column-to-monitor-left = [ ]; 220 + "${mod}+${mod1}+Shift+Ctrl+Right".action.move-column-to-monitor-right = [ ]; 221 + 222 + # Sizing 223 + "${mod}+${mod1}+Equal".action.set-column-width = [ "+5%" ]; 224 + "${mod}+${mod1}+Minus".action.set-column-width = [ "-5%" ]; 225 + }) 226 + // 227 + # Workspace Manipulation Bindings 228 + ({ 229 + # Focus 230 + "${mod}+Page_Up".action.focus-workspace-up = [ ]; 231 + "${mod}+Page_Down".action.focus-workspace-down = [ ]; 232 + 233 + # Within Itself 234 + "${mod}+Shift+Page_Up".action.move-workspace-up = [ ]; 235 + "${mod}+Shift+Page_Down".action.move-workspace-down = [ ]; 236 + 237 + # To Monitor 238 + "${mod}+Shift+Ctrl+Page_Up".action.move-workspace-to-monitor-up = [ ]; 239 + "${mod}+Shift+Ctrl+Page_Down".action.move-workspace-to-monitor-down = [ ]; 240 + "${mod}+Shift+Ctrl+Home".action.move-workspace-to-monitor-left = [ ]; 241 + "${mod}+Shift+Ctrl+End".action.move-workspace-to-monitor-right = [ ]; 242 + 243 + "${mod}+Space" = { 244 + action.toggle-overview = [ ]; 245 + repeat = false; 246 + }; 247 + }) 248 + // { 249 + # Audio 250 + "XF86AudioRaiseVolume" = { 251 + allow-when-locked = true; 252 + action.spawn = [ 253 + "${pkgs.wireplumber}/bin/wpctl" 254 + "set-volume" 255 + "@DEFAULT_AUDIO_SINK@" 256 + "0.05+" 257 + ]; 258 + }; 259 + "XF86AudioLowerVolume" = { 260 + allow-when-locked = true; 261 + action.spawn = [ 262 + "${pkgs.wireplumber}/bin/wpctl" 263 + "set-volume" 264 + "@DEFAULT_AUDIO_SINK@" 265 + "0.05-" 266 + ]; 267 + }; 268 + "XF86AudioMute" = { 269 + allow-when-locked = true; 270 + action.spawn = [ 271 + "${pkgs.wireplumber}/bin/wpctl" 272 + "set-mute" 273 + "@DEFAULT_AUDIO_SINK@" 274 + "toggle" 275 + ]; 276 + }; 277 + "XF86AudioMicMute" = { 278 + allow-when-locked = true; 279 + action.spawn = [ 280 + "${pkgs.wireplumber}/bin/wpctl" 281 + "set-mute" 282 + "@DEFAULT_AUDIO_SOURCE@" 283 + "toggle" 284 + ]; 285 + }; 286 + }; 287 + 288 + layout = { 289 + gaps = 16; 290 + 291 + center-focused-column = "on-overflow"; 292 + 293 + preset-column-widths = [ 294 + { proportion = 1. / 4.; } 295 + { proportion = 1. / 3.; } 296 + { proportion = 1. / 2.; } 297 + { proportion = 2. / 3.; } 298 + { proportion = 9. / 10.; } 299 + ]; # TODO: clicks to PR a docs update for niri-flake 300 + }; 301 + 302 + prefer-no-csd = true; # No "client-side-decorations" (i.e. client-side window open/close buttons) 303 + hotkey-overlay.skip-at-startup = true; 304 + screenshot-path = null; 305 + 306 + spawn-at-startup = [ 307 + { 308 + command = [ "${pkgs.xwayland-satellite}/bin/xwayland-satellite" ]; 309 + } 310 + { 311 + command = [ 312 + "${pkgs.swaybg}/bin/swaybg" 313 + "-i" 314 + "${config.ingredient.niri.niri.wallpaper}" 315 + "-m" 316 + "fill" 317 + ]; 318 + } 319 + { 320 + command = [ 321 + "${pkgs.swayidle}/bin/swayidle" 322 + "-w" 323 + "timeout" 324 + (toString config.ingredient.niri.niri.timers.lock) 325 + lock 326 + "timeout" 327 + (toString config.ingredient.niri.niri.timers.sleep) 328 + "niri msg action power-off-monitors" 329 + "resume" 330 + "niri msg action power-on-monitors" # Not sure if this is really needed - niri normally powers on monitors on a movement action anyway, but maybe this can affect resuming in different ways? 331 + "before-sleep" 332 + lock 333 + ]; 334 + } 335 + ]; 336 + }; 337 + }; 338 + 339 + programs.walker.enable = true; 340 + 341 + programs.bash.profileExtra = lib.mkBefore '' 342 + if [ -z $WAYLAND_DISPLAY ] && [ "$(tty)" = "/dev/tty1" ]; then 343 + exec ${config.programs.niri.package}/bin/niri-session -l 344 + fi 345 + ''; 346 + 347 + systemd.user.services.niri = { 348 + Unit = { 349 + Description = "A scrollable-tiling Wayland compositor"; 350 + BindsTo = "graphical-session.target"; 351 + Wants = [ 352 + "graphical-session-pre.target" 353 + "xdg-desktop-autostart.target" 354 + ]; 355 + After = [ 356 + "graphical-session-pre.target" 357 + "xdg-desktop-autostart.target" 358 + ]; 359 + }; 360 + 361 + Service = { 362 + Slice = "session.slice"; 363 + Type = "notify"; 364 + ExecStart = "${config.programs.niri.package}/bin/niri --session"; 365 + }; 366 + }; 367 + 368 + systemd.user.services.xdg-desktop-portal = { 369 + # Overrides the portals from NixOS' `xdg.portal.enable` 370 + Unit = { 371 + Description = "Portal service"; 372 + PartOf = "graphical-session.target"; 373 + Requires = "dbus.service"; 374 + After = [ 375 + "dbus.service" 376 + "niri.service" 377 + ]; 378 + }; 379 + 380 + Service = { 381 + Type = "dbus"; 382 + BusName = "org.freedesktop.portal.Desktop"; 383 + ExecStart = "${pkgs.xdg-desktop-portal}/libexec/xdg-desktop-portal"; 384 + Slice = "session.slice"; 385 + }; 386 + }; 387 + }; 388 + }
+49
packetmix/homes/niri/quickshell.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + project, 7 + system, 8 + config, 9 + pkgs, 10 + ... 11 + }: 12 + { 13 + imports = [ "${project.inputs.home-manager-unstable.src}/modules/programs/quickshell.nix" ]; 14 + 15 + programs.quickshell = { 16 + enable = true; 17 + 18 + package = project.inputs.nixos-unstable.result.${system}.quickshell; # Since as we have directly imported the module from home-manager, quickshell isn't in nixpkgs yet for us... 19 + 20 + activeConfig = "sprinkles"; 21 + configs.sprinkles = pkgs.stdenv.mkDerivation { 22 + name = "sprinkles-config"; 23 + 24 + src = ./quickshell; 25 + dontUnpack = true; 26 + 27 + buildPhase = '' 28 + mkdir -p $out 29 + 30 + cp -r $src/*.qml $out 31 + cp ${config.ingredient.niri.niri.overviewBackground} $out/background.png 32 + ''; 33 + }; 34 + 35 + systemd = { 36 + enable = true; 37 + target = "niri.service"; 38 + }; 39 + }; 40 + 41 + programs.niri.settings.layer-rules = [ 42 + { 43 + matches = [ 44 + { namespace = "^quickshell$"; } 45 + ]; 46 + place-within-backdrop = true; 47 + } 48 + ]; 49 + }
+19
packetmix/homes/niri/quickshell/Battery.qml
··· 1 + // SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + // 3 + // SPDX-License-Identifier: MIT 4 + 5 + pragma Singleton 6 + 7 + import Quickshell 8 + import QtQuick 9 + 10 + import Quickshell.Services.UPower 11 + 12 + Singleton { 13 + id: root 14 + 15 + readonly property UPowerDevice device: UPower.displayDevice 16 + readonly property real percentage: device.percentage * 100 17 + readonly property bool valid: device.ready && device.isLaptopBattery 18 + readonly property bool charging: (device.state === UPowerDeviceState.Charging) || (device.state === UPowerDeviceState.FullyCharged) 19 + }
+49
packetmix/homes/niri/quickshell/Clock.qml
··· 1 + // SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + // 3 + // SPDX-License-Identifier: MIT 4 + 5 + import QtQuick 6 + 7 + Rectangle { 8 + id: clock 9 + 10 + property string time: Time.time 11 + property string date: Time.date 12 + property bool charging: Battery.valid ? Battery.charging : false 13 + 14 + // These properties may be undefined *or* another type so we currently have to use var for them... 15 + property var batteryPercentage: Battery.valid ? Battery.percentage : undefined 16 + property var temperature: undefined 17 + property var event: undefined 18 + 19 + height: parent.height - 5 20 + width: childrenRect.width 21 + 22 + color: "transparent" 23 + 24 + x: parent.width / 2 - clock.width / 2 25 + y: parent.height / 2 - clock.height / 2 26 + 27 + Triline { 28 + line1: clock.time 29 + line2: { 30 + let date = clock.date; 31 + let battery = ""; 32 + let temperature = ""; 33 + 34 + if (Battery.valid) { 35 + battery = " " + (clock.charging ? `⚡ ${Math.round(clock.batteryPercentage)}%` : `${Math.round(clock.batteryPercentage)}%`); 36 + } 37 + 38 + if (clock.temperature !== undefined) { 39 + temperature = ` ${Math.round(clock.temperature)}`; 40 + } 41 + 42 + return date + battery + temperature; 43 + } 44 + line3: clock.event ? clock.event.text : "" 45 + 46 + width: childrenRect.width 47 + height: parent.height 48 + } 49 + }
+13
packetmix/homes/niri/quickshell/Event.qml
··· 1 + // SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + // 3 + // SPDX-License-Identifier: MIT 4 + 5 + import Quickshell 6 + 7 + Scope { 8 + required property string eventName 9 + required property string startTime 10 + required property string endTime 11 + 12 + property string text: `${eventName} • ${startTime}-${endTime}` 13 + }
+27
packetmix/homes/niri/quickshell/Niri.qml
··· 1 + // SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + // 3 + // SPDX-License-Identifier: MIT 4 + 5 + pragma Singleton 6 + 7 + import Quickshell 8 + import Quickshell.Io 9 + import QtQuick 10 + 11 + Singleton { 12 + id: root 13 + 14 + property bool overview: false 15 + 16 + Process { 17 + running: true 18 + command: ["niri", "msg", "event-stream"] 19 + stdout: SplitParser { 20 + onRead: data => { 21 + if (data.startsWith("Overview toggled: ")) { 22 + root.overview = data === "Overview toggled: true"; 23 + } 24 + } 25 + } 26 + } 27 + }
+19
packetmix/homes/niri/quickshell/Time.qml
··· 1 + // SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + // 3 + // SPDX-License-Identifier: MIT 4 + 5 + pragma Singleton 6 + 7 + import Quickshell 8 + import QtQuick 9 + 10 + Singleton { 11 + id: root 12 + readonly property string time: Qt.formatDateTime(clock.date, "hh:mm") 13 + readonly property string date: Qt.formatDateTime(clock.date, "ddd, MMM d") 14 + 15 + SystemClock { 16 + id: clock 17 + precision: SystemClock.Minutes 18 + } 19 + }
+81
packetmix/homes/niri/quickshell/Triline.qml
··· 1 + // SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + // 3 + // SPDX-License-Identifier: MIT 4 + 5 + import QtQuick 6 + 7 + Rectangle { 8 + id: triline 9 + 10 + property string line1 11 + property string line2 12 + property string line3 13 + 14 + property int sizeDecrease: line1metrics.tightBoundingRect.height / 16 // The decrease in font size (pixels) that lines 2 and 3 have - since as without this they will end up looking cramped 15 + 16 + color: "transparent" 17 + 18 + TextMetrics { 19 + id: line1metrics 20 + 21 + font.pixelSize: line1.height 22 + 23 + text: triline.line1 24 + } 25 + 26 + Text { 27 + id: line1 28 + 29 + color: "white" 30 + font.pixelSize: height 31 + 32 + text: triline.line1 33 + 34 + verticalAlignment: Text.AlignVCenter 35 + 36 + anchors { 37 + left: parent.left 38 + top: parent.top 39 + bottom: parent.bottom 40 + } 41 + } 42 + 43 + Text { 44 + id: line2 45 + 46 + color: "white" 47 + font.pixelSize: line1metrics.tightBoundingRect.height / 2 - triline.sizeDecrease 48 + 49 + text: triline.line2 50 + leftPadding: line1metrics.tightBoundingRect.height / 8 51 + 52 + anchors { 53 + left: line1.right 54 + 55 + top: line1.baseline 56 + topMargin: -line1metrics.tightBoundingRect.height 57 + 58 + bottom: line1.baseline 59 + bottomMargin: -(line1metrics.tightBoundingRect.height / 2) 60 + } 61 + } 62 + 63 + Text { 64 + id: line3 65 + 66 + color: "white" 67 + font.pixelSize: line1metrics.tightBoundingRect.height / 2 - triline.sizeDecrease 68 + 69 + text: triline.line3 70 + leftPadding: line1metrics.tightBoundingRect.height / 8 71 + 72 + anchors { 73 + left: line1.right 74 + 75 + top: line1.baseline 76 + topMargin: -(line1metrics.tightBoundingRect.height / 2) + triline.sizeDecrease 77 + 78 + bottom: line1.baseline 79 + } 80 + } 81 + }
+9
packetmix/homes/niri/quickshell/qmldir
··· 1 + module Sprinkles 2 + 3 + singleton Battery 1.0 Battery.qml 4 + singleton Niri 1.0 Niri.qml 5 + singleton Time 1.0 Time.qml 6 + 7 + Clock 1.0 Clock.qml 8 + Event 1.0 Event.qml 9 + Triline 1.0 Triline.qml
+3
packetmix/homes/niri/quickshell/qmldir.license
··· 1 + SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + 3 + SPDX-License-Identifier: MIT
+92
packetmix/homes/niri/quickshell/shell.qml
··· 1 + // SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + // 3 + // SPDX-License-Identifier: MIT 4 + 5 + pragma ComponentBehavior: Bound 6 + 7 + import Quickshell // for PanelWindow 8 + import QtQuick // for Text 9 + import Quickshell.Services.UPower 10 + import Quickshell.Wayland 11 + import Quickshell.Io 12 + 13 + /* 14 + 15 + Overview has the following gaps... 16 + 17 + fn workspace_gap(&self, zoom: f64) -> f64 { 18 + let scale = self.scale.fractional_scale(); 19 + let gap = self.view_size.h * 0.1 * zoom; 20 + round_logical_in_physical_max1(scale, gap) 21 + } 22 + 23 + ...zoom by default is 0.5 -> workspaces are 1/2 of the screen size in all dimensions 24 + 25 + so by default the gap we have to work with is 1/2 * 1/10 = 1/20th of the screen size 26 + and ranges between 1/4 - 1/20 and 1/4 at the top (=1/5) and 3/4 and 3/4 + 1/20 at the bottom... 27 + 28 + ...not sure if it's worth calcing this -> guess the cost of having some variables for this isn't really difficult and justifies any Magic Numbers 29 + 30 + */ 31 + 32 + // we can probably lazyload timers from the 'niri msg event-stream' to stop us doing any background work 33 + 34 + Scope { 35 + id: root 36 + 37 + Variants { 38 + model: Quickshell.screens 39 + 40 + PanelWindow { 41 + id: shell 42 + 43 + anchors { 44 + top: true 45 + left: true 46 + right: true 47 + bottom: true 48 + } 49 + 50 + property var modelData 51 + screen: modelData 52 + 53 + color: "transparent" 54 + 55 + Image { 56 + id: background 57 + anchors.fill: parent 58 + fillMode: Image.PreserveAspectCrop 59 + source: "./background.png" 60 + z: -1 61 + } 62 + 63 + /* Start properties needed to place this in the overlay */ 64 + WlrLayershell.layer: WlrLayer.Background 65 + exclusionMode: ExclusionMode.Ignore 66 + /* End properties needed to place this in the overlay */ 67 + 68 + Rectangle { 69 + id: overviewTopline 70 + visible: Niri.overview 71 + 72 + QtObject { 73 + id: position 74 + 75 + property int vmin: Math.min(shell.width, shell.height) 76 + property int height: vmin * 1 / 20 77 + property int toplineAreaBottom: shell.height / 4 78 + property int y: toplineAreaBottom - height 79 + } 80 + 81 + y: position.y 82 + 83 + width: parent.width 84 + height: position.height 85 + 86 + color: "transparent" 87 + 88 + Clock {} 89 + } 90 + } 91 + } 92 + }
+11
packetmix/homes/niri/scriptfs.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + systemd.user.services.scriptfs = { 8 + Install.WantedBy = lib.mkForce [ "niri.service" ]; 9 + Unit.After = [ "niri.service" ]; 10 + }; 11 + }
+11
packetmix/homes/niri/ssh.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + systemd.user.services.ssh-agent = { 8 + Install.WantedBy = lib.mkForce [ "niri.service" ]; 9 + Unit.After = [ "niri.service" ]; 10 + }; 11 + }
+7
packetmix/homes/niri/swaync.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + services.swaync.enable = true; 7 + }
+15
packetmix/homes/nix-index/nix-index.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { project, ... }: 6 + { 7 + imports = [ 8 + project.inputs.nix-index-database.result.homeModules.nix-index 9 + ]; 10 + 11 + config = { 12 + programs.nix-index-database.comma.enable = true; 13 + programs.nix-index.enable = true; 14 + }; 15 + }
+9
packetmix/homes/pinea/catppuccin.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { 5 + catppuccin = { 6 + flavor = "macchiato"; 7 + accent = "mauve"; 8 + }; 9 + }
+11
packetmix/homes/pinea/keyboard.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + home.keyboard = { 7 + layout = "gb"; 8 + variant = "mac"; 9 + options = [ "apple:alupckeys" ]; 10 + }; 11 + }
+19
packetmix/homes/pinea/misc.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + # Miscellaneous package installs that aren't really big enough to get their own folder 8 + # Don't place any config that isn't directly adding lines to home.packages here... 9 + home.packages = [ 10 + pkgs.obs-studio 11 + pkgs.vlc 12 + pkgs.python312 13 + pkgs.playerctl 14 + pkgs.bun 15 + pkgs.nodePackages_latest.nodejs 16 + pkgs.prusa-slicer 17 + pkgs.kitty 18 + ]; 19 + }
packetmix/homes/pinea/wallpaper-dark.png

This is a binary file and will not be displayed.

+3
packetmix/homes/pinea/wallpaper-dark.png.license
··· 1 + SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + 3 + SPDX-License-Identifier: MIT
packetmix/homes/pinea/wallpaper.png

This is a binary file and will not be displayed.

+3
packetmix/homes/pinea/wallpaper.png.license
··· 1 + SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + 3 + SPDX-License-Identifier: MIT
+26
packetmix/homes/pinea/zed.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + programs.zed-editor = { 8 + userSettings = { 9 + helix_mode = lib.mkForce false; 10 + edit_predictions.mode = lib.mkForce "eager"; 11 + assistant = { 12 + enabled = true; 13 + default_model = { 14 + provider = "copilot_chat"; 15 + model = "gpt-4o"; 16 + }; 17 + inline_alternatives = [ 18 + { 19 + provider = "copilot_chat"; 20 + model = "gpt-3.5-turbo"; 21 + } 22 + ]; 23 + }; 24 + }; 25 + }; 26 + }
+12
packetmix/homes/redhead/impermanence.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { project, ... }: 6 + { 7 + imports = [ 8 + project.inputs.impermanence.result.homeManagerModules.impermanence 9 + ]; 10 + 11 + clicks.storage.impermanence.enable = true; 12 + }
+21
packetmix/homes/redhead/niri.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.niri = { 7 + settings = { 8 + window-rules = [ 9 + { 10 + geometry-corner-radius = { 11 + top-left = 8.0; 12 + top-right = 8.0; 13 + bottom-left = 4.0; 14 + bottom-right = 4.0; 15 + }; 16 + clip-to-geometry = true; 17 + } 18 + ]; 19 + }; 20 + }; 21 + }
+11
packetmix/homes/remote/ssh.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + services.ssh-agent.enable = true; 8 + systemd.user.services.ssh-agent.Service.Environment = 9 + "SSH_ASKPASS=${pkgs.kdePackages.ksshaskpass}/bin/ksshaskpass"; 10 + home.sessionVariables.SSH_AUTH_SOCK = "/run/user/$UID/ssh-agent"; 11 + }
+23
packetmix/homes/scriptfs/scriptfs.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + project, 7 + pkgs, 8 + system, 9 + ... 10 + }: 11 + { 12 + systemd.user.services.scriptfs = { 13 + Install.WantedBy = [ "default.target" ]; 14 + 15 + Service = { 16 + ExecStartPre = [ 17 + "-${pkgs.coreutils}/bin/ln -fns %t %S/run" # Useful for some dependents (jujutsu) which cannot expand %t themselves, but prefixed with - to stop a failure bringing down scriptfs 18 + "${pkgs.coreutils}/bin/mkdir -p %t/scriptfs" 19 + ]; 20 + ExecStart = "${project.packages.scriptfs.result.${system}}/bin/scriptfs -f /nix/store %t/scriptfs"; 21 + }; 22 + }; 23 + }
+20
packetmix/inputs.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + let 6 + pins = import ./npins; 7 + 8 + settings = { 9 + nixpkgs.configuration.allowUnfree = true; 10 + "nixos-24.11" = settings.nixpkgs; 11 + nixos-unstable = settings.nixpkgs; 12 + }; 13 + in 14 + { config, ... }: 15 + { 16 + config.inputs = builtins.mapAttrs (name: value: { 17 + src = value; 18 + settings = settings.${name} or config.lib.constants.undefined; 19 + }) pins; 20 + }
+11
packetmix/lib/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, ... }: 6 + { 7 + includes = [ ./ingredients.nix ]; 8 + 9 + config.lib.ci = false; 10 + config.lib.constants.undefined = config.lib.modules.when false { }; 11 + }
+205
packetmix/lib/ingredients.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, config, ... }@nilla: 6 + let 7 + nixpkgs = config.inputs.nixpkgs.result; 8 + this = nilla.config.lib.ingredients; 9 + in 10 + { 11 + config.lib.ingredients = { 12 + # Normalizes a potentially-shorthand module attrset to pull its config into a config attribute 13 + # 14 + # this: {moduleAttrset -> normalizedModuleAttrset} 15 + # 16 + # moduleAttrset: {normalizedModuleAttrset | shorthandModuleAttrset} the attrset to a module 17 + # normalizedModuleAttrset: a module attrset that has been normalized to have either config or options as a top-level option 18 + # shorthandModuleAttrset: a module attrset that has its config as shorthand properties (and contains no options) 19 + normalizeModule = 20 + moduleAttrset: 21 + if moduleAttrset ? config || moduleAttrset ? options then 22 + # module is already normalized 23 + moduleAttrset 24 + else 25 + { 26 + config = builtins.removeAttrs moduleAttrset [ 27 + "disabledModules" 28 + "freeformType" 29 + "imports" 30 + # there can't be options at this point... 31 + ]; 32 + disabledModules = moduleAttrset.disabledModules or [ ]; 33 + freeformType = moduleAttrset.freeformType or null; 34 + imports = moduleAttrset.imports or [ ]; 35 + options = { }; 36 + }; 37 + 38 + # Lets you wrap a module without worrying about if the module you're wrapping is a function or an attrset 39 + # 40 + # this: {wrapper -> path -> overrideArgs -> moduleToWrap -> outModule} 41 + # 42 + # wrapper: {moduleArgs -> normalizedModuleAttrset -> moduleAttrset} a function which takes an applied module and returns an unapplied module 43 + # path: {string} a "path" that'll appear as "virtual:packetmix/wrapped/${path}" in evaluation failures for this module 44 + # overrideArgs: an attrset of arguments to override the defaults. Similar to specialArgs and co 45 + # moduleToWrap: {moduleArgs -> moduleAttrset | moduleAttrset} the module for which the wrapper will wrap the moduleAttrset 46 + # outModule: {moduleArgs -> moduleAttrset} the wrapped module 47 + # moduleArgs: the standard arguments to a module, including standard arguments (lib, pkgs, etc.), extraArgs, etc. 48 + # moduleAttrset: {normalizedModuleAttrset | shorthandModuleAttrset} the attrset to a module 49 + # normalizedModuleAttrset: a module attrset that has been normalized to have either config or options as a top-level option 50 + # shorthandModuleAttrset: a module attrset that has its config as shorthand properties (and contains no options) 51 + wrapModule = 52 + wrapper: path: overrideArgs: moduleToWrap: 53 + if builtins.isFunction moduleToWrap then 54 + let 55 + wrapped = 56 + args: 57 + let 58 + moduleArgs = builtins.intersectAttrs (nixpkgs.lib.functionArgs moduleToWrap) (args // overrideArgs); 59 + moduleAttrset = moduleToWrap moduleArgs; 60 + 61 + wrappedModule = this.wrapModule wrapper path overrideArgs moduleAttrset; # overrideArgs doesn't really matter at this point since as the other branch won't use it ... but we may as well 62 + wrapperArgs = builtins.intersectAttrs (nixpkgs.lib.functionArgs wrapper) (args // overrideArgs); 63 + in 64 + wrappedModule wrapperArgs; 65 + in 66 + nixpkgs.lib.setFunctionArgs wrapped ( 67 + nixpkgs.lib.functionArgs moduleToWrap // nixpkgs.lib.functionArgs wrapper 68 + ) 69 + else 70 + let 71 + wrapped = 72 + args: 73 + { 74 + _file = "virtual:packetmix/wrapped/${path}"; 75 + } 76 + // (wrapper args (this.normalizeModule moduleToWrap)); 77 + in 78 + nixpkgs.lib.setFunctionArgs wrapped (nixpkgs.lib.functionArgs wrapper); 79 + 80 + # Wraps a module so it is enabled by the config.ingredient.${name}.enable option 81 + # 82 + # Everything *except* the config, for example options, imports, 83 + # disabledModules, etc. *will always be applied* so they probably shouldn't have a 84 + # visible effect on the system 85 + # 86 + # this: {name -> subpath -> overrideArgs -> moduleToWrap -> outModule} 87 + # 88 + # name: {string} the name of the ingredient, used as the enable option and as "virtual:packetmix/wrapped/ingredient/${name}/${subpath}" in evaluation failures for this module 89 + # subpath: {string} a more specific name for the module - e.g. filename.nix - used as "virtual:packetmix/wrapped/ingredient/${name}/${subpath}" in evaluation failures for this module 90 + # overrideArgs: an attrset of arguments to override the defaults. Similar to specialArgs and co 91 + # moduleToWrap: {moduleArgs -> moduleAttrset | moduleAttrset} the module which should be enabled by config.ingredient.${name}.enable 92 + # outModule: {moduleArgs -> normalizedModuleAttrset} the wrapped module 93 + # moduleArgs: the standard arguments to a module, including standard arguments (lib, pkgs, etc.), extraArgs, etc. 94 + # moduleAttrset: {normalizedModuleAttrset | shorthandModuleAttrset} the attrset to a module 95 + # normalizedModuleAttrset: a module attrset that has been normalized to have either config or options as a top-level option 96 + # shorthandModuleAttrset: a module attrset that has its config as shorthand properties (and contains no options) 97 + mkIngredientModule = 98 + name: subpath: 99 + this.wrapModule ( 100 + { config, ... }@module: 101 + moduleToWrap: 102 + moduleToWrap 103 + // { 104 + config = nixpkgs.lib.mkIf module.config.ingredient.${name}.enable (moduleToWrap.config or { }); 105 + } 106 + ) "ingredient/${name}/${subpath}"; 107 + 108 + # Gets modules for a specific ingredient, based on a base directory, a name and any argument overrides 109 + # Includes a declaration of options.ingredient.${name}.enable and auto-imports for all modules in ${ingredientDirectory}/${name}, mkIfed behind that enable option 110 + # 111 + # this: {ingredientsDirectory -> overrideArgs -> name -> outModule[]} 112 + # 113 + # ingredientsDirectory: {path} the path in which your ingredients reside 114 + # overrideArgs: an attrset of arguments to override the defaults. Similar to specialArgs and co 115 + # name: {string} the name of the ingredient 116 + # outModule[]: {(moduleArgs -> normalizedModuleAttrset | normalizedModuleAttrset)[]} modules for the ingredient, with their config conditionally-enabled on the ingredient being enabled 117 + # moduleArgs: the standard arguments to a module, including standard arguments (lib, pkgs, etc.), extraArgs, etc. 118 + # normalizedModuleAttrset: a module attrset that has been normalized to have either config or options as a top-level option 119 + collectIngredientModules = 120 + ingredientsDirectory: overrideArgs: name: 121 + let 122 + ingredientDirectoryListing = builtins.readDir "${ingredientsDirectory}/${name}"; 123 + ingredientNixFiles = nilla.lib.attrs.filter ( 124 + name: value: nilla.lib.strings.hasSuffix ".nix" name && value == "regular" 125 + ) ingredientDirectoryListing; 126 + ingredientNixFileSubpaths = builtins.attrNames ingredientNixFiles; 127 + modules = map ( 128 + subpath: 129 + this.mkIngredientModule name subpath overrideArgs ( 130 + import "${ingredientsDirectory}/${name}/${subpath}" 131 + ) 132 + ) ingredientNixFileSubpaths; 133 + in 134 + modules 135 + ++ [ 136 + { 137 + options.ingredient.${name}.enable = nixpkgs.lib.mkEnableOption "the ${name} ingredient"; 138 + } 139 + ] 140 + ++ ( 141 + if nilla.lib.strings.hasInfix "+" name then 142 + [ 143 + ( 144 + { config, ... }@module: 145 + { 146 + config.ingredient.${name}.enable = 147 + let 148 + components = nilla.lib.strings.split "+" name; 149 + shouldEnable = builtins.all (component: module.config.ingredient.${component}.enable) components; 150 + in 151 + nixpkgs.lib.mkIf shouldEnable true; 152 + } 153 + ) 154 + ] 155 + else 156 + [ ] 157 + ); 158 + 159 + # Gets the names of all ingredients, based on just a directory 160 + # 161 + # this: {ingredientsDirectory -> moduleName[]} 162 + # 163 + # ingredientsDirectory: {path} the path in which your ingredients reside 164 + # moduleName[]: {string[]} names of all ingredients in your ingredientsDirectory 165 + getIngredientsNames = 166 + ingredientsDirectory: 167 + let 168 + ingredientsDirectoryListing = builtins.readDir ingredientsDirectory; 169 + ingredientSubdirectories = nilla.lib.attrs.filter ( 170 + _: value: value == "directory" 171 + ) ingredientsDirectoryListing; 172 + ingredientNames = builtins.attrNames ingredientSubdirectories; 173 + in 174 + ingredientNames; 175 + 176 + # Gets modules for all ingredients, based on just a directory and any argument overrides 177 + # Includes all required option declarations (under options.ingredient.${name}.enable for each ingredient) and conditional auto-imports 178 + # Includes any options, imports, etc. that are defined in *any* ingredient, even if it is not enabled 179 + # 180 + # this: {ingredientsDirectory -> overrideArgs -> outModule[]} 181 + # ingredientsDirectory: {path} the path in which your ingredients reside 182 + # name: {string} the name of the ingredient 183 + # overrideArgs: an attrset of arguments to override the defaults. Similar to specialArgs and co 184 + # outModule[]: {(moduleArgs -> normalizedModuleAttrset | normalizedModuleAttrset)[]} modules for all ingredients, with their config conditionally-enabled on their ingredient being enabled 185 + # moduleArgs: the standard arguments to a module, including standard arguments (lib, pkgs, etc.), extraArgs, etc. 186 + # normalizedModuleAttrset: a module attrset that has been normalized to have either config or options as a top-level option 187 + collectIngredientsModules = 188 + ingredientsDirectory: overrideArgs: 189 + let 190 + ingredientNames = this.getIngredientsNames ingredientsDirectory; 191 + modules = map (this.collectIngredientModules ingredientsDirectory overrideArgs) ingredientNames; 192 + in 193 + nilla.lib.lists.flatten modules; 194 + 195 + # Gets whether an ingredient exists, based on a directory name and a 196 + # 197 + # this: {ingredientsDirectory -> name -> exists} 198 + # 199 + # ingredientsDirectory: {path} the path in which your ingredients reside 200 + # name: {string} the name of the ingredient to search for 201 + # exists: {boolean} whether that ingredient exists 202 + ingredientExists = 203 + ingredientsDirectory: name: builtins.elem name (this.getIngredientsNames ingredientsDirectory); 204 + }; 205 + }
+11
packetmix/modules/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + includes = [ 7 + ./ingredients.nix 8 + ./nilla-home/home.nix 9 + ./nilla-home/nixos.nix 10 + ]; 11 + }
+34
packetmix/modules/ingredients.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, lib, ... }@nilla: 6 + { 7 + options.systems.nixos = 8 + let 9 + ingredientModules = nilla.config.lib.ingredients.collectIngredientsModules ../systems { }; 10 + ingredientExists = nilla.config.lib.ingredients.ingredientExists ../systems; 11 + in 12 + nilla.lib.options.create { 13 + type = nilla.lib.types.attrs.of ( 14 + nilla.lib.types.submodule ( 15 + { config, name, ... }@submodule: 16 + { 17 + options.ingredients = nilla.lib.options.create { 18 + description = "Ingredients to activate for the system. Defaults to the common ingredient, as well as the ingredient named as the system's hostname if it exists"; 19 + type = nilla.lib.types.list.of nilla.lib.types.string; 20 + }; 21 + 22 + config = { 23 + ingredients = [ "common" ] ++ (if ingredientExists name then [ name ] else [ ]); 24 + modules = 25 + ingredientModules 26 + ++ (map (ingredient: { 27 + config.ingredient.${ingredient}.enable = true; 28 + }) submodule.config.ingredients); 29 + }; 30 + } 31 + ) 32 + ); 33 + }; 34 + }
+23
packetmix/modules/nilla-home/home.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Nilla Home contributors 2 + # 3 + # SPDX-License-Identifier: Apache-2.0 4 + 5 + { lib, config }: 6 + let 7 + inherit (config) inputs; 8 + homes-type = import ./homes-type.nix { inherit lib config; }; 9 + in 10 + { 11 + options.homes = lib.options.create { 12 + description = "Home-Manager homes to create."; 13 + default.value = { }; 14 + type = homes-type; 15 + }; 16 + 17 + config = { 18 + assertions = lib.attrs.mapToList (name: value: { 19 + assertion = !(builtins.isNull value.pkgs); 20 + message = "A Nixpkgs instance is required for the home-manager home \"${name}\", but none was provided and \"inputs.nixpkgs\" does not exist."; 21 + }) config.homes; 22 + }; 23 + }
+128
packetmix/modules/nilla-home/homes-type.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Nilla Home contributors 2 + # 3 + # SPDX-License-Identifier: Apache-2.0 4 + 5 + { lib, config }@nilla: 6 + let 7 + inherit (config) inputs; 8 + 9 + ingredientModules = nilla.config.lib.ingredients.collectIngredientsModules ../../homes { 10 + project = nilla.config; 11 + }; 12 + ingredientExists = nilla.config.lib.ingredients.ingredientExists ../../homes; 13 + in 14 + lib.types.attrs.of ( 15 + lib.types.submodules.portable ({ 16 + name = "home"; 17 + description = "A home-manager home"; 18 + module = 19 + { config }@submodule: 20 + let 21 + home_name = config.__module__.args.dynamic.name; 22 + home_name_parts = builtins.match "([a-z][-a-z0-9]*)(@([-A-Za-z0-9]+))?(:([-_A-Za-z0-9]+))?" home_name; 23 + 24 + argsModule = { 25 + config._module.args = config.args; 26 + _file = "virtual:nilla-nix/home/${home_name}/args"; 27 + }; 28 + 29 + homeForSystem = 30 + system: 31 + config.home-manager.lib.homeManagerConfiguration { 32 + pkgs = config.pkgs.${system}; 33 + lib = config.pkgs.lib; 34 + modules = config.modules ++ [ argsModule ]; 35 + }; 36 + 37 + result = builtins.listToAttrs ( 38 + builtins.map (system: { 39 + name = system; 40 + value = homeForSystem system; 41 + }) config.systems 42 + ); 43 + 44 + username = builtins.elemAt home_name_parts 0; 45 + hostname = builtins.elemAt home_name_parts 2; 46 + system = builtins.elemAt home_name_parts 4; 47 + 48 + hostnameProvided = hostname != null; 49 + systemProvided = system != null; 50 + 51 + defaultModules = [ 52 + ( 53 + { lib, ... }: 54 + { 55 + home.username = lib.modules.mkDefault username; 56 + } 57 + ) 58 + ]; 59 + in 60 + { 61 + options = { 62 + systems = 63 + lib.options.create { 64 + description = "The systems this home is valid on."; 65 + type = lib.types.list.of lib.types.string; 66 + } 67 + // ( 68 + if systemProvided then 69 + { 70 + default.value = [ system ]; 71 + writeable = false; 72 + } 73 + else 74 + { } 75 + ); 76 + 77 + args = lib.options.create { 78 + description = "Additional arguments to pass to home-manager modules."; 79 + type = lib.types.attrs.any; 80 + default.value = { }; 81 + }; 82 + 83 + home-manager = lib.options.create { 84 + description = "The home-manager input to use."; 85 + type = lib.types.raw; 86 + default.value = if inputs ? home-manager then inputs.home-manager.result else null; 87 + }; 88 + 89 + pkgs = lib.options.create { 90 + description = "The Nixpkgs instance to use."; 91 + type = lib.types.raw; 92 + default.value = if inputs ? nixpkgs then inputs.nixpkgs.result else null; 93 + }; 94 + 95 + modules = lib.options.create { 96 + description = "A list of modules to use for home-manager."; 97 + type = lib.types.list.of lib.types.raw; 98 + }; 99 + 100 + ingredients = nilla.lib.options.create { 101 + description = "Ingredients to activate for the home. Defaults to the common ingredient, as well as one or more of the ingredients named as the username and the hostname if they are set in the home name and the ingredients exist"; 102 + type = nilla.lib.types.list.of nilla.lib.types.string; 103 + }; 104 + 105 + result = lib.options.create { 106 + description = "The created Home Manager home for each of the systems."; 107 + type = lib.types.attrs.of lib.types.raw; 108 + writable = false; 109 + default.value = result; 110 + }; 111 + }; 112 + 113 + config = { 114 + ingredients = [ 115 + "common" 116 + ] 117 + ++ (if ingredientExists username then [ username ] else [ ]) 118 + ++ (if hostnameProvided && ingredientExists hostname then [ hostname ] else [ ]); 119 + modules = 120 + defaultModules 121 + ++ ingredientModules 122 + ++ (map (ingredient: { 123 + config.ingredient.${ingredient}.enable = true; 124 + }) submodule.config.ingredients); # Provided down here rather than as a default so they don't get overriden when a user specifies additional modules 125 + }; 126 + }; 127 + }) 128 + )
+163
packetmix/modules/nilla-home/nixos.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Nilla Home contributors 2 + # 3 + # SPDX-License-Identifier: Apache-2.0 4 + 5 + { config, lib }@global: 6 + let 7 + inherit (global.config) inputs; 8 + homes-type = import ./homes-type.nix { inherit config lib; }; 9 + in 10 + { 11 + options.systems = { 12 + nixos = lib.options.create { 13 + type = lib.types.attrs.of ( 14 + lib.types.submodule ( 15 + { config, name, ... }@submodule: 16 + { 17 + options = { 18 + home-manager = lib.options.create { 19 + description = "The home-manager input to use."; 20 + type = lib.types.raw; 21 + default.value = if inputs ? home-manager then inputs.home-manager.result else null; 22 + }; 23 + 24 + homes = lib.options.create { 25 + description = "Homes to activate for the system, with the same naming scheme as nilla-home's config.homes option"; 26 + type = homes-type; 27 + default.value = { }; 28 + }; 29 + }; 30 + 31 + config.modules = 32 + let 33 + system = submodule.config.pkgs.system; 34 + warn' = builtins.warn or builtins.trace; # builtins.warn doesn't exist on some versions of nix/lix 35 + warnIf = 36 + condition: message: value: 37 + if condition then warn' message value else value; 38 + homeManager = submodule.config.home-manager; 39 + in 40 + (lib.fp.pipe [ 41 + (lib.attrs.mapToList ( 42 + homeName: home: 43 + let 44 + homeNameParts = builtins.match "([a-z][-a-z0-9]*)(@([-A-Za-z0-9]+))?(:([-_A-Za-z0-9]+))?" homeName; 45 + username = builtins.elemAt homeNameParts 0; 46 + in 47 + { 48 + inherit home homeName username; 49 + } 50 + )) 51 + (builtins.map ( 52 + { 53 + home, 54 + homeName, 55 + username, 56 + }@identity: 57 + warnIf (home.home-manager != homeManager) 58 + "The home \"${homeName}\" isn't using the same home-manager input as the NixOS system \"${name}\". This may work, but is not officially supported by the Nilla Home or Nilla NixOS maintainers. Please fix this before reporting any bugs you may find." 59 + identity 60 + )) 61 + (builtins.map ( 62 + { 63 + home, 64 + homeName, 65 + username, 66 + }: 67 + { lib, ... }: 68 + { 69 + _file = "virtual:nilla-nix/home/nixos/${homeName}/nixos"; 70 + config.home-manager.useGlobalPkgs = true; # Required or home modules will lose the nixpkgs config defined in nilla 71 + config.home-manager.users.${username} = 72 + { ... }: 73 + { 74 + _file = "virtual:nilla-nix/home/nixos/${homeName}/homeModule"; 75 + imports = home.modules ++ [ 76 + { 77 + config._module.args = home.args; 78 + _file = "virtual:nilla-nix/home/nixos/${homeName}/args"; 79 + } 80 + ]; 81 + }; 82 + config.users.users.${username}.isNormalUser = lib.modules.mkDefault true; 83 + } 84 + )) 85 + lib.lists.flatten 86 + ] submodule.config.homes) 87 + ++ ( 88 + if submodule.config.homes != [ ] then 89 + [ submodule.config.home-manager.nixosModules.default ] 90 + else 91 + [ ] 92 + ); 93 + } 94 + ) 95 + ); 96 + }; 97 + }; 98 + 99 + config = { 100 + assertions = lib.lists.flatten ( 101 + lib.attrs.mapToList ( 102 + name: value: 103 + let 104 + hasNixpkgs = !(builtins.isNull value.pkgs); 105 + requestedHomes = value.homes != [ ]; 106 + hasHomeManager = !(builtins.isNull value.home-manager); 107 + in 108 + [ 109 + { 110 + assertion = hasNixpkgs; 111 + message = "A Nixpkgs instance is required for the NixOS system \"${name}\", but none was provided and \"inputs.nixpkgs\" does not exist."; 112 + } 113 + { 114 + assertion = !requestedHomes || hasHomeManager; 115 + message = "A home-manager instance is required to enable homes for the NixOS system \"${name}\", but none was provided and \"inputs.home-manager\" does not exist."; 116 + } 117 + (lib.attrs.mapToList ( 118 + homeName: home: 119 + let 120 + homeHasHomeManager = !(builtins.isNull home.home-manager); 121 + homeIsValidForSystem = home ? result.${value.pkgs.system}; 122 + in 123 + [ 124 + { 125 + assertion = homeHasHomeManager; 126 + message = "You've asked for the home \"${homeName}\" to be activated in the NixOS system \"${name}\", but it needs a home-manager instance, none was provided and \"inputs.home-manager\" does not exist."; 127 + } 128 + { 129 + assertion = !homeHasHomeManager || !hasNixpkgs || homeIsValidForSystem; 130 + message = "You've asked for the home \"${homeName}\" to be activated in the NixOS system \"${name}\", but it isn't valid for \"${value.pkgs.system}\" systems."; 131 + } 132 + ] 133 + ) value.homes) 134 + ( 135 + let 136 + usernames = lib.attrs.mapToList ( 137 + homeName: home: 138 + let 139 + homeHasHomeManager = !(builtins.isNull home.home-manager); 140 + homeIsValidForSystem = home ? result.${value.pkgs.system}; 141 + in 142 + if homeHasHomeManager && hasNixpkgs && homeIsValidForSystem then 143 + let 144 + homeNameParts = builtins.match "([a-z][-a-z0-9]*)(@([-A-Za-z0-9]+))?(:([-_A-Za-z0-9]+))?" homeName; 145 + username = builtins.elemAt homeNameParts 0; 146 + in 147 + username 148 + else 149 + null 150 + ) value.homes; 151 + existingUsernames = builtins.filter (username: username != null) usernames; 152 + uniqueUsernames = lib.lists.unique existingUsernames; 153 + in 154 + { 155 + assertion = !hasNixpkgs || (existingUsernames == uniqueUsernames); 156 + message = "There are multiple homes for a single user in the NixOS system \"${name}\". Please make sure you've only enabled a single home per user."; 157 + } 158 + ) 159 + ] 160 + ) global.config.systems.nixos 161 + ); 162 + }; 163 + }
+147
packetmix/nilla.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + let 6 + pins = import ./npins; 7 + 8 + nilla = import pins.nilla; 9 + 10 + settings = { 11 + nixpkgs.configuration.allowUnfree = true; 12 + "nixos-24.11" = settings.nixpkgs; 13 + nixos-unstable = settings.nixpkgs; 14 + }; 15 + in 16 + nilla.create ( 17 + { config, lib }: 18 + { 19 + includes = [ 20 + ./homes 21 + ./inputs.nix 22 + ./lib 23 + ./modules 24 + ./packages 25 + ./systems 26 + "${pins.nilla-nixos}/modules/nixos.nix" # We can't use config.inputs here without infinitely-recursing 27 + ]; 28 + 29 + config = { 30 + packages.allNixOSSystems = { 31 + systems = [ "x86_64-linux" ]; 32 + 33 + package = 34 + { stdenv }: 35 + stdenv.mkDerivation { 36 + name = "all-nixos-systems"; 37 + 38 + dontUnpack = true; 39 + 40 + buildPhase = '' 41 + mkdir -p $out 42 + '' 43 + + (builtins.concatStringsSep "\n" ( 44 + config.lib.attrs.mapToList ( 45 + name: value: ''ln -s "${value.result.config.system.build.toplevel}" "$out/${name}"'' 46 + ) config.systems.nixos 47 + )); 48 + }; 49 + }; 50 + 51 + packages.allHomes = { 52 + systems = [ "x86_64-linux" ]; 53 + 54 + package = 55 + { system, stdenv }: 56 + stdenv.mkDerivation { 57 + name = "all-homes"; 58 + 59 + dontUnpack = true; 60 + 61 + buildPhase = '' 62 + mkdir -p $out 63 + '' 64 + + (builtins.concatStringsSep "\n" ( 65 + config.lib.attrs.mapToList ( 66 + name: value: ''ln -s "${value.result.${system}.activationPackage}" "$out/${name}"'' 67 + ) (config.lib.attrs.filter (_: value: value.result ? ${system}) config.homes) 68 + )); 69 + }; 70 + }; 71 + 72 + packages.helix = { 73 + systems = [ "x86_64-linux" ]; 74 + 75 + package = 76 + { helix }: 77 + helix.overrideAttrs ( 78 + { 79 + patches ? [ ], 80 + ... 81 + }: 82 + { 83 + doCheck = false; 84 + patches = patches ++ [ ./patches/helix/3958-labels-for-config-menus.patch ]; 85 + } 86 + ); 87 + }; 88 + 89 + packages.reuse = { 90 + systems = [ "x86_64-linux" ]; 91 + 92 + package = 93 + { reuse }: 94 + reuse.overrideAttrs ( 95 + { 96 + patches ? [ ], 97 + ... 98 + }: 99 + { 100 + patches = patches ++ [ ./patches/reuse/1191-correct-invocation-for-jujutsu-file-listing.patch ]; 101 + } 102 + ); 103 + }; 104 + 105 + # With a package set defined, we can create a shell. 106 + shells.default = { 107 + # Declare what systems the shell can be used on. 108 + systems = [ "x86_64-linux" ]; 109 + 110 + # Define our shell environment. 111 + shell = 112 + { 113 + pkgs, 114 + system, 115 + npins, 116 + mkShell, 117 + kdePackages, 118 + ... 119 + }: 120 + mkShell { 121 + QML_IMPORT_PATH = 122 + lib.fp.pipe 123 + [ 124 + (map (pkg: "${pkg}/lib/qt-6/qml")) 125 + (builtins.concatStringsSep ":") 126 + ] 127 + [ 128 + config.inputs.nixos-unstable.result.${system}.quickshell 129 + kdePackages.qtdeclarative 130 + ]; 131 + 132 + packages = [ 133 + config.inputs.nilla-cli.result.packages.nilla-cli.result.${system} 134 + config.inputs.nilla-home.result.packages.nilla-home.result.${system} 135 + config.inputs.nilla-nixos.result.packages.nilla-nixos.result.${system} 136 + config.inputs.nixos-unstable.result.${system}.quickshell 137 + config.packages.nilla-fmt.result.${system} 138 + config.packages.treefmt.result.${system} 139 + (config.inputs.npins.result { inherit pkgs system; }) 140 + kdePackages.qtdeclarative 141 + config.packages.reuse.result.${system} 142 + ]; 143 + }; 144 + }; 145 + }; 146 + } 147 + )
+146
packetmix/npins/default.nix
··· 1 + /* 2 + This file is provided under the MIT licence: 3 + 4 + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 + 6 + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 + 8 + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 + */ 10 + # Generated by npins. Do not modify; will be overwritten regularly 11 + let 12 + data = builtins.fromJSON (builtins.readFile ./sources.json); 13 + version = data.version; 14 + 15 + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 16 + range = 17 + first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1); 18 + 19 + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 20 + stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 21 + 22 + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 23 + stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 24 + concatMapStrings = f: list: concatStrings (map f list); 25 + concatStrings = builtins.concatStringsSep ""; 26 + 27 + # If the environment variable NPINS_OVERRIDE_${name} is set, then use 28 + # the path directly as opposed to the fetched source. 29 + # (Taken from Niv for compatibility) 30 + mayOverride = 31 + name: path: 32 + let 33 + envVarName = "NPINS_OVERRIDE_${saneName}"; 34 + saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; 35 + ersatz = builtins.getEnv envVarName; 36 + in 37 + if ersatz == "" then 38 + path 39 + else 40 + # this turns the string into an actual Nix path (for both absolute and 41 + # relative paths) 42 + builtins.trace "Overriding path of \"${name}\" with \"${ersatz}\" due to set \"${envVarName}\"" ( 43 + if builtins.substring 0 1 ersatz == "/" then 44 + /. + ersatz 45 + else 46 + /. + builtins.getEnv "PWD" + "/${ersatz}" 47 + ); 48 + 49 + mkSource = 50 + name: spec: 51 + assert spec ? type; 52 + let 53 + path = 54 + if spec.type == "Git" then 55 + mkGitSource spec 56 + else if spec.type == "GitRelease" then 57 + mkGitSource spec 58 + else if spec.type == "PyPi" then 59 + mkPyPiSource spec 60 + else if spec.type == "Channel" then 61 + mkChannelSource spec 62 + else if spec.type == "Tarball" then 63 + mkTarballSource spec 64 + else 65 + builtins.throw "Unknown source type ${spec.type}"; 66 + in 67 + spec // { outPath = mayOverride name path; }; 68 + 69 + mkGitSource = 70 + { 71 + repository, 72 + revision, 73 + url ? null, 74 + submodules, 75 + hash, 76 + branch ? null, 77 + ... 78 + }: 79 + assert repository ? type; 80 + # At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository 81 + # In the latter case, there we will always be an url to the tarball 82 + if url != null && !submodules then 83 + builtins.fetchTarball { 84 + inherit url; 85 + sha256 = hash; 86 + } 87 + else 88 + let 89 + url = 90 + if repository.type == "Git" then 91 + repository.url 92 + else if repository.type == "GitHub" then 93 + "https://github.com/${repository.owner}/${repository.repo}.git" 94 + else if repository.type == "GitLab" then 95 + "${repository.server}/${repository.repo_path}.git" 96 + else 97 + throw "Unrecognized repository type ${repository.type}"; 98 + urlToName = 99 + url: rev: 100 + let 101 + matched = builtins.match "^.*/([^/]*)(\\.git)?$" url; 102 + 103 + short = builtins.substring 0 7 rev; 104 + 105 + appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else ""; 106 + in 107 + "${if matched == null then "source" else builtins.head matched}${appendShort}"; 108 + name = urlToName url revision; 109 + in 110 + builtins.fetchGit { 111 + rev = revision; 112 + narHash = hash; 113 + 114 + inherit name submodules url; 115 + }; 116 + 117 + mkPyPiSource = 118 + { url, hash, ... }: 119 + builtins.fetchurl { 120 + inherit url; 121 + sha256 = hash; 122 + }; 123 + 124 + mkChannelSource = 125 + { url, hash, ... }: 126 + builtins.fetchTarball { 127 + inherit url; 128 + sha256 = hash; 129 + }; 130 + 131 + mkTarballSource = 132 + { 133 + url, 134 + locked_url ? url, 135 + hash, 136 + ... 137 + }: 138 + builtins.fetchTarball { 139 + url = locked_url; 140 + sha256 = hash; 141 + }; 142 + in 143 + if version == 6 then 144 + builtins.mapAttrs mkSource data.pins 145 + else 146 + throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
+3
packetmix/npins/default.nix.license
··· 1 + SPDX-FileCopyrightText: 2025 Npins Contributors 2 + 3 + SPDX-License-Identifier: MIT
+333
packetmix/npins/sources.json
··· 1 + { 2 + "pins": { 3 + "beancount-autobean": { 4 + "type": "Git", 5 + "repository": { 6 + "type": "GitHub", 7 + "owner": "SEIAROTg", 8 + "repo": "autobean" 9 + }, 10 + "branch": "master", 11 + "submodules": false, 12 + "revision": "1a45fc465a9e2eefb595ecadac52c92083922375", 13 + "url": "https://github.com/SEIAROTg/autobean/archive/1a45fc465a9e2eefb595ecadac52c92083922375.tar.gz", 14 + "hash": "sha256-9P/kAMYWfTnMWEfMLiDQdJgOUtUy+Zmso6u/qKtSqi0=" 15 + }, 16 + "beancount-beancount_plugin_utils": { 17 + "type": "Git", 18 + "repository": { 19 + "type": "GitHub", 20 + "owner": "Akuukis", 21 + "repo": "beancount_plugin_utils" 22 + }, 23 + "branch": "master", 24 + "submodules": false, 25 + "revision": "053807f55b062a4d7bca91612a856da3812cb465", 26 + "url": "https://github.com/Akuukis/beancount_plugin_utils/archive/053807f55b062a4d7bca91612a856da3812cb465.tar.gz", 27 + "hash": "sha256-oyfL2K/sS4zZ7cq1P36h0dTcW1m5GUyQ9+IyZGfpb2E=" 28 + }, 29 + "beancount-beancount_share": { 30 + "type": "Git", 31 + "repository": { 32 + "type": "GitHub", 33 + "owner": "Akuukis", 34 + "repo": "beancount_share" 35 + }, 36 + "branch": "master", 37 + "submodules": false, 38 + "revision": "4101c45e16948c0683520cebcf88afe4862224c4", 39 + "url": "https://github.com/Akuukis/beancount_share/archive/4101c45e16948c0683520cebcf88afe4862224c4.tar.gz", 40 + "hash": "sha256-BW2KEC0pmervT71FBixPcQciEuGcElCd2wW7BZL1xUg=" 41 + }, 42 + "beancount-smart_importer": { 43 + "type": "Git", 44 + "repository": { 45 + "type": "GitHub", 46 + "owner": "beancount", 47 + "repo": "smart_importer" 48 + }, 49 + "branch": "main", 50 + "submodules": false, 51 + "revision": "d288eb31c580883491294c5e6c0abb4962f16e79", 52 + "url": "https://github.com/beancount/smart_importer/archive/d288eb31c580883491294c5e6c0abb4962f16e79.tar.gz", 53 + "hash": "sha256-1hE/2za1grfCeo1UK81dpLL/JkiLgaRYqLtF09mbDHc=" 54 + }, 55 + "catppuccin": { 56 + "type": "Git", 57 + "repository": { 58 + "type": "GitHub", 59 + "owner": "catppuccin", 60 + "repo": "nix" 61 + }, 62 + "branch": "main", 63 + "submodules": false, 64 + "revision": "cd22197da06df1eb6fabdaa2fc22c170c4f67382", 65 + "url": "https://github.com/catppuccin/nix/archive/cd22197da06df1eb6fabdaa2fc22c170c4f67382.tar.gz", 66 + "hash": "sha256-n+mgH3NoQf8d1jd8cDp/9Mt++hhyuE3LO3ZAxzjWRZw=" 67 + }, 68 + "collabora-gtimelog": { 69 + "type": "Git", 70 + "repository": { 71 + "type": "GitLab", 72 + "repo_path": "collabora/gtimelog", 73 + "server": "https://gitlab.collabora.com/" 74 + }, 75 + "branch": "master", 76 + "submodules": false, 77 + "revision": "8395ec4576cf54411d974675d26f64208acdcee0", 78 + "url": "https://gitlab.collabora.com/api/v4/projects/collabora%2Fgtimelog/repository/archive.tar.gz?sha=8395ec4576cf54411d974675d26f64208acdcee0", 79 + "hash": "sha256-M9pCF+XVf5ylxgq0BSUn5Vkg1HZ6i88LDiUDM4Y1Ghs=" 80 + }, 81 + "copyparty": { 82 + "type": "GitRelease", 83 + "repository": { 84 + "type": "GitHub", 85 + "owner": "9001", 86 + "repo": "copyparty" 87 + }, 88 + "pre_releases": false, 89 + "version_upper_bound": null, 90 + "release_prefix": null, 91 + "submodules": false, 92 + "version": "v1.19.7", 93 + "revision": "26a29797a6c476bffffb8c9cc332d578f6bc1614", 94 + "url": "https://api.github.com/repos/9001/copyparty/tarball/refs/tags/v1.19.7", 95 + "hash": "sha256-0dPIr7Cpy8bUhjNS125qN0MX2jIUTEsaTRo5nPT8oZY=" 96 + }, 97 + "home-manager": { 98 + "type": "Git", 99 + "repository": { 100 + "type": "GitHub", 101 + "owner": "nix-community", 102 + "repo": "home-manager" 103 + }, 104 + "branch": "release-25.05", 105 + "submodules": false, 106 + "revision": "07fc025fe10487dd80f2ec694f1cd790e752d0e8", 107 + "url": "https://github.com/nix-community/home-manager/archive/07fc025fe10487dd80f2ec694f1cd790e752d0e8.tar.gz", 108 + "hash": "sha256-Xd1vOeY9ccDf5VtVK12yM0FS6qqvfUop8UQlxEB+gTQ=" 109 + }, 110 + "home-manager-unstable": { 111 + "type": "Git", 112 + "repository": { 113 + "type": "GitHub", 114 + "owner": "nix-community", 115 + "repo": "home-manager" 116 + }, 117 + "branch": "master", 118 + "submodules": false, 119 + "revision": "f56bf065f9abedc7bc15e1f2454aa5c8edabaacf", 120 + "url": "https://github.com/nix-community/home-manager/archive/f56bf065f9abedc7bc15e1f2454aa5c8edabaacf.tar.gz", 121 + "hash": "sha256-a+NMGl5tcvm+hyfSG2DlVPa8nZLpsumuRj1FfcKb2mQ=" 122 + }, 123 + "impermanence": { 124 + "type": "Git", 125 + "repository": { 126 + "type": "GitHub", 127 + "owner": "nix-community", 128 + "repo": "impermanence" 129 + }, 130 + "branch": "master", 131 + "submodules": false, 132 + "revision": "4b3e914cdf97a5b536a889e939fb2fd2b043a170", 133 + "url": "https://github.com/nix-community/impermanence/archive/4b3e914cdf97a5b536a889e939fb2fd2b043a170.tar.gz", 134 + "hash": "sha256-LJggUHbpyeDvNagTUrdhe/pRVp4pnS6wVKALS782gRI=" 135 + }, 136 + "lix": { 137 + "type": "Git", 138 + "repository": { 139 + "type": "Forgejo", 140 + "server": "https://git.lix.systems/", 141 + "owner": "lix-project", 142 + "repo": "lix" 143 + }, 144 + "branch": "main", 145 + "submodules": false, 146 + "revision": "f4bdddf0fdaabc68546cf561c5343b83d95d2466", 147 + "url": "https://git.lix.systems/lix-project/lix/archive/f4bdddf0fdaabc68546cf561c5343b83d95d2466.tar.gz", 148 + "hash": "sha256-EVJDo/KjdGtvJKelVPoL92TsPNrqnOJUnaLTIqP+F0o=" 149 + }, 150 + "lix-module": { 151 + "type": "Git", 152 + "repository": { 153 + "type": "Forgejo", 154 + "server": "https://git.lix.systems/", 155 + "owner": "lix-project", 156 + "repo": "nixos-module" 157 + }, 158 + "branch": "main", 159 + "submodules": false, 160 + "revision": "3f09a5eb772e02d98bb8878ab687d5b721f00d16", 161 + "url": "https://git.lix.systems/lix-project/nixos-module/archive/3f09a5eb772e02d98bb8878ab687d5b721f00d16.tar.gz", 162 + "hash": "sha256-IgD1JR7scSEwlK/YAbmrcTWpAYT30LPldCUHdzXkaMs=" 163 + }, 164 + "lua-multipart": { 165 + "type": "GitRelease", 166 + "repository": { 167 + "type": "GitHub", 168 + "owner": "Kong", 169 + "repo": "lua-multipart" 170 + }, 171 + "pre_releases": true, 172 + "version_upper_bound": null, 173 + "release_prefix": null, 174 + "submodules": false, 175 + "version": "0.5.11-1", 176 + "revision": "423092b5d6d03db33bd70773077e8c185e982846", 177 + "url": "https://api.github.com/repos/Kong/lua-multipart/tarball/refs/tags/0.5.11-1", 178 + "hash": "sha256-CWQf76/SQEHYX0Xv1UudA4RJtZsMpLY+IU8vjlqnsQY=" 179 + }, 180 + "nilla": { 181 + "type": "GitRelease", 182 + "repository": { 183 + "type": "GitHub", 184 + "owner": "nilla-nix", 185 + "repo": "nilla" 186 + }, 187 + "pre_releases": true, 188 + "version_upper_bound": null, 189 + "release_prefix": null, 190 + "submodules": false, 191 + "version": "v0.0.0-alpha.14", 192 + "revision": "2e98ae315a592ad6b6de44670514c048dcc88dc7", 193 + "url": "https://api.github.com/repos/nilla-nix/nilla/tarball/refs/tags/v0.0.0-alpha.14", 194 + "hash": "sha256-15lwhWcMonJH6UholMMHDc+p2BoSpGA4AYGrsXQA9Do=" 195 + }, 196 + "nilla-cli": { 197 + "type": "Git", 198 + "repository": { 199 + "type": "GitHub", 200 + "owner": "nilla-nix", 201 + "repo": "cli" 202 + }, 203 + "branch": "main", 204 + "submodules": false, 205 + "revision": "6c6c42eaae3d095de6d1b47396c8b74ea57cb442", 206 + "url": "https://github.com/nilla-nix/cli/archive/6c6c42eaae3d095de6d1b47396c8b74ea57cb442.tar.gz", 207 + "hash": "sha256-0+d6LZfofBG+4OxnZcFaNg2ycgj1zcOJQUcPL1TEaSc=" 208 + }, 209 + "nilla-home": { 210 + "type": "GitRelease", 211 + "repository": { 212 + "type": "GitHub", 213 + "owner": "nilla-nix", 214 + "repo": "home" 215 + }, 216 + "pre_releases": true, 217 + "version_upper_bound": null, 218 + "release_prefix": null, 219 + "submodules": false, 220 + "version": "v0.1.1-alpha", 221 + "revision": "8d8d783cd3ebe38246f66c027a312e5ec0914c58", 222 + "url": "https://api.github.com/repos/nilla-nix/home/tarball/refs/tags/v0.1.1-alpha", 223 + "hash": "sha256-34qP2aqJgvJ6rQo5vi9o65kxrxbp2dFi8S7z3B+P74g=" 224 + }, 225 + "nilla-nixos": { 226 + "type": "Git", 227 + "repository": { 228 + "type": "GitHub", 229 + "owner": "nilla-nix", 230 + "repo": "nixos" 231 + }, 232 + "branch": "main", 233 + "submodules": false, 234 + "revision": "52c623ae89fe77de669a981c7e92b1504cd99eac", 235 + "url": "https://github.com/nilla-nix/nixos/archive/52c623ae89fe77de669a981c7e92b1504cd99eac.tar.gz", 236 + "hash": "sha256-7tadYU5GzOUAxo8XLC18+dk0Rj+QSORUO5cFdpqfSy4=" 237 + }, 238 + "niri": { 239 + "type": "Git", 240 + "repository": { 241 + "type": "GitHub", 242 + "owner": "sodiboo", 243 + "repo": "niri-flake" 244 + }, 245 + "branch": "main", 246 + "submodules": false, 247 + "revision": "efa08fc58d7da5be64cfebc52b7dc44bf8d19ba9", 248 + "url": "https://github.com/sodiboo/niri-flake/archive/efa08fc58d7da5be64cfebc52b7dc44bf8d19ba9.tar.gz", 249 + "hash": "sha256-I3ppQKxd2oxQfwMCW04TSWnIwp5an5kTMY+tx0W8jaA=" 250 + }, 251 + "nix-index-database": { 252 + "type": "Git", 253 + "repository": { 254 + "type": "GitHub", 255 + "owner": "nix-community", 256 + "repo": "nix-index-database" 257 + }, 258 + "branch": "main", 259 + "submodules": false, 260 + "revision": "3fe768e1f058961095b4a0d7a2ba15dc9736bdc6", 261 + "url": "https://github.com/nix-community/nix-index-database/archive/3fe768e1f058961095b4a0d7a2ba15dc9736bdc6.tar.gz", 262 + "hash": "sha256-/glV6VAq8Va3ghIbmhET3S1dzkbZqicsk5h+FtvwiPE=" 263 + }, 264 + "nixos-unstable": { 265 + "type": "Channel", 266 + "name": "nixos-unstable", 267 + "url": "https://releases.nixos.org/nixos/unstable/nixos-25.11pre855242.d0fc30899600/nixexprs.tar.xz", 268 + "hash": "sha256-nDWEPoSmjN0P/NG9XntBijws0vwxnbeq+pKBLJGC0sA=" 269 + }, 270 + "nixpkgs": { 271 + "type": "Channel", 272 + "name": "nixos-25.05", 273 + "url": "https://releases.nixos.org/nixos/25.05/nixos-25.05.809451.fe83bbdde2cc/nixexprs.tar.xz", 274 + "hash": "sha256-Bp4oYE+R51hn04h20WLUkHbWZgRj0IH3AS3QUg0r74c=" 275 + }, 276 + "npins": { 277 + "type": "Git", 278 + "repository": { 279 + "type": "GitHub", 280 + "owner": "andir", 281 + "repo": "npins" 282 + }, 283 + "branch": "master", 284 + "submodules": false, 285 + "revision": "e4683671e145c652c371b6b8ad9b0d757c88853c", 286 + "url": "https://github.com/andir/npins/archive/e4683671e145c652c371b6b8ad9b0d757c88853c.tar.gz", 287 + "hash": "sha256-Nu86s1xok+1EFM0J9e55hrYPgfoutEZUDBpeXReCOaY=" 288 + }, 289 + "scriptfs": { 290 + "type": "Git", 291 + "repository": { 292 + "type": "GitHub", 293 + "owner": "eewanco", 294 + "repo": "scriptfs" 295 + }, 296 + "branch": "master", 297 + "submodules": false, 298 + "revision": "82d7e4865c9a8e40bc717c8745d052420365069c", 299 + "url": "https://github.com/eewanco/scriptfs/archive/82d7e4865c9a8e40bc717c8745d052420365069c.tar.gz", 300 + "hash": "sha256-WLuOt8zemooHDzK+zWJNBicKu8bg0wXyAGvZIH/po6o=" 301 + }, 302 + "treefmt-nix": { 303 + "type": "Git", 304 + "repository": { 305 + "type": "GitHub", 306 + "owner": "numtide", 307 + "repo": "treefmt-nix" 308 + }, 309 + "branch": "main", 310 + "submodules": false, 311 + "revision": "1aabc6c05ccbcbf4a635fb7a90400e44282f61c4", 312 + "url": "https://github.com/numtide/treefmt-nix/archive/1aabc6c05ccbcbf4a635fb7a90400e44282f61c4.tar.gz", 313 + "hash": "sha256-F1oFfV51AE259I85av+MAia221XwMHCOtZCMcZLK2Jk=" 314 + }, 315 + "walker": { 316 + "type": "GitRelease", 317 + "repository": { 318 + "type": "GitHub", 319 + "owner": "abenz1267", 320 + "repo": "walker" 321 + }, 322 + "pre_releases": false, 323 + "version_upper_bound": null, 324 + "release_prefix": null, 325 + "submodules": false, 326 + "version": "v0.13.26", 327 + "revision": "91fb283fbdc7ef36b1fb971795472322558dcfdb", 328 + "url": "https://api.github.com/repos/abenz1267/walker/tarball/refs/tags/v0.13.26", 329 + "hash": "sha256-LslpfHXj31Lvq+26ZDzCTaGBbxmp7yXlgKT+uwUEEts=" 330 + } 331 + }, 332 + "version": 6 333 + }
+3
packetmix/npins/sources.json.license
··· 1 + SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + 3 + SPDX-License-Identifier: CC0-1.0
+24
packetmix/packages/OpenLinkHub/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { config, ... }: 5 + { 6 + config.packages.openlinkhub = { 7 + systems = [ "x86_64-linux" ]; 8 + package = 9 + { openlinkhub, ... }: 10 + openlinkhub.overrideAttrs (prev: { 11 + postInstall = '' 12 + mkdir -p $out/var/lib/OpenLinkHub 13 + cp -r ${prev.src}/{static,web} $out/var/lib/OpenLinkHub 14 + mkdir $out/var/lib/OpenLinkHub/database 15 + cp ${prev.src}/database/rgb.json $out/var/lib/OpenLinkHub/database 16 + echo "{}" >> $out/var/lib/OpenLinkHub/database/scheduler.json 17 + cp -r ${prev.src}/database/keyboard $out/var/lib/OpenLinkHub/database 18 + 19 + mkdir -p $out/lib/udev/rules.d 20 + cp ${prev.src}/99-openlinkhub.rules $out/lib/udev/rules.d 21 + ''; 22 + }); 23 + }; 24 + }
+46
packetmix/packages/beancount-autobean/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, ... }: 6 + { 7 + config.packages.beancount-autobean = { 8 + systems = [ "x86_64-linux" ]; 9 + package = 10 + { 11 + lib, 12 + python3, 13 + }: 14 + let 15 + pname = "autobean"; 16 + version = "0.2.2"; 17 + in 18 + python3.pkgs.buildPythonApplication { 19 + inherit pname version; 20 + 21 + src = config.inputs.beancount-autobean.src; 22 + 23 + format = "pyproject"; 24 + 25 + propagatedBuildInputs = [ 26 + python3.pkgs.beancount 27 + python3.pkgs.python-dateutil 28 + python3.pkgs.pyyaml 29 + python3.pkgs.requests 30 + ]; 31 + 32 + buildInputs = [ 33 + python3.pkgs.setuptools 34 + python3.pkgs.pdm-pep517 35 + ]; 36 + 37 + meta = { 38 + homepage = "https://github.com/SEIAROTg/autobean"; 39 + description = "A collection of plugins and scripts that help automating bookkeeping with beancount"; 40 + license = lib.licenses.gpl2; 41 + maintainers = [ lib.maintainers.minion3665 ]; 42 + }; 43 + }; 44 + 45 + }; 46 + }
+46
packetmix/packages/beancount-beancount_plugin_utils/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, ... }: 6 + { 7 + config.packages.beancount-beancount_plugin_utils = { 8 + systems = [ "x86_64-linux" ]; 9 + package = 10 + { 11 + lib, 12 + python3, 13 + }: 14 + let 15 + pname = "beancount_plugin_utils"; 16 + version = "0.0.4"; 17 + in 18 + python3.pkgs.buildPythonApplication { 19 + inherit pname version; 20 + 21 + src = config.inputs.beancount-beancount_plugin_utils.src; 22 + 23 + format = "pyproject"; 24 + 25 + propagatedBuildInputs = [ 26 + python3.pkgs.beancount 27 + ]; 28 + 29 + buildInputs = [ 30 + python3.pkgs.setuptools 31 + python3.pkgs.pdm-pep517 32 + ]; 33 + 34 + meta = { 35 + homepage = "https://github.com/Akuukis/beancount_plugin_utils"; 36 + description = '' 37 + Utils for beancount plugin writers - BeancountError, mark, metaset, etc. 38 + 39 + Not ready for public use, but used by various Akuukis plugins. 40 + Appears unmaintained''; 41 + license = lib.licenses.agpl3Only; 42 + maintainers = [ lib.maintainers.minion3665 ]; 43 + }; 44 + }; 45 + }; 46 + }
+44
packetmix/packages/beancount-beancount_share/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, ... }: 6 + { 7 + config.packages.beancount-beancount_share = { 8 + systems = [ "x86_64-linux" ]; 9 + package = 10 + { 11 + system, 12 + lib, 13 + python3, 14 + }: 15 + let 16 + pname = "beancount_share"; 17 + version = "0.1.10"; 18 + in 19 + python3.pkgs.buildPythonApplication { 20 + inherit pname version; 21 + 22 + src = config.inputs.beancount-beancount_share.src; 23 + 24 + format = "pyproject"; 25 + 26 + propagatedBuildInputs = [ 27 + python3.pkgs.beancount 28 + config.packages.beancount-beancount_plugin_utils.result.${system} 29 + ]; 30 + 31 + buildInputs = [ 32 + python3.pkgs.setuptools 33 + python3.pkgs.pdm-pep517 34 + ]; 35 + 36 + meta = { 37 + homepage = "https://github.com/Akuukis/beancount_share"; 38 + description = "A beancount plugin to share expenses with external partners within one ledger."; 39 + license = lib.licenses.agpl3Only; 40 + maintainers = [ lib.maintainers.minion3665 ]; 41 + }; 42 + }; 43 + }; 44 + }
+45
packetmix/packages/beancount-smart_importer/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, ... }: 6 + { 7 + config.packages.beancount-smart_importer = { 8 + systems = [ "x86_64-linux" ]; 9 + package = 10 + { 11 + lib, 12 + python3, 13 + }: 14 + let 15 + pname = "smart_importer"; 16 + version = "0.5"; 17 + in 18 + python3.pkgs.buildPythonApplication { 19 + inherit pname version; 20 + 21 + src = config.inputs.beancount-smart_importer.result; 22 + 23 + format = "pyproject"; 24 + 25 + propagatedBuildInputs = [ 26 + python3.pkgs.beancount 27 + python3.pkgs.scikit-learn 28 + python3.pkgs.numpy 29 + python3.pkgs.beangulp 30 + ]; 31 + 32 + buildInputs = [ 33 + python3.pkgs.setuptools 34 + python3.pkgs.setuptools_scm 35 + ]; 36 + 37 + meta = { 38 + homepage = "https://github.com/beancount/smart_importer"; 39 + description = "Augment Beancount importers with machine learning functionality"; 40 + license = lib.licenses.mit; 41 + maintainers = [ lib.maintainers.minion3665 ]; 42 + }; 43 + }; 44 + }; 45 + }
+54
packetmix/packages/collabora-gtimelog/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, ... }: 6 + { 7 + config.packages.collabora-gtimelog = { 8 + systems = [ "x86_64-linux" ]; 9 + 10 + package = 11 + { 12 + atk, 13 + gdk-pixbuf, 14 + glib, 15 + glib-networking, 16 + gobject-introspection, 17 + gtimelog, 18 + gtk3, 19 + harfbuzz, 20 + lib, 21 + libsecret, 22 + libsoup_2_4, 23 + pango, 24 + }: 25 + (gtimelog.overrideAttrs (oldAttrs: { 26 + src = config.inputs.collabora-gtimelog.src; 27 + makeWrapperArgs = [ 28 + "--set GIO_MODULE_DIR ${ 29 + lib.makeSearchPathOutput "out" "lib/gio/modules" ([ 30 + glib-networking 31 + ]) 32 + }" 33 + "--set GI_TYPELIB_PATH ${ 34 + lib.makeSearchPathOutput "out" "lib/girepository-1.0" [ 35 + atk 36 + gdk-pixbuf 37 + glib 38 + gtk3 39 + harfbuzz 40 + libsecret 41 + libsoup_2_4 42 + pango 43 + ] 44 + }" 45 + ]; 46 + postInstall = '' 47 + install -Dm644 gtimelog.desktop $out/share/applications/gtimelog.desktop 48 + install -Dm644 src/gtimelog/gtimelog.png $out/share/icons/hicolor/48x48/apps/gtimelog.png 49 + ''; 50 + buildInputs = oldAttrs.buildInputs ++ [ glib-networking ]; 51 + nativeBuildInputs = oldAttrs.nativeBuildInputs ++ [ gobject-introspection ]; 52 + })); 53 + }; 54 + }
+18
packetmix/packages/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + includes = [ 7 + ./beancount-autobean 8 + ./beancount-beancount_plugin_utils 9 + ./beancount-beancount_share 10 + ./beancount-smart_importer 11 + ./collabora-gtimelog 12 + ./jujutsu 13 + ./lua-multipart 14 + ./OpenLinkHub 15 + ./scriptfs 16 + ./treefmt 17 + ]; 18 + }
+732
packetmix/packages/jujutsu/7245-jj-gerrit-upload.patch
··· 1 + From a85628df2f2bc1b46374de546052f624b09f172b Mon Sep 17 00:00:00 2001 2 + From: Austin Seipp <aseipp@pobox.com> 3 + Date: Thu, 18 Jan 2024 00:35:09 -0600 4 + Subject: [PATCH] cli: basic `jj gerrit upload` implementation 5 + 6 + This implements the most basic workflow for submitting changes to Gerrit, 7 + through a verb called 'upload'. This verb is intended to be distinct from the word 8 + 'submit', which for Gerrit means 'merge a change into the repository.' 9 + 10 + Given a list of revsets (specified by multiple `-r` options), this will parse 11 + the footers of every commit, collect them, insert a `Change-Id` (if one doesn't 12 + already exist), and then push them into the given remote. 13 + 14 + Because the argument is a revset, you may submit entire trees of changes at 15 + once, including multiple trees of independent changes, e.g. 16 + 17 + jj gerrit upload -r foo:: -r baz:: 18 + 19 + There are many other improvements that can be applied on top of this, including 20 + a ton of consistency and "does this make sense?" checks. However, it is flexible 21 + and a good starting point, and you can in fact both submit and cycle reviews 22 + with this interface. 23 + 24 + Signed-off-by: Austin Seipp <aseipp@pobox.com> 25 + --- 26 + CHANGELOG.md | 2 + 27 + cli/src/commands/gerrit/mod.rs | 57 +++++ 28 + cli/src/commands/gerrit/upload.rs | 384 +++++++++++++++++++++++++++++++ 29 + cli/src/commands/mod.rs | 7 + 30 + cli/src/config-schema.json | 14 ++ 31 + cli/tests/cli-reference@.md.snap | 38 +++ 32 + cli/tests/runner.rs | 1 + 33 + cli/tests/test_gerrit_upload.rs | 89 +++++++ 34 + 8 files changed, 592 insertions(+) 35 + create mode 100644 cli/src/commands/gerrit/mod.rs 36 + create mode 100644 cli/src/commands/gerrit/upload.rs 37 + create mode 100644 cli/tests/test_gerrit_upload.rs 38 + 39 + diff --git a/CHANGELOG.md b/CHANGELOG.md 40 + index 267b5ed303..9bc1029fcf 100644 41 + --- a/CHANGELOG.md 42 + +++ b/CHANGELOG.md 43 + @@ -107,6 +107,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 44 + * The new command `jj redo` can progressively redo operations that were 45 + previously undone by multiple calls to `jj undo`. 46 + 47 + +* Gerrit support implemented with the new command `jj gerrit upload` 48 + + 49 + ### Fixed bugs 50 + 51 + * `jj git clone` now correctly fetches all tags, unless `--fetch-tags` is 52 + diff --git a/cli/src/commands/gerrit/mod.rs b/cli/src/commands/gerrit/mod.rs 53 + new file mode 100644 54 + index 0000000000..60abdb6702 55 + --- /dev/null 56 + +++ b/cli/src/commands/gerrit/mod.rs 57 + @@ -0,0 +1,57 @@ 58 + +// Copyright 2024 The Jujutsu Authors 59 + +// 60 + +// Licensed under the Apache License, Version 2.0 (the "License"); 61 + +// you may not use this file except in compliance with the License. 62 + +// You may obtain a copy of the License at 63 + +// 64 + +// https://www.apache.org/licenses/LICENSE-2.0 65 + +// 66 + +// Unless required by applicable law or agreed to in writing, software 67 + +// distributed under the License is distributed on an "AS IS" BASIS, 68 + +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 69 + +// See the License for the specific language governing permissions and 70 + +// limitations under the License. 71 + + 72 + +use std::fmt::Debug; 73 + + 74 + +use clap::Subcommand; 75 + + 76 + +use crate::cli_util::CommandHelper; 77 + +use crate::command_error::CommandError; 78 + +use crate::commands::gerrit; 79 + +use crate::ui::Ui; 80 + + 81 + +/// Interact with Gerrit Code Review. 82 + +#[derive(Subcommand, Clone, Debug)] 83 + +pub enum GerritCommand { 84 + + /// Upload changes to Gerrit for code review, or update existing changes. 85 + + /// 86 + + /// Uploading in a set of revisions to Gerrit creates a single "change" for 87 + + /// each revision included in the revset. This change is then available for 88 + + /// review on your Gerrit instance. 89 + + /// 90 + + /// This command modifies each commit in the revset to include a `Change-Id` 91 + + /// footer in its commit message if one does not already exist. Note that 92 + + /// this ID is NOT compatible with jj IDs, and is Gerrit-specific. 93 + + /// 94 + + /// If a change already exists for a given revision (i.e. it contains the 95 + + /// same `Change-Id`), this command will update the contents of the existing 96 + + /// change to match. 97 + + /// 98 + + /// Note: this command takes 1-or-more revsets arguments, each of which can 99 + + /// resolve to multiple revisions; so you may post trees or ranges of 100 + + /// commits to Gerrit for review all at once. 101 + + Upload(gerrit::upload::UploadArgs), 102 + +} 103 + + 104 + +pub fn cmd_gerrit( 105 + + ui: &mut Ui, 106 + + command: &CommandHelper, 107 + + subcommand: &GerritCommand, 108 + +) -> Result<(), CommandError> { 109 + + match subcommand { 110 + + GerritCommand::Upload(review) => gerrit::upload::cmd_upload(ui, command, review), 111 + + } 112 + +} 113 + + 114 + +mod upload; 115 + diff --git a/cli/src/commands/gerrit/upload.rs b/cli/src/commands/gerrit/upload.rs 116 + new file mode 100644 117 + index 0000000000..88c3ca5e97 118 + --- /dev/null 119 + +++ b/cli/src/commands/gerrit/upload.rs 120 + @@ -0,0 +1,384 @@ 121 + +// Copyright 2024 The Jujutsu Authors 122 + +// 123 + +// Licensed under the Apache License, Version 2.0 (the "License"); 124 + +// you may not use this file except in compliance with the License. 125 + +// You may obtain a copy of the License at 126 + +// 127 + +// https://www.apache.org/licenses/LICENSE-2.0 128 + +// 129 + +// Unless required by applicable law or agreed to in writing, software 130 + +// distributed under the License is distributed on an "AS IS" BASIS, 131 + +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 132 + +// See the License for the specific language governing permissions and 133 + +// limitations under the License. 134 + + 135 + +use std::fmt::Debug; 136 + +use std::io::Write as _; 137 + +use std::rc::Rc; 138 + +use std::sync::Arc; 139 + + 140 + +use bstr::BStr; 141 + +use indexmap::IndexMap; 142 + +use itertools::Itertools as _; 143 + +use jj_lib::backend::CommitId; 144 + +use jj_lib::commit::Commit; 145 + +use jj_lib::commit::CommitIteratorExt as _; 146 + +use jj_lib::git::GitRefUpdate; 147 + +use jj_lib::git::{self}; 148 + +use jj_lib::object_id::ObjectId as _; 149 + +use jj_lib::repo::Repo as _; 150 + +use jj_lib::revset::RevsetExpression; 151 + +use jj_lib::settings::UserSettings; 152 + +use jj_lib::store::Store; 153 + +use jj_lib::trailer::Trailer; 154 + +use jj_lib::trailer::parse_description_trailers; 155 + + 156 + +use crate::cli_util::CommandHelper; 157 + +use crate::cli_util::RevisionArg; 158 + +use crate::cli_util::short_commit_hash; 159 + +use crate::command_error::CommandError; 160 + +use crate::command_error::internal_error; 161 + +use crate::command_error::user_error; 162 + +use crate::command_error::user_error_with_hint; 163 + +use crate::command_error::user_error_with_message; 164 + +use crate::git_util::with_remote_git_callbacks; 165 + +use crate::ui::Ui; 166 + + 167 + +#[derive(clap::Args, Clone, Debug)] 168 + +pub struct UploadArgs { 169 + + /// The revset, selecting which commits are sent in to Gerrit. This can be 170 + + /// any arbitrary set of commits; they will be modified to include a 171 + + /// `Change-Id` footer if one does not already exist, and then sent off to 172 + + /// Gerrit for review. 173 + + #[arg(long, short = 'r')] 174 + + revisions: Vec<RevisionArg>, 175 + + 176 + + /// The location where your changes are intended to land. This should be 177 + + /// an upstream branch. 178 + + #[arg(long = "remote-branch", short = 'b')] 179 + + remote_branch: Option<String>, 180 + + 181 + + /// The Gerrit remote to push to. Can be configured with the `gerrit.remote` 182 + + /// repository option as well. This is typically a full SSH URL for your 183 + + /// Gerrit instance. 184 + + #[arg(long)] 185 + + remote: Option<String>, 186 + + 187 + + /// If true, do not actually add `Change-Id`s to commits, and do not push 188 + + /// the changes to Gerrit. 189 + + #[arg(long = "dry-run", short = 'n')] 190 + + dry_run: bool, 191 + +} 192 + + 193 + +/// calculate push remote. The logic is: 194 + +/// 1. If the user specifies `--remote`, use that 195 + +/// 2. If the user has 'gerrit.remote' configured, use that 196 + +/// 3. If there is a default push remote, use that 197 + +/// 4. If the user has a remote named 'gerrit', use that 198 + +/// 5. otherwise, bail out 199 + +fn calculate_push_remote( 200 + + store: &Arc<Store>, 201 + + config: &UserSettings, 202 + + remote: Option<String>, 203 + +) -> Result<String, CommandError> { 204 + + let git_repo = git::get_git_repo(store)?; // will fail if not a git repo 205 + + let remotes = git_repo.remote_names(); 206 + + 207 + + // case 1 208 + + if let Some(remote) = remote { 209 + + if remotes.contains(BStr::new(&remote)) { 210 + + return Ok(remote); 211 + + } 212 + + return Err(user_error(format!( 213 + + "The remote '{remote}' (specified via `--remote`) does not exist", 214 + + ))); 215 + + } 216 + + 217 + + // case 2 218 + + if let Ok(remote) = config.get_string("gerrit.default-remote") { 219 + + if remotes.contains(BStr::new(&remote)) { 220 + + return Ok(remote); 221 + + } 222 + + return Err(user_error(format!( 223 + + "The remote '{remote}' (configured via `gerrit.default-remote`) does not exist", 224 + + ))); 225 + + } 226 + + 227 + + // case 3 228 + + if let Some(remote) = git_repo.remote_default_name(gix::remote::Direction::Push) { 229 + + return Ok(remote.to_string()); 230 + + } 231 + + 232 + + // case 4 233 + + if remotes.iter().any(|r| **r == "gerrit") { 234 + + return Ok("gerrit".to_owned()); 235 + + } 236 + + 237 + + // case 5 238 + + Err(user_error( 239 + + "No remote specified, and no 'gerrit' remote was found", 240 + + )) 241 + +} 242 + + 243 + +/// Determine what Gerrit ref and remote to use. The logic is: 244 + +/// 245 + +/// 1. If the user specifies `--remote-branch branch`, use that 246 + +/// 2. If the user has 'gerrit.default-remote-branch' configured, use that 247 + +/// 3. Otherwise, bail out 248 + +fn calculate_push_ref( 249 + + config: &UserSettings, 250 + + remote_branch: Option<String>, 251 + +) -> Result<String, CommandError> { 252 + + // case 1 253 + + if let Some(remote_branch) = remote_branch { 254 + + return Ok(remote_branch); 255 + + } 256 + + 257 + + // case 2 258 + + if let Ok(branch) = config.get_string("gerrit.default-remote-branch") { 259 + + return Ok(branch); 260 + + } 261 + + 262 + + // case 3 263 + + Err(user_error( 264 + + "No target branch specified via --remote-branch, and no 'gerrit.default-remote-branch' \ 265 + + was found", 266 + + )) 267 + +} 268 + + 269 + +pub fn cmd_upload(ui: &mut Ui, command: &CommandHelper, upload: &UploadArgs) -> Result<(), CommandError> { 270 + + let mut workspace_command = command.workspace_helper(ui)?; 271 + + 272 + + let revisions: Vec<_> = workspace_command 273 + + .parse_union_revsets(ui, &upload.revisions)? 274 + + .evaluate_to_commits()? 275 + + .try_collect()?; 276 + + if revisions.is_empty() { 277 + + writeln!(ui.status(), "No revisions to upload.")?; 278 + + return Ok(()); 279 + + } 280 + + 281 + + if revisions 282 + + .iter() 283 + + .any(|commit| commit.id() == workspace_command.repo().store().root_commit_id()) 284 + + { 285 + + return Err(user_error("Cannot upload the virtual 'root()' commit")); 286 + + } 287 + + 288 + + workspace_command.check_rewritable(revisions.iter().ids())?; 289 + + 290 + + // If you have the changes main -> A -> B, and then run `jj gerrit upload B`, 291 + + // then that uploads both A and B. Thus, we need to ensure that A also 292 + + // has a Change-ID. 293 + + // We make an assumption here that all immutable commits already have a 294 + + // Change-ID. 295 + + let to_upload: Vec<Commit> = workspace_command 296 + + .attach_revset_evaluator( 297 + + // I'm unsure, but this *might* have significant performance 298 + + // implications. If so, we can change it to a maximum depth. 299 + + Rc::new(RevsetExpression::Difference( 300 + + // Unfortunately, DagRange{root: immutable_heads, heads: commits} 301 + + // doesn't work if you're, for example, working on top of an 302 + + // immutable commit that isn't in immutable_heads(). 303 + + Rc::new(RevsetExpression::Ancestors { 304 + + heads: RevsetExpression::commits( 305 + + revisions.iter().ids().cloned().collect::<Vec<_>>(), 306 + + ), 307 + + generation: jj_lib::revset::GENERATION_RANGE_FULL, 308 + + parents_range: jj_lib::revset::PARENTS_RANGE_FULL, 309 + + }), 310 + + workspace_command.env().immutable_expression().clone(), 311 + + )), 312 + + ) 313 + + .evaluate_to_commits()? 314 + + .try_collect()?; 315 + + 316 + + let mut tx = workspace_command.start_transaction(); 317 + + let base_repo = tx.base_repo().clone(); 318 + + let store = base_repo.store(); 319 + + 320 + + let old_heads = base_repo 321 + + .index() 322 + + .heads(&mut revisions.iter().ids()) 323 + + .map_err(internal_error)?; 324 + + 325 + + let git_settings = command.settings().git_settings()?; 326 + + let remote = calculate_push_remote(store, command.settings(), upload.remote.clone())?; 327 + + let remote_branch = calculate_push_ref(command.settings(), upload.remote_branch.clone())?; 328 + + 329 + + // immediately error and reject any discardable commits, i.e. the 330 + + // the empty wcc 331 + + for commit in &to_upload { 332 + + if commit.is_discardable(tx.repo_mut())? { 333 + + return Err(user_error_with_hint( 334 + + format!( 335 + + "Refusing to upload commit {} because it is an empty commit with no description", 336 + + short_commit_hash(commit.id()) 337 + + ), 338 + + "Perhaps you squashed then ran upload? Maybe you meant to upload the parent commit \ 339 + + instead (eg. @-)", 340 + + )); 341 + + } 342 + + } 343 + + 344 + + let mut old_to_new: IndexMap<CommitId, Commit> = IndexMap::new(); 345 + + for commit_id in to_upload.iter().map(|c| c.id()).rev() { 346 + + let original_commit = store.get_commit(commit_id).unwrap(); 347 + + let description = original_commit.description().to_owned(); 348 + + let trailers = parse_description_trailers(&description); 349 + + 350 + + let change_id_trailers: Vec<&Trailer> = trailers 351 + + .iter() 352 + + .filter(|trailer| trailer.key == "Change-Id") 353 + + .collect(); 354 + + 355 + + // There shouldn't be multiple change-ID fields. So just error out if 356 + + // there is. 357 + + if change_id_trailers.len() > 1 { 358 + + return Err(user_error(format!( 359 + + "multiple Change-Id footers in commit {}", 360 + + short_commit_hash(commit_id) 361 + + ))); 362 + + } 363 + + 364 + + // The user can choose to explicitly set their own change-ID to 365 + + // override the default change-ID based on the jj change-ID. 366 + + if let Some(trailer) = change_id_trailers.first() { 367 + + // Check the change-id format is correct. 368 + + if trailer.value.len() != 41 || !trailer.value.starts_with('I') { 369 + + // Intentionally leave the invalid change IDs as-is. 370 + + writeln!( 371 + + ui.warning_default(), 372 + + "warning: invalid Change-Id footer in commit {}", 373 + + short_commit_hash(original_commit.id()), 374 + + )?; 375 + + } 376 + + 377 + + // map the old commit to itself 378 + + old_to_new.insert(original_commit.id().clone(), original_commit.clone()); 379 + + continue; 380 + + } 381 + + 382 + + // Gerrit change id is 40 chars, jj change id is 32, so we need padding. 383 + + // To be consistent with `format_gerrit_change_id_trailer``, we pad with 384 + + // 6a6a6964 (hex of "jjid"). 385 + + let gerrit_change_id = format!("I6a6a6964{}", original_commit.change_id().hex()); 386 + + 387 + + let new_description = format!( 388 + + "{}{}Change-Id: {}\n", 389 + + description.trim(), 390 + + if trailers.is_empty() { "\n\n" } else { "\n" }, 391 + + gerrit_change_id 392 + + ); 393 + + 394 + + let new_parents = original_commit 395 + + .parents() 396 + + .map(|parent| { 397 + + let p = parent.unwrap(); 398 + + if let Some(rewritten_parent) = old_to_new.get(p.id()) { 399 + + rewritten_parent 400 + + } else { 401 + + &p 402 + + } 403 + + .id() 404 + + .clone() 405 + + }) 406 + + .collect(); 407 + + 408 + + // rewrite the set of parents to point to the commits that were 409 + + // previously rewritten in toposort order 410 + + // 411 + + // TODO FIXME (aseipp): this whole dance with toposorting, calculating 412 + + // new_parents, and then doing rewrite_commit is roughly equivalent to 413 + + // what we do in duplicate.rs as well. we should probably refactor this? 414 + + let new_commit = tx 415 + + .repo_mut() 416 + + .rewrite_commit(&original_commit) 417 + + .set_description(new_description) 418 + + .set_parents(new_parents) 419 + + // Set the timestamp back to the timestamp of the original commit. 420 + + // Otherwise, `jj gerrit upload @ && jj gerrit upload @` will upload 421 + + // two patchsets with the only difference being the timestamp. 422 + + .set_committer(original_commit.committer().clone()) 423 + + .set_author(original_commit.author().clone()) 424 + + .write()?; 425 + + 426 + + old_to_new.insert(original_commit.id().clone(), new_commit.clone()); 427 + + } 428 + + writeln!(ui.stderr())?; 429 + + 430 + + let remote_ref = format!("refs/for/{remote_branch}"); 431 + + writeln!( 432 + + ui.stderr(), 433 + + "Found {} heads to push to Gerrit (remote '{}'), target branch '{}'", 434 + + old_heads.len(), 435 + + remote, 436 + + remote_branch, 437 + + )?; 438 + + 439 + + writeln!(ui.stderr())?; 440 + + 441 + + // NOTE (aseipp): because we are pushing everything to the same remote ref, 442 + + // we have to loop and push each commit one at a time, even though 443 + + // push_updates in theory supports multiple GitRefUpdates at once, because 444 + + // we obviously can't push multiple heads to the same ref. 445 + + for head in &old_heads { 446 + + write!( 447 + + ui.stderr(), 448 + + "{}", 449 + + if upload.dry_run { 450 + + "Dry-run: Would push " 451 + + } else { 452 + + "Pushing " 453 + + } 454 + + )?; 455 + + // We have to write the old commit here, because the until we finish 456 + + // the transaction (which we don't), the new commit is labelled as 457 + + // "hidden". 458 + + tx.base_workspace_helper().write_commit_summary( 459 + + ui.stderr_formatter().as_mut(), 460 + + &store.get_commit(head).unwrap(), 461 + + )?; 462 + + writeln!(ui.stderr())?; 463 + + 464 + + if upload.dry_run { 465 + + continue; 466 + + } 467 + + 468 + + let new_commit = store 469 + + .get_commit(old_to_new.get(head).unwrap().id()) 470 + + .unwrap(); 471 + + 472 + + // how do we get better errors from the remote? 'git push' tells us 473 + + // about rejected refs AND ALSO '(nothing changed)' when there are no 474 + + // changes to push, but we don't get that here. 475 + + with_remote_git_callbacks(ui, |cb| { 476 + + git::push_updates( 477 + + tx.repo_mut(), 478 + + &git_settings, 479 + + remote.as_ref(), 480 + + &[GitRefUpdate { 481 + + qualified_name: remote_ref.clone().into(), 482 + + expected_current_target: None, 483 + + new_target: Some(new_commit.id().clone()), 484 + + }], 485 + + cb, 486 + + ) 487 + + }) 488 + + // Despite the fact that a manual git push will error out with 'no new 489 + + // changes' if you're up to date, this git backend appears to silently 490 + + // succeed - no idea why. 491 + + // It'd be nice if we could distinguish this. We should ideally succeed, 492 + + // but give the user a warning. 493 + + .map_err(|err| match err { 494 + + git::GitPushError::NoSuchRemote(_) 495 + + | git::GitPushError::RemoteName(_) 496 + + | git::GitPushError::UnexpectedBackend(_) => user_error(err), 497 + + git::GitPushError::Subprocess(_) => { 498 + + user_error_with_message("Internal git error while pushing to gerrit", err) 499 + + } 500 + + })?; 501 + + } 502 + + 503 + + Ok(()) 504 + +} 505 + diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs 506 + index cdf3c9c3ad..cb7b4ca185 100644 507 + --- a/cli/src/commands/mod.rs 508 + +++ b/cli/src/commands/mod.rs 509 + @@ -30,6 +30,8 @@ mod evolog; 510 + mod file; 511 + mod fix; 512 + #[cfg(feature = "git")] 513 + +mod gerrit; 514 + +#[cfg(feature = "git")] 515 + mod git; 516 + mod help; 517 + mod interdiff; 518 + @@ -115,6 +117,9 @@ enum Command { 519 + Fix(fix::FixArgs), 520 + #[cfg(feature = "git")] 521 + #[command(subcommand)] 522 + + Gerrit(gerrit::GerritCommand), 523 + + #[cfg(feature = "git")] 524 + + #[command(subcommand)] 525 + Git(git::GitCommand), 526 + Help(help::HelpArgs), 527 + Interdiff(interdiff::InterdiffArgs), 528 + @@ -180,6 +185,8 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co 529 + Command::File(args) => file::cmd_file(ui, command_helper, args), 530 + Command::Fix(args) => fix::cmd_fix(ui, command_helper, args), 531 + #[cfg(feature = "git")] 532 + + Command::Gerrit(sub_args) => gerrit::cmd_gerrit(ui, command_helper, sub_args), 533 + + #[cfg(feature = "git")] 534 + Command::Git(args) => git::cmd_git(ui, command_helper, args), 535 + Command::Help(args) => help::cmd_help(ui, command_helper, args), 536 + Command::Interdiff(args) => interdiff::cmd_interdiff(ui, command_helper, args), 537 + diff --git a/cli/src/config-schema.json b/cli/src/config-schema.json 538 + index 887c34e2ba..d15b334ecf 100644 539 + --- a/cli/src/config-schema.json 540 + +++ b/cli/src/config-schema.json 541 + @@ -490,6 +490,20 @@ 542 + } 543 + } 544 + }, 545 + + "gerrit": { 546 + + "type": "object", 547 + + "description": "Settings for interacting with Gerrit", 548 + + "properties": { 549 + + "default-remote": { 550 + + "type": "string", 551 + + "description": "The Gerrit remote to interact with" 552 + + }, 553 + + "default-remote-branch": { 554 + + "type": "string", 555 + + "description": "The default branch to propose changes for" 556 + + } 557 + + } 558 + + }, 559 + "merge-tools": { 560 + "type": "object", 561 + "description": "Tables of custom options to pass to the given merge tool (selected in ui.merge-editor)", 562 + diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap 563 + index a97a0ffc55..dff8bcbe37 100644 564 + --- a/cli/tests/cli-reference@.md.snap 565 + +++ b/cli/tests/cli-reference@.md.snap 566 + @@ -45,6 +45,8 @@ This document contains the help content for the `jj` command-line program. 567 + * [`jj file track`↴](#jj-file-track) 568 + * [`jj file untrack`↴](#jj-file-untrack) 569 + * [`jj fix`↴](#jj-fix) 570 + +* [`jj gerrit`↴](#jj-gerrit) 571 + +* [`jj gerrit upload`↴](#jj-gerrit-upload) 572 + * [`jj git`↴](#jj-git) 573 + * [`jj git clone`↴](#jj-git-clone) 574 + * [`jj git export`↴](#jj-git-export) 575 + @@ -139,6 +141,7 @@ To get started, see the tutorial [`jj help -k tutorial`]. 576 + * `evolog` — Show how a change has evolved over time 577 + * `file` — File operations 578 + * `fix` — Update files with formatting fixes or other changes 579 + +* `gerrit` — Interact with Gerrit Code Review 580 + * `git` — Commands for working with Git remotes and the underlying Git repo 581 + * `help` — Print this message or the help of the given subcommand(s) 582 + * `interdiff` — Compare the changes of two commits 583 + @@ -1177,6 +1180,41 @@ output of the first tool. 584 + 585 + 586 + 587 + +## `jj gerrit` 588 + + 589 + +Interact with Gerrit Code Review 590 + + 591 + +**Usage:** `jj gerrit <COMMAND>` 592 + + 593 + +###### **Subcommands:** 594 + + 595 + +* `upload` — Upload changes to Gerrit for code review, or update existing changes 596 + + 597 + + 598 + + 599 + +## `jj gerrit upload` 600 + + 601 + +Upload changes to Gerrit for code review, or update existing changes. 602 + + 603 + +Uploading in a set of revisions to Gerrit creates a single "change" for each revision included in the revset. This change is then available for review on your Gerrit instance. 604 + + 605 + +This command modifies each commit in the revset to include a `Change-Id` footer in its commit message if one does not already exist. Note that this ID is NOT compatible with jj IDs, and is Gerrit-specific. 606 + + 607 + +If a change already exists for a given revision (i.e. it contains the same `Change-Id`), this command will update the contents of the existing change to match. 608 + + 609 + +Note: this command takes 1-or-more revsets arguments, each of which can resolve to multiple revisions; so you may post trees or ranges of commits to Gerrit for review all at once. 610 + + 611 + +**Usage:** `jj gerrit upload [OPTIONS]` 612 + + 613 + +###### **Options:** 614 + + 615 + +* `-r`, `--revisions <REVISIONS>` — The revset, selecting which commits are sent in to Gerrit. This can be any arbitrary set of commits; they will be modified to include a `Change-Id` footer if one does not already exist, and then sent off to Gerrit for review 616 + +* `-b`, `--remote-branch <REMOTE_BRANCH>` — The location where your changes are intended to land. This should be an upstream branch 617 + +* `--remote <REMOTE>` — The Gerrit remote to push to. Can be configured with the `gerrit.remote` repository option as well. This is typically a full SSH URL for your Gerrit instance 618 + +* `-n`, `--dry-run` — If true, do not actually add `Change-Id`s to commits, and do not push the changes to Gerrit 619 + + 620 + + 621 + + 622 + ## `jj git` 623 + 624 + Commands for working with Git remotes and the underlying Git repo 625 + diff --git a/cli/tests/runner.rs b/cli/tests/runner.rs 626 + index 88c1ca2319..f228da5e70 100644 627 + --- a/cli/tests/runner.rs 628 + +++ b/cli/tests/runner.rs 629 + @@ -37,6 +37,7 @@ mod test_file_show_command; 630 + mod test_file_track_untrack_commands; 631 + mod test_fix_command; 632 + mod test_generate_md_cli_help; 633 + +mod test_gerrit_upload; 634 + mod test_git_clone; 635 + mod test_git_colocated; 636 + mod test_git_fetch; 637 + diff --git a/cli/tests/test_gerrit_upload.rs b/cli/tests/test_gerrit_upload.rs 638 + new file mode 100644 639 + index 0000000000..71543cedd8 640 + --- /dev/null 641 + +++ b/cli/tests/test_gerrit_upload.rs 642 + @@ -0,0 +1,89 @@ 643 + +// Copyright 2025 The Jujutsu Authors 644 + +// 645 + +// Licensed under the Apache License, Version 2.0 (the "License"); 646 + +// you may not use this file except in compliance with the License. 647 + +// You may obtain a copy of the License at 648 + +// 649 + +// https://www.apache.org/licenses/LICENSE-2.0 650 + +// 651 + +// Unless required by applicable law or agreed to in writing, software 652 + +// distributed under the License is distributed on an "AS IS" BASIS, 653 + +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 654 + +// See the License for the specific language governing permissions and 655 + +// limitations under the License. 656 + + 657 + +use crate::common::TestEnvironment; 658 + +use crate::common::create_commit; 659 + + 660 + +#[test] 661 + +fn test_gerrit_upload_dryrun() { 662 + + let test_env = TestEnvironment::default(); 663 + + test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 664 + + let work_dir = test_env.work_dir("repo"); 665 + + 666 + + create_commit(&work_dir, "a", &[]); 667 + + create_commit(&work_dir, "b", &["a"]); 668 + + create_commit(&work_dir, "c", &["a"]); 669 + + let output = work_dir.run_jj(["gerrit", "upload", "-r", "b"]); 670 + + insta::assert_snapshot!(output, @r###" 671 + + ------- stderr ------- 672 + + Error: No remote specified, and no 'gerrit' remote was found 673 + + [EOF] 674 + + [exit status: 1] 675 + + "###); 676 + + 677 + + // With remote specified but. 678 + + test_env.add_config(r#"gerrit.default-remote="origin""#); 679 + + let output = work_dir.run_jj(["gerrit", "upload", "-r", "b"]); 680 + + insta::assert_snapshot!(output, @r###" 681 + + ------- stderr ------- 682 + + Error: The remote 'origin' (configured via `gerrit.default-remote`) does not exist 683 + + [EOF] 684 + + [exit status: 1] 685 + + "###); 686 + + 687 + + let output = work_dir.run_jj(["gerrit", "upload", "-r", "b", "--remote=origin"]); 688 + + insta::assert_snapshot!(output, @r###" 689 + + ------- stderr ------- 690 + + Error: The remote 'origin' (specified via `--remote`) does not exist 691 + + [EOF] 692 + + [exit status: 1] 693 + + "###); 694 + + 695 + + let output = work_dir.run_jj([ 696 + + "git", 697 + + "remote", 698 + + "add", 699 + + "origin", 700 + + "http://example.com/repo/foo", 701 + + ]); 702 + + insta::assert_snapshot!(output, @""); 703 + + let output = work_dir.run_jj(["gerrit", "upload", "-r", "b", "--remote=origin"]); 704 + + insta::assert_snapshot!(output, @r###" 705 + + ------- stderr ------- 706 + + Error: No target branch specified via --remote-branch, and no 'gerrit.default-remote-branch' was found 707 + + [EOF] 708 + + [exit status: 1] 709 + + "###); 710 + + 711 + + test_env.add_config(r#"gerrit.default-remote-branch="main""#); 712 + + let output = work_dir.run_jj(["gerrit", "upload", "-r", "b", "--dry-run"]); 713 + + insta::assert_snapshot!(output, @r###" 714 + + ------- stderr ------- 715 + + 716 + + Found 1 heads to push to Gerrit (remote 'origin'), target branch 'main' 717 + + 718 + + Dry-run: Would push zsuskuln 123b4d91 b | b 719 + + [EOF] 720 + + "###); 721 + + 722 + + let output = work_dir.run_jj(["gerrit", "upload", "-r", "b", "--dry-run", "-b", "other"]); 723 + + insta::assert_snapshot!(output, @r###" 724 + + ------- stderr ------- 725 + + 726 + + Found 1 heads to push to Gerrit (remote 'origin'), target branch 'other' 727 + + 728 + + Dry-run: Would push zsuskuln 123b4d91 b | b 729 + + [EOF] 730 + + "###); 731 + +} 732 +
+4
packetmix/packages/jujutsu/7245-jj-gerrit-upload.patch.license
··· 1 + SPDX-FileCopyrightText: 2025 Austin Seipp <aseipp@pobox.com> 2 + SPDX-FileCopyrightText: 2025 Matt Stark <msta@google.com> 3 + 4 + SPDX-License-Identifier: Apache-2.0
+18
packetmix/packages/jujutsu/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, ... }: 6 + { 7 + config.packages.jujutsu = { 8 + systems = [ "x86_64-linux" ]; 9 + package = 10 + { 11 + system, 12 + ... 13 + }: 14 + config.inputs.nixos-unstable.result.${system}.jujutsu.overrideAttrs (prevAttrs: { 15 + patches = (prevAttrs.patches or [ ]) ++ [ ./7245-jj-gerrit-upload.patch ]; 16 + }); 17 + }; 18 + }
+30
packetmix/packages/lua-multipart/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, ... }: 6 + { 7 + config.packages.lua-multipart = { 8 + systems = [ "x86_64-linux" ]; 9 + 10 + package = 11 + { 12 + lib, 13 + luaPackages, 14 + ... 15 + }: 16 + luaPackages.buildLuarocksPackage { 17 + pname = "multipart"; 18 + version = config.inputs.lua-multipart.src.version; 19 + 20 + src = config.inputs.lua-multipart.src; 21 + 22 + meta = { 23 + homepage = "https://github.com/Kong/lua-multipart"; 24 + description = "Multipart Parser for Lua"; 25 + maintainers = [ lib.maintainers.minion3665 ]; 26 + license = lib.licenses.mit; 27 + }; 28 + }; 29 + }; 30 + }
+48
packetmix/packages/scriptfs/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, ... }: 6 + { 7 + config.packages.scriptfs = { 8 + systems = [ "x86_64-linux" ]; 9 + 10 + package = 11 + { 12 + fuse3, 13 + lib, 14 + pkg-config, 15 + stdenv, 16 + ... 17 + }: 18 + stdenv.mkDerivation { 19 + pname = "scriptfs"; 20 + version = config.inputs.scriptfs.src.revision; 21 + 22 + src = config.inputs.scriptfs.src; 23 + 24 + buildInputs = [ 25 + fuse3 26 + fuse3.dev 27 + ]; 28 + 29 + nativeBuildInputs = [ 30 + pkg-config 31 + ]; 32 + 33 + RELEASE = 1; 34 + 35 + installPhase = '' 36 + mkdir -p $out/bin 37 + cp scriptfs $out/bin 38 + ''; 39 + 40 + meta = { 41 + homepage = "https://github.com/eewanco/scriptfs"; 42 + description = "FUSE that replicates a file system but replaces scripts by the result of their execution"; 43 + maintainers = [ lib.maintainers.minion3665 ]; 44 + license = lib.licenses.gpl3Only; 45 + }; 46 + }; 47 + }; 48 + }
+46
packetmix/packages/treefmt/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { config, ... }: 6 + { 7 + config.packages.treefmt = { 8 + systems = [ "x86_64-linux" ]; 9 + 10 + package = 11 + { pkgs }: 12 + config.inputs.treefmt-nix.result.lib.mkWrapper pkgs { 13 + projectRootFile = "nilla.nix"; 14 + programs.nixfmt.enable = true; 15 + 16 + settings.formatter.qmlformat = { 17 + command = "${pkgs.bash}/bin/bash"; 18 + options = [ 19 + "-euc" 20 + "${pkgs.kdePackages.qtdeclarative}/bin/qmlformat -i $@" 21 + "--" 22 + ]; 23 + includes = [ "*.qml" ]; 24 + }; 25 + }; 26 + }; 27 + 28 + config.packages.nilla-fmt = { 29 + systems = [ "x86_64-linux" ]; 30 + 31 + package = 32 + { stdenv, system }: 33 + stdenv.mkDerivation { 34 + name = "nilla-fmt"; 35 + 36 + src = config.packages.treefmt.result.${system}; 37 + 38 + dontBuild = true; 39 + 40 + installPhase = '' 41 + mkdir -p $out/bin 42 + cp $src/bin/treefmt $out/bin/nilla-fmt 43 + ''; 44 + }; 45 + }; 46 + }
+928
packetmix/patches/helix/3958-labels-for-config-menus.patch
··· 1 + From c92e341ee42b33c4c6dcb4e8287a12c03434c164 Mon Sep 17 00:00:00 2001 2 + From: Matthew Cheely <matt_cheely@fastmail.com> 3 + Date: Fri, 23 Sep 2022 21:37:23 -0400 4 + Subject: [PATCH 01/15] Add support for labels in custom menu keymaps 5 + 6 + --- 7 + helix-term/src/config.rs | 33 +++++++++++++++++++++++++++++++++ 8 + helix-term/src/keymap.rs | 17 +++++++++++++---- 9 + 2 files changed, 46 insertions(+), 4 deletions(-) 10 + 11 + diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs 12 + index bcba8d8e1d45..91738f3ef562 100644 13 + --- a/helix-term/src/config.rs 14 + +++ b/helix-term/src/config.rs 15 + @@ -174,6 +174,39 @@ mod tests { 16 + ); 17 + } 18 + 19 + + #[test] 20 + + fn parsing_menus() { 21 + + use crate::keymap; 22 + + use crate::keymap::Keymap; 23 + + use helix_core::hashmap; 24 + + use helix_view::document::Mode; 25 + + 26 + + let sample_keymaps = r#" 27 + + [keys.normal] 28 + + f = { f = "file_picker", c = "wclose" } 29 + + b = { label = "buffer", b = "buffer_picker", n = "goto_next_buffer" } 30 + + "#; 31 + + 32 + + assert_eq!( 33 + + toml::from_str::<Config>(sample_keymaps).unwrap(), 34 + + Config { 35 + + keys: hashmap! { 36 + + Mode::Normal => Keymap::new(keymap!({ "Normal mode" 37 + + "f" => { "" 38 + + "f" => file_picker, 39 + + "c" => wclose, 40 + + }, 41 + + "b" => { "buffer" 42 + + "b" => buffer_picker, 43 + + "n" => goto_next_buffer, 44 + + }, 45 + + })), 46 + + }, 47 + + ..Default::default() 48 + + } 49 + + ); 50 + + } 51 + + 52 + #[test] 53 + fn keys_resolve_to_correct_defaults() { 54 + // From serde default 55 + diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs 56 + index 020ecaf40f0f..01abd708604e 100644 57 + --- a/helix-term/src/keymap.rs 58 + +++ b/helix-term/src/keymap.rs 59 + @@ -197,13 +197,22 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 60 + where 61 + M: serde::de::MapAccess<'de>, 62 + { 63 + + let mut name = ""; 64 + let mut mapping = HashMap::new(); 65 + let mut order = Vec::new(); 66 + - while let Some((key, value)) = map.next_entry::<KeyEvent, KeyTrie>()? { 67 + - mapping.insert(key, value); 68 + - order.push(key); 69 + + 70 + + while let Some(key) = map.next_key::<&str>()? { 71 + + match key { 72 + + "label" => name = map.next_value::<&str>()?, 73 + + _ => { 74 + + let key_event = key.parse::<KeyEvent>().map_err(serde::de::Error::custom)?; 75 + + let key_trie = map.next_value::<KeyTrie>()?; 76 + + mapping.insert(key_event, key_trie); 77 + + order.push(key_event); 78 + + } 79 + + } 80 + } 81 + - Ok(KeyTrie::Node(KeyTrieNode::new("", mapping, order))) 82 + + Ok(KeyTrie::Node(KeyTrieNode::new(name, mapping, order))) 83 + } 84 + } 85 + 86 + 87 + From be13c26c803ce887fec065ecf817306e89022f1e Mon Sep 17 00:00:00 2001 88 + From: Matthew Cheely <matt_cheely@fastmail.com> 89 + Date: Fri, 23 Sep 2022 23:03:56 -0400 90 + Subject: [PATCH 02/15] Add support for labels on typable commands 91 + 92 + --- 93 + helix-term/src/config.rs | 38 ++++++++++++++++++++++++++++++++++++++ 94 + helix-term/src/keymap.rs | 28 +++++++++++++++++++++++++--- 95 + 2 files changed, 63 insertions(+), 3 deletions(-) 96 + 97 + diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs 98 + index 91738f3ef562..92b4fb8565ce 100644 99 + --- a/helix-term/src/config.rs 100 + +++ b/helix-term/src/config.rs 101 + @@ -207,6 +207,44 @@ mod tests { 102 + ); 103 + } 104 + 105 + + #[test] 106 + + fn parsing_typable_commands() { 107 + + use crate::keymap; 108 + + use crate::keymap::MappableCommand; 109 + + use helix_view::document::Mode; 110 + + use helix_view::input::KeyEvent; 111 + + use std::str::FromStr; 112 + + 113 + + let sample_keymaps = r#" 114 + + [keys.normal] 115 + + o = { label = "Edit Config", command = ":open ~/.config" } 116 + + c = ":buffer-close" 117 + + "#; 118 + + 119 + + let config = toml::from_str::<Config>(sample_keymaps).unwrap(); 120 + + 121 + + let tree = config.keys.get(&Mode::Normal).unwrap().root(); 122 + + 123 + + if let keymap::KeyTrie::Node(node) = tree { 124 + + let open_node = node.get(&KeyEvent::from_str("o").unwrap()).unwrap(); 125 + + 126 + + if let keymap::KeyTrie::Leaf(MappableCommand::Typable { doc, .. }) = open_node { 127 + + assert_eq!(doc, "Edit Config"); 128 + + } else { 129 + + panic!("Edit Config did not parse to typable command"); 130 + + } 131 + + 132 + + let close_node = node.get(&KeyEvent::from_str("c").unwrap()).unwrap(); 133 + + if let keymap::KeyTrie::Leaf(MappableCommand::Typable { doc, .. }) = close_node { 134 + + assert_eq!(doc, ":buffer-close []"); 135 + + } else { 136 + + panic!(":buffer-close command did not parse to typable command"); 137 + + } 138 + + } else { 139 + + panic!("Config did not parse to trie"); 140 + + } 141 + + } 142 + + 143 + #[test] 144 + fn keys_resolve_to_correct_defaults() { 145 + // From serde default 146 + diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs 147 + index 01abd708604e..f157e2fee0dc 100644 148 + --- a/helix-term/src/keymap.rs 149 + +++ b/helix-term/src/keymap.rs 150 + @@ -197,13 +197,15 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 151 + where 152 + M: serde::de::MapAccess<'de>, 153 + { 154 + - let mut name = ""; 155 + + let mut label = ""; 156 + + let mut command = None; 157 + let mut mapping = HashMap::new(); 158 + let mut order = Vec::new(); 159 + 160 + while let Some(key) = map.next_key::<&str>()? { 161 + match key { 162 + - "label" => name = map.next_value::<&str>()?, 163 + + "label" => label = map.next_value::<&str>()?, 164 + + "command" => command = Some(map.next_value::<MappableCommand>()?), 165 + _ => { 166 + let key_event = key.parse::<KeyEvent>().map_err(serde::de::Error::custom)?; 167 + let key_trie = map.next_value::<KeyTrie>()?; 168 + @@ -212,7 +214,27 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 169 + } 170 + } 171 + } 172 + - Ok(KeyTrie::Node(KeyTrieNode::new(name, mapping, order))) 173 + + 174 + + match command { 175 + + None => Ok(KeyTrie::Node(KeyTrieNode::new(label, mapping, order))), 176 + + Some(cmd) => { 177 + + if label.is_empty() { 178 + + Ok(KeyTrie::Leaf(cmd)) 179 + + } else { 180 + + match cmd { 181 + + MappableCommand::Typable { name, args, .. } => { 182 + + Ok(MappableCommand::Typable { 183 + + name, 184 + + args, 185 + + doc: label.to_string(), 186 + + }) 187 + + .map(KeyTrie::Leaf) 188 + + } 189 + + MappableCommand::Static { .. } => Ok(KeyTrie::Leaf(cmd)), 190 + + } 191 + + } 192 + + } 193 + + } 194 + } 195 + } 196 + 197 + 198 + From 73d8700f601fd0e8bedd19daf1bb094e1e5c0d0b Mon Sep 17 00:00:00 2001 199 + From: Matthew Cheely <matt_cheely@fastmail.com> 200 + Date: Sat, 24 Sep 2022 21:31:15 -0400 201 + Subject: [PATCH 03/15] refactor keymap map visitor to reduce # of cases 202 + 203 + --- 204 + helix-term/src/keymap.rs | 22 +++++++++------------- 205 + 1 file changed, 9 insertions(+), 13 deletions(-) 206 + 207 + diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs 208 + index f157e2fee0dc..98521eb3de08 100644 209 + --- a/helix-term/src/keymap.rs 210 + +++ b/helix-term/src/keymap.rs 211 + @@ -218,20 +218,16 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 212 + match command { 213 + None => Ok(KeyTrie::Node(KeyTrieNode::new(label, mapping, order))), 214 + Some(cmd) => { 215 + - if label.is_empty() { 216 + - Ok(KeyTrie::Leaf(cmd)) 217 + + let status = (cmd, label.is_empty()); 218 + + if let (MappableCommand::Typable { name, args, .. }, false) = status { 219 + + Ok(MappableCommand::Typable { 220 + + name, 221 + + args, 222 + + doc: label.to_string(), 223 + + }) 224 + + .map(KeyTrie::Leaf) 225 + } else { 226 + - match cmd { 227 + - MappableCommand::Typable { name, args, .. } => { 228 + - Ok(MappableCommand::Typable { 229 + - name, 230 + - args, 231 + - doc: label.to_string(), 232 + - }) 233 + - .map(KeyTrie::Leaf) 234 + - } 235 + - MappableCommand::Static { .. } => Ok(KeyTrie::Leaf(cmd)), 236 + - } 237 + + Ok(KeyTrie::Leaf(status.0)) 238 + } 239 + } 240 + } 241 + 242 + From a1c746cdb98a3d492bd81c2d80def81d45463091 Mon Sep 17 00:00:00 2001 243 + From: Matthew Cheely <matt_cheely@fastmail.com> 244 + Date: Tue, 4 Oct 2022 19:18:22 -0400 245 + Subject: [PATCH 04/15] Simplify labelled command pattern match 246 + 247 + Co-authored-by: Michael Davis <mcarsondavis@gmail.com> 248 + --- 249 + helix-term/src/keymap.rs | 19 +++++++------------ 250 + 1 file changed, 7 insertions(+), 12 deletions(-) 251 + 252 + diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs 253 + index 98521eb3de08..cdad51a32567 100644 254 + --- a/helix-term/src/keymap.rs 255 + +++ b/helix-term/src/keymap.rs 256 + @@ -217,19 +217,14 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 257 + 258 + match command { 259 + None => Ok(KeyTrie::Node(KeyTrieNode::new(label, mapping, order))), 260 + - Some(cmd) => { 261 + - let status = (cmd, label.is_empty()); 262 + - if let (MappableCommand::Typable { name, args, .. }, false) = status { 263 + - Ok(MappableCommand::Typable { 264 + - name, 265 + - args, 266 + - doc: label.to_string(), 267 + - }) 268 + - .map(KeyTrie::Leaf) 269 + - } else { 270 + - Ok(KeyTrie::Leaf(status.0)) 271 + - } 272 + + Some(MappableCommand::Typable { name, args, .. }) if !label.is_empty() => { 273 + + Ok(KeyTrie::Leaf(MappableCommand::Typable { 274 + + name, 275 + + args, 276 + + doc: label.to_string(), 277 + + })) 278 + } 279 + + Some(command) => Ok(KeyTrie::Leaf(command)), 280 + } 281 + } 282 + } 283 + 284 + From 789af1ec9fb522a4f3ef70f19f001729a6a0dd25 Mon Sep 17 00:00:00 2001 285 + From: Matthew Cheely <matt_cheely@fastmail.com> 286 + Date: Mon, 17 Oct 2022 20:15:27 -0400 287 + Subject: [PATCH 05/15] Add some basic docs 288 + 289 + --- 290 + book/src/remapping.md | 7 +++++++ 291 + 1 file changed, 7 insertions(+) 292 + 293 + diff --git a/book/src/remapping.md b/book/src/remapping.md 294 + index e3efdf16f851..a0c8acada596 100644 295 + --- a/book/src/remapping.md 296 + +++ b/book/src/remapping.md 297 + @@ -19,6 +19,13 @@ w = "move_line_up" # Maps the 'w' key move_line_up 298 + g = { a = "code_action" } # Maps `ga` to show possible code actions 299 + "ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode 300 + 301 + +# You can create labeled sub-menus and provide friendly labels for typeable commands 302 + +[keys.normal.space.f] # Registering multiple mappings under a single entry creates a sub-menu (accesed by 'space', 'f' in this case) 303 + +label = "File" # The menu is called file and within it: 304 + +f = "file_picker" # 'f' opens the file picker 305 + +s = { label = "Save", command = ":write" } # 's' saves the current file 306 + +c = { label = "Edit Config", command = ":open ~/.config/helix/config.toml" } # 'c' opens the helix config file 307 + + 308 + [keys.insert] 309 + "A-x" = "normal_mode" # Maps Alt-X to enter normal mode 310 + j = { k = "normal_mode" } # Maps `jk` to exit insert mode 311 + 312 + From c1b77b541803a41a2aa0a96a000993eb68b03ef7 Mon Sep 17 00:00:00 2001 313 + From: Matthew Cheely <matt_cheely@fastmail.com> 314 + Date: Tue, 15 Nov 2022 18:42:36 -0500 315 + Subject: [PATCH 06/15] fix typos in menu label docs 316 + 317 + --- 318 + book/src/remapping.md | 4 ++-- 319 + 1 file changed, 2 insertions(+), 2 deletions(-) 320 + 321 + diff --git a/book/src/remapping.md b/book/src/remapping.md 322 + index a0c8acada596..4fb92109e448 100644 323 + --- a/book/src/remapping.md 324 + +++ b/book/src/remapping.md 325 + @@ -20,8 +20,8 @@ g = { a = "code_action" } # Maps `ga` to show possible code actions 326 + "ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode 327 + 328 + # You can create labeled sub-menus and provide friendly labels for typeable commands 329 + -[keys.normal.space.f] # Registering multiple mappings under a single entry creates a sub-menu (accesed by 'space', 'f' in this case) 330 + -label = "File" # The menu is called file and within it: 331 + +[keys.normal.space.f] # Registering multiple mappings under a single entry creates a sub-menu (accessed by 'space', 'f' in this case) 332 + +label = "File" # The menu is called file and within it: 333 + f = "file_picker" # 'f' opens the file picker 334 + s = { label = "Save", command = ":write" } # 's' saves the current file 335 + c = { label = "Edit Config", command = ":open ~/.config/helix/config.toml" } # 'c' opens the helix config file 336 + 337 + From fa8c2372b359674dc1874561351701b95b1b8d02 Mon Sep 17 00:00:00 2001 338 + From: Matthew Cheely <matt_cheely@fastmail.com> 339 + Date: Tue, 15 Nov 2022 21:20:39 -0500 340 + Subject: [PATCH 07/15] return errors for ambiguous and unsupported labels in 341 + menus 342 + 343 + --- 344 + helix-term/src/keymap.rs | 6 ++++++ 345 + 1 file changed, 6 insertions(+) 346 + 347 + diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs 348 + index cdad51a32567..d4eb41176de6 100644 349 + --- a/helix-term/src/keymap.rs 350 + +++ b/helix-term/src/keymap.rs 351 + @@ -217,6 +217,12 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 352 + 353 + match command { 354 + None => Ok(KeyTrie::Node(KeyTrieNode::new(label, mapping, order))), 355 + + Some(_command) if !order.is_empty() => { 356 + + Err(serde::de::Error::custom("ambiguous mapping: 'command' is only valid with 'label', but I found other keys")) 357 + + } 358 + + Some(MappableCommand::Static { .. }) if !label.is_empty() => { 359 + + Err(serde::de::Error::custom("custom labels are only available for typable commands (the ones starting with ':')")) 360 + + } 361 + Some(MappableCommand::Typable { name, args, .. }) if !label.is_empty() => { 362 + Ok(KeyTrie::Leaf(MappableCommand::Typable { 363 + name, 364 + 365 + From fb649610ebd6097789a64a5013d2cb1ef6f04de6 Mon Sep 17 00:00:00 2001 366 + From: Matthew Cheely <matt_cheely@fastmail.com> 367 + Date: Sat, 10 Jun 2023 19:31:36 -0400 368 + Subject: [PATCH 08/15] Fix runtime config parse issues after rebase on latest 369 + master 370 + 371 + --- 372 + helix-term/src/config.rs | 34 ++++++++++++++++++++-------------- 373 + helix-term/src/keymap.rs | 10 +++++----- 374 + 2 files changed, 25 insertions(+), 19 deletions(-) 375 + 376 + diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs 377 + index 92b4fb8565ce..0363311c4d5a 100644 378 + --- a/helix-term/src/config.rs 379 + +++ b/helix-term/src/config.rs 380 + @@ -187,21 +187,27 @@ mod tests { 381 + b = { label = "buffer", b = "buffer_picker", n = "goto_next_buffer" } 382 + "#; 383 + 384 + + let mut keys = keymap::default(); 385 + + merge_keys( 386 + + &mut keys, 387 + + hashmap! { 388 + + Mode::Normal => Keymap::new(keymap!({ "Normal mode" 389 + + "f" => { "" 390 + + "f" => file_picker, 391 + + "c" => wclose, 392 + + }, 393 + + "b" => { "buffer" 394 + + "b" => buffer_picker, 395 + + "n" => goto_next_buffer, 396 + + }, 397 + + })), 398 + + }, 399 + + ); 400 + + 401 + assert_eq!( 402 + - toml::from_str::<Config>(sample_keymaps).unwrap(), 403 + + Config::load_test(sample_keymaps), 404 + Config { 405 + - keys: hashmap! { 406 + - Mode::Normal => Keymap::new(keymap!({ "Normal mode" 407 + - "f" => { "" 408 + - "f" => file_picker, 409 + - "c" => wclose, 410 + - }, 411 + - "b" => { "buffer" 412 + - "b" => buffer_picker, 413 + - "n" => goto_next_buffer, 414 + - }, 415 + - })), 416 + - }, 417 + + keys, 418 + ..Default::default() 419 + } 420 + ); 421 + @@ -221,7 +227,7 @@ mod tests { 422 + c = ":buffer-close" 423 + "#; 424 + 425 + - let config = toml::from_str::<Config>(sample_keymaps).unwrap(); 426 + + let config = Config::load_test(sample_keymaps); 427 + 428 + let tree = config.keys.get(&Mode::Normal).unwrap().root(); 429 + 430 + diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs 431 + index d4eb41176de6..8b4247f03211 100644 432 + --- a/helix-term/src/keymap.rs 433 + +++ b/helix-term/src/keymap.rs 434 + @@ -197,14 +197,14 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 435 + where 436 + M: serde::de::MapAccess<'de>, 437 + { 438 + - let mut label = ""; 439 + + let mut label = String::from(""); 440 + let mut command = None; 441 + let mut mapping = HashMap::new(); 442 + let mut order = Vec::new(); 443 + 444 + - while let Some(key) = map.next_key::<&str>()? { 445 + - match key { 446 + - "label" => label = map.next_value::<&str>()?, 447 + + while let Some(key) = map.next_key::<String>()? { 448 + + match &key as &str { 449 + + "label" => label = map.next_value::<String>()?, 450 + "command" => command = Some(map.next_value::<MappableCommand>()?), 451 + _ => { 452 + let key_event = key.parse::<KeyEvent>().map_err(serde::de::Error::custom)?; 453 + @@ -216,7 +216,7 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 454 + } 455 + 456 + match command { 457 + - None => Ok(KeyTrie::Node(KeyTrieNode::new(label, mapping, order))), 458 + + None => Ok(KeyTrie::Node(KeyTrieNode::new(label.as_str(), mapping, order))), 459 + Some(_command) if !order.is_empty() => { 460 + Err(serde::de::Error::custom("ambiguous mapping: 'command' is only valid with 'label', but I found other keys")) 461 + } 462 + 463 + From e652d01e0c4b5d1a0f2d784711d2631a914994cb Mon Sep 17 00:00:00 2001 464 + From: Matthew Cheely <matt_cheely@fastmail.com> 465 + Date: Sun, 22 Oct 2023 13:16:40 +1300 466 + Subject: [PATCH 09/15] Fix build after latest rebase 467 + 468 + --- 469 + helix-term/src/keymap.rs | 4 ++-- 470 + 1 file changed, 2 insertions(+), 2 deletions(-) 471 + 472 + diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs 473 + index 8b4247f03211..f4c1b9e7e8dd 100644 474 + --- a/helix-term/src/keymap.rs 475 + +++ b/helix-term/src/keymap.rs 476 + @@ -224,13 +224,13 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 477 + Err(serde::de::Error::custom("custom labels are only available for typable commands (the ones starting with ':')")) 478 + } 479 + Some(MappableCommand::Typable { name, args, .. }) if !label.is_empty() => { 480 + - Ok(KeyTrie::Leaf(MappableCommand::Typable { 481 + + Ok(KeyTrie::MappableCommand(MappableCommand::Typable { 482 + name, 483 + args, 484 + doc: label.to_string(), 485 + })) 486 + } 487 + - Some(command) => Ok(KeyTrie::Leaf(command)), 488 + + Some(command) => Ok(KeyTrie::MappableCommand(command)), 489 + } 490 + } 491 + } 492 + 493 + From 029f7b441464aa3335c48a53d90ca73aee20e458 Mon Sep 17 00:00:00 2001 494 + From: Vulpesx <potaytochipgamer@gmail.com> 495 + Date: Thu, 6 Jun 2024 11:58:19 +1000 496 + Subject: [PATCH 10/15] fix: tests after merging master 497 + 498 + --- 499 + helix-term/src/config.rs | 15 +++++++++------ 500 + 1 file changed, 9 insertions(+), 6 deletions(-) 501 + 502 + diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs 503 + index 0363311c4d5a..1cec5c139741 100644 504 + --- a/helix-term/src/config.rs 505 + +++ b/helix-term/src/config.rs 506 + @@ -177,7 +177,6 @@ mod tests { 507 + #[test] 508 + fn parsing_menus() { 509 + use crate::keymap; 510 + - use crate::keymap::Keymap; 511 + use helix_core::hashmap; 512 + use helix_view::document::Mode; 513 + 514 + @@ -191,7 +190,7 @@ mod tests { 515 + merge_keys( 516 + &mut keys, 517 + hashmap! { 518 + - Mode::Normal => Keymap::new(keymap!({ "Normal mode" 519 + + Mode::Normal => keymap!({ "Normal mode" 520 + "f" => { "" 521 + "f" => file_picker, 522 + "c" => wclose, 523 + @@ -200,7 +199,7 @@ mod tests { 524 + "b" => buffer_picker, 525 + "n" => goto_next_buffer, 526 + }, 527 + - })), 528 + + }), 529 + }, 530 + ); 531 + 532 + @@ -229,19 +228,23 @@ mod tests { 533 + 534 + let config = Config::load_test(sample_keymaps); 535 + 536 + - let tree = config.keys.get(&Mode::Normal).unwrap().root(); 537 + + let tree = config.keys.get(&Mode::Normal).unwrap(); 538 + 539 + if let keymap::KeyTrie::Node(node) = tree { 540 + let open_node = node.get(&KeyEvent::from_str("o").unwrap()).unwrap(); 541 + 542 + - if let keymap::KeyTrie::Leaf(MappableCommand::Typable { doc, .. }) = open_node { 543 + + if let keymap::KeyTrie::MappableCommand(MappableCommand::Typable { doc, .. }) = 544 + + open_node 545 + + { 546 + assert_eq!(doc, "Edit Config"); 547 + } else { 548 + panic!("Edit Config did not parse to typable command"); 549 + } 550 + 551 + let close_node = node.get(&KeyEvent::from_str("c").unwrap()).unwrap(); 552 + - if let keymap::KeyTrie::Leaf(MappableCommand::Typable { doc, .. }) = close_node { 553 + + if let keymap::KeyTrie::MappableCommand(MappableCommand::Typable { doc, .. }) = 554 + + close_node 555 + + { 556 + assert_eq!(doc, ":buffer-close []"); 557 + } else { 558 + panic!(":buffer-close command did not parse to typable command"); 559 + 560 + From 6764c3374c5716a8952b8759317dfb9403f2edf9 Mon Sep 17 00:00:00 2001 561 + From: Vulpesx <potaytochipgamer@gmail.com> 562 + Date: Thu, 6 Jun 2024 15:17:35 +1000 563 + Subject: [PATCH 11/15] feat: labels for sequences 564 + 565 + --- 566 + helix-term/src/config.rs | 28 ++++++++++++ 567 + helix-term/src/keymap.rs | 93 +++++++++++++++++++++++++++++++--------- 568 + 2 files changed, 100 insertions(+), 21 deletions(-) 569 + 570 + diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs 571 + index 1cec5c139741..f9d6966d6ef9 100644 572 + --- a/helix-term/src/config.rs 573 + +++ b/helix-term/src/config.rs 574 + @@ -224,6 +224,8 @@ mod tests { 575 + [keys.normal] 576 + o = { label = "Edit Config", command = ":open ~/.config" } 577 + c = ":buffer-close" 578 + + h = ["vsplit", "normal_mode", "swap_view_left"] 579 + + j = {command = ["hsplit", "normal_mode", {}], label = "split down"} 580 + "#; 581 + 582 + let config = Config::load_test(sample_keymaps); 583 + @@ -249,6 +251,32 @@ mod tests { 584 + } else { 585 + panic!(":buffer-close command did not parse to typable command"); 586 + } 587 + + 588 + + let split_left = node.get(&KeyEvent::from_str("h").unwrap()).unwrap(); 589 + + if let keymap::KeyTrie::Sequence(label, cmds) = split_left { 590 + + assert_eq!(label, KeyTrie::DEFAULT_SEQUENCE_LABEL); 591 + + assert_eq!( 592 + + *cmds, 593 + + vec![ 594 + + MappableCommand::vsplit, 595 + + MappableCommand::normal_mode, 596 + + MappableCommand::swap_view_left 597 + + ] 598 + + ); 599 + + } 600 + + 601 + + let split_down = node.get(&KeyEvent::from_str("j").unwrap()).unwrap(); 602 + + if let keymap::KeyTrie::Sequence(label, cmds) = split_down { 603 + + assert_eq!(label, "split down"); 604 + + assert_eq!( 605 + + *cmds, 606 + + vec![ 607 + + MappableCommand::hsplit, 608 + + MappableCommand::normal_mode, 609 + + MappableCommand::swap_view_down 610 + + ] 611 + + ); 612 + + } 613 + } else { 614 + panic!("Config did not parse to trie"); 615 + } 616 + diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs 617 + index f4c1b9e7e8dd..77a8bc58695b 100644 618 + --- a/helix-term/src/keymap.rs 619 + +++ b/helix-term/src/keymap.rs 620 + @@ -12,6 +12,7 @@ use std::{ 621 + borrow::Cow, 622 + collections::{BTreeSet, HashMap}, 623 + ops::{Deref, DerefMut}, 624 + + str::FromStr, 625 + sync::Arc, 626 + }; 627 + 628 + @@ -83,7 +84,7 @@ impl KeyTrieNode { 629 + cmd.doc() 630 + } 631 + KeyTrie::Node(n) => &n.name, 632 + - KeyTrie::Sequence(_) => "[Multiple commands]", 633 + + KeyTrie::Sequence(..) => KeyTrie::DEFAULT_SEQUENCE_LABEL, 634 + }; 635 + match body.iter().position(|(_, d)| d == &desc) { 636 + Some(pos) => { 637 + @@ -133,10 +134,18 @@ impl DerefMut for KeyTrieNode { 638 + #[derive(Debug, Clone, PartialEq)] 639 + pub enum KeyTrie { 640 + MappableCommand(MappableCommand), 641 + - Sequence(Vec<MappableCommand>), 642 + + Sequence(String, Vec<MappableCommand>), 643 + Node(KeyTrieNode), 644 + } 645 + 646 + +impl KeyTrie { 647 + + pub const DEFAULT_SEQUENCE_LABEL: &'static str = "[Multiple commands]"; 648 + + 649 + + pub fn sequence(commands: Vec<MappableCommand>) -> Self { 650 + + Self::Sequence(Self::DEFAULT_SEQUENCE_LABEL.to_string(), commands) 651 + + } 652 + +} 653 + + 654 + impl<'de> Deserialize<'de> for KeyTrie { 655 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 656 + where 657 + @@ -190,7 +199,10 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 658 + )); 659 + } 660 + 661 + - Ok(KeyTrie::Sequence(commands)) 662 + + Ok(KeyTrie::Sequence( 663 + + KeyTrie::DEFAULT_SEQUENCE_LABEL.to_string(), 664 + + commands, 665 + + )) 666 + } 667 + 668 + fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error> 669 + @@ -205,7 +217,35 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 670 + while let Some(key) = map.next_key::<String>()? { 671 + match &key as &str { 672 + "label" => label = map.next_value::<String>()?, 673 + - "command" => command = Some(map.next_value::<MappableCommand>()?), 674 + + "command" => { 675 + + command = Some(match map.next_value::<toml::Value>()? { 676 + + toml::Value::String(s) => { 677 + + vec![MappableCommand::from_str(&s).map_err(serde::de::Error::custom)?] 678 + + } 679 + + toml::Value::Array(arr) => { 680 + + let mut vec = Vec::with_capacity(arr.len()); 681 + + for value in arr { 682 + + let toml::Value::String(s) = value else { 683 + + return Err(serde::de::Error::invalid_type( 684 + + serde::de::Unexpected::Other(value.type_str()), 685 + + &"string", 686 + + )); 687 + + }; 688 + + vec.push( 689 + + MappableCommand::from_str(&s) 690 + + .map_err(serde::de::Error::custom)?, 691 + + ); 692 + + } 693 + + vec 694 + + } 695 + + value => { 696 + + return Err(serde::de::Error::invalid_type( 697 + + serde::de::Unexpected::Other(value.type_str()), 698 + + &"string or array", 699 + + )) 700 + + } 701 + + }); 702 + + } 703 + _ => { 704 + let key_event = key.parse::<KeyEvent>().map_err(serde::de::Error::custom)?; 705 + let key_trie = map.next_value::<KeyTrie>()?; 706 + @@ -220,17 +260,28 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 707 + Some(_command) if !order.is_empty() => { 708 + Err(serde::de::Error::custom("ambiguous mapping: 'command' is only valid with 'label', but I found other keys")) 709 + } 710 + - Some(MappableCommand::Static { .. }) if !label.is_empty() => { 711 + - Err(serde::de::Error::custom("custom labels are only available for typable commands (the ones starting with ':')")) 712 + - } 713 + - Some(MappableCommand::Typable { name, args, .. }) if !label.is_empty() => { 714 + - Ok(KeyTrie::MappableCommand(MappableCommand::Typable { 715 + - name, 716 + - args, 717 + - doc: label.to_string(), 718 + - })) 719 + + Some(mut commands) if commands.len() == 1 => match commands.pop() { 720 + + None => Err(serde::de::Error::custom("UNREACHABLE!, vec is empty after checking len == 1")), 721 + + Some(MappableCommand::Static { .. }) if !label.is_empty() => { 722 + + Err(serde::de::Error::custom("custom labels are only available for typable commands (the ones starting with ':')")) 723 + + } 724 + + Some(MappableCommand::Typable { name, args, .. }) if !label.is_empty() => { 725 + + Ok(KeyTrie::MappableCommand(MappableCommand::Typable { 726 + + name, 727 + + args, 728 + + doc: label, 729 + + })) 730 + + } 731 + + Some(command) => Ok(KeyTrie::MappableCommand(command)), 732 + } 733 + - Some(command) => Ok(KeyTrie::MappableCommand(command)), 734 + + Some(commands) => { 735 + + let label = if label.is_empty() { 736 + + KeyTrie::DEFAULT_SEQUENCE_LABEL.to_string() 737 + + } else { 738 + + label 739 + + }; 740 + + Ok(KeyTrie::Sequence(label, commands)) 741 + + }, 742 + } 743 + } 744 + } 745 + @@ -254,7 +305,7 @@ impl KeyTrie { 746 + keys.pop(); 747 + } 748 + } 749 + - KeyTrie::Sequence(_) => {} 750 + + KeyTrie::Sequence(..) => {} 751 + }; 752 + } 753 + 754 + @@ -266,14 +317,14 @@ impl KeyTrie { 755 + pub fn node(&self) -> Option<&KeyTrieNode> { 756 + match *self { 757 + KeyTrie::Node(ref node) => Some(node), 758 + - KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None, 759 + + KeyTrie::MappableCommand(_) | KeyTrie::Sequence(..) => None, 760 + } 761 + } 762 + 763 + pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> { 764 + match *self { 765 + KeyTrie::Node(ref mut node) => Some(node), 766 + - KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None, 767 + + KeyTrie::MappableCommand(_) | KeyTrie::Sequence(..) => None, 768 + } 769 + } 770 + 771 + @@ -290,7 +341,7 @@ impl KeyTrie { 772 + trie = match trie { 773 + KeyTrie::Node(map) => map.get(key), 774 + // leaf encountered while keys left to process 775 + - KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None, 776 + + KeyTrie::MappableCommand(_) | KeyTrie::Sequence(..) => None, 777 + }? 778 + } 779 + Some(trie) 780 + @@ -380,7 +431,7 @@ impl Keymaps { 781 + Some(KeyTrie::MappableCommand(ref cmd)) => { 782 + return KeymapResult::Matched(cmd.clone()); 783 + } 784 + - Some(KeyTrie::Sequence(ref cmds)) => { 785 + + Some(KeyTrie::Sequence(_, ref cmds)) => { 786 + return KeymapResult::MatchedSequence(cmds.clone()); 787 + } 788 + None => return KeymapResult::NotFound, 789 + @@ -400,7 +451,7 @@ impl Keymaps { 790 + self.state.clear(); 791 + KeymapResult::Matched(cmd.clone()) 792 + } 793 + - Some(KeyTrie::Sequence(cmds)) => { 794 + + Some(KeyTrie::Sequence(_, cmds)) => { 795 + self.state.clear(); 796 + KeymapResult::MatchedSequence(cmds.clone()) 797 + } 798 + @@ -625,7 +676,7 @@ mod tests { 799 + let expectation = KeyTrie::Node(KeyTrieNode::new( 800 + "", 801 + hashmap! { 802 + - key => KeyTrie::Sequence(vec!{ 803 + + key => KeyTrie::sequence(vec!{ 804 + MappableCommand::select_all, 805 + MappableCommand::Typable { 806 + name: "pipe".to_string(), 807 + 808 + From bb8a44cec0cfcff0cd4eba3b3c4157b65bddcc86 Mon Sep 17 00:00:00 2001 809 + From: Vulpesx <potaytochipgamer@gmail.com> 810 + Date: Thu, 6 Jun 2024 15:48:56 +1000 811 + Subject: [PATCH 12/15] fix: forgor to tell helix to display sequence labels 812 + 813 + --- 814 + helix-term/src/keymap.rs | 2 +- 815 + 1 file changed, 1 insertion(+), 1 deletion(-) 816 + 817 + diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs 818 + index 77a8bc58695b..f47160e73b2f 100644 819 + --- a/helix-term/src/keymap.rs 820 + +++ b/helix-term/src/keymap.rs 821 + @@ -84,7 +84,7 @@ impl KeyTrieNode { 822 + cmd.doc() 823 + } 824 + KeyTrie::Node(n) => &n.name, 825 + - KeyTrie::Sequence(..) => KeyTrie::DEFAULT_SEQUENCE_LABEL, 826 + + KeyTrie::Sequence(l, ..) => l, 827 + }; 828 + match body.iter().position(|(_, d)| d == &desc) { 829 + Some(pos) => { 830 + 831 + From e625945398dbbae475f13a2375a0a6b427a4ff16 Mon Sep 17 00:00:00 2001 832 + From: Nylme <nylme@protonmail.com> 833 + Date: Tue, 19 Nov 2024 08:09:21 +1100 834 + Subject: [PATCH 13/15] Fixed deserializing macro labels/names from .toml 835 + keymap 836 + 837 + --- 838 + helix-term/src/keymap.rs | 9 +++++++++ 839 + 1 file changed, 9 insertions(+) 840 + 841 + diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs 842 + index f47160e73b2f..a01a05f866bb 100644 843 + --- a/helix-term/src/keymap.rs 844 + +++ b/helix-term/src/keymap.rs 845 + @@ -272,6 +272,15 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor { 846 + doc: label, 847 + })) 848 + } 849 + + 850 + + // To label/name macro commands from config 851 + + Some(MappableCommand::Macro { keys, .. }) if !label.is_empty() => { 852 + + Ok(KeyTrie::MappableCommand(MappableCommand::Macro { 853 + + keys, 854 + + name: label 855 + + })) 856 + + } 857 + + 858 + Some(command) => Ok(KeyTrie::MappableCommand(command)), 859 + } 860 + Some(commands) => { 861 + 862 + From af83072ebf66a04f8033cbbfcb7ca0a454c8c6f2 Mon Sep 17 00:00:00 2001 863 + From: Nylme <nylme@protonmail.com> 864 + Date: Tue, 19 Nov 2024 22:29:26 +1100 865 + Subject: [PATCH 14/15] parsing_typeable_commands: fixed test 866 + 867 + --- 868 + helix-term/src/config.rs | 2 +- 869 + 1 file changed, 1 insertion(+), 1 deletion(-) 870 + 871 + diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs 872 + index f9d6966d6ef9..abc6784486cb 100644 873 + --- a/helix-term/src/config.rs 874 + +++ b/helix-term/src/config.rs 875 + @@ -225,7 +225,7 @@ mod tests { 876 + o = { label = "Edit Config", command = ":open ~/.config" } 877 + c = ":buffer-close" 878 + h = ["vsplit", "normal_mode", "swap_view_left"] 879 + - j = {command = ["hsplit", "normal_mode", {}], label = "split down"} 880 + + j = {command = ["hsplit", "normal_mode", "swap_view_down"], label = "split down"} 881 + "#; 882 + 883 + let config = Config::load_test(sample_keymaps); 884 + 885 + From 8e7e1283a572379bef40e5b8deb3ecef4e7962ac Mon Sep 17 00:00:00 2001 886 + From: Nylme <nylme@protonmail.com> 887 + Date: Tue, 19 Nov 2024 22:32:20 +1100 888 + Subject: [PATCH 15/15] parsing_typeable_commands: added test for macro command 889 + labels 890 + 891 + --- 892 + helix-term/src/config.rs | 15 +++++++++++++++ 893 + 1 file changed, 15 insertions(+) 894 + 895 + diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs 896 + index abc6784486cb..4c2340e91226 100644 897 + --- a/helix-term/src/config.rs 898 + +++ b/helix-term/src/config.rs 899 + @@ -226,6 +226,7 @@ mod tests { 900 + c = ":buffer-close" 901 + h = ["vsplit", "normal_mode", "swap_view_left"] 902 + j = {command = ["hsplit", "normal_mode", "swap_view_down"], label = "split down"} 903 + + n = { label = "Delete word", command = "@wd" } 904 + "#; 905 + 906 + let config = Config::load_test(sample_keymaps); 907 + @@ -277,6 +278,20 @@ mod tests { 908 + ] 909 + ); 910 + } 911 + + 912 + + let macro_keys = node.get(&KeyEvent::from_str("n").unwrap()).unwrap(); 913 + + if let keymap::KeyTrie::MappableCommand(MappableCommand::Macro { name, keys }) = 914 + + macro_keys 915 + + { 916 + + assert_eq!(name, "Delete word"); 917 + + assert_eq!( 918 + + keys, 919 + + &vec![ 920 + + KeyEvent::from_str("w").unwrap(), 921 + + KeyEvent::from_str("d").unwrap() 922 + + ] 923 + + ); 924 + + } 925 + } else { 926 + panic!("Config did not parse to trie"); 927 + } 928 +
+5
packetmix/patches/helix/3958-labels-for-config-menus.patch.license
··· 1 + SPDX-FileCopyrightText: 2025 Matthew Cheely <matt_cheely@fastmail.com> 2 + SPDX-FileCopyrightText: 2025 Nylme <nylme@protonmail.com> 3 + SPDX-FileCopyrightText: 2025 Vulpesx <potaytochipgamer@gmail.com> 4 + 5 + SPDX-License-Identifier: MPL-2.0
+63
packetmix/patches/reuse/1191-correct-invocation-for-jujutsu-file-listing.patch
··· 1 + From 44d6d1a802e92c4837348329b32f1d0a158b5c30 Mon Sep 17 00:00:00 2001 2 + From: Jonas Fierlings <fnoegip@gmail.com> 3 + Date: Mon, 9 Jun 2025 18:28:42 +0200 4 + Subject: [PATCH] Correct invocation for jujutsu file listing 5 + 6 + The `jj files` command was deprecated in jujutsu 0.19.0, and removed in 7 + jujutsu 0.26.0 (released in february of 2025). The fix is to use the new 8 + `jj file list` command instead. 9 + --- 10 + src/reuse/vcs.py | 29 ++++++++++++++++++++++++++++- 11 + 1 files changed, 28 insertions(+), 1 deletion(-) 12 + 13 + diff --git a/src/reuse/vcs.py b/src/reuse/vcs.py 14 + index 8c1cb5c8e..63b220048 100644 15 + --- a/src/reuse/vcs.py 16 + +++ b/src/reuse/vcs.py 17 + @@ -2,6 +2,7 @@ 18 + # SPDX-FileCopyrightText: 2020 John Mulligan <jmulligan@redhat.com> 19 + # SPDX-FileCopyrightText: 2023 Markus Haug <korrat@proton.me> 20 + # SPDX-FileCopyrightText: 2024 Skyler Grey <sky@a.starrysky.fyi> 21 + +# SPDX-FileCopyrightText: 2025 Jonas Fierlings <fnoegip@gmail.com> 22 + # SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com> 23 + # 24 + # SPDX-License-Identifier: GPL-3.0-or-later 25 + @@ -261,11 +262,37 @@ def _find_all_tracked_files(self) -> set[Path]: 26 + """ 27 + Return a set of all files tracked in the current jj revision 28 + """ 29 + - command = [str(self.EXE), "files"] 30 + + version = self._version() 31 + + # TODO: Remove the version check once most distributions ship jj 0.19.0 32 + + # or higher. 33 + + if version is None or version >= (0, 19, 0): 34 + + command = [str(self.EXE), "file", "list"] 35 + + else: 36 + + command = [str(self.EXE), "files"] 37 + result = execute_command(command, _LOGGER, cwd=self.root) 38 + all_files = result.stdout.decode("utf-8").split("\n") 39 + return {Path(file_) for file_ in all_files if file_} 40 + 41 + + def _version(self) -> Optional[tuple[int, int, int]]: 42 + + """ 43 + + Returns the (major, minor, patch) version of the jujutsu executable, 44 + + or None if the version components cannot be determined. 45 + + """ 46 + + result = execute_command( 47 + + [str(self.EXE), "--version"], _LOGGER, cwd=self.root 48 + + ) 49 + + lines = result.stdout.decode("utf-8").split("\n") 50 + + # Output has the form `jj major.minor.patch[-hash]\n`. 51 + + try: 52 + + line = lines[0] 53 + + version = line.split(" ")[-1] 54 + + without_hash = version.split("-")[0] 55 + + components = without_hash.split(".") 56 + + return (int(components[0]), int(components[1]), int(components[2])) 57 + + except (IndexError, ValueError) as e: 58 + + _LOGGER.debug("unable to parse jj version: %s", e) 59 + + return None 60 + + 61 + def is_ignored(self, path: StrPath) -> bool: 62 + path = relative_from_root(path, self.root) 63 +
+3
packetmix/patches/reuse/1191-correct-invocation-for-jujutsu-file-listing.patch.license
··· 1 + SPDX-FileCopyrightText: 2025 Jonas Fierlings <fnoegip@gmail.com> 2 + 3 + SPDX-License-Identifier: GPL-3.0-or-later
+10
packetmix/systems/coded/appimage.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + programs.appimage = { 7 + enable = true; 8 + binfmt = true; 9 + }; 10 + }
+32
packetmix/systems/coded/locale.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + # Set your time zone. 7 + time.timeZone = "Etc/UTC"; 8 + 9 + # Select internationalisation properties. 10 + i18n.defaultLocale = "en_US.UTF-8"; 11 + 12 + i18n.extraLocaleSettings = { 13 + LC_ADDRESS = "en_US.UTF-8"; 14 + LC_IDENTIFICATION = "en_US.UTF-8"; 15 + LC_MEASUREMENT = "en_US.UTF-8"; 16 + LC_MONETARY = "en_US.UTF-8"; 17 + LC_NAME = "en_US.UTF-8"; 18 + LC_NUMERIC = "en_US.UTF-8"; 19 + LC_PAPER = "en_US.UTF-8"; 20 + LC_TELEPHONE = "en_US.UTF-8"; 21 + LC_TIME = "en_US.UTF-8"; 22 + }; 23 + 24 + # Configure keymap in X11 25 + services.xserver.xkb = { 26 + layout = "us"; 27 + variant = "qwerty"; 28 + }; 29 + 30 + # Configure console keymap 31 + console.keyMap = "us"; 32 + }
+8
packetmix/systems/common/boot.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + boot.loader.systemd-boot.enable = true; 7 + boot.loader.efi.canTouchEfiVariables = true; 8 + }
+16
packetmix/systems/common/fonts.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, lib, ... }: 6 + { 7 + fonts.packages = [ 8 + pkgs.cantarell-fonts 9 + pkgs.carlito 10 + pkgs.corefonts 11 + pkgs.fira-code 12 + pkgs.fira-sans 13 + ]; 14 + 15 + fonts.enableDefaultPackages = true; 16 + }
+13
packetmix/systems/common/git.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + pkgs, 7 + ... 8 + }: 9 + { 10 + environment.systemPackages = [ 11 + pkgs.git 12 + ]; 13 + }
+13
packetmix/systems/common/helix.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + pkgs, 7 + ... 8 + }: 9 + { 10 + environment.systemPackages = [ 11 + pkgs.helix 12 + ]; 13 + }
+10
packetmix/systems/common/images.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + environment.systemPackages = [ 8 + pkgs.timg # Display images in your terminal 9 + ]; 10 + }
+130
packetmix/systems/common/impermanence.nix
··· 1 + # SPDX-FileCopyrightText: 2020 Nix Community Projects 2 + # SPDX-FileCopyrightText: 2024 Clicks Codes 3 + # 4 + # SPDX-License-Identifier: MIT 5 + # postDeviceCommands based of code from https://github.com/nix-community/impermanence/tree/d5f1ed7141fa407880ff5956ded2c88a307ca940?tab=readme-ov-file#btrfs-subvolumes 6 + 7 + { 8 + project, 9 + lib, 10 + config, 11 + ... 12 + }: 13 + let 14 + cfg = config.clicks.storage.impermanence; 15 + in 16 + { 17 + imports = [ project.inputs.impermanence.result.nixosModules.impermanence ]; 18 + 19 + options.clicks.storage.impermanence = { 20 + enable = lib.mkEnableOption "Enable impermanent rootfs with btrfs subvolumes"; 21 + devices = { 22 + root = lib.mkOption { 23 + type = lib.types.str; 24 + description = "Rootfs device path"; 25 + }; 26 + persist = lib.mkOption { 27 + type = lib.types.str; 28 + description = "Persistent data device path"; 29 + }; 30 + }; 31 + volumes = { 32 + mount = lib.mkOption { 33 + type = lib.types.str; 34 + description = "Path on rootfs device to the mounting subvolume, everything on here will be deleted"; 35 + default = "@"; 36 + }; 37 + old_roots = lib.mkOption { 38 + type = lib.types.str; 39 + description = "Path on rootfs device to store old roots on"; 40 + default = "old_roots"; 41 + }; 42 + persistent_data = lib.mkOption { 43 + type = lib.types.str; 44 + description = "Path on persist device to store persistent data on"; 45 + default = "data"; 46 + }; 47 + }; 48 + delete_days = lib.mkOption { 49 + type = lib.types.int; 50 + description = "How many days to wait before deleting an old root from `cfg.volumes.old_roots`"; 51 + default = 7; 52 + }; 53 + persist = { 54 + directories = lib.mkOption { 55 + type = lib.types.listOf ( 56 + lib.types.oneOf [ 57 + lib.types.str 58 + (lib.types.attrsOf ( 59 + lib.types.oneOf [ 60 + lib.types.str 61 + (lib.types.attrsOf lib.types.str) 62 + ] 63 + )) 64 + ] 65 + ); 66 + description = "List of directories to store between boots"; 67 + default = [ ]; 68 + }; 69 + files = lib.mkOption { 70 + type = lib.types.listOf lib.types.str; 71 + description = "List of files to store between boots"; 72 + default = [ ]; 73 + }; 74 + }; 75 + }; 76 + config = lib.mkIf cfg.enable ( 77 + { 78 + boot.initrd.postDeviceCommands = lib.mkAfter '' 79 + mkdir /impermanent_fs 80 + mount ${cfg.devices.root} /impermanent_fs 81 + if [[ -e /impermanent_fs/${cfg.volumes.mount} ]]; then 82 + mkdir -p /impermanent_fs/${cfg.volumes.old_roots} 83 + timestamp=$(date --date="@$(stat -c %Y /impermanent_fs/${cfg.volumes.mount})" "+%Y-%m-%-d_%H:%M:%S") 84 + mv /impermanent_fs/${cfg.volumes.mount} "/impermanent_fs/${cfg.volumes.old_roots}/$timestamp" 85 + fi 86 + delete_subvolume_recursively() { 87 + IFS=$'\n' 88 + for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do 89 + delete_subvolume_recursively "/impermanent_fs/$i" 90 + done 91 + btrfs subvolume delete "$1" 92 + } 93 + for i in $(find /impermanent_fs/${cfg.volumes.old_roots}/ -maxdepth 1 -mtime +${builtins.toString cfg.delete_days}); do 94 + delete_subvolume_recursively "$i" 95 + done 96 + btrfs subvolume create /impermanent_fs/${cfg.volumes.mount} 97 + umount /impermanent_fs 98 + ''; 99 + fileSystems."/" = { 100 + device = cfg.devices.root; 101 + fsType = "btrfs"; 102 + options = [ "subvol=${cfg.volumes.mount}" ]; 103 + }; 104 + fileSystems."/persist" = { 105 + device = cfg.devices.persist; 106 + neededForBoot = true; 107 + fsType = "btrfs"; 108 + }; 109 + programs.fuse.userAllowOther = true; 110 + } 111 + // { 112 + environment = lib.optionalAttrs cfg.enable { 113 + persistence."/persist/${cfg.volumes.persistent_data}" = { 114 + directories = [ 115 + "/var/lib/nixos" # https://github.com/nix-community/impermanence/issues/178 116 + ] 117 + ++ cfg.persist.directories; 118 + files = [ 119 + "/etc/machine-id" 120 + "/etc/ssh/ssh_host_ed25519_key" 121 + "/etc/ssh/ssh_host_ed25519_key.pub" 122 + "/etc/ssh/ssh_host_rsa_key" 123 + "/etc/ssh/ssh_host_rsa_key.pub" 124 + ] 125 + ++ cfg.persist.files; 126 + }; 127 + }; 128 + } 129 + ); 130 + }
+31
packetmix/systems/common/inputs.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + project, 7 + pkgs, 8 + lib, 9 + ... 10 + }: 11 + { 12 + nix = { 13 + channel.enable = false; 14 + nixPath = [ "/etc/nix/inputs" ]; 15 + # Inspired by this blog post from piegamesde: https://piegames.de/dumps/pinning-nixos-with-npins-revisited/ 16 + # I've used /etc/nix as /etc/nixos would conflict with our packetmix.nix auto-upgrading... 17 + # Also, it feels like something adjacent to nix.conf so I think it fits better 18 + }; 19 + 20 + environment.etc = lib.mapAttrs' (name: value: { 21 + name = "nix/inputs/${name}"; 22 + value.source = 23 + if 24 + (lib.strings.isStringLike value.result) 25 + && (lib.strings.hasPrefix builtins.storeDir (builtins.toString value.result)) # We convert to a string here to force paths out of any attrsets/etc. 26 + then 27 + builtins.storePath value.result 28 + else 29 + builtins.storePath value.src; 30 + }) project.inputs; 31 + }
+19
packetmix/systems/common/lix.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { project, ... }: 6 + { 7 + imports = [ 8 + (import "${project.inputs.lix-module.result}/module.nix" { lix = project.inputs.lix.src; }) 9 + ]; 10 + 11 + nix.settings.experimental-features = [ "nix-command" ]; 12 + 13 + nix.gc = { 14 + automatic = true; 15 + persistent = true; 16 + options = "--delete-older-than 7d"; 17 + dates = "08:30"; 18 + }; 19 + }
+11
packetmix/systems/common/networking.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + networking.networkmanager.enable = true; 7 + 8 + clicks.storage.impermanence.persist.directories = [ 9 + "/etc/NetworkManager" 10 + ]; 11 + }
+12
packetmix/systems/common/nilla-nix.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { project, system, ... }: 6 + { 7 + environment.systemPackages = [ 8 + project.inputs.nilla-cli.result.packages.nilla-cli.result.${system} 9 + project.inputs.nilla-home.result.packages.nilla-home.result.${system} 10 + project.inputs.nilla-nixos.result.packages.nilla-nixos.result.${system} 11 + ]; 12 + }
+49
packetmix/systems/common/packetmix.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + # packetmix.nix: packetmix support configuration, including our binary cache and auto-updating 6 + { config, pkgs, ... }: 7 + { 8 + nix.settings.substituters = [ 9 + "https://freshlybakedcake.cachix.org" 10 + ]; 11 + nix.settings.trusted-public-keys = [ 12 + "freshlybakedcake.cachix.org-1:YmhsHdeKjqbaS33PPJXJllTHBupT3hliQrPcllJXkE0=" 13 + ]; 14 + 15 + system.autoUpgrade = { 16 + enable = true; 17 + operation = "boot"; # The default is "switch", but that can lead to some nasty inconsistencies - boot is cleaner 18 + flags = [ 19 + "-f" 20 + "/etc/nixos/nilla.nix" 21 + "-A" 22 + "systems.nixos.${config.networking.hostName}.result" 23 + ]; 24 + }; 25 + 26 + systemd.services.nixos-upgrade.preStart = '' 27 + ${pkgs.networkmanager}/bin/nm-online -s -q # wait until the internet is online, as esp. if we go offline we need to wait to retry... 28 + cd /etc/nixos 29 + ${pkgs.git}/bin/git fetch 30 + ${pkgs.git}/bin/git checkout origin/release 31 + ''; 32 + 33 + systemd.services.nixos-upgrade.serviceConfig = { 34 + Restart = "on-failure"; 35 + RestartSec = 5; 36 + RestartSteps = 5; 37 + RestartMaxDelaySec = 86400; 38 + }; 39 + 40 + # This value determines the NixOS release from which the default 41 + # settings for stateful data, like file locations and database versions 42 + # on your system were taken. It‘s perfectly fine and recommended to leave 43 + # this value at the release version of the first install of this system. 44 + # Before changing this value read the documentation for this option 45 + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). 46 + system.stateVersion = "25.05"; 47 + 48 + clicks.storage.impermanence.persist.directories = [ "/etc/nixos" ]; 49 + }
+7
packetmix/systems/common/smartcard.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + services.pcscd.enable = true; 7 + }
+9
packetmix/systems/common/sudo.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { 5 + security.sudo-rs = { 6 + enable = true; 7 + execWheelOnly = true; 8 + }; 9 + }
+9
packetmix/systems/common/sysctl.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + boot.kernel.sysctl = { 7 + "net.ipv4.ip_unprivileged_port_start" = 0; 8 + }; 9 + }
+10
packetmix/systems/common/tailscale.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { 5 + services.tailscale.enable = true; 6 + 7 + systemd.services.tailscaled.environment.TS_NO_LOGS_NO_SUPPORT = "true"; 8 + 9 + clicks.storage.impermanence.persist.directories = [ "/var/lib/tailscale" ]; 10 + }
+10
packetmix/systems/common/ulimit.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { ... }: 6 + { 7 + systemd.user.extraConfig = '' 8 + DefaultLimitNOFILE=65535 9 + ''; 10 + }
+79
packetmix/systems/corsair/openlinkhub.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + pkgs, 7 + config, 8 + lib, 9 + system, 10 + project, 11 + ... 12 + }: 13 + { 14 + options.services.openlinkhub.enable = lib.mkOption { 15 + type = lib.types.bool; 16 + default = true; 17 + description = "Enable OpenLinkHub as a service"; 18 + }; 19 + 20 + config = 21 + let 22 + pkg = project.packages.openlinkhub.result.${system}; 23 + in 24 + { 25 + users.groups.openlinkhub = { }; 26 + 27 + users.users.openlinkhub = { 28 + isSystemUser = true; 29 + group = config.users.groups.openlinkhub.name; 30 + extraGroups = [ config.users.groups.input.name ]; 31 + }; 32 + 33 + services.udev.packages = [ pkg ]; 34 + 35 + systemd.services.openlinkhub = 36 + let 37 + path = "/var/lib/OpenLinkHub"; 38 + in 39 + { 40 + enable = true; 41 + description = "Open source interface for iCUE LINK System Hub, Corsair AIOs and Hubs"; 42 + 43 + preStart = '' 44 + mkdir -p ${path}/database 45 + [ -f ${path}/database/rgb.json ] || cp ${pkg}/var/lib/OpenLinkHub/rgb.json ${path}/database/rgb.json 46 + [ -f ${path}/database/scheduler.json ] || cp ${pkg}/var/lib/OpenLinkHub/schduler.json ${path}/database/scheduler.json 47 + mkdir -p ${path}/database/temperatures 48 + mkdir -p ${path}/database/profiles 49 + 50 + cp -r ${pkg}/var/lib/OpenLinkHub/database/keyboard ${path}/database/keyboard 51 + 52 + [ -L ${path}/static ] || ln -s ${pkg}/var/lib/OpenLinkHub/static ${path}/static 53 + [ -L ${path}/web ] || ln -s ${pkg}/var/lib/OpenLinkHub/web ${path}/web 54 + 55 + ${pkgs.coreutils}/bin/chmod -R 744 ${path} 56 + ${pkgs.coreutils}/bin/chown -R openlinkhub:openlinkhub ${path} 57 + ''; 58 + 59 + postStop = '' 60 + ${pkgs.coreutils}/bin/rm -rf /var/lib/OpenLinkHub/web 61 + ${pkgs.coreutils}/bin/rm -rf /var/lib/OpenLinkHub/static 62 + ''; 63 + 64 + path = [ pkgs.pciutils ]; 65 + 66 + serviceConfig = { 67 + DynamicUser = true; 68 + ExecStart = "${pkg}/bin/OpenLinkHub"; 69 + ExecReload = "${pkgs.coreutils}/bin/kill -s HUP \$MAINPID"; 70 + RestartSec = 5; 71 + PermissionsStartOnly = true; 72 + StateDirectory = "OpenLinkHub"; 73 + WorkingDirectory = "/var/lib/OpenLinkHub"; 74 + }; 75 + 76 + wantedBy = [ "multi-user.target" ]; 77 + }; 78 + }; 79 + }
+134
packetmix/systems/default.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { config, lib, ... }: 7 + let 8 + nixpkgs = config.inputs.nixpkgs.result; 9 + in 10 + { 11 + config.systems.nixos."redhead" = { 12 + pkgs = nixpkgs.x86_64-linux; 13 + ingredients = [ 14 + "espanso" 15 + "freshlybakedcake" 16 + "javelin" 17 + "minion" 18 + "niri" 19 + "personal" 20 + "portable" 21 + ]; 22 + args = { 23 + system = "x86_64-linux"; 24 + project = config; 25 + }; 26 + homes = { inherit (config.homes) "minion@redhead:x86_64-linux"; }; 27 + }; 28 + config.systems.nixos."emden" = { 29 + pkgs = nixpkgs.x86_64-linux; 30 + ingredients = [ 31 + "espanso" 32 + "freshlybakedcake" 33 + "gaming" 34 + "javelin" 35 + "minion" 36 + "niri" 37 + "personal" 38 + ]; 39 + args = { 40 + system = "x86_64-linux"; 41 + project = config; 42 + }; 43 + homes = { inherit (config.homes) "minion:x86_64-linux"; }; 44 + }; 45 + config.systems.nixos."marbled" = { 46 + pkgs = nixpkgs.x86_64-linux; 47 + ingredients = [ 48 + "espanso" 49 + "freshlybakedcake" 50 + "javelin" 51 + "minion" 52 + "niri" 53 + "personal" 54 + "portable" 55 + ]; 56 + args = { 57 + system = "x86_64-linux"; 58 + project = config; 59 + }; 60 + homes = { inherit (config.homes) "maya:x86_64-linux" "minion:x86_64-linux"; }; 61 + }; 62 + config.systems.nixos."ocicat" = { 63 + pkgs = nixpkgs.x86_64-linux; 64 + ingredients = [ 65 + "coded" 66 + "espanso" 67 + "freshlybakedcake" 68 + "gaming" 69 + "niri" 70 + "personal" 71 + "portable" 72 + ]; 73 + args = { 74 + system = "x86_64-linux"; 75 + project = config; 76 + }; 77 + homes = { inherit (config.homes) "coded:x86_64-linux"; }; 78 + }; 79 + config.systems.nixos."saurosuchus" = { 80 + pkgs = nixpkgs.x86_64-linux; 81 + ingredients = [ 82 + "espanso" 83 + "freshlybakedcake" 84 + "gaming" 85 + "kde" 86 + "personal" 87 + "pinea" 88 + ]; 89 + args = { 90 + system = "x86_64-linux"; 91 + project = config; 92 + }; 93 + homes = { inherit (config.homes) "pinea:x86_64-linux"; }; 94 + }; 95 + config.systems.nixos."shorthair" = { 96 + pkgs = nixpkgs.x86_64-linux; 97 + ingredients = [ 98 + "coded" 99 + "corsair" 100 + "espanso" 101 + "freshlybakedcake" 102 + "gaming" 103 + "niri" 104 + "personal" 105 + ]; 106 + args = { 107 + system = "x86_64-linux"; 108 + project = config; 109 + }; 110 + homes = { inherit (config.homes) "coded:x86_64-linux"; }; 111 + }; 112 + config.systems.nixos."midnight" = { 113 + pkgs = nixpkgs.x86_64-linux; 114 + ingredients = [ 115 + "freshlybakedcake" 116 + "server" 117 + ]; 118 + args = { 119 + system = "x86_64-linux"; 120 + project = config; 121 + }; 122 + }; 123 + config.systems.nixos."teal" = { 124 + pkgs = nixpkgs.x86_64-linux; 125 + ingredients = [ 126 + "freshlybakedcake" 127 + "server" 128 + ]; 129 + args = { 130 + system = "x86_64-linux"; 131 + project = config; 132 + }; 133 + }; 134 + }
+59
packetmix/systems/emden/hardware-configuration.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + config, 7 + lib, 8 + pkgs, 9 + modulesPath, 10 + ... 11 + }: 12 + 13 + { 14 + imports = [ 15 + (modulesPath + "/installer/scan/not-detected.nix") 16 + ]; 17 + 18 + boot.initrd.availableKernelModules = [ 19 + "nvme" 20 + "xhci_pci" 21 + "ahci" 22 + "usb_storage" 23 + "usbhid" 24 + "sd_mod" 25 + "sr_mod" 26 + ]; 27 + boot.initrd.kernelModules = [ ]; 28 + boot.kernelModules = [ "kvm-amd" ]; 29 + boot.extraModulePackages = [ ]; 30 + 31 + fileSystems."/" = { 32 + device = "/dev/disk/by-uuid/15cdef91-cf03-4fd9-897b-c468102a3c59"; 33 + fsType = "ext4"; 34 + }; 35 + 36 + fileSystems."/boot" = { 37 + device = "/dev/disk/by-uuid/AE27-818A"; 38 + fsType = "vfat"; 39 + options = [ 40 + "fmask=0077" 41 + "dmask=0077" 42 + ]; 43 + }; 44 + 45 + swapDevices = [ 46 + { device = "/dev/disk/by-uuid/28336c37-2aef-4c77-82e9-5a681730398d"; } 47 + ]; 48 + 49 + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 50 + # (the default) this is the recommended approach. When using systemd-networkd it's 51 + # still possible to use this option, but it's recommended to use it in conjunction 52 + # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`. 53 + networking.useDHCP = lib.mkDefault true; 54 + # networking.interfaces.enp42s0.useDHCP = lib.mkDefault true; 55 + # networking.interfaces.wlo1.useDHCP = lib.mkDefault true; 56 + 57 + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 58 + hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 59 + }
+7
packetmix/systems/emden/hostname.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + networking.hostName = "emden"; 7 + }
+11
packetmix/systems/emden/steam.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + programs.steam = { 8 + enable = true; 9 + remotePlay.openFirewall = true; 10 + }; 11 + }
+13
packetmix/systems/espanso/espanso.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, config, ... }: 6 + { 7 + services.espanso = { 8 + enable = true; 9 + package = if config.services.xserver.enable then pkgs.espanso else pkgs.espanso-wayland; 10 + }; 11 + 12 + programs.espanso.capdacoverride.enable = !config.services.xserver.enable; 13 + }
+63
packetmix/systems/espanso/nixpkgs-328890--espanso-capdacoverride.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Eelco Dolstra and the Nixpkgs/NixOS contributors 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + config, 7 + lib, 8 + pkgs, 9 + ... 10 + }: 11 + 12 + let 13 + cfg = config.programs.espanso.capdacoverride; 14 + in 15 + { 16 + meta = { 17 + maintainers = with lib.maintainers; [ pitkling ]; 18 + }; 19 + 20 + options = { 21 + programs.espanso.capdacoverride = { 22 + enable = (lib.mkEnableOption "espanso-wayland overlay with DAC_OVERRIDE capability") // { 23 + description = '' 24 + Creates an espanso binary with the DAC_OVERRIDE capability (via `security.wrappers`) and overlays `pkgs.espanso-wayland` such that self-forks call the capability-enabled binary. 25 + Required for `pkgs.espanso-wayland` to work correctly if not run with root privileges. 26 + ''; 27 + }; 28 + 29 + package = lib.mkOption { 30 + type = lib.types.package // { 31 + check = package: lib.types.package.check package && (builtins.elem "wayland" package.buildFeatures); 32 + description = 33 + lib.types.package.description 34 + + " for espanso with wayland support (`package.builtFeatures` must contain `\"wayland\"`)"; 35 + }; 36 + default = pkgs._espanso-wayland-orig; 37 + defaultText = "pkgs.espanso-wayland (before applying the overlay)"; 38 + description = "The espanso-wayland package used as the base to generate the capability-enabled package."; 39 + }; 40 + }; 41 + }; 42 + 43 + config = lib.mkIf cfg.enable { 44 + nixpkgs.overlays = [ 45 + (final: prev: { 46 + _espanso-wayland-orig = prev.espanso-wayland; 47 + espanso-wayland = 48 + pkgs.callPackage ./nixpkgs-328890--espanso-capdacoverride/espanso-capdacoverride.nix 49 + { 50 + capDacOverrideWrapperDir = "${config.security.wrapperDir}"; 51 + espanso = cfg.package; 52 + }; 53 + }) 54 + ]; 55 + 56 + security.wrappers."espanso-wayland" = { 57 + source = lib.getExe pkgs.espanso-wayland; 58 + capabilities = "cap_dac_override+p"; 59 + owner = "root"; 60 + group = "root"; 61 + }; 62 + }; 63 + }
+63
packetmix/systems/espanso/nixpkgs-328890--espanso-capdacoverride/espanso-capdacoverride.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Eelco Dolstra and the Nixpkgs/NixOS contributors 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + autoPatchelfHook, 7 + capDacOverrideWrapperDir, 8 + espanso, 9 + patchelfUnstable, # have to use patchelfUnstable to support --rename-dynamic-symbols 10 + stdenv, 11 + }: 12 + let 13 + inherit (espanso) version; 14 + pname = "${espanso.pname}-capdacoverride"; 15 + 16 + wrapperLibName = "wrapper-lib.so"; 17 + 18 + # On Wayland, Espanso requires the DAC_OVERRIDE capability. One can create a wrapper binary with this 19 + # capability using the `config.security.wrappers.<name>` framework. However, this is not enough: the 20 + # capability is required by a worker process of Espanso created by forking `/proc/self/exe`, which points 21 + # to the executable **without** the DAC_OVERRIDE capability. Thus, we inject a wrapper library into Espanso 22 + # that redirects requests to `/proc/self/exe` to the binary with the proper capabilities. 23 + wrapperLib = stdenv.mkDerivation { 24 + name = "${pname}-${version}-wrapper-lib"; 25 + 26 + dontUnpack = true; 27 + 28 + postPatch = '' 29 + substitute ${./wrapper-lib.c} lib.c --subst-var-by to "${capDacOverrideWrapperDir}/espanso-wayland" 30 + ''; 31 + 32 + buildPhase = '' 33 + runHook preBuild 34 + cc -fPIC -shared lib.c -o ${wrapperLibName} 35 + runHook postBuild 36 + ''; 37 + 38 + installPhase = '' 39 + runHook preInstall 40 + install -D -t $out/lib ${wrapperLibName} 41 + runHook postInstall 42 + ''; 43 + }; 44 + in 45 + espanso.overrideAttrs (previousAttrs: { 46 + inherit pname; 47 + 48 + buildInputs = previousAttrs.buildInputs ++ [ wrapperLib ]; 49 + 50 + nativeBuildInputs = previousAttrs.nativeBuildInputs ++ [ 51 + autoPatchelfHook 52 + patchelfUnstable 53 + ]; 54 + 55 + postInstall = '' 56 + echo readlink readlink_wrapper > readlink_name_map 57 + patchelf \ 58 + --rename-dynamic-symbols readlink_name_map \ 59 + --add-needed ${wrapperLibName} \ 60 + "$out/bin/espanso" 61 + '' 62 + + previousAttrs.postInstall; 63 + })
+26
packetmix/systems/espanso/nixpkgs-328890--espanso-capdacoverride/wrapper-lib.c
··· 1 + /* 2 + * SPDX-FileCopyrightText: 2025 Eelco Dolstra and the Nixpkgs/NixOS contributors 3 + * 4 + * SPDX-License-Identifier: MIT 5 + */ 6 + 7 + #include <stdio.h> 8 + #include <string.h> 9 + #include <unistd.h> 10 + 11 + static const char from[] = "/proc/self/exe"; 12 + static const char to[] = "@to@"; 13 + 14 + ssize_t readlink_wrapper(const char *restrict path, char *restrict buf, size_t bufsize) { 15 + if (strcmp(path, from) == 0) { 16 + printf("readlink_wrapper.c: Resolving readlink call to '%s' to '%s'\n", from, to); 17 + size_t to_length = strlen(to); 18 + if (to_length > bufsize) { 19 + to_length = bufsize; 20 + } 21 + memcpy(buf, to, to_length); 22 + return to_length; 23 + } else { 24 + return readlink(path, buf, bufsize); 25 + } 26 + }
+41
packetmix/systems/freshlybakedcake+personal/copyparty.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { pkgs, ... }: 5 + let 6 + rcloneConfig = builtins.toFile "rclone-freshly.conf" '' 7 + [freshly] 8 + type = webdav 9 + url = https://files.freshly.space 10 + vendor = owncloud 11 + pacer_min_sleep = 0.01ms 12 + ''; 13 + # owncloud is used as a vendor because copyparty recommends it - it activates some features like mtime for servers that support it 14 + # think how every browser useragent has "Mozilla" in it: https://webaim.org/blog/user-agent-string-history/ 15 + in 16 + { 17 + environment.systemPackages = [ pkgs.rclone ]; 18 + 19 + systemd.mounts = [ 20 + { 21 + description = "Freshly Baked Cake mount"; 22 + after = [ "network-online.target" ]; 23 + wants = [ "network-online.target" ]; 24 + what = "freshly:"; 25 + where = "/mnt/freshly"; 26 + options = "x-systemd.automount,uid=1000,gid=100,vfs-cache-mode=writes,dir-cache-time=5s,config=${rcloneConfig},allow-other"; 27 + type = "rclone"; 28 + } 29 + ]; 30 + 31 + systemd.automounts = [ 32 + { 33 + description = "Freshly Baked Cake automount"; 34 + where = "/mnt/freshly"; 35 + wantedBy = [ "multi-user.target" ]; 36 + automountConfig = { 37 + TimeoutIdleSec = "2m"; 38 + }; 39 + } 40 + ]; 41 + }
+14
packetmix/systems/freshlybakedcake+personal/kanidm.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + services.kanidm = { 8 + enableClient = true; 9 + 10 + package = pkgs.kanidm_1_6; 11 + 12 + clientSettings.uri = "https://idm.freshly.space"; 13 + }; 14 + }
+20
packetmix/systems/freshlybakedcake+server/remoteBuilds.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { 5 + users.users.remoteBuilds = { 6 + isSystemUser = true; 7 + 8 + openssh.authorizedKeys.keys = [ 9 + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFZepe0b+YKW/tdjauBYGFwNRkD0pLEgdRTDB984OuLR redhead" 10 + ]; 11 + 12 + group = "remoteBuilds"; 13 + useDefaultShell = true; # still allow logging in or we can't use this as a remote builder account... 14 + }; 15 + users.groups.remoteBuilds = { }; 16 + 17 + nix.settings.trusted-users = [ 18 + "remoteBuilds" 19 + ]; 20 + }
+54
packetmix/systems/freshlybakedcake/users.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { 5 + users.users.coded = { 6 + isNormalUser = true; 7 + description = "Samuel Shuert"; 8 + extraGroups = [ 9 + "networkmanager" 10 + "wheel" 11 + ]; 12 + 13 + openssh.authorizedKeys.keys = [ 14 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAILrwKN4dJQ0BiLmjsA/66QHhu06+JyokWtHkLcjhWU79AAAABHNzaDo= coded@Portable5cNFC" 15 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOMSUqXuH1bQZJc9rLV0H7/UY0c2BlkzAKWkwrXFWbQ7AAAABHNzaDo= coded@ShorthairNanoResident" 16 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIHgyfY0KnxIsw8h9/cmbZ8InRFMqOmWn94teVsAHMvNkAAAABHNzaDo= coded@OcicatNanoResident" 17 + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKpBNIHk/kRhQL7Nl3Fd+UBVRoS2bTpbeerA//vwL2D4 coded@passwordKey" 18 + ]; 19 + }; 20 + 21 + users.users.minion = { 22 + isNormalUser = true; 23 + description = "Skyler Grey"; 24 + extraGroups = [ 25 + "networkmanager" 26 + "wheel" 27 + ]; 28 + 29 + openssh.authorizedKeys.keys = [ 30 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIIteIdlZv52nUDxW2SUsoJ2NZi/w9j1NZwuHanQ/o/DuAAAAHnNzaDpjb2xsYWJvcmFfeXViaWtleV9yZXNpZGVudA== collabora_yubikey_resident" 31 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIJRzQbQjXFpHKtt8lpNKmoNx57+EJ/z3wnKOn3/LjM6cAAAAFXNzaDppeXViaWtleV9yZXNpZGVudA== iyubikey_resident" 32 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOhzJ0p9bFRSURUjV05rrt5jCbxPXke7juNbEC9ZJXS/AAAAGXNzaDp0aW55X3l1YmlrZXlfcmVzaWRlbnQ= tiny_yubikey_resident" 33 + ]; 34 + }; 35 + 36 + users.users.pinea = { 37 + isNormalUser = true; 38 + description = "Pinea"; 39 + extraGroups = [ 40 + "networkmanager" 41 + "wheel" 42 + ]; 43 + 44 + openssh.authorizedKeys.keys = [ 45 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIFXa8ow7H8XpTrwYI+oSgLFfb6YNZanwv/QCKvEKiERSAAAABHNzaDo= pinea-yubikey" 46 + ]; 47 + }; 48 + 49 + nix.settings.trusted-users = [ 50 + "coded" 51 + "minion" 52 + "pinea" 53 + ]; 54 + }
+11
packetmix/systems/gaming/steam.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + programs.steam = { 8 + enable = true; 9 + remotePlay.openFirewall = true; 10 + }; 11 + }
+9
packetmix/systems/javelin/udev.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + services.udev.extraRules = '' 7 + SUBSYSTEM=="hidraw", ATTRS{idVendor}=="9000", ATTRS{idProduct}=="400d", MODE="0666" 8 + ''; 9 + }
+29
packetmix/systems/kde/kde.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + services.desktopManager.plasma6.enable = true; 8 + services.displayManager.sddm.enable = true; 9 + services.displayManager.sddm.wayland.enable = true; 10 + services.displayManager.sddm.settings.General.DisplayServer = "wayland"; 11 + 12 + environment.plasma6.excludePackages = with pkgs.kdePackages; [ 13 + plasma-browser-integration 14 + elisa 15 + okular 16 + krdp 17 + ]; 18 + 19 + environment.systemPackages = with pkgs; [ 20 + kdePackages.kcalc # Calculator 21 + kdePackages.kcharselect # Tool to select and copy special characters from all installed fonts 22 + kdePackages.kcolorchooser # A small utility to select a color 23 + kdePackages.ksystemlog # KDE SystemLog Application 24 + kdePackages.sddm-kcm # Configuration module for SDDM 25 + hardinfo2 # System information and benchmarks for Linux systems 26 + haruna # Open source video player built with Qt/QML and libmpv 27 + wayland-utils # Wayland utilities 28 + ]; 29 + }
+70
packetmix/systems/marbled/hardware-configuration.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { 7 + config, 8 + lib, 9 + pkgs, 10 + modulesPath, 11 + ... 12 + }: 13 + 14 + { 15 + imports = [ 16 + (modulesPath + "/installer/scan/not-detected.nix") 17 + ]; 18 + 19 + boot.initrd.availableKernelModules = [ 20 + "xhci_pci" 21 + "thunderbolt" 22 + "nvme" 23 + "uas" 24 + "usbhid" 25 + "usb_storage" 26 + "sd_mod" 27 + ]; 28 + boot.initrd.kernelModules = [ "dm-snapshot" ]; 29 + boot.kernelModules = [ "kvm-intel" ]; 30 + boot.extraModulePackages = [ ]; 31 + 32 + boot.initrd = { 33 + systemd.enable = true; 34 + luks.fido2Support = false; 35 + luks.devices.cryptroot = { 36 + device = "/dev/disk/by-label/marbled"; 37 + crypttabExtraOpts = [ "fido2-device=auto" ]; 38 + }; 39 + }; 40 + 41 + fileSystems."/" = { 42 + device = "/dev/disk/by-uuid/f1ade33a-4554-40c8-85bf-85b327cd6311"; 43 + fsType = "btrfs"; 44 + options = [ "subvol=@" ]; 45 + }; 46 + 47 + fileSystems."/boot" = { 48 + device = "/dev/disk/by-uuid/D2DE-84A5"; 49 + fsType = "vfat"; 50 + options = [ 51 + "fmask=0077" 52 + "dmask=0077" 53 + ]; 54 + }; 55 + 56 + swapDevices = [ ]; 57 + 58 + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 59 + # (the default) this is the recommended approach. When using systemd-networkd it's 60 + # still possible to use this option, but it's recommended to use it in conjunction 61 + # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`. 62 + networking.useDHCP = lib.mkDefault true; 63 + # networking.interfaces.enp0s13f0u4u4u4.useDHCP = lib.mkDefault true; 64 + # networking.interfaces.wlp166s0.useDHCP = lib.mkDefault true; 65 + 66 + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 67 + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 68 + 69 + services.hardware.bolt.enable = true; 70 + }
+8
packetmix/systems/marbled/hostname.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { 7 + networking.hostName = "marbled"; 8 + }
+9
packetmix/systems/marbled/locale.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, ... }: 6 + { 7 + services.xserver.xkb.variant = lib.mkForce ""; 8 + console.keyMap = lib.mkForce "us"; 9 + }
+14
packetmix/systems/marbled/users.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + users.users.maya = { 7 + isNormalUser = true; 8 + description = "Maya Stephens"; 9 + extraGroups = [ 10 + "networkmanager" 11 + "wheel" 12 + ]; 13 + }; 14 + }
+37
packetmix/systems/midnight/hardware-configuration.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { lib, config, ... }: 6 + { 7 + boot.initrd.availableKernelModules = [ 8 + "nvme" 9 + "xhci_pci" 10 + "usbhid" 11 + "usb_storage" 12 + "sd_mod" 13 + ]; 14 + boot.initrd.kernelModules = [ ]; 15 + boot.kernelModules = [ "kvm-amd" ]; 16 + boot.extraModulePackages = [ ]; 17 + fileSystems."/" = { 18 + device = "/dev/disk/by-uuid/0456a002-1692-4ed0-a233-d6cd76c8c2dd"; 19 + fsType = "btrfs"; 20 + }; 21 + boot.initrd.luks.devices."luks-ssd0".device = 22 + "/dev/disk/by-uuid/a50b2c75-dd36-4d31-924f-d4b77b94efa9"; 23 + fileSystems."/boot" = { 24 + device = "/dev/disk/by-uuid/9416-209A"; 25 + fsType = "vfat"; 26 + }; 27 + swapDevices = [ { device = "/dev/disk/by-uuid/a1cb08ad-39b3-4a36-bf5a-fad7714a85c0"; } ]; 28 + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 29 + # (the default) this is the recommended approach. When using systemd-networkd it's 30 + # still possible to use this option, but it's recommended to use it in conjunction 31 + # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`. 32 + networking.useDHCP = lib.mkDefault true; 33 + # networking.interfaces.enp1s0.useDHCP = lib.mkDefault true; 34 + # networking.interfaces.wlo1.useDHCP = lib.mkDefault true; 35 + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 36 + hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 37 + }
+5
packetmix/systems/midnight/hostname.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { networking.hostName = "midnight"; }
+17
packetmix/systems/minion/keyboard.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + services.keyd = { 7 + enable = true; 8 + 9 + keyboards.default = { 10 + ids = [ 11 + "*" 12 + "-1234:5678" # Espanso virtual keyboard 13 + ]; 14 + settings.main.capslock = "overload(control, esc)"; 15 + }; 16 + }; 17 + }
+32
packetmix/systems/minion/locale.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + # Set your time zone. 7 + time.timeZone = "Etc/UTC"; 8 + 9 + # Select internationalisation properties. 10 + i18n.defaultLocale = "en_GB.UTF-8"; 11 + 12 + i18n.extraLocaleSettings = { 13 + LC_ADDRESS = "en_GB.UTF-8"; 14 + LC_IDENTIFICATION = "en_GB.UTF-8"; 15 + LC_MEASUREMENT = "en_GB.UTF-8"; 16 + LC_MONETARY = "en_GB.UTF-8"; 17 + LC_NAME = "en_GB.UTF-8"; 18 + LC_NUMERIC = "en_GB.UTF-8"; 19 + LC_PAPER = "en_GB.UTF-8"; 20 + LC_TELEPHONE = "en_GB.UTF-8"; 21 + LC_TIME = "en_GB.UTF-8"; 22 + }; 23 + 24 + # Configure keymap in X11 25 + services.xserver.xkb = { 26 + layout = "us"; 27 + variant = "dvorak"; 28 + }; 29 + 30 + # Configure console keymap 31 + console.keyMap = "dvorak"; 32 + }
+65
packetmix/systems/niri/niri.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # SPDX-FileCopyrightText: 2024 sodiboo 3 + # 4 + # SPDX-License-Identifier: MIT 5 + # 6 + # This file is based on some work from sodiboo's niri-flake, see https://github.com/sodiboo/niri-flake/blob/main/flake.nix 7 + { 8 + project, 9 + pkgs, 10 + lib, 11 + ... 12 + }: 13 + let 14 + package = pkgs.niri; 15 + in 16 + { 17 + # we do not use the niri-flake nixos module, as it imports the home-module which causes a duplicate for attached homes 18 + 19 + nix.settings = { 20 + substituters = [ "https://niri.cachix.org" ]; 21 + trusted-public-keys = [ "niri.cachix.org-1:Wv0OmO7PsuocRKzfDoJ3mulSl7Z6oezYhGhR+3W2964=" ]; 22 + }; 23 + 24 + xdg = { 25 + autostart.enable = lib.mkDefault true; 26 + menus.enable = lib.mkDefault true; 27 + mime.enable = lib.mkDefault true; 28 + icons.enable = lib.mkDefault true; 29 + }; 30 + 31 + services.displayManager.sessionPackages = [ package ]; 32 + hardware.graphics.enable = lib.mkDefault true; 33 + 34 + xdg.portal = { 35 + enable = true; 36 + extraPortals = [ pkgs.xdg-desktop-portal-gnome ]; 37 + configPackages = [ package ]; 38 + }; 39 + 40 + security.polkit.enable = true; 41 + services.gnome.gnome-keyring.enable = true; 42 + systemd.user.services.niri-flake-polkit = { 43 + description = "PolicyKit Authentication Agent provided by niri-flake"; 44 + wantedBy = [ "niri.service" ]; 45 + after = [ "graphical-session.target" ]; 46 + partOf = [ "graphical-session.target" ]; 47 + serviceConfig = { 48 + Type = "simple"; 49 + ExecStart = "${pkgs.libsForQt5.polkit-kde-agent}/libexec/polkit-kde-authentication-agent-1"; 50 + Restart = "on-failure"; 51 + RestartSec = 1; 52 + TimeoutStopSec = 10; 53 + }; 54 + }; 55 + 56 + security.pam.services.swaylock = { }; 57 + programs.dconf.enable = lib.mkDefault true; 58 + fonts.enableDefaultPackages = lib.mkDefault true; 59 + 60 + home-manager.sharedModules = [ 61 + { 62 + programs.niri.package = lib.mkForce package; 63 + } 64 + ]; 65 + }
+63
packetmix/systems/ocicat/hardware.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { 5 + config, 6 + lib, 7 + modulesPath, 8 + ... 9 + }: 10 + 11 + { 12 + imports = [ 13 + (modulesPath + "/installer/scan/not-detected.nix") 14 + ]; 15 + 16 + boot.initrd.availableKernelModules = [ 17 + "nvme" 18 + "xhci_pci" 19 + "thunderbolt" 20 + "usb_storage" 21 + "usbhid" 22 + "sd_mod" 23 + ]; 24 + boot.initrd.kernelModules = [ ]; 25 + boot.kernelModules = [ 26 + "kvm-amd" 27 + "amdgpu" 28 + ]; 29 + boot.extraModulePackages = [ ]; 30 + 31 + fileSystems."/" = { 32 + device = "/dev/disk/by-uuid/3023361f-cbfe-4d78-8b02-a9c0f4d5fa6c"; 33 + fsType = "btrfs"; 34 + options = [ "subvol=@" ]; 35 + }; 36 + 37 + fileSystems."/boot" = { 38 + device = "/dev/disk/by-uuid/D1A1-791D"; 39 + fsType = "vfat"; 40 + options = [ 41 + "fmask=0022" 42 + "dmask=0022" 43 + ]; 44 + }; 45 + 46 + fileSystems."/nix" = { 47 + device = "/dev/disk/by-uuid/3023361f-cbfe-4d78-8b02-a9c0f4d5fa6c"; 48 + fsType = "btrfs"; 49 + options = [ "subvol=@nix" ]; 50 + }; 51 + 52 + swapDevices = [ ]; 53 + 54 + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 55 + # (the default) this is the recommended approach. When using systemd-networkd it's 56 + # still possible to use this option, but it's recommended to use it in conjunction 57 + # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`. 58 + networking.useDHCP = lib.mkDefault true; 59 + # networking.interfaces.wlp2s0.useDHCP = lib.mkDefault true; 60 + 61 + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 62 + hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 63 + }
+7
packetmix/systems/ocicat/hostname.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + networking.hostName = "ocicat"; 7 + }
+20
packetmix/systems/personal/audio.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + environment.systemPackages = [ 8 + pkgs.pwvucontrol 9 + pkgs.qpwgraph 10 + ]; 11 + 12 + services.pulseaudio.enable = false; 13 + security.rtkit.enable = true; 14 + services.pipewire = { 15 + enable = true; 16 + alsa.enable = true; 17 + alsa.support32Bit = true; 18 + pulse.enable = true; 19 + }; 20 + }
+10
packetmix/systems/personal/bluetooth.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + environment.systemPackages = [ pkgs.overskride ]; 8 + 9 + hardware.bluetooth.enable = true; 10 + }
+52
packetmix/systems/personal/configuration.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + # Edit this configuration file to define what should be installed on 6 + # your system. Help is available in the configuration.nix(5) man page 7 + # and in the NixOS manual (accessible by running ‘nixos-help’). 8 + 9 + { 10 + project, 11 + config, 12 + system, 13 + pkgs, 14 + ... 15 + }: 16 + { 17 + # Enable CUPS to print documents. 18 + services.printing.enable = true; 19 + 20 + # Enable touchpad support (enabled default in most desktopManager). 21 + # services.xserver.libinput.enable = true; 22 + 23 + # List packages installed in system profile. To search, run: 24 + # $ nix search wget 25 + environment.systemPackages = with pkgs; [ 26 + # vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. 27 + # wget 28 + dogdns 29 + ghostty 30 + (project.inputs.npins.result { inherit pkgs system; }) 31 + wl-clipboard 32 + ]; 33 + 34 + # Some programs need SUID wrappers, can be configured further or are 35 + # started in user sessions. 36 + # programs.mtr.enable = true; 37 + # programs.gnupg.agent = { 38 + # enable = true; 39 + # enableSSHSupport = true; 40 + # }; 41 + 42 + # List services that you want to enable: 43 + 44 + # Enable the OpenSSH daemon. 45 + # services.openssh.enable = true; 46 + 47 + # Open ports in the firewall. 48 + # networking.firewall.allowedTCPPorts = [ ... ]; 49 + # networking.firewall.allowedUDPPorts = [ ... ]; 50 + # Or disable the firewall altogether. 51 + # networking.firewall.enable = false; 52 + }
+7
packetmix/systems/personal/homes.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + home-manager.backupFileExtension = "backup"; 7 + }
+33
packetmix/systems/pinea/locale.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + # Set your time zone. 7 + time.timeZone = "Europe/London"; 8 + 9 + # Select internationalisation properties. 10 + i18n.defaultLocale = "en_GB.UTF-8"; 11 + 12 + i18n.extraLocaleSettings = { 13 + LC_ADDRESS = "en_GB.UTF-8"; 14 + LC_IDENTIFICATION = "en_GB.UTF-8"; 15 + LC_MEASUREMENT = "en_GB.UTF-8"; 16 + LC_MONETARY = "en_GB.UTF-8"; 17 + LC_NAME = "en_GB.UTF-8"; 18 + LC_NUMERIC = "en_GB.UTF-8"; 19 + LC_PAPER = "en_GB.UTF-8"; 20 + LC_TELEPHONE = "en_GB.UTF-8"; 21 + LC_TIME = "en_GB.UTF-8"; 22 + }; 23 + 24 + # Configure keymap in X11 25 + services.xserver.xkb = { 26 + layout = "gb"; 27 + variant = "mac"; 28 + options = "apple:alupckeys"; 29 + }; 30 + 31 + # Configure console keymap 32 + console.keyMap = "uk"; 33 + }
+26
packetmix/systems/pinea/samba.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, config, ... }: 6 + { 7 + environment.systemPackages = [ pkgs.cifs-utils ]; 8 + users.users.pinea.uid = 1002; 9 + fileSystems."/mnt/family" = { 10 + device = "//192.168.0.151/Family"; 11 + fsType = "cifs"; 12 + options = [ 13 + "x-systemd.automount" 14 + "noauto" 15 + "x-systemd.idle-timeout=60" 16 + "x-systemd.device-timeout=5s" 17 + "x-systemd.mount-timeout=5s" 18 + "credentials=/etc/nixos/smb-secrets" 19 + "uid=${toString config.users.users.pinea.uid}" 20 + "gid=${toString config.users.groups.users.gid}" 21 + ]; 22 + }; 23 + systemd.tmpfiles.rules = [ 24 + "d /mnt/family 0755 pinea users -" 25 + ]; 26 + }
+7
packetmix/systems/portable/upower.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + services.upower.enable = true; 7 + }
+11
packetmix/systems/redhead/android.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + services.udev.packages = [ 8 + pkgs.android-udev-rules 9 + ]; 10 + users.users.minion.extraGroups = [ "adbusers" ]; 11 + }
+12
packetmix/systems/redhead/docker.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + virtualisation.docker.enable = true; 7 + users.users.minion.extraGroups = [ "docker" ]; 8 + 9 + clicks.storage.impermanence.persist.directories = [ 10 + "/var/lib/docker" 11 + ]; 12 + }
+83
packetmix/systems/redhead/hardware-configuration.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + config, 7 + lib, 8 + pkgs, 9 + modulesPath, 10 + ... 11 + }: 12 + 13 + { 14 + imports = [ 15 + (modulesPath + "/installer/scan/not-detected.nix") 16 + ]; 17 + 18 + boot.initrd.availableKernelModules = [ 19 + "xhci_pci" 20 + "thunderbolt" 21 + "nvme" 22 + "uas" 23 + "usbhid" 24 + "usb_storage" 25 + "sd_mod" 26 + ]; 27 + boot.initrd.kernelModules = [ "dm-snapshot" ]; 28 + boot.kernelModules = [ "kvm-intel" ]; 29 + boot.extraModulePackages = [ ]; 30 + 31 + boot.initrd = { 32 + systemd.enable = true; 33 + luks.fido2Support = false; 34 + luks.devices.cryptroot = { 35 + device = "/dev/disk/by-label/redhead"; 36 + crypttabExtraOpts = [ "fido2-device=auto" ]; 37 + }; 38 + }; 39 + 40 + fileSystems."/" = { 41 + device = "/dev/disk/by-uuid/9ceb17f8-af04-42ae-9c5c-98b1e72c5a45"; 42 + fsType = "btrfs"; 43 + options = [ "subvol=@" ]; 44 + }; 45 + 46 + fileSystems."/nix" = { 47 + device = "/dev/disk/by-uuid/9ceb17f8-af04-42ae-9c5c-98b1e72c5a45"; 48 + fsType = "btrfs"; 49 + options = [ "subvol=@nix" ]; 50 + }; 51 + 52 + fileSystems."/persist".options = [ "subvol=@persist" ]; 53 + 54 + clicks.storage.impermanence = { 55 + enable = true; 56 + devices = { 57 + root = "/dev/disk/by-uuid/9ceb17f8-af04-42ae-9c5c-98b1e72c5a45"; 58 + persist = "/dev/disk/by-uuid/9ceb17f8-af04-42ae-9c5c-98b1e72c5a45"; # Not a typo - using subvol=@persist on fileSystems."/persist" to put in a specific subvolume 59 + }; 60 + }; 61 + 62 + fileSystems."/boot" = { 63 + device = "/dev/disk/by-uuid/6181-C692"; 64 + fsType = "vfat"; 65 + options = [ 66 + "fmask=0077" 67 + "dmask=0077" 68 + ]; 69 + }; 70 + 71 + swapDevices = [ ]; 72 + 73 + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 74 + # (the default) this is the recommended approach. When using systemd-networkd it's 75 + # still possible to use this option, but it's recommended to use it in conjunction 76 + # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`. 77 + networking.useDHCP = lib.mkDefault true; 78 + # networking.interfaces.enp0s13f0u4u4u4.useDHCP = lib.mkDefault true; 79 + # networking.interfaces.wlp166s0.useDHCP = lib.mkDefault true; 80 + 81 + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 82 + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 83 + }
+7
packetmix/systems/redhead/hostname.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + networking.hostName = "redhead"; 7 + }
+87
packetmix/systems/redhead/impermanence.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + config, 7 + lib, 8 + ... 9 + }: 10 + { 11 + # FIXME: migrate everything to systemd initrd so we can fold into our core impermanence module... 12 + boot.initrd = { 13 + postDeviceCommands = lib.mkForce ""; 14 + systemd.services.impermanence = 15 + let 16 + cfg = config.clicks.storage.impermanence; 17 + in 18 + { 19 + wantedBy = [ 20 + "initrd.target" 21 + ]; 22 + before = [ 23 + "sysroot.mount" 24 + ]; 25 + requires = [ 26 + "dev-disk-by\\x2did-dm\\x2dname\\x2dredhead\\x2dpersist.device" 27 + ]; 28 + after = [ 29 + "dev-disk-by\\x2did-dm\\x2dname\\x2dredhead\\x2dpersist.device" 30 + ]; 31 + unitConfig.DefaultDependencies = "no"; 32 + serviceConfig.Type = "oneshot"; 33 + script = '' 34 + mkdir /impermanent_fs 35 + mount ${cfg.devices.root} /impermanent_fs 36 + if [[ -e /impermanent_fs/${cfg.volumes.mount} ]]; then 37 + mkdir -p /impermanent_fs/${cfg.volumes.old_roots} 38 + timestamp=$(date --date="@$(stat -c %Y /impermanent_fs/${cfg.volumes.mount})" "+%Y-%m-%-d_%H:%M:%S") 39 + mv /impermanent_fs/${cfg.volumes.mount} "/impermanent_fs/${cfg.volumes.old_roots}/$timestamp" 40 + fi 41 + delete_subvolume_recursively() { 42 + IFS=$'\n' 43 + for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do 44 + delete_subvolume_recursively "/impermanent_fs/$i" 45 + done 46 + btrfs subvolume delete "$1" 47 + } 48 + for i in $(find /impermanent_fs/${cfg.volumes.old_roots}/ -maxdepth 1 -mtime +${builtins.toString cfg.delete_days}); do 49 + delete_subvolume_recursively "$i" 50 + done 51 + btrfs subvolume create /impermanent_fs/${cfg.volumes.mount} 52 + umount /impermanent_fs 53 + ''; 54 + }; 55 + systemd.services.make-home = { 56 + wantedBy = [ 57 + "initrd.target" 58 + ]; 59 + requires = [ 60 + "sysroot.mount" 61 + ]; 62 + after = [ 63 + "sysroot.mount" 64 + ]; 65 + unitConfig.DefaultDependencies = "no"; 66 + serviceConfig.Type = "oneshot"; 67 + script = '' 68 + mkdir -p /persist/data/home/minion 69 + 70 + chown 1000:100 /persist/data/home/minion # minion:users 71 + chmod 700 /persist/data/home/minion 72 + ''; 73 + }; 74 + }; 75 + 76 + systemd.services."persist-persist-data-etc-ssh-ssh_host_ed25519_key".preStart = 77 + "rm /etc/ssh/ssh_host_ed25519_key"; 78 + systemd.services."persist-persist-data-etc-ssh-ssh_host_ed25519_key.pub".preStart = 79 + "rm /etc/ssh/ssh_host_ed25519_key.pub"; 80 + systemd.services."persist-persist-data-etc-ssh-ssh_host_rsa_key".preStart = 81 + "rm /etc/ssh/ssh_host_rsa_key"; 82 + systemd.services."persist-persist-data-etc-ssh-ssh_host_rsa_key.pub".preStart = 83 + "rm /etc/ssh/ssh_host_rsa_key.pub"; 84 + 85 + users.mutableUsers = false; 86 + users.users.minion.hashedPasswordFile = "/persist/data/secrets/impermanence/minion-password.hash"; # cannot do /secrets/impermanence/minion-password.hash because it is loaded too late... 87 + }
+152
packetmix/systems/redhead/nextcloud.nix
··· 1 + # SPDX-FileCopyrightText: 2025 Collabora Productivity Limited 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { 7 + config, 8 + pkgs, 9 + lib, 10 + ... 11 + }: 12 + { 13 + services.nextcloud = { 14 + enable = true; 15 + https = true; 16 + 17 + config.adminpassFile = builtins.toFile "nextcloud-admin-password" "admin"; 18 + 19 + hostName = "nextcloud.dev.redhead.starrysky.fyi"; 20 + 21 + package = pkgs.nextcloud31; 22 + 23 + poolSettings = { 24 + pm = "dynamic"; 25 + "pm.max_children" = "32"; 26 + "pm.max_requests" = "500"; 27 + "pm.max_spare_servers" = "4"; 28 + "pm.min_spare_servers" = "2"; 29 + "pm.start_servers" = "2"; 30 + "listen.owner" = "nextcloud"; 31 + "listen.group" = "nextcloud"; 32 + }; 33 + phpOptions."opcache.interned_strings_buffer" = "32"; 34 + 35 + config = { 36 + dbtype = "pgsql"; 37 + dbhost = "/run/postgresql"; 38 + }; 39 + 40 + settings = { 41 + loglevel = 3; 42 + social_login_auto_redirect = true; 43 + default_phone_region = "US"; 44 + "overwrite.cli.url" = "https://nextcloud.dev.redhead.starrysky.fyi"; 45 + }; 46 + 47 + notify_push.enable = false; 48 + configureRedis = true; 49 + }; 50 + 51 + services.nginx.virtualHosts."nextcloud.dev.redhead.starrysky.fyi" = { 52 + enableACME = true; 53 + forceSSL = true; 54 + acmeRoot = null; 55 + 56 + serverAliases = [ "nextcloud.dev.redhead.starrysky.fyi" ]; 57 + }; 58 + 59 + security.acme.acceptTerms = true; 60 + security.acme.certs."nextcloud.dev.redhead.starrysky.fyi" = { 61 + dnsProvider = "cloudflare"; 62 + environmentFile = "/secrets/acme/environmentFile"; 63 + email = "skyler.grey@collabora.com"; 64 + }; 65 + security.acme.certs."collabora.dev.redhead.starrysky.fyi" = { 66 + dnsProvider = "cloudflare"; 67 + environmentFile = "/secrets/acme/environmentFile"; 68 + email = "skyler.grey@collabora.com"; 69 + }; 70 + 71 + services.nginx.virtualHosts."collabora.dev.redhead.starrysky.fyi" = { 72 + addSSL = true; 73 + enableACME = true; 74 + acmeRoot = null; 75 + 76 + locations."/" = { 77 + proxyPass = "https://127.0.0.1:9980"; 78 + recommendedProxySettings = true; 79 + proxyWebsockets = true; 80 + }; 81 + }; 82 + 83 + services.postgresql = { 84 + enable = true; 85 + 86 + ensureDatabases = [ "nextcloud" ]; 87 + ensureUsers = [ 88 + { 89 + name = "nextcloud"; 90 + ensureDBOwnership = true; 91 + } 92 + ]; 93 + }; 94 + 95 + environment.systemPackages = [ config.services.nextcloud.occ ]; 96 + 97 + systemd.targets = { 98 + nextcloud = { }; 99 + phpfpm.wantedBy = lib.mkForce [ "nextcloud.target" ]; 100 + }; 101 + 102 + systemd.timers = { 103 + nextcloud-cron = { 104 + wantedBy = lib.mkForce [ "nextcloud.target" ]; 105 + partOf = [ "nextcloud.target" ]; 106 + }; 107 + }; 108 + 109 + systemd.services = { 110 + # By default nextcloud is brought up on multi-user.target... but we only want it on redhead for testing/development 111 + nextcloud-setup = { 112 + wantedBy = lib.mkForce [ "nextcloud.target" ]; 113 + partOf = [ "nextcloud.target" ]; 114 + requires = [ "postgresql.service" ]; 115 + }; 116 + nginx = { 117 + partOf = [ "nextcloud.target" ]; 118 + wantedBy = lib.mkForce [ "nextcloud.target" ]; 119 + }; 120 + nginx-config-reload = { 121 + wantedBy = lib.mkForce [ 122 + "nextcloud.target" 123 + "acme-finished-nextcloud.redhead.dev.starrysky.fyi.target" 124 + ]; 125 + partOf = [ "nextcloud.target" ]; 126 + }; 127 + postgresql = { 128 + wantedBy = lib.mkForce [ "nextcloud.target" ]; 129 + partOf = [ "nextcloud.target" ]; 130 + }; 131 + redis-nextcloud = { 132 + wantedBy = lib.mkForce [ "nextcloud.target" ]; 133 + partOf = [ "nextcloud.target" ]; 134 + }; 135 + nextcloud-cron = { 136 + wantedBy = lib.mkForce [ "nextcloud.target" ]; 137 + partOf = [ "nextcloud.target" ]; 138 + requires = [ "postgresql.service" ]; 139 + }; 140 + nextcloud-notify_push = { 141 + wantedBy = lib.mkForce [ "nextcloud.target" ]; 142 + partOf = [ "nextcloud.target" ]; 143 + requires = [ "postgresql.service" ]; 144 + }; 145 + }; 146 + 147 + clicks.storage.impermanence.persist.directories = [ 148 + "/var/lib/acme" 149 + "/var/lib/nextcloud" 150 + "/var/lib/postgresql" 151 + ]; 152 + }
+8
packetmix/systems/redhead/office.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + environment.systemPackages = [ pkgs.libreoffice ]; 8 + }
+94
packetmix/systems/redhead/remoteBuilds.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + lib, 7 + ... 8 + }: 9 + { 10 + nix = { 11 + distributedBuilds = true; 12 + buildMachines = [ 13 + { 14 + hostName = "eu.nixbuild.net"; 15 + sshKey = "/secrets/remoteBuilds/id_ed25519"; 16 + publicHostKey = "c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSVBJUUNaYzU0cG9KOHZxYXdkOFRyYU5yeVFlSm52SDFlTHBJRGdiaXF5bU0="; 17 + 18 + system = "x86_64-linux"; 19 + 20 + speedFactor = 1; # We have a low speed for nixbuild for x86_64-linux machines: "we have x86_64-linux builders at home" (teal, midnight) 21 + maxJobs = 100; 22 + 23 + supportedFeatures = [ 24 + "benchmark" 25 + "big-parallel" 26 + ]; 27 + } 28 + { 29 + hostName = "eu.nixbuild.net"; 30 + sshKey = "/secrets/remoteBuilds/id_ed25519"; 31 + publicHostKey = "c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSVBJUUNaYzU0cG9KOHZxYXdkOFRyYU5yeVFlSm52SDFlTHBJRGdiaXF5bU0="; 32 + 33 + system = "aarch64-linux"; 34 + 35 + maxJobs = 100; 36 + 37 + supportedFeatures = [ 38 + "benchmark" 39 + "big-parallel" 40 + ]; 41 + } 42 + { 43 + hostName = "eu.nixbuild.net"; 44 + sshKey = "/secrets/remoteBuilds/id_ed25519"; 45 + publicHostKey = "c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSVBJUUNaYzU0cG9KOHZxYXdkOFRyYU5yeVFlSm52SDFlTHBJRGdiaXF5bU0="; 46 + 47 + system = "armv7l-linux"; 48 + 49 + maxJobs = 100; 50 + 51 + supportedFeatures = [ 52 + "benchmark" 53 + "big-parallel" 54 + ]; 55 + } 56 + { 57 + hostName = "midnight.clicks.domains"; 58 + sshUser = "remoteBuilds"; 59 + sshKey = "/secrets/remoteBuilds/id_ed25519"; 60 + publicHostKey = "c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSU5wbnFKeDlBTGVSS0k0ekVvZnNIL0ZZMFJLaTVsWWtDRVMvR2NWbHNSWncgcm9vdEBhMWQyCg=="; 61 + 62 + system = "x86_64-linux"; 63 + 64 + speedFactor = 1000; 65 + maxJobs = 100; 66 + 67 + supportedFeatures = [ 68 + "nixos-test" 69 + "benchmark" 70 + "big-parallel" 71 + "kvm" 72 + ]; 73 + } 74 + { 75 + hostName = "teal.clicks.domains"; 76 + sshUser = "remoteBuilds"; 77 + sshKey = "/secrets/remoteBuilds/id_ed25519"; 78 + publicHostKey = "c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSVBrS2RQU1B4c0xkeDNHVWpqeWliUkxqTGwzWGZhWG1mcnJ2ZW1ERmtqSTMgcm9vdEBhMWQxCg=="; 79 + 80 + system = "x86_64-linux"; 81 + 82 + speedFactor = 500; # teal isn't necessarily *half as fast*, but we want to build on it less... 83 + maxJobs = 100; 84 + 85 + supportedFeatures = [ 86 + "nixos-test" 87 + "benchmark" 88 + "big-parallel" 89 + "kvm" 90 + ]; 91 + } 92 + ]; 93 + }; 94 + }
+7
packetmix/systems/redhead/secrets.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + clicks.storage.impermanence.persist.directories = [ "/secrets" ]; 7 + }
+67
packetmix/systems/saurosuchus/hardware.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { 5 + config, 6 + lib, 7 + modulesPath, 8 + ... 9 + }: 10 + 11 + { 12 + imports = [ 13 + (modulesPath + "/installer/scan/not-detected.nix") 14 + ]; 15 + 16 + boot.initrd.availableKernelModules = [ 17 + "nvme" 18 + "xhci_pci" 19 + "ahci" 20 + "usbhid" 21 + "sd_mod" 22 + "ext4" 23 + ]; 24 + boot.initrd.kernelModules = [ 25 + "kvm-amd" 26 + "amdgpu" 27 + ]; 28 + boot.extraModulePackages = with config.boot.kernelPackages; [ v4l2loopback ]; 29 + boot.kernelModules = [ 30 + "v4l2loopback" 31 + ]; 32 + boot.kernel.sysctl."kernel.sysrq" = 1; 33 + boot.initrd = { 34 + systemd.enable = true; 35 + luks.devices."key".device = "/dev/disk/by-uuid/f3547d7f-707e-4b17-a22b-d31b6af0a67a"; 36 + luks.devices."MAIN" = { 37 + device = "/dev/disk/by-uuid/5183512d-92c1-4272-a746-8518ff7cde4b"; 38 + keyFile = "/key:/dev/mapper/key"; 39 + }; 40 + }; 41 + 42 + fileSystems."/" = { 43 + device = "/dev/mapper/MAIN"; 44 + fsType = "btrfs"; 45 + }; 46 + 47 + fileSystems."/boot" = { 48 + device = "/dev/disk/by-uuid/DCBE-AA38"; 49 + fsType = "vfat"; 50 + }; 51 + 52 + swapDevices = [ 53 + { 54 + device = "/dev/disk/by-uuid/c956d054-0dda-42c1-950d-26aefd3a8135"; 55 + } 56 + ]; 57 + 58 + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 59 + # (the default) this is the recommended approach. When using systemd-networkd it's 60 + # still possible to use this option, but it's recommended to use it in conjunction 61 + # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`. 62 + networking.useDHCP = lib.mkDefault true; 63 + # networking.interfaces.wlp2s0.useDHCP = lib.mkDefault true; 64 + 65 + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 66 + hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 67 + }
+7
packetmix/systems/saurosuchus/hostname.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + networking.hostName = "saurosuchus"; 7 + }
+16
packetmix/systems/server/ghostty.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + environment.systemPackages = [ pkgs.ghostty.terminfo ]; 8 + 9 + programs.bash = { 10 + interactiveShellInit = '' 11 + if [[ "$TERM" == "xterm-ghostty" ]]; then 12 + builtin source ${pkgs.ghostty.shell_integration}/bash/ghostty.bash 13 + fi 14 + ''; 15 + }; 16 + }
+8
packetmix/systems/server/locale.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + time.timeZone = "Etc/UTC"; 7 + i18n.defaultLocale = "en_US.UTF-8"; 8 + }
+22
packetmix/systems/server/packetmix.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + systemd.services.nixos-upgrade.onSuccess = [ "nixos-upgrade-restart.service" ]; 8 + systemd.services.nixos-upgrade-restart = { 9 + description = "Restart the server after a successful nixos-upgrade"; 10 + 11 + serviceConfig.oneshot = true; 12 + 13 + script = '' 14 + booted="$(${pkgs.coreutils}/bin/readlink -f /run/booted-system)" 15 + latest="$(${pkgs.coreutils}/bin/readlink -f /nix/var/nix/profiles/system)" 16 + 17 + if [ "''${booted}" != "''${latest}" ]; then 18 + shutdown -r +5 19 + fi 20 + ''; 21 + }; 22 + }
+5
packetmix/systems/server/ssh.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { services.openssh.enable = true; }
+7
packetmix/systems/server/tailscale.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + services.tailscale.useRoutingFeatures = "both"; 7 + }
+8
packetmix/systems/server/users.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + { 5 + users.mutableUsers = false; 6 + 7 + security.sudo-rs.wheelNeedsPassword = false; 8 + }
+150
packetmix/systems/shorthair/audio.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + services.pipewire.extraConfig.pipewire."new-virtual-devices" = { 7 + "context.modules" = [ 8 + { 9 + name = "libpipewire-module-combine-stream"; 10 + args = { 11 + "combine.mode" = "sink"; 12 + "node.name" = "output"; 13 + "node.description" = "Combined Outputs"; 14 + "combine.latency-compensate" = false; 15 + "combine.props" = { 16 + "audio.position" = [ 17 + "FL" 18 + "FR" 19 + ]; 20 + }; 21 + "stream.props" = { 22 + "stream.dont-remix" = true; 23 + }; 24 + "stream.rules" = [ 25 + { 26 + matches = [ 27 + { 28 + "media.class" = "Audio/Sink"; 29 + "node.name" = "alsa_output.usb-NXP_SEMICONDUCTORS_Razer_Kraken_V3_Pro-00.analog-stereo"; 30 + } 31 + ]; 32 + actions = { 33 + create-stream = { 34 + "audio.position" = [ 35 + "FL" 36 + "FR" 37 + ]; 38 + "combine.audio.position" = [ 39 + "FL" 40 + "FR" 41 + ]; 42 + }; 43 + }; 44 + } 45 + { 46 + matches = [ 47 + { 48 + "media.class" = "Audio/Sink"; 49 + "node.name" = "alsa_output.usb-R__DE_RODECaster_Duo_IR0023015-00.pro-output-1"; 50 + } 51 + ]; 52 + actions = { 53 + create-stream = { 54 + "audio.position" = [ 55 + "AUX0" 56 + "AUX1" 57 + ]; 58 + "combine.audio.position" = [ 59 + "FL" 60 + "FR" 61 + ]; 62 + }; 63 + }; 64 + } 65 + ]; 66 + }; 67 + } 68 + { 69 + name = "libpipewire-module-combine-stream"; 70 + args = { 71 + "combine.mode" = "sink"; 72 + "node.name" = "chat_output"; 73 + "node.description" = "Chat Output"; 74 + "combine.latency-compensate" = false; 75 + "combine.props" = { 76 + "audio.position" = [ 77 + "FL" 78 + "FR" 79 + ]; 80 + }; 81 + "stream.props" = { 82 + "stream.dont-remix" = true; 83 + }; 84 + "stream.rules" = [ 85 + { 86 + matches = [ 87 + { 88 + "media.class" = "Audio/Sink"; 89 + "node.name" = "alsa_output.usb-R__DE_RODECaster_Duo_IR0023015-00.pro-output-0"; 90 + } 91 + ]; 92 + actions = { 93 + create-stream = { 94 + "audio.position" = [ 95 + "AUX0" 96 + "AUX1" 97 + ]; 98 + "combine.audio.position" = [ 99 + "FL" 100 + "FR" 101 + ]; 102 + }; 103 + }; 104 + } 105 + ]; 106 + }; 107 + } 108 + { 109 + name = "libpipewire-module-combine-stream"; 110 + args = { 111 + "combine.mode" = "source"; 112 + "node.name" = "input"; 113 + "node.description" = "Input"; 114 + "combine.latency-compensate" = false; 115 + "combine.props" = { 116 + "audio.position" = [ 117 + "FL" 118 + "FR" 119 + ]; 120 + }; 121 + "stream.props" = { 122 + "stream.dont-remix" = true; 123 + }; 124 + "stream.rules" = [ 125 + { 126 + matches = [ 127 + { 128 + "media.class" = "Audio/Source"; 129 + "node.name" = "alsa_input.usb-R__DE_RODECaster_Duo_IR0023015-00.pro-input-1"; 130 + } 131 + ]; 132 + actions = { 133 + create-stream = { 134 + "audio.position" = [ 135 + "AUX0" 136 + "AUX1" 137 + ]; 138 + "combine.audio.position" = [ 139 + "FL" 140 + "FR" 141 + ]; 142 + }; 143 + }; 144 + } 145 + ]; 146 + }; 147 + } 148 + ]; 149 + }; 150 + }
+18
packetmix/systems/shorthair/filesystems.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + fileSystems."/" = { 7 + device = "/dev/mapper/NIXROOT"; 8 + fsType = "btrfs"; 9 + }; 10 + fileSystems."/bigdata" = { 11 + device = "/dev/mapper/BIGDATA"; 12 + fsType = "btrfs"; 13 + }; 14 + fileSystems."/boot" = { 15 + device = "/dev/disk/by-uuid/F1F9-C8D5"; 16 + fsType = "vfat"; 17 + }; 18 + }
+59
packetmix/systems/shorthair/hardware.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + config, 7 + lib, 8 + modulesPath, 9 + ... 10 + }: 11 + 12 + { 13 + imports = [ 14 + (modulesPath + "/installer/scan/not-detected.nix") 15 + ]; 16 + 17 + boot.initrd.availableKernelModules = [ 18 + "nvme" 19 + "xhci_pci" 20 + "ahci" 21 + "usbhid" 22 + "sd_mod" 23 + "ext4" 24 + ]; 25 + boot.initrd.kernelModules = [ ]; 26 + boot.kernelModules = [ 27 + "kvm-amd" 28 + "amdgpu" 29 + ]; 30 + boot.extraModulePackages = [ ]; 31 + 32 + swapDevices = [ ]; 33 + 34 + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 35 + # (the default) this is the recommended approach. When using systemd-networkd it's 36 + # still possible to use this option, but it's recommended to use it in conjunction 37 + # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`. 38 + networking.useDHCP = lib.mkDefault true; 39 + # networking.interfaces.wlp2s0.useDHCP = lib.mkDefault true; 40 + 41 + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 42 + hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 43 + 44 + boot.loader.systemd-boot.enable = true; 45 + boot.loader.efi.canTouchEfiVariables = true; 46 + 47 + boot.initrd = { 48 + systemd.enable = true; 49 + luks.devices."key".device = "/dev/disk/by-uuid/3ddef258-93b2-459c-9420-121b0631d69a"; 50 + luks.devices."NIXROOT" = { 51 + device = "/dev/disk/by-uuid/744c83f8-f8d9-4604-8e44-ceb7bf7fdf87"; 52 + keyFile = "/key:/dev/mapper/key"; 53 + }; 54 + luks.devices."BIGDATA" = { 55 + device = "/dev/disk/by-uuid/640b7c00-5cfa-472f-9338-c7adafa9ea6a"; 56 + keyFile = "/key:/dev/mapper/key"; 57 + }; 58 + }; 59 + }
+7
packetmix/systems/shorthair/hostname.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + networking.hostName = "shorthair"; 7 + }
+14
packetmix/systems/shorthair/ollama.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + services.ollama = { 7 + enable = true; 8 + acceleration = "rocm"; 9 + }; 10 + services.nextjs-ollama-llm-ui = { 11 + enable = true; 12 + port = 1144; 13 + }; 14 + }
+16
packetmix/systems/teal/acme.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + security.acme = { 7 + acceptTerms = true; 8 + defaults = { 9 + email = "acme@freshlybakedca.ke"; 10 + dnsProvider = "cloudflare"; 11 + environmentFile = "/secrets/acme/environmentFile"; 12 + }; 13 + }; 14 + 15 + clicks.storage.impermanence.persist.directories = [ "/var/lib/acme" ]; 16 + }
+271
packetmix/systems/teal/copyparty.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + project, 7 + pkgs, 8 + config, 9 + lib, 10 + ... 11 + }: 12 + { 13 + imports = [ 14 + project.inputs.copyparty.result.nixosModules.default 15 + ]; 16 + 17 + config = { 18 + nixpkgs.overlays = [ project.inputs.copyparty.result.overlays.default ]; 19 + 20 + services.copyparty = 21 + let 22 + admins = [ 23 + "coded" 24 + "minion" 25 + ]; 26 + in 27 + { 28 + enable = true; 29 + 30 + settings = { 31 + i = "127.0.0.1"; # ip 32 + p = 1030; # port 33 + 34 + # we'll be using nginx for this... 35 + http-only = true; 36 + no-crt = true; 37 + 38 + idp-store = 3; 39 + idp-h-usr = "X-Webauth-Login"; 40 + idp-adm = admins; 41 + 42 + shr = "/share"; 43 + shr-db = "/var/lib/copyparty/shares.db"; 44 + shr-adm = admins; 45 + 46 + # as we might have private directories, better to be a bit conservative about permissions... 47 + chmod-f = 700; 48 + chmod-d = 700; 49 + 50 + magic = true; # "enable filetype detection on nameless uploads" 51 + 52 + e2dsa = true; # index files to allow searching, upload undo, etc. 53 + e2ts = true; # and scan metadata... 54 + 55 + rss = true; # allow (experimental) rss support -> useful for antennapod/miniflux/co. 56 + dav-auth = true; # "force auth for all folders" notably "(required by davfs2 when only some folders are world-readable)" 57 + 58 + xvol = true; # don't allow symlinks to break out of confinement... 59 + no-robots = true; # not really meant to be indexed. Maybe we want to add anubis at some point too... 60 + 61 + ah-alg = "argon2"; 62 + 63 + og = true; # opengraph support for the benefit of Discord 64 + og-ua = "Discordbot"; 65 + og-site = "Freshly Baked Cake Files"; 66 + spinner = "🧁"; # [hopefully this isn't too boring for you, tripflag](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#boring-loader-spinner) 67 + 68 + xm = "aw,f,j,t3600,${project.inputs.copyparty.src}/bin/hooks/wget.py"; # download URLs that are pasted into the message box 69 + 70 + xff-src = "127.0.0.1"; 71 + rproxy = 1; 72 + 73 + exp = true; 74 + }; 75 + 76 + volumes = { 77 + "/" = { 78 + path = "/var/lib/copyparty/data"; 79 + 80 + access = { 81 + r = "*"; 82 + A = admins; 83 + }; 84 + }; 85 + "/users/_" = { 86 + path = "/var/lib/copyparty/data/users/_"; 87 + 88 + access = { }; 89 + }; 90 + "/users/\${u}" = { 91 + path = "/var/lib/copyparty/data/users/_/\${u}"; 92 + 93 + access = { 94 + A = "\${u}"; 95 + }; 96 + }; 97 + "/groups/freshly" = { 98 + path = "/var/lib/copyparty/data/groups/freshly"; 99 + 100 + access = { 101 + A = [ 102 + "coded" 103 + "minion" 104 + ]; 105 + }; 106 + }; 107 + }; 108 + }; 109 + 110 + systemd.services.copyparty = { 111 + path = [ pkgs.wget ]; # Needed for downloading files by URL 112 + serviceConfig = { 113 + BindReadOnlyPaths = [ 114 + "/etc/ssl" 115 + "/etc/static/ssl" 116 + ]; # Required for wget to validate SSL for downloads 117 + StateDirectory = 118 + "copyparty " 119 + + (lib.pipe config.services.copyparty.volumes [ 120 + builtins.attrValues 121 + (map (mount: mount.path)) 122 + (map (lib.removePrefix "/var/lib/")) 123 + (lib.concatStringsSep " ") 124 + ]); 125 + }; 126 + }; 127 + 128 + services.nginx.enable = true; 129 + services.nginx.additionalModules = [ pkgs.nginxModules.lua ]; 130 + services.nginx.appendHttpConfig = '' 131 + lua_package_path "${ 132 + "${pkgs.luaPackages.lua-resty-core}/lib/lua/5.2/?.lua;" 133 + + "${pkgs.luaPackages.lua-resty-lrucache}/lib/lua/5.2/?.lua;" 134 + + "${project.packages.lua-multipart.result.x86_64-linux}/share/lua/5.2/?.lua;;" 135 + }"; # The double-semicolon makes the default search paths also be included 136 + ''; 137 + services.nginx.virtualHosts."files.freshly.space" = { 138 + listenAddresses = [ 139 + "0.0.0.0" 140 + "[::0]" 141 + ]; 142 + 143 + addSSL = true; 144 + enableACME = true; 145 + acmeRoot = null; 146 + 147 + locations."/" = { 148 + proxyPass = "http://127.0.0.1:1030"; 149 + recommendedProxySettings = true; 150 + proxyWebsockets = true; 151 + 152 + extraConfig = '' 153 + proxy_hide_header X-Webauth-Login; 154 + proxy_set_header X-Webauth-Login $preferred_username; 155 + 156 + proxy_intercept_errors on; 157 + error_page 403 = @redirectToAuth2ProxyLogin; 158 + 159 + access_by_lua_block { 160 + if ngx.var.request_uri:sub(-3) == "/?h" and ngx.var.user == "" then 161 + return ngx.redirect("https://files.freshly.space/oauth2/start?rd=" .. ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.request_uri, ngx.HTTP_MOVED_TEMPORARILY) 162 + end 163 + 164 + local headers, err = ngx.req.get_headers() 165 + if not headers["Content-Type"] then 166 + return 167 + end 168 + 169 + ngx.req.read_body() 170 + local body = ngx.req.get_body_data() 171 + 172 + if not body then 173 + return 174 + end 175 + 176 + local Multipart = require("multipart") 177 + local multipart_data, err = pcall(Multipart(body, headers["Content-Type"])) 178 + 179 + if not err and multipart_data and multipart_data:get("act").value == "logout" then 180 + return ngx.redirect("https://files.freshly.space/oauth2/sign_out?rd=" .. ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.request_uri, ngx.HTTP_MOVED_TEMPORARILY) 181 + end 182 + } 183 + ''; 184 + }; 185 + 186 + locations."@empty" = { 187 + return = "200"; 188 + }; 189 + 190 + locations."= /oauth2/auth" = { 191 + extraConfig = '' 192 + proxy_intercept_errors on; 193 + error_page 401 =200 @empty; # We always want to return 200 so as to allow anon access... 194 + ''; 195 + }; 196 + 197 + extraConfig = '' 198 + auth_request_set $preferred_username $upstream_http_x_auth_request_preferred_username; 199 + 200 + client_max_body_size 1024M; 201 + ''; 202 + }; 203 + 204 + services.oauth2-proxy = { 205 + enable = true; 206 + 207 + keyFile = "/secrets/copyparty/oauth2-proxy"; 208 + 209 + httpAddress = "http://127.0.0.1:1031"; 210 + nginx = { 211 + domain = "files.freshly.space"; 212 + virtualHosts."files.freshly.space" = { }; 213 + }; 214 + reverseProxy = true; 215 + 216 + provider = "oidc"; 217 + scope = "openid profile email"; 218 + clientID = "copyparty"; 219 + 220 + setXauthrequest = true; 221 + 222 + email.domains = [ "*" ]; 223 + 224 + oidcIssuerUrl = "https://idm.freshly.space/oauth2/openid/copyparty"; 225 + 226 + extraConfig = { 227 + skip-provider-button = "true"; 228 + whitelist-domain = "files.freshly.space"; 229 + }; 230 + }; 231 + systemd.services.oauth2-proxy = { 232 + after = [ "kanidm.service" ]; 233 + requires = [ "kanidm.service" ]; 234 + preStart = "while [[ \"$(${pkgs.curl}/bin/curl -s -L https://idm.freshly.space/status)\" != \"true\" ]]; do sleep 5; done"; 235 + }; 236 + 237 + services.headscale.settings.dns.extra_records = [ 238 + { 239 + # files.freshly.space -> teal 240 + name = "files.freshly.space"; 241 + type = "A"; 242 + value = "100.64.0.5"; 243 + } 244 + ]; 245 + services.nginx.virtualHosts."internal.files.freshly.space" = { 246 + listenAddresses = [ "localhost.tailscale" ]; 247 + 248 + serverName = "files.freshly.space"; 249 + 250 + addSSL = true; 251 + enableACME = true; 252 + acmeRoot = null; 253 + 254 + locations."/" = { 255 + proxyPass = "http://127.0.0.1:1030"; 256 + recommendedProxySettings = true; 257 + proxyWebsockets = true; 258 + }; 259 + 260 + extraConfig = '' 261 + client_max_body_size 1024M; 262 + ''; 263 + }; 264 + services.nginx.tailscaleAuth = { 265 + enable = true; 266 + virtualHosts = [ "internal.files.freshly.space" ]; 267 + }; 268 + 269 + clicks.storage.impermanence.persist.directories = [ "/var/lib/copyparty" ]; 270 + }; 271 + }
+11
packetmix/systems/teal/ddns.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + services.cloudflare-dyndns = { 7 + enable = true; 8 + domains = [ "a1.clicks.domains" ]; 9 + apiTokenFile = "/secrets/ddns/cloudflareAPIToken"; 10 + }; 11 + }
+178
packetmix/systems/teal/fava.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + project, 7 + pkgs, 8 + lib, 9 + system, 10 + ... 11 + }: 12 + let 13 + /** 14 + These users are listed in a specific order: this is the order they will show up in the fava dropdown menu (the first one being the default) 15 + - The "slug" is user visible - it's the name of the config file which is displayed in the UI in a few places. Changing this will lose the userdata unless it is manually copied 16 + - The "name" is the name that gets actually shown as the account name in the UI 17 + - The "beancountOptions" are documented here: <https://beancount.github.io/docs/beancount_options_reference.html>. They get written to a read-only file in the nix store 18 + - The "favaOptions" are documented here - <https://fava.pythonanywhere.com/huge-example-file/help/options>. They get written alongside the beancount options 19 + - The "extraConfig" also gets written alongside the beancount options 20 + */ 21 + users = [ 22 + { 23 + slug = "freshlybakedcake"; 24 + name = "FreshlyBakedCake"; 25 + beancountOptions.operating_currency = "GBP"; 26 + } 27 + { 28 + slug = "minion"; 29 + name = "Skyler Grey"; 30 + beancountOptions.operating_currency = "GBP"; 31 + favaOptions = { 32 + invert-income-liabilities-equity = "true"; 33 + auto-reload = "true"; 34 + fiscal-year-end = "04-05"; 35 + import-config = builtins.toString ./fava/minion/truelayer.py; 36 + import-dirs = "/var/lib/private/fava/minion/"; 37 + }; 38 + extraConfig = '' 39 + plugin "fava.plugins.tag_discovered_documents" 40 + plugin "fava.plugins.link_documents" 41 + 42 + plugin "beancount.plugins.pedantic" 43 + plugin "beancount.plugins.unrealized" "Unrealized" 44 + plugin "beancount.plugins.implicit_prices" 45 + 46 + plugin "beancount_share.share" "{ 47 + 'mark_name': 'share', 48 + 'meta_name': 'shared', 49 + 'account_debtors': 'Assets:People', 50 + 'account_creditors': 'Liabilities:People', 51 + 'open_date': None, 52 + 'quantize': '0.01' 53 + }" 54 + ''; 55 + } 56 + { 57 + slug = "coded"; 58 + name = "Samuel Shuert"; 59 + beancountOptions.operating_currency = "USD"; 60 + } 61 + ]; 62 + 63 + userConfigs = 64 + (map ( 65 + user: 66 + lib.recursiveUpdate { 67 + beancountOptions.title = user.name; 68 + favaOptions.default-file = "/var/lib/private/fava/${user.slug}.beancount"; 69 + } user 70 + )) 71 + users; 72 + 73 + userFiles = 74 + (map ( 75 + userConfig: 76 + builtins.toString ( 77 + let 78 + generateFavaOption = 79 + name: value: 80 + if value == null then 81 + ''1970-01-01 custom "fava-option" "${name}"'' 82 + else 83 + ''1970-01-01 custom "fava-option" "${name}" "${value}"''; 84 + favaOptions = lib.pipe userConfig.favaOptions [ 85 + (lib.attrsets.mapAttrsToList generateFavaOption) 86 + (lib.strings.concatStringsSep "\n") 87 + ]; 88 + 89 + generateBeancountOption = name: value: ''option "${name}" "${value}"''; 90 + beancountOptions = lib.pipe userConfig.beancountOptions [ 91 + (lib.attrsets.mapAttrsToList generateBeancountOption) 92 + (lib.strings.concatStringsSep "\n") 93 + ]; 94 + in 95 + pkgs.writeText "${userConfig.slug}-config" '' 96 + ${favaOptions} 97 + ${beancountOptions} 98 + ${userConfig.extraConfig or ""} 99 + 100 + include "${userConfig.favaOptions.default-file}" 101 + '' 102 + ) 103 + )) 104 + userConfigs; 105 + in 106 + { 107 + systemd.services.fava = { 108 + wants = [ "nginx.service" ]; 109 + after = [ "nginx.service" ]; 110 + wantedBy = [ "multi-user.target" ]; 111 + 112 + environment = { 113 + FAVA_HOST = "127.0.0.1"; 114 + FAVA_PORT = "1025"; 115 + }; 116 + 117 + serviceConfig = { 118 + StateDirectory = "fava"; 119 + DynamicUser = true; 120 + LoadCredential = [ 121 + "truelayer_client_secret:/secrets/fava/truelayer_client_secret" 122 + ]; 123 + }; 124 + 125 + script = 126 + let 127 + fava = pkgs.fava.overrideAttrs (prevAttrs: { 128 + propagatedBuildInputs = prevAttrs.propagatedBuildInputs ++ [ 129 + project.packages.beancount-autobean.result.${system} 130 + project.packages.beancount-beancount_share.result.${system} 131 + project.packages.beancount-smart_importer.result.${system} 132 + ]; 133 + }); 134 + in 135 + "${fava}/bin/fava ${builtins.concatStringsSep " " userFiles}"; 136 + 137 + preStart = 138 + let 139 + userConfigToCreationScript = userConfig: '' 140 + ${pkgs.coreutils}/bin/mkdir -p "$(${pkgs.coreutils}/bin/dirname "${userConfig.favaOptions.default-file}")" 141 + ${pkgs.coreutils}/bin/touch -a ${userConfig.favaOptions.default-file} 142 + ''; 143 + in 144 + lib.trivial.pipe userConfigs [ 145 + (map userConfigToCreationScript) 146 + (lib.strings.concatStringsSep "\n") 147 + ]; 148 + }; 149 + 150 + services.nginx.enable = true; 151 + services.nginx.virtualHosts."fava.clicks.codes" = { 152 + listenAddresses = [ "localhost.tailscale" ]; 153 + 154 + addSSL = true; 155 + enableACME = true; 156 + acmeRoot = null; 157 + 158 + serverAliases = [ "www.fava.clicks.codes" ]; 159 + 160 + locations."/" = { 161 + proxyPass = "http://127.0.0.1:1025"; 162 + recommendedProxySettings = true; 163 + }; 164 + }; 165 + 166 + services.nginx.tailscaleAuth = { 167 + enable = true; 168 + virtualHosts = [ "fava.clicks.codes" ]; 169 + }; 170 + 171 + clicks.storage.impermanence.persist.directories = [ 172 + { 173 + directory = "/var/lib/private/fava"; 174 + mode = "0700"; 175 + defaultPerms.mode = "0700"; 176 + } 177 + ]; 178 + }
+25
packetmix/systems/teal/fava/minion/truelayer.py
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + import autobean.truelayer 6 + from smart_importer import apply_hooks, PredictPayees, PredictPostings 7 + 8 + import os 9 + import pathlib 10 + 11 + with open(pathlib.Path(os.environ["CREDENTIALS_DIRECTORY"]) / pathlib.Path("truelayer_client_secret")) as f: 12 + truelayer_client_secret = f.read().strip() 13 + 14 + CONFIG = [ 15 + apply_hooks( 16 + autobean.truelayer.Importer( 17 + "fava-228732", 18 + truelayer_client_secret 19 + ), 20 + [ 21 + PredictPayees(), 22 + PredictPostings(), 23 + ] 24 + ) 25 + ]
+44
packetmix/systems/teal/hardware-configuration.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + boot.initrd.availableKernelModules = [ 7 + "nvme" 8 + "xhci_pci" 9 + "ahci" 10 + "usbhid" 11 + "uas" 12 + "usb_storage" 13 + "sd_mod" 14 + ]; 15 + boot.initrd.kernelModules = [ ]; 16 + boot.kernelModules = [ "kvm-amd" ]; 17 + boot.extraModulePackages = [ ]; 18 + fileSystems."/nix" = { 19 + device = "/dev/disk/by-uuid/ab5c2f52-a737-4b29-a505-e3d0b9d0714c"; 20 + fsType = "btrfs"; 21 + options = [ "subvol=@nix" ]; 22 + }; 23 + fileSystems."/boot" = { 24 + device = "/dev/disk/by-uuid/880D-BBAB"; 25 + fsType = "vfat"; 26 + options = [ 27 + "fmask=0022" 28 + "dmask=0022" 29 + ]; 30 + }; 31 + 32 + boot.swraid.enable = true; 33 + boot.swraid.mdadmConf = '' 34 + PROGRAM=true 35 + ''; # Disable reporting for this system 36 + 37 + clicks.storage.impermanence = { 38 + enable = true; 39 + devices = { 40 + root = "/dev/disk/by-uuid/ab5c2f52-a737-4b29-a505-e3d0b9d0714c"; 41 + persist = "/dev/md/a1d1:persist"; 42 + }; 43 + }; 44 + }
+159
packetmix/systems/teal/headscale.nix
··· 1 + # SPDX-FileCopyrightText: 2024 Clicks Codes 2 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 3 + # 4 + # SPDX-License-Identifier: MIT 5 + 6 + { pkgs, ... }: 7 + let 8 + groups = { 9 + /** 10 + Users have full access to contact other users or servers 11 + 12 + This gives them access to internal-only things such as all SilverBullet 13 + notes on https://silverbullet.clicks.codes or finance information on 14 + https://fava.clicks.codes 15 + 16 + They tend to be either close friends, people who host servers on our 17 + network or straight-up Clicks or Freshly Baked Cake developers 18 + 19 + Servers are owned by users but have the tag `tag:server` to give them a 20 + different permission level. Servers can only access other servers 21 + */ 22 + "group:users" = [ 23 + "coded" 24 + "matei" 25 + "minion" 26 + "mostlyturquoise" 27 + "pinea" 28 + "zanderp25" 29 + ]; 30 + 31 + /** 32 + Friends only have access to other users 33 + 34 + This doesn't give them access to internal-only services, but it could let 35 + them join, say, Minecraft servers which are owned by someone else on the 36 + tailnet 37 + */ 38 + "group:friends" = [ 39 + "sirdigalot" 40 + ]; 41 + }; 42 + 43 + exceptional_acls = [ 44 + { 45 + action = "accept"; 46 + src = [ "zulu" ]; 47 + dst = [ "zanderp25:3000" ]; 48 + } # Used to let Zan reverse proxy to their personal machine for development - port-locked so probably OK 49 + { 50 + action = "accept"; 51 + src = [ 52 + "mostlyturquoise" 53 + "starrylee" 54 + ]; 55 + dst = [ "tag:mostlyturquoise-minecraft-server:*" ]; 56 + } # Used to let mostlyturquoise and their friends access their minecraft servers without giving people too many permissions 57 + ]; 58 + 59 + acls = [ 60 + { 61 + action = "accept"; 62 + src = [ "group:users" ]; 63 + dst = [ 64 + "group:users:*" 65 + "group:friends:*" 66 + "autogroup:internet:*" 67 + "tag:server:*" 68 + ]; 69 + } 70 + { 71 + action = "accept"; 72 + src = [ "group:friends" ]; 73 + dst = [ 74 + "group:users:*" 75 + "group:friends:*" 76 + ]; 77 + } 78 + { 79 + action = "accept"; 80 + src = [ "tag:server" ]; 81 + dst = [ "tag:server:*" ]; 82 + } 83 + ] 84 + ++ exceptional_acls; 85 + 86 + tagOwners = { 87 + "tag:server" = [ "group:users" ]; 88 + "tag:mostlyturquoise-minecraft-server" = [ "mostlyturquoise" ]; 89 + }; 90 + in 91 + { 92 + # Headscale service 93 + services.headscale = { 94 + enable = true; 95 + 96 + address = "127.0.0.1"; 97 + port = 1024; 98 + 99 + settings = { 100 + server_url = "https://vpn.clicks.codes"; 101 + noise.private_key_path = "/secrets/headscale/noise_private_key"; 102 + policy = { 103 + mode = "file"; 104 + path = ( 105 + pkgs.writers.writeJSON "tailscale-acls.json" { 106 + inherit groups acls tagOwners; 107 + } 108 + ); 109 + }; 110 + dns = { 111 + nameservers.global = [ 112 + "1.1.1.1" 113 + "1.0.0.1" 114 + "2606:4700:4700::1111" 115 + "2606:4700:4700::1001" 116 + ]; 117 + base_domain = "clicks.domains"; 118 + }; 119 + oidc = { 120 + only_start_if_oidc_is_available = false; # Otherwise we can end up locking ourselves out... 121 + strip_email_domain = true; 122 + 123 + issuer = "https://idm.freshly.space/oauth2/openid/headscale"; 124 + 125 + client_id = "headscale"; 126 + client_secret_path = "/secrets/headscale/oidc_client_secret"; 127 + 128 + allowed_groups = [ "tailscale@idm.freshly.space" ]; # Cannot share the same name as the openid client 129 + 130 + pkce.enabled = true; 131 + 132 + scope = [ 133 + "groups" 134 + "openid" 135 + "profile" 136 + ]; 137 + }; 138 + }; 139 + }; 140 + 141 + # Nginx 142 + services.nginx.enable = true; 143 + services.nginx.virtualHosts."vpn.clicks.codes" = { 144 + addSSL = true; 145 + enableACME = true; 146 + acmeRoot = null; 147 + 148 + serverAliases = [ "www.vpn.clicks.codes" ]; 149 + 150 + locations."/" = { 151 + proxyPass = "http://127.0.0.1:1024"; 152 + recommendedProxySettings = true; 153 + proxyWebsockets = true; 154 + }; 155 + }; 156 + 157 + # Impermanence 158 + clicks.storage.impermanence.persist.directories = [ "/var/lib/headscale" ]; 159 + }
+5
packetmix/systems/teal/hostname.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { networking.hostName = "teal"; }
+55
packetmix/systems/teal/kanidm.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { pkgs, ... }: 6 + { 7 + services.kanidm = { 8 + package = pkgs.kanidm_1_6; 9 + enableServer = true; 10 + enableClient = true; 11 + serverSettings = { 12 + version = "2"; 13 + 14 + bindaddress = "127.0.0.1:1029"; 15 + domain = "idm.freshly.space"; 16 + origin = "https://idm.freshly.space"; 17 + 18 + tls_key = "/var/lib/kanidm/key.pem"; 19 + tls_chain = "/var/lib/kanidm/cert.pem"; 20 + 21 + http_client_address_info.x-forward-for = [ 22 + "127.0.0.1" 23 + "127.0.0.0/8" 24 + ]; 25 + }; 26 + 27 + clientSettings.uri = "https://idm.freshly.space"; 28 + }; 29 + 30 + services.nginx.enable = true; 31 + services.nginx.virtualHosts."idm.freshly.space" = { 32 + addSSL = true; 33 + enableACME = true; 34 + acmeRoot = null; 35 + 36 + locations."/" = { 37 + proxyPass = "https://127.0.0.1:1029"; 38 + recommendedProxySettings = true; 39 + extraConfig = "proxy_http_version 1.1;"; 40 + }; 41 + }; 42 + 43 + security.acme.certs."idm.freshly.space" = { 44 + postRun = '' 45 + cp -Lv {cert,key,chain}.pem /var/lib/kanidm/ 46 + chown kanidm:kanidm /var/lib/kanidm/{cert,key,chain}.pem 47 + chmod 400 /var/lib/kanidm/{cert,key,chain}.pem 48 + ''; 49 + reloadServices = [ "kanidm.service" ]; 50 + }; 51 + 52 + clicks.storage.impermanence.persist.directories = [ 53 + "/var/lib/kanidm" 54 + ]; 55 + }
+33
packetmix/systems/teal/midnight.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + services.nginx.enable = true; 7 + services.nginx.virtualHosts."login.clicks.codes" = { 8 + addSSL = true; 9 + enableACME = true; 10 + acmeRoot = null; 11 + 12 + serverAliases = [ "www.login.clicks.codes" ]; 13 + 14 + locations."/" = { 15 + proxyPass = "https://100.64.0.11:443"; 16 + recommendedProxySettings = true; 17 + proxyWebsockets = true; 18 + }; 19 + }; 20 + services.nginx.virtualHosts."mail.clicks.codes" = { 21 + addSSL = true; 22 + enableACME = true; 23 + acmeRoot = null; 24 + 25 + serverAliases = [ "www.mail.clicks.codes" ]; 26 + 27 + locations."/" = { 28 + proxyPass = "https://100.64.0.11:443"; 29 + recommendedProxySettings = true; 30 + proxyWebsockets = true; 31 + }; 32 + }; 33 + }
+10
packetmix/systems/teal/networking.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + networking.firewall.allowedTCPPorts = [ 7 + 80 8 + 443 9 + ]; 10 + }
+7
packetmix/systems/teal/secrets.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + clicks.storage.impermanence.persist.directories = [ "/secrets" ]; 7 + }
+47
packetmix/systems/teal/silverbullet.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + project, 7 + system, 8 + config, 9 + ... 10 + }: 11 + { 12 + clicks.storage.impermanence.persist.directories = [ 13 + { 14 + directory = config.services.silverbullet.spaceDir; 15 + mode = "0700"; 16 + defaultPerms.mode = "0700"; 17 + } 18 + ]; 19 + 20 + services.silverbullet = { 21 + enable = true; 22 + listenPort = 1026; 23 + listenAddress = "127.0.0.1"; 24 + package = project.inputs.nixos-unstable.result.${system}.silverbullet; 25 + }; 26 + 27 + services.nginx.enable = true; 28 + services.nginx.virtualHosts."silverbullet.clicks.codes" = { 29 + listenAddresses = [ "localhost.tailscale" ]; 30 + 31 + addSSL = true; 32 + enableACME = true; 33 + acmeRoot = null; 34 + 35 + serverAliases = [ "www.silverbullet.clicks.codes" ]; 36 + 37 + locations."/" = { 38 + proxyPass = "http://127.0.0.1:1026"; 39 + recommendedProxySettings = true; 40 + }; 41 + }; 42 + 43 + services.nginx.tailscaleAuth = { 44 + enable = true; 45 + virtualHosts = [ "silverbullet.clicks.codes" ]; 46 + }; 47 + }
+242
packetmix/systems/teal/stalwart.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + project, 7 + config, 8 + pkgs, 9 + lib, 10 + ... 11 + }: 12 + let 13 + mail_domains = [ 14 + "a.starrysky.fyi" 15 + "clicks.codes" 16 + "clicksminuteper.net" 17 + "coded.codes" 18 + "companies.clicks.codes" 19 + "companies.freshlybakedca.ke" 20 + "companies.starrysky.fyi" 21 + "companies.thecoded.prof" 22 + "freshly.space" 23 + "freshlybakedca.ke" 24 + "hopescaramels.com" 25 + "starrysky.fyi" 26 + "thecoded.prof" 27 + "turquoise.fyi" 28 + ]; 29 + in 30 + { 31 + disabledModules = [ "services/mail/stalwart-mail.nix" ]; 32 + imports = [ "${project.inputs.nixos-unstable.src}/nixos/modules/services/mail/stalwart-mail.nix" ]; 33 + 34 + config = { 35 + services.headscale.settings.dns.extra_records = [ 36 + { 37 + # mail.freshly.space -> teal 38 + name = "mail.freshly.space"; 39 + type = "A"; 40 + value = "100.64.0.5"; 41 + } 42 + ]; 43 + 44 + services.stalwart-mail = { 45 + enable = true; 46 + openFirewall = true; 47 + 48 + package = 49 + if project.lib.ci then 50 + project.inputs.nixos-unstable.result.x86_64-linux.stalwart-mail 51 + else 52 + project.inputs.nixos-unstable.result.x86_64-linux.stalwart-mail-enterprise; 53 + 54 + settings = { 55 + config.local-keys = [ 56 + "store.*" 57 + "directory.*" 58 + "tracer.*" 59 + "!server.blocked-ip.*" 60 + "!server.allowed-ip.*" 61 + "server.*" 62 + "authentication.fallback-admin.*" 63 + "cluster.*" 64 + "config.local-keys.*" 65 + "storage.data" 66 + "storage.blob" 67 + "storage.lookup" 68 + "storage.fts" 69 + "storage.directory" 70 + "certificate.*" 71 + 72 + "resolver.public-suffix.*" 73 + "file-storage.max-size" 74 + "auth.dkim.sign.*" 75 + "resolver.type" 76 + "webadmin.path" 77 + "webadmin.resource" 78 + "spam-filter.resource" 79 + ]; 80 + auth.dkim.sign = "false"; 81 + certificate = { 82 + "mail.freshly.space" = { 83 + cert = "%{file:${config.security.acme.certs."mail.freshly.space".directory}/fullchain.pem}%"; 84 + private-key = "%{file:${config.security.acme.certs."mail.freshly.space".directory}/key.pem}%"; 85 + default = true; 86 + }; 87 + } 88 + // (lib.pipe mail_domains [ 89 + (map (domain: { 90 + name = domain; 91 + value = { 92 + cert = "%{file:${config.security.acme.certs.${domain}.directory}/fullchain.pem}%"; 93 + private-key = "%{file:${config.security.acme.certs.${domain}.directory}/key.pem}%"; 94 + }; 95 + })) 96 + builtins.listToAttrs 97 + ]); 98 + file-storage.max-size = 8589934592; 99 + server = { 100 + hostname = "mail.freshly.space"; 101 + listener = { 102 + smtp = { 103 + protocol = "smtp"; 104 + bind = "0.0.0.0:25"; 105 + }; 106 + submissions = { 107 + protocol = "smtp"; 108 + bind = "0.0.0.0:465"; 109 + tls = { 110 + enable = true; 111 + implicit = true; 112 + }; 113 + }; 114 + imaps = { 115 + protocol = "imap"; 116 + bind = "0.0.0.0:993"; 117 + tls = { 118 + enable = true; 119 + implicit = true; 120 + }; 121 + }; 122 + web = { 123 + protocol = "http"; 124 + bind = "127.0.0.1:1027"; 125 + url = "https://mail.freshly.space"; 126 + }; 127 + sieve = { 128 + protocol = "managesieve"; 129 + bind = "0.0.0.0:4190"; 130 + tls = { 131 + enable = true; 132 + implicit = false; # We can't use =true as clients seem to not support it 133 + }; 134 + }; 135 + }; 136 + }; 137 + http.url = "'https://mail.freshly.space'"; 138 + store.db = { 139 + type = "postgresql"; 140 + host = "/run/postgresql"; 141 + database = "stalwart-mail"; 142 + query = { 143 + name = "SELECT name, type, secret, description, quota FROM accounts WHERE name = $1 AND active = true"; 144 + members = "SELECT member_of FROM group_members WHERE name = $1"; 145 + recipients = "SELECT name FROM emails WHERE address = $1 ORDER BY name ASC"; 146 + emails = "SELECT address FROM emails WHERE name = $1 ORDER BY type DESC, address ASC"; 147 + }; 148 + }; 149 + tracer.stdout.level = "debug"; 150 + }; 151 + }; 152 + 153 + systemd.services.stalwart-mail = { 154 + requires = [ "postgresql.service" ]; 155 + wants = [ 156 + "acme-finished-mail.freshly.space.target" 157 + ] 158 + ++ (map (domain: "acme-finished-${domain}.target") mail_domains); 159 + after = [ 160 + "acme-selfsigned-mail.freshly.space.service" 161 + "acme-mail.freshly.space.service" 162 + "postgresql.service" 163 + ] 164 + ++ (map (domain: "acme-selfsigned-${domain}.service") mail_domains) 165 + ++ (map (domain: "acme-${domain}.service") mail_domains); 166 + serviceConfig.RestrictAddressFamilies = lib.mkForce [ ]; # We need the default restricted address families to access the postgres socket 167 + }; 168 + 169 + services.nginx.enable = true; 170 + services.nginx.virtualHosts = 171 + lib.pipe 172 + ( 173 + [ "mail.freshly.space" ] 174 + ++ (map (domain: [ 175 + "autoconfig.${domain}" 176 + "autodiscover.${domain}" 177 + "mta-sts.${domain}" 178 + ]) mail_domains) 179 + ) 180 + [ 181 + lib.flatten 182 + (map (domain: { 183 + name = domain; 184 + value = { 185 + addSSL = true; 186 + enableACME = true; 187 + acmeRoot = null; 188 + 189 + locations."/" = { 190 + proxyPass = "http://127.0.0.1:1027"; 191 + recommendedProxySettings = true; 192 + proxyWebsockets = true; 193 + }; 194 + 195 + extraConfig = '' 196 + client_max_body_size 1024M; 197 + ''; 198 + }; 199 + })) 200 + builtins.listToAttrs 201 + ]; 202 + 203 + security.acme.certs = 204 + (lib.pipe mail_domains [ 205 + (map (domain: { 206 + name = domain; 207 + value = { 208 + group = "nginx+stalwart-mail"; 209 + extraDomainNames = [ 210 + "autoconfig.${domain}" 211 + "autodiscover.${domain}" 212 + "mta-sts.${domain}" 213 + ]; 214 + reloadServices = [ "stalwart-mail.service" ]; 215 + }; 216 + })) 217 + builtins.listToAttrs 218 + ]) 219 + // { 220 + "mail.freshly.space" = { 221 + reloadServices = [ "stalwart-mail.service" ]; 222 + group = "nginx+stalwart-mail"; 223 + }; 224 + }; 225 + 226 + users.groups."nginx+stalwart-mail".members = [ 227 + "nginx" 228 + "stalwart-mail" 229 + ]; 230 + 231 + services.postgresql = { 232 + enable = true; 233 + ensureDatabases = [ "stalwart-mail" ]; 234 + ensureUsers = [ 235 + { 236 + name = "stalwart-mail"; 237 + ensureDBOwnership = true; 238 + } 239 + ]; 240 + }; 241 + }; 242 + }
+13
packetmix/systems/teal/tailscale.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + networking.hosts."100.64.0.5" = [ "localhost.tailscale" ]; 7 + 8 + services.nginx.defaultListenAddresses = [ 9 + "0.0.0.0" 10 + "[::0]" 11 + "localhost.tailscale" 12 + ]; 13 + }
+104
packetmix/systems/teal/vaultwarden.nix
··· 1 + # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 + # 3 + # SPDX-License-Identifier: MIT 4 + 5 + { 6 + pkgs, 7 + lib, 8 + config, 9 + ... 10 + }: 11 + { 12 + services.vaultwarden = { 13 + enable = true; 14 + dbBackend = "postgresql"; 15 + 16 + config = { 17 + # Server Settings 18 + DOMAIN = "https://vaultwarden.clicks.codes"; # Not moving off the clicks domain due to passkey migration - maybe at a later date... 19 + ROCKET_ADDRESS = "127.0.0.1"; 20 + ROCKET_PORT = 1028; 21 + 22 + # Mail Settings 23 + SMTP_HOST = "mail.freshly.space"; 24 + SMTP_FROM = "vaultwarden@clicks.codes"; 25 + SMTP_FROM_NAME = "Clicks vaultwarden"; 26 + SMTP_SECURITY = "force_tls"; 27 + SMTP_PORT = 465; 28 + SMTP_USERNAME = "automated@freshly.space"; 29 + 30 + REQUIRE_DEVICE_EMAIL = true; 31 + 32 + # General Settings 33 + SIGNUPS_ALLOWED = false; 34 + INVITATIONS_ALLOWED = true; 35 + SIGNUPS_DOMAINS_WHITELIST = builtins.concatStringsSep "," [ 36 + "a.starrysky.fyi" 37 + "clicks.codes" 38 + "freshlybakedca.ke" 39 + "hopescaramels.com" 40 + "thecoded.prof" 41 + "trans.gg" 42 + "turquoise.fyi" 43 + ]; 44 + # This is similar to our mail domains, with the following changes 45 + # - Add trans.gg 46 + # - Remove special purpose domains 47 + # - Remove deprecated domains 48 + # - Deduplicate to a single canonical domain per group 49 + SIGNUPS_VERIFY = true; 50 + 51 + DISABLE_2FA_REMEMBER = true; 52 + 53 + IP_HEADER = "X-Forwarded-For"; 54 + 55 + # YubiKey Settings 56 + YUBICO_CLIENT_ID = "89788"; 57 + 58 + ORG_ENABLE_GROUPS = true; 59 + # I have looked at the risks. They seem relatively small in comparison 60 + # to the utility (stuff like sync issues if you don't refresh your page) 61 + # Additionally, we have run with this in production for a significant 62 + # amount of time with no noticed adverse effects... 63 + 64 + DATABASE_URL = "postgresql:///vaultwarden?host=/run/postgresql"; 65 + }; 66 + 67 + environmentFile = "/secrets/vaultwarden/secrets.env"; 68 + }; 69 + 70 + systemd.services.vaultwarden = { 71 + requires = [ "postgresql.service" ]; 72 + after = [ "postgresql.service" ]; 73 + }; 74 + 75 + services.postgresql = { 76 + enable = true; 77 + ensureDatabases = [ "vaultwarden" ]; 78 + ensureUsers = [ 79 + { 80 + name = "vaultwarden"; 81 + ensureDBOwnership = true; 82 + 83 + } 84 + ]; 85 + }; 86 + 87 + services.nginx.enable = true; 88 + services.nginx.virtualHosts."vaultwarden.clicks.codes" = { 89 + addSSL = true; 90 + enableACME = true; 91 + acmeRoot = null; 92 + 93 + locations."/" = { 94 + proxyPass = "http://127.0.0.1:1028"; 95 + recommendedProxySettings = true; 96 + proxyWebsockets = true; 97 + }; 98 + }; 99 + 100 + clicks.storage.impermanence.persist.directories = [ 101 + "/var/lib/postgresql" 102 + "/var/lib/vaultwarden" 103 + ]; 104 + }