Skip to content

Commit 42154d2

Browse files
committed
Locate config file
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
1 parent f490d74 commit 42154d2

5 files changed

Lines changed: 123 additions & 36 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
2929

3030
- The CLI now works without a configuration file, falling back to sensible
3131
defaults (expire: 1day, formatter: plaintext, gzip: enabled).
32+
- Configuration file lookup now follows the XDG Base Directory Specification.
33+
The CLI searches `$HOME/.config/privatebin/config.json`, then
34+
`$XDG_CONFIG_HOME/privatebin/config.json`, the platform-native user config
35+
directory, and finally `$XDG_CONFIG_DIRS` (defaults to `/etc/xdg`).
3236
- Remove duplicate error output in the main function.
3337

3438
### Security

cmd/privatebin/main.go

Lines changed: 86 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"io"
2525
"net/url"
2626
"os"
27-
"path"
2827
"path/filepath"
2928
"strings"
3029

@@ -82,12 +81,12 @@ var (
8281
}
8382

8483
if cfgPath == "" {
85-
homeDir, err := os.UserHomeDir()
84+
p, err := locateConfigFile()
8685
if err != nil {
87-
return fmt.Errorf("cannot get user home directory: %w", err)
86+
return err
8887
}
8988

90-
cfgPath = path.Join(homeDir, ".config", "privatebin", "config.json")
89+
cfgPath = p
9190
}
9291

9392
cfg, err := loadCfgFile(cfgPath)
@@ -225,18 +224,18 @@ var (
225224
)
226225
}
227226

