tangled
alpha
login
or
join now
dunkirk.sh
/
dots
3
fork
atom
Kieran's opinionated (and probably slightly dumb) nix config
3
fork
atom
overview
issues
pulls
pipelines
feat: add pbnj
dunkirk.sh
2 months ago
27d4c9fd
0738d054
verified
This commit was signed with the committer's
known signature
.
dunkirk.sh
SSH Key Fingerprint:
SHA256:DqcG0RXYExE26KiWo3VxJnsxswN1QNfTBvB+bdSpk80=
+731
10 changed files
expand all
collapse all
unified
split
machines
atalanta
default.nix
home
default.nix
ember
default.nix
modules
home
apps
pbnj
completions
pbnj.bash
pbnj.fish
pbnj.zsh
default.nix
pbnj.1.md
secrets
pbnj.age
secrets.nix
+4
machines/atalanta/default.nix
···
113
113
file = ../../secrets/bore/auth-token.age;
114
114
owner = "kierank";
115
115
};
116
116
+
pbnj = {
117
117
+
file = ../../secrets/pbnj.age;
118
118
+
owner = "kierank";
119
119
+
};
116
120
};
117
121
118
122
environment.variables = {
+5
machines/atalanta/home/default.nix
···
40
40
enable = true;
41
41
authTokenFile = osConfig.age.secrets."bore/auth-token".path;
42
42
};
43
43
+
pbnj = {
44
44
+
enable = true;
45
45
+
host = "https://pbnj.dunkirk.sh";
46
46
+
authKeyFile = osConfig.age.secrets.pbnj.path;
47
47
+
};
43
48
ssh = {
44
49
enable = true;
45
50
+4
machines/ember/default.nix
···
32
32
apps = {
33
33
helix.enable = true;
34
34
};
35
35
+
pbnj = {
36
36
+
enable = true;
37
37
+
host = "https://pbnj.dunkirk.sh";
38
38
+
};
35
39
ssh = {
36
40
enable = true;
37
41
zmx.enable = true;
+39
modules/home/apps/pbnj/completions/pbnj.bash
···
1
1
+
_pbnj() {
2
2
+
local cur prev words cword
3
3
+
_init_completion || return
4
4
+
5
5
+
local commands="init config list delete delete-all"
6
6
+
7
7
+
case $prev in
8
8
+
-L|--language)
9
9
+
COMPREPLY=($(compgen -W "go python javascript typescript rust ruby java c cpp csharp php bash html css json yaml xml sql markdown swift kotlin scala nix lua vim toml" -- "$cur"))
10
10
+
return
11
11
+
;;
12
12
+
-f|--filename|-u|--update|-k|--key)
13
13
+
return
14
14
+
;;
15
15
+
delete|-d)
16
16
+
return
17
17
+
;;
18
18
+
list)
19
19
+
return
20
20
+
;;
21
21
+
esac
22
22
+
23
23
+
case $cur in
24
24
+
-*)
25
25
+
COMPREPLY=($(compgen -W "-L --language -f --filename -p --private -k --key -u --update -n --no-copy -l --list -h --help" -- "$cur"))
26
26
+
return
27
27
+
;;
28
28
+
esac
29
29
+
30
30
+
if [[ $cword -eq 1 ]]; then
31
31
+
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
32
32
+
COMPREPLY+=($(compgen -f -- "$cur"))
33
33
+
return
34
34
+
fi
35
35
+
36
36
+
_filedir
37
37
+
}
38
38
+
39
39
+
complete -F _pbnj pbnj
+26
modules/home/apps/pbnj/completions/pbnj.fish
···
1
1
+
# pbnj completions for fish
2
2
+
3
3
+
set -l commands init config list delete delete-all
4
4
+
set -l languages go python javascript typescript rust ruby java c cpp csharp php bash html css json yaml xml sql markdown swift kotlin scala nix lua vim toml
5
5
+
6
6
+
complete -c pbnj -f
7
7
+
8
8
+
# Commands
9
9
+
complete -c pbnj -n "not __fish_seen_subcommand_from $commands" -a init -d "Configure pbnj instance"
10
10
+
complete -c pbnj -n "not __fish_seen_subcommand_from $commands" -a config -d "Show current configuration"
11
11
+
complete -c pbnj -n "not __fish_seen_subcommand_from $commands" -a list -d "List recent pastes"
12
12
+
complete -c pbnj -n "not __fish_seen_subcommand_from $commands" -a delete -d "Delete a paste"
13
13
+
complete -c pbnj -n "not __fish_seen_subcommand_from $commands" -a delete-all -d "Delete all pastes"
14
14
+
15
15
+
# File completion for default
16
16
+
complete -c pbnj -n "not __fish_seen_subcommand_from $commands" -F
17
17
+
18
18
+
# Options
19
19
+
complete -c pbnj -s L -l language -d "Override language" -xa "$languages"
20
20
+
complete -c pbnj -s f -l filename -d "Set filename" -r
21
21
+
complete -c pbnj -s p -l private -d "Create private paste"
22
22
+
complete -c pbnj -s k -l key -d "Add secret key" -r
23
23
+
complete -c pbnj -s u -l update -d "Update existing paste" -r
24
24
+
complete -c pbnj -s n -l no-copy -d "Don't copy URL"
25
25
+
complete -c pbnj -s l -l list -d "List pastes"
26
26
+
complete -c pbnj -s h -l help -d "Show help"
+57
modules/home/apps/pbnj/completions/pbnj.zsh
···
1
1
+
#compdef pbnj
2
2
+
3
3
+
_pbnj() {
4
4
+
local -a commands
5
5
+
commands=(
6
6
+
'init:Configure pbnj instance'
7
7
+
'config:Show current configuration'
8
8
+
'list:List recent pastes'
9
9
+
'delete:Delete a paste'
10
10
+
'delete-all:Delete all pastes'
11
11
+
)
12
12
+
13
13
+
local -a languages
14
14
+
languages=(go python javascript typescript rust ruby java c cpp csharp php bash html css json yaml xml sql markdown swift kotlin scala nix lua vim toml)
15
15
+
16
16
+
_arguments -C \
17
17
+
'1: :->cmd' \
18
18
+
'*: :->args' \
19
19
+
'-L+[Override language]:language:($languages)' \
20
20
+
'--language+[Override language]:language:($languages)' \
21
21
+
'-f+[Set filename]:filename:_files' \
22
22
+
'--filename+[Set filename]:filename:_files' \
23
23
+
'-p[Create private paste]' \
24
24
+
'--private[Create private paste]' \
25
25
+
'-k+[Add secret key]:key:' \
26
26
+
'--key+[Add secret key]:key:' \
27
27
+
'-u+[Update existing paste]:id:' \
28
28
+
'--update+[Update existing paste]:id:' \
29
29
+
'-n[Do not copy URL]' \
30
30
+
'--no-copy[Do not copy URL]' \
31
31
+
'-l+[List pastes]:limit:' \
32
32
+
'--list+[List pastes]:limit:' \
33
33
+
'-h[Show help]' \
34
34
+
'--help[Show help]'
35
35
+
36
36
+
case $state in
37
37
+
cmd)
38
38
+
_describe -t commands 'command' commands
39
39
+
_files
40
40
+
;;
41
41
+
args)
42
42
+
case $words[2] in
43
43
+
list)
44
44
+
_message 'limit (number)'
45
45
+
;;
46
46
+
delete)
47
47
+
_message 'paste id'
48
48
+
;;
49
49
+
*)
50
50
+
_files
51
51
+
;;
52
52
+
esac
53
53
+
;;
54
54
+
esac
55
55
+
}
56
56
+
57
57
+
_pbnj "$@"
+477
modules/home/apps/pbnj/default.nix
···
1
1
+
{
2
2
+
config,
3
3
+
lib,
4
4
+
pkgs,
5
5
+
...
6
6
+
}:
7
7
+
let
8
8
+
cfg = config.atelier.pbnj;
9
9
+
10
10
+
hostArg = if cfg.host != null then ''"${cfg.host}"'' else "\"\"";
11
11
+
authKeyFileArg = if cfg.authKeyFile != null then ''"${cfg.authKeyFile}"'' else "\"\"";
12
12
+
13
13
+
# Platform-specific clipboard commands
14
14
+
clipboardCopy = if pkgs.stdenv.isDarwin then
15
15
+
"printf '%s' \"$url\" | pbcopy 2>/dev/null"
16
16
+
else
17
17
+
"echo \"$url\" | ${pkgs.wl-clipboard}/bin/wl-copy 2>/dev/null || echo \"$url\" | ${pkgs.xclip}/bin/xclip -selection clipboard 2>/dev/null";
18
18
+
19
19
+
pbnjScript = pkgs.writeShellScript "pbnj" ''
20
20
+
set -e
21
21
+
set -o pipefail
22
22
+
23
23
+
CONFIG_FILE="''${PBNJ_CONFIG:-$HOME/.pbnj.json}"
24
24
+
CONFIGURED_HOST=${hostArg}
25
25
+
CONFIGURED_AUTH_KEY_FILE=${authKeyFileArg}
26
26
+
27
27
+
# Load config
28
28
+
load_config() {
29
29
+
# Priority: env var > nix config > config file
30
30
+
if [ -n "$PBNJ_HOST" ]; then
31
31
+
HOST="$PBNJ_HOST"
32
32
+
elif [ -n "$CONFIGURED_HOST" ]; then
33
33
+
HOST="$CONFIGURED_HOST"
34
34
+
elif [ -f "$CONFIG_FILE" ]; then
35
35
+
HOST=$(${pkgs.jq}/bin/jq -r '.host // empty' "$CONFIG_FILE" 2>/dev/null)
36
36
+
fi
37
37
+
38
38
+
if [ -n "$PBNJ_AUTH_KEY" ]; then
39
39
+
AUTH_KEY="$PBNJ_AUTH_KEY"
40
40
+
elif [ -n "$CONFIGURED_AUTH_KEY_FILE" ] && [ -f "$CONFIGURED_AUTH_KEY_FILE" ]; then
41
41
+
AUTH_KEY=$(${pkgs.coreutils}/bin/cat "$CONFIGURED_AUTH_KEY_FILE")
42
42
+
elif [ -f "$CONFIG_FILE" ]; then
43
43
+
AUTH_KEY=$(${pkgs.jq}/bin/jq -r '.auth_key // empty' "$CONFIG_FILE" 2>/dev/null)
44
44
+
fi
45
45
+
}
46
46
+
47
47
+
check_config() {
48
48
+
if [ -z "$HOST" ] || [ -z "$AUTH_KEY" ]; then
49
49
+
${pkgs.gum}/bin/gum style --foreground 196 "Not configured. Run 'pbnj init' first."
50
50
+
exit 1
51
51
+
fi
52
52
+
}
53
53
+
54
54
+
# Detect language from filename
55
55
+
detect_language() {
56
56
+
local filename="$1"
57
57
+
local ext="''${filename##*.}"
58
58
+
case "$ext" in
59
59
+
go) echo "go" ;;
60
60
+
py) echo "python" ;;
61
61
+
js) echo "javascript" ;;
62
62
+
ts) echo "typescript" ;;
63
63
+
rs) echo "rust" ;;
64
64
+
rb) echo "ruby" ;;
65
65
+
java) echo "java" ;;
66
66
+
c|h) echo "c" ;;
67
67
+
cpp|hpp|cc) echo "cpp" ;;
68
68
+
cs) echo "csharp" ;;
69
69
+
php) echo "php" ;;
70
70
+
sh|bash|zsh) echo "bash" ;;
71
71
+
html) echo "html" ;;
72
72
+
css) echo "css" ;;
73
73
+
json) echo "json" ;;
74
74
+
yaml|yml) echo "yaml" ;;
75
75
+
xml) echo "xml" ;;
76
76
+
sql) echo "sql" ;;
77
77
+
md) echo "markdown" ;;
78
78
+
swift) echo "swift" ;;
79
79
+
kt) echo "kotlin" ;;
80
80
+
scala) echo "scala" ;;
81
81
+
nix) echo "nix" ;;
82
82
+
lua) echo "lua" ;;
83
83
+
vim) echo "vim" ;;
84
84
+
toml) echo "toml" ;;
85
85
+
*) echo "" ;;
86
86
+
esac
87
87
+
}
88
88
+
89
89
+
# Format age
90
90
+
format_age() {
91
91
+
local created="$1"
92
92
+
local now=$(${pkgs.coreutils}/bin/date +%s)
93
93
+
local then=$(${pkgs.coreutils}/bin/date -d "$created" +%s 2>/dev/null || echo "$now")
94
94
+
local diff=$((now - then))
95
95
+
96
96
+
if [ $diff -lt 60 ]; then
97
97
+
echo "just now"
98
98
+
elif [ $diff -lt 3600 ]; then
99
99
+
echo "$((diff / 60))m ago"
100
100
+
elif [ $diff -lt 86400 ]; then
101
101
+
echo "$((diff / 3600))h ago"
102
102
+
elif [ $diff -lt 604800 ]; then
103
103
+
echo "$((diff / 86400))d ago"
104
104
+
else
105
105
+
echo "$((diff / 604800))w ago"
106
106
+
fi
107
107
+
}
108
108
+
109
109
+
# Commands
110
110
+
cmd_init() {
111
111
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "Configure pbnj"
112
112
+
echo
113
113
+
114
114
+
host=$(${pkgs.gum}/bin/gum input --placeholder "https://paste.example.com" --prompt "Host URL: ")
115
115
+
if [ -z "$host" ]; then
116
116
+
${pkgs.gum}/bin/gum style --foreground 196 "No host provided"
117
117
+
exit 1
118
118
+
fi
119
119
+
120
120
+
auth_key=$(${pkgs.gum}/bin/gum input --placeholder "your-auth-key" --prompt "Auth Key: " --password)
121
121
+
if [ -z "$auth_key" ]; then
122
122
+
${pkgs.gum}/bin/gum style --foreground 196 "No auth key provided"
123
123
+
exit 1
124
124
+
fi
125
125
+
126
126
+
# Remove trailing slash
127
127
+
host="''${host%/}"
128
128
+
129
129
+
echo "{\"host\": \"$host\", \"auth_key\": \"$auth_key\"}" > "$CONFIG_FILE"
130
130
+
chmod 600 "$CONFIG_FILE"
131
131
+
132
132
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Configuration saved to $CONFIG_FILE"
133
133
+
}
134
134
+
135
135
+
cmd_config() {
136
136
+
load_config
137
137
+
138
138
+
if [ -z "$HOST" ] || [ -z "$AUTH_KEY" ]; then
139
139
+
${pkgs.gum}/bin/gum style --foreground 117 "Not configured. Run 'pbnj init' first."
140
140
+
exit 0
141
141
+
fi
142
142
+
143
143
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "pbnj config"
144
144
+
echo
145
145
+
echo " Host: $HOST"
146
146
+
# Mask auth key
147
147
+
masked="''${AUTH_KEY:0:4}...''${AUTH_KEY: -4}"
148
148
+
echo " Auth: $masked"
149
149
+
}
150
150
+
151
151
+
cmd_list() {
152
152
+
load_config
153
153
+
check_config
154
154
+
155
155
+
local limit="''${1:-10}"
156
156
+
157
157
+
response=$(${pkgs.curl}/bin/curl -s -f -X GET \
158
158
+
-H "Authorization: Bearer $AUTH_KEY" \
159
159
+
"$HOST/api?limit=$limit" 2>&1) || {
160
160
+
${pkgs.gum}/bin/gum style --foreground 196 "Failed to fetch pastes"
161
161
+
exit 1
162
162
+
}
163
163
+
164
164
+
count=$(echo "$response" | ${pkgs.jq}/bin/jq -r '.pastes | length')
165
165
+
166
166
+
if [ "$count" = "0" ] || [ -z "$count" ]; then
167
167
+
${pkgs.gum}/bin/gum style --foreground 117 "No pastes found."
168
168
+
exit 0
169
169
+
fi
170
170
+
171
171
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "Recent pastes"
172
172
+
echo
173
173
+
174
174
+
echo "$response" | ${pkgs.jq}/bin/jq -r '.pastes[] | "\(.id)|\(.language // "-")|\(.created_at // "-")|\(.filename // "-")"' | while IFS='|' read -r id lang created filename; do
175
175
+
age=$(format_age "$created")
176
176
+
printf " %-24s %-10s %-10s %s\n" "$id" "$lang" "$age" "$filename"
177
177
+
done
178
178
+
179
179
+
echo
180
180
+
${pkgs.gum}/bin/gum style --foreground 117 "Select paste to copy URL:"
181
181
+
182
182
+
selected=$(echo "$response" | ${pkgs.jq}/bin/jq -r '.pastes[].id' | ${pkgs.gum}/bin/gum choose --no-limit=false)
183
183
+
184
184
+
if [ -n "$selected" ]; then
185
185
+
url="$HOST/$selected"
186
186
+
${clipboardCopy} || true
187
187
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Copied: $url"
188
188
+
fi
189
189
+
}
190
190
+
191
191
+
cmd_delete() {
192
192
+
load_config
193
193
+
check_config
194
194
+
195
195
+
local id="$1"
196
196
+
197
197
+
if [ -z "$id" ]; then
198
198
+
${pkgs.gum}/bin/gum style --foreground 196 "Usage: pbnj delete <id>"
199
199
+
exit 1
200
200
+
fi
201
201
+
202
202
+
if ! ${pkgs.gum}/bin/gum confirm "Delete paste $id?"; then
203
203
+
${pkgs.gum}/bin/gum style --foreground 117 "Cancelled."
204
204
+
exit 0
205
205
+
fi
206
206
+
207
207
+
${pkgs.curl}/bin/curl -s -f -X DELETE \
208
208
+
-H "Authorization: Bearer $AUTH_KEY" \
209
209
+
"$HOST/api/$id" >/dev/null || {
210
210
+
${pkgs.gum}/bin/gum style --foreground 196 "Failed to delete paste"
211
211
+
exit 1
212
212
+
}
213
213
+
214
214
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Deleted"
215
215
+
}
216
216
+
217
217
+
cmd_delete_all() {
218
218
+
load_config
219
219
+
check_config
220
220
+
221
221
+
if ! ${pkgs.gum}/bin/gum confirm "Delete ALL pastes? This cannot be undone."; then
222
222
+
${pkgs.gum}/bin/gum style --foreground 117 "Cancelled."
223
223
+
exit 0
224
224
+
fi
225
225
+
226
226
+
${pkgs.curl}/bin/curl -s -f -X DELETE \
227
227
+
-H "Authorization: Bearer $AUTH_KEY" \
228
228
+
"$HOST/api" >/dev/null || {
229
229
+
${pkgs.gum}/bin/gum style --foreground 196 "Failed to delete pastes"
230
230
+
exit 1
231
231
+
}
232
232
+
233
233
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ All pastes deleted"
234
234
+
}
235
235
+
236
236
+
cmd_upload() {
237
237
+
load_config
238
238
+
check_config
239
239
+
240
240
+
local content=""
241
241
+
local filename=""
242
242
+
local language=""
243
243
+
local private="false"
244
244
+
local key=""
245
245
+
local update_id=""
246
246
+
local no_copy="false"
247
247
+
248
248
+
# Parse arguments
249
249
+
while [ $# -gt 0 ]; do
250
250
+
case "$1" in
251
251
+
-L|--language)
252
252
+
language="$2"
253
253
+
shift 2
254
254
+
;;
255
255
+
-f|--filename)
256
256
+
filename="$2"
257
257
+
shift 2
258
258
+
;;
259
259
+
-p|--private)
260
260
+
private="true"
261
261
+
shift
262
262
+
;;
263
263
+
-k|--key)
264
264
+
key="$2"
265
265
+
shift 2
266
266
+
;;
267
267
+
-u|--update)
268
268
+
update_id="$2"
269
269
+
shift 2
270
270
+
;;
271
271
+
-n|--no-copy)
272
272
+
no_copy="true"
273
273
+
shift
274
274
+
;;
275
275
+
-|/dev/stdin)
276
276
+
shift
277
277
+
;;
278
278
+
*)
279
279
+
# Assume it's a file
280
280
+
if [ -f "$1" ]; then
281
281
+
content=$(${pkgs.coreutils}/bin/cat "$1")
282
282
+
if [ -z "$filename" ]; then
283
283
+
filename=$(${pkgs.coreutils}/bin/basename "$1")
284
284
+
fi
285
285
+
else
286
286
+
${pkgs.gum}/bin/gum style --foreground 196 "File not found: $1"
287
287
+
exit 1
288
288
+
fi
289
289
+
shift
290
290
+
;;
291
291
+
esac
292
292
+
done
293
293
+
294
294
+
# Read from stdin if no file provided
295
295
+
if [ -z "$content" ]; then
296
296
+
if [ -t 0 ]; then
297
297
+
${pkgs.gum}/bin/gum style --foreground 196 "No input provided (pipe content or specify a file)"
298
298
+
exit 1
299
299
+
fi
300
300
+
content=$(${pkgs.coreutils}/bin/cat)
301
301
+
fi
302
302
+
303
303
+
# Detect language if not specified
304
304
+
if [ -z "$language" ] && [ -n "$filename" ]; then
305
305
+
language=$(detect_language "$filename")
306
306
+
fi
307
307
+
308
308
+
# Build JSON payload
309
309
+
payload=$(${pkgs.jq}/bin/jq -n \
310
310
+
--arg content "$content" \
311
311
+
--arg filename "$filename" \
312
312
+
--arg language "$language" \
313
313
+
--argjson private "$private" \
314
314
+
--arg key "$key" \
315
315
+
'{code: $content} +
316
316
+
(if $filename != "" then {filename: $filename} else {} end) +
317
317
+
(if $language != "" then {language: $language} else {} end) +
318
318
+
(if $private then {private: true} else {} end) +
319
319
+
(if $key != "" then {key: $key} else {} end)')
320
320
+
321
321
+
# Make request
322
322
+
local method="POST"
323
323
+
local url="$HOST/api"
324
324
+
325
325
+
if [ -n "$update_id" ]; then
326
326
+
method="PUT"
327
327
+
url="$HOST/api/$update_id"
328
328
+
fi
329
329
+
330
330
+
response=$(echo "$payload" | ${pkgs.curl}/bin/curl -s -f -X "$method" \
331
331
+
-H "Content-Type: application/json" \
332
332
+
-H "Authorization: Bearer $AUTH_KEY" \
333
333
+
-d @- \
334
334
+
"$url" 2>&1) || {
335
335
+
${pkgs.gum}/bin/gum style --foreground 196 "Failed to upload paste"
336
336
+
echo "$response" >&2
337
337
+
exit 1
338
338
+
}
339
339
+
340
340
+
paste_id=$(echo "$response" | ${pkgs.jq}/bin/jq -r '.id // empty')
341
341
+
paste_url=$(echo "$response" | ${pkgs.jq}/bin/jq -r '.url // empty')
342
342
+
343
343
+
if [ -z "$paste_url" ] && [ -n "$paste_id" ]; then
344
344
+
paste_url="$HOST/$paste_id"
345
345
+
fi
346
346
+
347
347
+
# Copy to clipboard
348
348
+
if [ "$no_copy" != "true" ]; then
349
349
+
url="$paste_url"
350
350
+
if ${clipboardCopy}; then
351
351
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Copied to clipboard"
352
352
+
fi
353
353
+
fi
354
354
+
355
355
+
${pkgs.gum}/bin/gum style --foreground 35 "$paste_url"
356
356
+
}
357
357
+
358
358
+
# Main
359
359
+
case "''${1:-}" in
360
360
+
init|--init)
361
361
+
cmd_init
362
362
+
;;
363
363
+
config|--show-config)
364
364
+
cmd_config
365
365
+
;;
366
366
+
list)
367
367
+
shift
368
368
+
cmd_list "$@"
369
369
+
;;
370
370
+
-l|--list)
371
371
+
shift
372
372
+
cmd_list "$@"
373
373
+
;;
374
374
+
delete|-d)
375
375
+
shift
376
376
+
cmd_delete "$@"
377
377
+
;;
378
378
+
delete-all|-D)
379
379
+
cmd_delete_all
380
380
+
;;
381
381
+
-h|--help)
382
382
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "pbnj - pastebin CLI"
383
383
+
echo
384
384
+
echo "Usage: pbnj [options] [file]"
385
385
+
echo " cat file | pbnj"
386
386
+
echo
387
387
+
echo "Commands:"
388
388
+
echo " init Configure pbnj instance"
389
389
+
echo " config Show current configuration"
390
390
+
echo " list [n] List recent pastes (default: 10)"
391
391
+
echo " delete <id> Delete a paste"
392
392
+
echo " delete-all Delete all pastes"
393
393
+
echo
394
394
+
echo "Options:"
395
395
+
echo " -L, --language <lang> Override language detection"
396
396
+
echo " -f, --filename <name> Set custom filename"
397
397
+
echo " -p, --private Create private paste"
398
398
+
echo " -k, --key <key> Add secret key"
399
399
+
echo " -u, --update <id> Update existing paste"
400
400
+
echo " -n, --no-copy Don't copy URL to clipboard"
401
401
+
echo " -l, --list [n] List recent pastes"
402
402
+
echo " -h, --help Show this help"
403
403
+
;;
404
404
+
-*)
405
405
+
# Flags for upload
406
406
+
cmd_upload "$@"
407
407
+
;;
408
408
+
"")
409
409
+
# No args, try stdin
410
410
+
cmd_upload "$@"
411
411
+
;;
412
412
+
*)
413
413
+
# File argument
414
414
+
cmd_upload "$@"
415
415
+
;;
416
416
+
esac
417
417
+
'';
418
418
+
419
419
+
pbnj = pkgs.stdenv.mkDerivation {
420
420
+
pname = "pbnj";
421
421
+
version = "1.0";
422
422
+
423
423
+
dontUnpack = true;
424
424
+
425
425
+
nativeBuildInputs = with pkgs; [ pandoc installShellFiles ];
426
426
+
427
427
+
manPageSrc = ./pbnj.1.md;
428
428
+
bashCompletionSrc = ./completions/pbnj.bash;
429
429
+
zshCompletionSrc = ./completions/pbnj.zsh;
430
430
+
fishCompletionSrc = ./completions/pbnj.fish;
431
431
+
432
432
+
buildPhase = ''
433
433
+
${pkgs.pandoc}/bin/pandoc -s -t man $manPageSrc -o pbnj.1
434
434
+
'';
435
435
+
436
436
+
installPhase = ''
437
437
+
mkdir -p $out/bin
438
438
+
439
439
+
cp ${pbnjScript} $out/bin/pbnj
440
440
+
chmod +x $out/bin/pbnj
441
441
+
442
442
+
installManPage pbnj.1
443
443
+
444
444
+
installShellCompletion --bash --name pbnj $bashCompletionSrc
445
445
+
installShellCompletion --zsh --name _pbnj $zshCompletionSrc
446
446
+
installShellCompletion --fish --name pbnj.fish $fishCompletionSrc
447
447
+
'';
448
448
+
449
449
+
meta = with lib; {
450
450
+
description = "Pastebin CLI with charm";
451
451
+
homepage = "https://github.com/bhavnicksm/pbnj";
452
452
+
license = licenses.mit;
453
453
+
maintainers = [ ];
454
454
+
};
455
455
+
};
456
456
+
in
457
457
+
{
458
458
+
options.atelier.pbnj = {
459
459
+
enable = lib.mkEnableOption "pbnj pastebin CLI";
460
460
+
461
461
+
host = lib.mkOption {
462
462
+
type = lib.types.nullOr lib.types.str;
463
463
+
default = null;
464
464
+
description = "pbnj instance URL";
465
465
+
};
466
466
+
467
467
+
authKeyFile = lib.mkOption {
468
468
+
type = lib.types.nullOr lib.types.path;
469
469
+
default = null;
470
470
+
description = "Path to file containing auth key (e.g. agenix secret)";
471
471
+
};
472
472
+
};
473
473
+
474
474
+
config = lib.mkIf cfg.enable {
475
475
+
home.packages = [ pbnj ];
476
476
+
};
477
477
+
}
+103
modules/home/apps/pbnj/pbnj.1.md
···
1
1
+
% PBNJ(1) pbnj 1.0
2
2
+
%
3
3
+
% January 2026
4
4
+
5
5
+
# NAME
6
6
+
7
7
+
pbnj - pastebin CLI
8
8
+
9
9
+
# SYNOPSIS
10
10
+
11
11
+
**pbnj** [*OPTIONS*] [*FILE*]
12
12
+
13
13
+
**cat** *file* | **pbnj**
14
14
+
15
15
+
# DESCRIPTION
16
16
+
17
17
+
**pbnj** is a command-line interface for uploading and managing code snippets on a pbnj pastebin instance.
18
18
+
19
19
+
# COMMANDS
20
20
+
21
21
+
**init**
22
22
+
: Configure pbnj instance (host URL and auth key).
23
23
+
24
24
+
**config**
25
25
+
: Show current configuration.
26
26
+
27
27
+
**list** [*n*]
28
28
+
: List recent pastes (default: 10). Interactive selection to copy URL.
29
29
+
30
30
+
**delete** *id*
31
31
+
: Delete a specific paste.
32
32
+
33
33
+
**delete-all**
34
34
+
: Delete all pastes (with confirmation).
35
35
+
36
36
+
# OPTIONS
37
37
+
38
38
+
**-L**, **--language** *lang*
39
39
+
: Override automatic language detection.
40
40
+
41
41
+
**-f**, **--filename** *name*
42
42
+
: Set custom filename for the paste.
43
43
+
44
44
+
**-p**, **--private**
45
45
+
: Create private/unlisted paste.
46
46
+
47
47
+
**-k**, **--key** *key*
48
48
+
: Add secret key to paste.
49
49
+
50
50
+
**-u**, **--update** *id*
51
51
+
: Update an existing paste by ID.
52
52
+
53
53
+
**-n**, **--no-copy**
54
54
+
: Don't automatically copy URL to clipboard.
55
55
+
56
56
+
**-l**, **--list** [*n*]
57
57
+
: List recent pastes.
58
58
+
59
59
+
**-h**, **--help**
60
60
+
: Show help message.
61
61
+
62
62
+
# EXAMPLES
63
63
+
64
64
+
Upload a file:
65
65
+
66
66
+
pbnj script.py
67
67
+
68
68
+
Pipe content:
69
69
+
70
70
+
cat file.txt | pbnj
71
71
+
72
72
+
Upload with custom language:
73
73
+
74
74
+
pbnj -L rust main.rs
75
75
+
76
76
+
Create private paste:
77
77
+
78
78
+
pbnj -p secret.txt
79
79
+
80
80
+
Update existing paste:
81
81
+
82
82
+
pbnj -u crunchy-peanut updated.py
83
83
+
84
84
+
List recent pastes:
85
85
+
86
86
+
pbnj list 20
87
87
+
88
88
+
# CONFIGURATION
89
89
+
90
90
+
Configuration is stored in **~/.pbnj.json** or can be set via environment variables:
91
91
+
92
92
+
**PBNJ_HOST**
93
93
+
: The pbnj instance URL.
94
94
+
95
95
+
**PBNJ_AUTH_KEY**
96
96
+
: Authentication key.
97
97
+
98
98
+
**PBNJ_CONFIG**
99
99
+
: Override config file path.
100
100
+
101
101
+
# SEE ALSO
102
102
+
103
103
+
**curl**(1), **jq**(1)
+13
secrets/pbnj.age
···
1
1
+
age-encryption.org/v1
2
2
+
-> ssh-rsa DqcG0Q
3
3
+
MGtlHPaqmXCFdy9L0hWX8+jFYnr4OoDJMkWCyKWD6ZROVqQpmZSA5rSR6K10pQfl
4
4
+
KcHhlfWDApzQdpI70IGFglvH27coIQQ718kP/HnlLyAW0zCLxTxt1tz6uOHmKepO
5
5
+
DlSj8zGzED324pu7wmTz7Ji5VS3UdiKCcYx8UNg/AepCeFJSKQiouiBCUGFh4h6v
6
6
+
V3jI/LT/RifEwIoJAmj/w7ba3RWdaQm0+0t+TRu81ZZNzk2d8rDoVPsG6QybE44p
7
7
+
XOHhsOgcvDmhqFXw6UyWQWI0zKJ1dB71rRU7kjUeMvs+QvMEQ/Poi4h3QyblWpEZ
8
8
+
q5wa7v9QEEMd1aXw3KMsDY1GWzprsejU3mdHAcp+xQgOe6kh3upsdPQ8vkPXaq4f
9
9
+
6b2BvI1+MErzMKGuXgHWyGTyhcZEaVhPR13LIKjkuzUhTGwfHA/4k6yt1UQdpD8D
10
10
+
AAQgWWTHfGbSSktaXtp/QSwl/E17PrPlFObIX0Yb22r8+krhJI+xPPGUvza5fjal
11
11
+
12
12
+
--- 9NHTxSngPXZ/0VOvf0oP2ONipWdbrfjVmaOWEAw4baU
13
13
+
��+I�@�k3��5)Kai �`=��K�='�}��Z}^��O8݁�D^��"�����D4�����\U鹺�
+3
secrets/secrets.nix
···
74
74
"zai.age".publicKeys = [
75
75
kierank
76
76
];
77
77
+
"pbnj.age".publicKeys = [
78
78
+
kierank
79
79
+
];
77
80
}