|
1 | | -{ pkgs, lib, config, is-home-manager, ... }: |
| 1 | +{ |
| 2 | + pkgs, |
| 3 | + lib, |
| 4 | + config, |
| 5 | + is-home-manager, |
| 6 | + ... |
| 7 | +}: |
2 | 8 | with lib; |
3 | 9 | let |
4 | 10 | cfg = config.programs._1password-shell-plugins; |
5 | 11 |
|
6 | | - supported_plugins = splitString "\n" ( |
7 | | - lib.readFile "${ |
8 | | - # get the list of supported plugin executable names |
9 | | - pkgs.runCommand "op-plugin-list" { } |
10 | | - # 1Password CLI tries to create the config directory automatically, so set a temp XDG_CONFIG_HOME |
11 | | - # since we don't actually need it for this |
12 | | - "mkdir $out && XDG_CONFIG_HOME=$out ${ |
13 | | - if cfg.package != null then cfg.package else pkgs._1password-cli |
14 | | - }/bin/op plugin list | cut -d ' ' -f1 | tail -n +2 > $out/plugins.txt" |
15 | | - }/plugins.txt" |
16 | | - ); |
| 12 | + opPkg = if cfg.package != null then cfg.package else pkgs._1password-cli; |
| 13 | + |
17 | 14 | getExeName = |
18 | 15 | package: |
19 | 16 | # NOTE: SAFETY: This is okay because the `packages` list is also referred |
|
22 | 19 | # compute the dependency tree, even though we're discarding string context here, |
23 | 20 | # since the packages are still referred to below without discarding string context. |
24 | 21 | strings.unsafeDiscardStringContext (baseNameOf (getExe package)); |
| 22 | + |
| 23 | + mkPluginSupportCheck = |
| 24 | + pluginExeNames: |
| 25 | + let |
| 26 | + configuredFile = pkgs.writeText "op-configured-plugins.txt" ( |
| 27 | + # ensure trailing newline |
| 28 | + lib.concatStringsSep "\n" pluginExeNames + "\n" |
| 29 | + ); |
| 30 | + in |
| 31 | + pkgs.runCommand "op-shell-plugins-support-check" |
| 32 | + { |
| 33 | + nativeBuildInputs = [ |
| 34 | + pkgs.coreutils |
| 35 | + pkgs.gnugrep |
| 36 | + pkgs.gawk |
| 37 | + pkgs.gnused |
| 38 | + ]; |
| 39 | + } |
| 40 | + '' |
| 41 | + set -euo pipefail |
| 42 | +
|
| 43 | + export XDG_CONFIG_HOME="$TMPDIR/xdg-config" |
| 44 | + mkdir -p "$XDG_CONFIG_HOME" |
| 45 | +
|
| 46 | + "${opPkg}/bin/op" plugin list \ |
| 47 | + | awk 'NR>1 { print $1 }' \ |
| 48 | + | sed '/^$/d' \ |
| 49 | + | sort -u > supported.txt |
| 50 | +
|
| 51 | + if [ ! -s supported.txt ]; then |
| 52 | + echo "ERROR: \`op plugin list\` produced no supported plugins (unexpected output or CLI failure)." >&2 |
| 53 | + echo "Raw output was:" >&2 |
| 54 | + "${opPkg}/bin/op" plugin list >&2 || true |
| 55 | + exit 1 |
| 56 | + fi |
| 57 | +
|
| 58 | + missing=0 |
| 59 | + while IFS= read -r plugin; do |
| 60 | + [ -z "$plugin" ] && continue |
| 61 | + if ! grep -Fxq "$plugin" supported.txt; then |
| 62 | + echo "ERROR: Configured plugin '$plugin' is not supported by this op binary (\`${opPkg.name or "op"}\`)." >&2 |
| 63 | + missing=1 |
| 64 | + fi |
| 65 | + done < "${configuredFile}" |
| 66 | +
|
| 67 | + if [ "$missing" -ne 0 ]; then |
| 68 | + echo "" >&2 |
| 69 | + echo "Supported plugins according to \`op plugin list\`:" >&2 |
| 70 | + cat supported.txt >&2 |
| 71 | + exit 1 |
| 72 | + fi |
| 73 | + mkdir -p "$out" |
| 74 | + echo "Plugin support check passed." > "$out/check.txt" |
| 75 | +
|
| 76 | + ''; |
| 77 | + |
25 | 78 | in |
26 | 79 | { |
27 | 80 | options = { |
|
39 | 92 | ] |
40 | 93 | ''; |
41 | 94 | 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/"; |
42 | | - # this is a bit of a hack to do option validation; |
43 | | - # ensure that the list of packages include only packages |
44 | | - # for which the executable has a supported 1Password Shell Plugin |
45 | | - apply = |
46 | | - package_list: |
47 | | - map |
48 | | - ( |
49 | | - package: |
50 | | - if (elem (getExeName package) supported_plugins) then |
51 | | - package |
52 | | - else |
53 | | - 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/" |
54 | | - ) |
55 | | - package_list; |
| 95 | + |
56 | 96 | }; |
57 | 97 | }; |
58 | 98 | }; |
59 | 99 |
|
60 | 100 | config = |
61 | 101 | let |
| 102 | + |
62 | 103 | # executable names as strings, e.g. `pkgs.gh` => `"gh"`, `pkgs.awscli2` => `"aws"` |
63 | 104 | pkg-exe-names = map getExeName cfg.plugins; |
| 105 | + plugin-support-check = mkPluginSupportCheck pkg-exe-names; |
64 | 106 | # Explanation: |
65 | 107 | # Map over `cfg.plugins` (the value of the `plugins` option provided by the user) |
66 | 108 | # and for each package specified, get the executable name, then create a shell function |
|
80 | 122 | # end |
81 | 123 | # ``` |
82 | 124 | # where `{pkg}` is the executable name of the package |
83 | | - posixFunctions = map |
84 | | - (package: '' |
85 | | - ${package}() { |
86 | | - op plugin run -- ${package} "$@"; |
87 | | - } |
88 | | - '') |
89 | | - pkg-exe-names; |
90 | | - fishFunctions = map |
91 | | - (package: '' |
92 | | - function ${package} --wraps "${package}" --description "1Password Shell Plugin for ${package}" |
93 | | - op plugin run -- ${package} $argv |
94 | | - end |
95 | | - '') |
96 | | - pkg-exe-names; |
| 125 | + posixFunctions = map (package: '' |
| 126 | + ${package}() { |
| 127 | + op plugin run -- ${package} "$@"; |
| 128 | + } |
| 129 | + '') pkg-exe-names; |
| 130 | + fishFunctions = map (package: '' |
| 131 | + function ${package} --wraps "${package}" --description "1Password Shell Plugin for ${package}" |
| 132 | + op plugin run -- ${package} $argv |
| 133 | + end |
| 134 | + '') pkg-exe-names; |
97 | 135 | packages = lib.optional (cfg.package != null) cfg.package ++ cfg.plugins; |
98 | 136 | initExtraPosix = strings.concatStringsSep "\n" posixFunctions; |
99 | 137 | in |
|
102 | 140 | programs.fish.interactiveShellInit = strings.concatStringsSep "\n" fishFunctions; |
103 | 141 | } |
104 | 142 | (optionalAttrs is-home-manager { |
| 143 | + home.checks = [ plugin-support-check ]; |
105 | 144 | programs = { |
106 | 145 | # for the Bash and Zsh home-manager modules, |
107 | 146 | # the initExtra/initContent option is equivalent to Fish's interactiveShellInit |
|
110 | 149 | }; |
111 | 150 | home = { |
112 | 151 | inherit packages; |
113 | | - sessionVariables = { OP_PLUGINS_SOURCED = "1"; }; |
| 152 | + sessionVariables = { |
| 153 | + OP_PLUGINS_SOURCED = "1"; |
| 154 | + }; |
114 | 155 | }; |
115 | 156 | }) |
116 | 157 | (optionalAttrs (!is-home-manager) { |
| 158 | + system.checks = [ plugin-support-check ]; |
117 | 159 | programs = { |
118 | | - bash.interactiveShellInit = |
119 | | - strings.concatStringsSep "\n" posixFunctions; |
| 160 | + bash.interactiveShellInit = strings.concatStringsSep "\n" posixFunctions; |
120 | 161 | zsh.interactiveShellInit = strings.concatStringsSep "\n" posixFunctions; |
121 | 162 | }; |
122 | 163 | environment = { |
123 | 164 | systemPackages = packages; |
124 | | - variables = { OP_PLUGINS_SOURCED = "1"; }; |
| 165 | + variables = { |
| 166 | + OP_PLUGINS_SOURCED = "1"; |
| 167 | + }; |
125 | 168 | }; |
126 | 169 | }) |
127 | 170 | ]); |
128 | 171 | } |
129 | | - |
|
0 commit comments