228-
_ = json.NewEncoder(os.Stdout).Encode(
229-
map[string]any{
230-
"paste_id": result.PasteID,
231-
"paste": map[string]string{
232-
"attachment_name": result.Paste.AttachmentName,
233-
"attachment": base64.StdEncoding.EncodeToString(result.Paste.Attachment),
234-
"data": base64.StdEncoding.EncodeToString(result.Paste.Data),
227+
_ = json.NewEncoder(os.Stdout).Encode(
228+
map[string]any{
229+
"paste_id": result.PasteID,
230+
"paste": map[string]string{
231+
"attachment_name": result.Paste.AttachmentName,
232+
"attachment": base64.StdEncoding.EncodeToString(result.Paste.Attachment),
233+
"data": base64.StdEncoding.EncodeToString(result.Paste.Data),
234+
},
235+
"comment_count": result.CommentCount,
236+
"comments": comments,
235237
},
236-
"comment_count": result.CommentCount,
237-
"comments": comments,
238-
},
239-
)
238+
)
240239
}
241240
return nil
242241
},
@@ -323,15 +322,15 @@ var (
323322

324323
switch output {
325324
case "":
326-
_, _ = fmt.Fprintf(os.Stdout, "%s\n", result.PasteURL.String())
325+
_, _ = fmt.Fprintf(os.Stdout, "%s\n", result.PasteURL.String())
327326
case "json":
328-
_ = json.NewEncoder(os.Stdout).Encode(
329-
map[string]any{
330-
"paste_id": result.PasteID,
331-
"paste_url": result.PasteURL.String(),
332-
"delete_token": result.DeleteToken,
333-
},
334-
)
327+
_ = json.NewEncoder(os.Stdout).Encode(
328+
map[string]any{
329+
"paste_id": result.PasteID,
330+
"paste_url": result.PasteURL.String(),
331+
"delete_token": result.DeleteToken,
332+
},
333+
)
335334
}
336335

337336
return nil
@@ -344,12 +343,12 @@ var (
344343
SilenceUsage: true,
345344
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
346345
if cfgPath == "" {
347-
homeDir, err := os.UserHomeDir()
346+
p, err := locateConfigFile()
348347
if err != nil {
349-
return fmt.Errorf("cannot get user home directory: %w", err)
348+
return err
350349
}
351350

352-
cfgPath = path.Join(homeDir, ".config", "privatebin", "config.json")
351+
cfgPath = p
353352
}
354353

355354
return nil
@@ -396,9 +395,69 @@ var (
396395
}
397396
)
398397

398+
func configFileCandidates() ([]string, error) {
399+
var candidates []string
400+
seen := make(map[string]bool)
401+
402+
add := func(p string) {
403+
if !seen[p] {
404+
seen[p] = true
405+
candidates = append(candidates, p)
406+
}
407+
}
408+
409+
// $HOME/.config: conventional location for CLI tools.
410+
if home, err := os.UserHomeDir(); err == nil {
411+
add(filepath.Join(home, ".config", "privatebin", "config.json"))
412+
}
413+
414+
// XDG_CONFIG_HOME: user-specific config directory override.
415+
if dir := os.Getenv("XDG_CONFIG_HOME"); dir != "" {
416+
add(filepath.Join(dir, "privatebin", "config.json"))
417+
}
418+
419+
// Platform-native user config directory.
420+
if dir, err := os.UserConfigDir(); err == nil {
421+
add(filepath.Join(dir, "privatebin", "config.json"))
422+
}
423+
424+
// XDG_CONFIG_DIRS: system-wide config directories (default /etc/xdg).
425+
xdgDirs := os.Getenv("XDG_CONFIG_DIRS")
426+
if xdgDirs == "" {
427+
xdgDirs = "/etc/xdg"
428+
}
429+
for _, dir := range filepath.SplitList(xdgDirs) {
430+
if dir != "" {
431+
add(filepath.Join(dir, "privatebin", "config.json"))
432+
}
433+
}
434+
435+
if len(candidates) == 0 {
436+
return nil, fmt.Errorf("cannot determine configuration file location")
437+
}
438+
439+
return candidates, nil
440+
}
441+
442+
func locateConfigFile() (string, error) {
443+
candidates, err := configFileCandidates()
444+
if err != nil {
445+
return "", err
446+
}
447+
448+
for _, path := range candidates {
449+
if _, err := os.Stat(path); err == nil {
450+
return path, nil
451+
}
452+
}
453+
454+
// No existing file found; return the preferred default for creation.
455+
return candidates[0], nil
456+
}
457+
399458
func init() {
400459
rootCmd.PersistentFlags().StringVarP(&output, "output", "o", "", "the command output format")
401-
rootCmd.PersistentFlags().StringVarP(&cfgPath, "config", "c", "", "the config file (default is $HOME/.config/privatebin/config.json)")
460+
rootCmd.PersistentFlags().StringVarP(&cfgPath, "config", "c", "", "the config file (default is ~/.config/privatebin/config.json)")
402461
rootCmd.PersistentFlags().StringVarP(&binName, "bin", "b", "", "the name of the privatebin instance to use (default \"\")")
403462
rootCmd.PersistentFlags().StringSliceVarP(&extraHeaderFields, "header", "H", []string{}, "extra HTTP header fields to include in the request sent")
404463
rootCmd.PersistentFlags().StringVar(&proxy, "proxy", "", "proxy URL to use for requests (e.g. socks5://127.0.0.1:9050 for TOR)")

doc/privatebin-init.1.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ section: 1
1313

1414
# DESCRIPTION
1515
Generate a configuration file with sensible defaults and write it to
16-
the default location at _~/.config/privatebin/config.json_. If the
17-
**-\-config** flag is set, the file is written to that path instead.
16+
the preferred default location at _~/.config/privatebin/config.json_.
17+
If the **-\-config** flag is set, the file is written to that path
18+
instead.
1819

1920
The generated configuration contains a single bin entry pointing to the
2021
host specified by **-\-host** (defaulting to _https://privatebin.net_),

doc/privatebin.1.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ instances.
2929
: The privatebin instance name.
3030

3131
**-c, -\-config** \<path\>
32-
: The path of the configuration file (default
33-
"~/.config/privatebin/config.json").
32+
: The path of the configuration file. When not set, the CLI searches
33+
_$HOME/.config/privatebin/config.json_, then
34+
_$XDG\_CONFIG\_HOME/privatebin/config.json_, the platform-native user
35+
configuration directory, and finally the directories listed in
36+
**$XDG\_CONFIG\_DIRS** (see **privatebin.conf**(5) for details).
3437

3538
**-H, -\-header** \<key=value\>
3639
: The extra HTTP header fields to include in the request sent.
@@ -67,6 +70,17 @@ Create a paste through a SOCKS5 proxy (e.g. TOR):
6770

6871
# ENVIRONMENT
6972

73+
**XDG\_CONFIG\_HOME**
74+
: User-specific configuration directory. When set, the CLI searches
75+
this directory for _privatebin/config.json_ (after checking
76+
_$HOME/.config_). See **privatebin.conf**(5) for the full search
77+
order.
78+
79+
**XDG\_CONFIG\_DIRS**
80+
: Colon-separated list of system-wide configuration directories.
81+
Defaults to _/etc/xdg_ when not set. Each directory is searched for
82+
_privatebin/config.json_.
83+
7084
**HTTP_PROXY**, **HTTPS_PROXY**, **ALL_PROXY**
7185
: When no **-\-proxy** flag is provided and no **proxy** configuration
7286
value is set, the standard proxy environment variables are honored.

doc/privatebin.conf.5.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,20 @@ Configuration using a SOCKS5 proxy (e.g. TOR):
147147

148148
# FILES
149149

150-
_~/.config/privatebin/config.json_
151-
: Default location of the privatebin configuration. The file has to be
152-
created manually as it is not installed with a standard installation.
153-
The configuration file is optional; when absent, the CLI uses sensible
154-
defaults (expire: 1day, formatter: plaintext, gzip: enabled).
150+
The CLI searches for the configuration file in the following locations,
151+
in order, and uses the first one found:
152+
153+
1. _$HOME/.config/privatebin/config.json_
154+
2. _$XDG\_CONFIG\_HOME/privatebin/config.json_ (if **XDG\_CONFIG\_HOME** is set)
155+
3. The platform-native user configuration directory (as returned by
156+
Go's **os.UserConfigDir**): _$HOME/.config_ on Linux,
157+
_$HOME/Library/Application Support_ on macOS, _%AppData%_ on Windows
158+
4. Each directory in **$XDG\_CONFIG\_DIRS** (defaults to _/etc/xdg_)
159+
160+
Duplicate paths are skipped. When no **-\-config** flag is provided and
161+
no file is found, the CLI falls back to sensible defaults (expire:
162+
1day, formatter: plaintext, gzip: enabled). The **privatebin init**
163+
command writes to the first candidate path by default.
155164

156165
# AUTHORS
157166

0 commit comments

Comments
 (0)