diff --git a/.github/workflows/check-flake.yml b/.github/workflows/check-flake.yml new file mode 100644 index 000000000..542a0c2c3 --- /dev/null +++ b/.github/workflows/check-flake.yml @@ -0,0 +1,20 @@ +name: Check Nix flake +on: + pull_request: + push: + branches: + - main +jobs: + check-flake: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@main + - uses: DeterminateSystems/magic-nix-cache-action@main + - name: Check Nix flake inputs + uses: DeterminateSystems/flake-checker-action@v5 + with: + fail-mode: true + send-statistics: false + - name: Run nix flake check + run: nix flake check diff --git a/.github/workflows/update-flake-dependencies.yml b/.github/workflows/update-flake-dependencies.yml new file mode 100644 index 000000000..7f7f7d55d --- /dev/null +++ b/.github/workflows/update-flake-dependencies.yml @@ -0,0 +1,37 @@ +# CI job to periodically (once a week) update flake.lock +name: Update flake dependencies + +on: + schedule: + - cron: '0 16 * * 5' + workflow_dispatch: # for allowing manual triggers of the workflow + +jobs: + update-dependencies: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@main + - uses: DeterminateSystems/magic-nix-cache-action@main + - name: update flake.lock + run: nix flake update + - name: Create signed commit with flake.lock changes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FILE_TO_COMMIT: flake.lock + COMMIT_BRANCH: automation/update-flake-dependencies + COMMIT_MESSAGE: "chore(nix): Update Flake dependencies" + run: | + # make sure something actually changed first, if not, no updates required + if [[ `git status --porcelain` ]]; then + # create the branch on the remote + git branch "$COMMIT_BRANCH" + git push -u origin "$COMMIT_BRANCH" + # commit via the GitHub API so we get automatic commit signing + gh api --method PUT /repos/1Password/shell-plugins/contents/$FILE_TO_COMMIT \ + --field message="$COMMIT_MESSAGE" \ + --field content=@<(base64 -i $FILE_TO_COMMIT) \ + --field branch="$COMMIT_BRANCH" \ + --field sha="$(git rev-parse $COMMIT_BRANCH:$FILE_TO_COMMIT)" + gh pr create --title "[automation]: Update Flake dependencies" --body "This is an automated PR to update \`flake.lock\`" --reviewer mrjones2014 --base main --head $COMMIT_BRANCH + fi diff --git a/.gitignore b/.gitignore index 10710b115..8ed151494 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,7 @@ plugins/registry.json # 1Password .op + +# direnv +.direnv +.envrc diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..20f0a1fcf --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1709126324, + "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "d465f4819400de7c8d874d50b982301f28a84605", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1709675310, + "narHash": "sha256-w61tqFEmuJ+/1rAwU7nkYZ+dN6sLwyobfLwX2Yn42FE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "43d259f8d726113fac056e8bb17d5ac2dea3e0a8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..fcd773003 --- /dev/null +++ b/flake.nix @@ -0,0 +1,18 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils = { url = "github:numtide/flake-utils"; }; + }; + outputs = { nixpkgs, flake-utils, ... }: + (flake-utils.lib.eachDefaultSystem (system: + let pkgs = nixpkgs.legacyPackages.${system}; + in { + devShells.default = pkgs.mkShell { + name = "Shell with Go toolchain"; + packages = with pkgs; [ go gopls ]; + }; + })) // { + nixosModules.default = import ./nix/nixos.nix; + hmModules.default = import ./nix/home-manager.nix; + }; +} diff --git a/nix/home-manager.nix b/nix/home-manager.nix new file mode 100644 index 000000000..590a1fa3b --- /dev/null +++ b/nix/home-manager.nix @@ -0,0 +1,7 @@ +{ pkgs, lib, config, ... }: +import ./shell-plugins.nix { + inherit pkgs; + inherit lib; + inherit config; + is-home-manager = true; +} diff --git a/nix/nixos.nix b/nix/nixos.nix new file mode 100644 index 000000000..7ae12ba3c --- /dev/null +++ b/nix/nixos.nix @@ -0,0 +1,7 @@ +{ pkgs, lib, config, ... }: +import ./shell-plugins.nix { + inherit pkgs; + inherit lib; + inherit config; + is-home-manager = false; +} diff --git a/nix/shell-plugins.nix b/nix/shell-plugins.nix new file mode 100644 index 000000000..907bf2f5a --- /dev/null +++ b/nix/shell-plugins.nix @@ -0,0 +1,85 @@ +{ pkgs, lib, config, is-home-manager, ... }: +with lib; +let + cfg = config.programs._1password-shell-plugins; + + supported_plugins = splitString "\n" (lib.readFile "${ + # get the list of supported plugin executable names + pkgs.runCommand "op-plugin-list" { } + # 1Password CLI tries to create the config directory automatically, so set a temp XDG_CONFIG_HOME + # since we don't actually need it for this + "mkdir $out && XDG_CONFIG_HOME=$out ${pkgs._1password}/bin/op plugin list | cut -d ' ' -f1 | tail -n +2 > $out/plugins.txt" + }/plugins.txt"); + getExeName = package: + # NOTE: SAFETY: This is okay because the `packages` list is also referred + # to below as `home.packages = packages;` or `environment.systemPackages = packages;` + # depending on if it's using `home-manager` or not; this means that Nix can still + # compute the dependency tree, even though we're discarding string context here, + # since the packages are still referred to below without discarding string context. + strings.unsafeDiscardStringContext (baseNameOf (getExe package)); +in { + options = { + programs._1password-shell-plugins = { + enable = mkEnableOption "1Password Shell Plugins"; + plugins = mkOption { + type = types.listOf types.package; + default = [ ]; + example = literalExpression '' + with pkgs; [ + gh + awscli2 + cachix + ] + ''; + description = + "CLI Packages to enable 1Password Shell Plugins for; ensure that a Shell Plugin exists by checking the docs: https://developer.1password.com/docs/cli/shell-plugins/"; + # this is a bit of a hack to do option validation; + # ensure that the list of packages include only packages + # for which the executable has a supported 1Password Shell Plugin + apply = package_list: + map (package: + if (elem (getExeName package) supported_plugins) then + package + else + abort "${ + getExeName package + } is not a valid 1Password Shell Plugin. A list of supported plugins can be found by running `op plugin list` or at: https://developer.1password.com/docs/cli/shell-plugins/") + package_list; + }; + }; + }; + + config = let + # executable names as strings, e.g. `pkgs.gh` => `"gh"`, `pkgs.awscli2` => `"aws"` + pkg-exe-names = map getExeName cfg.plugins; + # Explanation: + # Map over `cfg.plugins` (the value of the `plugins` option provided by the user) + # and for each package specified, get the executable name, then create a shell alias + # of the form: + # `alias {pkg}="op plugin run -- {pkg}"` + # where `{pkg}` is the executable name of the package + aliases = listToAttrs (map (package: { + name = package; + value = "op plugin run -- ${package}"; + }) pkg-exe-names); + packages = [ pkgs._1password ] ++ cfg.plugins; + in mkIf cfg.enable (mkMerge [ + ({ + programs = { + bash.shellAliases = aliases; + zsh.shellAliases = aliases; + fish.shellAliases = aliases; + }; + } // optionalAttrs is-home-manager { + home = { + inherit packages; + sessionVariables = { OP_PLUGINS_SOURCED = "1"; }; + }; + } // optionalAttrs (!is-home-manager) { + environment = { + systemPackages = packages; + variables = { OP_PLUGINS_SOURCED = "1"; }; + }; + }) + ]); +}