configdir

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 15, 2026 License: MIT Imports: 6 Imported by: 222

README

configdir for Golang
=====================

Multi platform library of configuration directory for Golang.

This library helps to get regular directories for configuration files or cache files that matches target operationg system's convention.

It assumes the following folders are standard paths of each environment:

.. list-table::
   :header-rows: 1

   - * 
     * Windows:
     * Linux/BSDs:
     * MacOSX:
   - * System level configuration folder
     * ``%PROGRAMDATA%`` (``C:\\ProgramData``)
     * ``${XDG_CONFIG_DIRS}`` (``/etc/xdg``)
     * ``/Library/Application Support``
   - * User level configuration folder
     * ``%APPDATA%`` (``C:\\Users\\<User>\\AppData\\Roaming``)
     * ``${XDG_CONFIG_HOME}`` (``${HOME}/.config``)
     * ``${HOME}/Library/Application Support``
   - * User wide cache folder
     * ``%LOCALAPPDATA%`` ``(C:\\Users\\<User>\\AppData\\Local)``
     * ``${XDG_CACHE_HOME}`` (``${HOME}/.cache``)
     * ``${HOME}/Library/Caches``

Notes:

* Windows: folders are retrieved by ``SHGetKnownFolderPath`` (``FOLDERID_ProgramData``, ``FOLDERID_RoamingAppData``, ``FOLDERID_LocalAppData``). The environment variables are used only when the API fails.
* MacOSX: set ``ConfigDir.XDG = true`` to use XDG Base Directory folders (same as Linux/BSDs). It is useful for CLI tools.
* snap: when ``${SNAP_REAL_HOME}`` differs from ``${HOME}``, ``${SNAP_REAL_HOME}/.config`` is used as the user level folder, and ``${HOME}/.config`` (the snap private folder) is returned as a ``Confined`` folder right after it. Accessing the real home folder requires the ``personal-files`` interface.

Installation
------------

.. code-block:: bash

   go get github.com/shibukawa/configdir

Requires Go 1.26 or later.

Examples
------------

Getting Configuration
~~~~~~~~~~~~~~~~~~~~~~~~

``configdir.ConfigDir.QueryFolderContainsFile()`` searches files in the following order:

* Local path (if you add the path via LocalPath parameter)
* User level configuration folder(e.g. ``$HOME/.config/<vendor-name>/<application-name>/setting.json`` in Linux)
* System level configuration folder(e.g. ``/etc/xdg/<vendor-name>/<application-name>/setting.json`` in Linux)

``configdir.Config`` provides some convenient methods(``ReadFile``, ``WriteFile`` and so on).
File names must be local paths inside the folder (see ``filepath.IsLocal``). Names like ``../secret`` or absolute paths are rejected with ``fs.ErrInvalid``.

.. code-block:: go

   var config Config

   configDirs := configdir.New("vendor-name", "application-name")
   // optional: local path has the highest priority
   configDirs.LocalPath, _ = filepath.Abs(".")
   folder := configDirs.QueryFolderContainsFile("setting.json")
   if folder != nil {
       data, _ := folder.ReadFile("setting.json")
       json.Unmarshal(data, &config)
   } else {
       config = DefaultConfig
   }

Using fs.FS
~~~~~~~~~~~~~~~~~~~~~~

``configdir.ConfigDir.FS()`` returns a read-only ``fs.FS`` that resolves each file in the same order as ``QueryFolderContainsFile()``.
``configdir.Config.FS()`` returns an ``fs.FS`` rooted at a single folder.

.. code-block:: go

   configDirs := configdir.New("vendor-name", "application-name")
   configDirs.LocalPath, _ = filepath.Abs(".")
   data, err := fs.ReadFile(configDirs.FS(), "setting.json")

Reading Installed Resources
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

``configdir.ConfigDir.QueryResourceFolders()`` returns read-only folders that contain files installed with the application (default settings, templates, assets and so on).
The install location differs by package manager, so it is detected from the executable path (symbolic links are resolved):

.. list-table::
   :header-rows: 1

   - * Executable
     * Resource folder
   - * ``<prefix>/bin/app`` (``/usr``, ``/usr/local``, Homebrew, snap, Flatpak, AppImage...)
     * ``<prefix>/share/<application-name>``
   - * ``X.app/Contents/MacOS/app`` (macOS app bundle)
     * ``X.app/Contents/Resources``
   - * ``<dir>\app.exe`` (Windows)
     * ``<dir>``

On Linux/BSDs (and macOS with ``XDG = true``), ``${XDG_DATA_DIRS}/<application-name>`` (``/usr/local/share``, ``/usr/share``) is searched next.
``ResourcePath`` has the highest priority. It is useful for development because ``go run`` and ``go test`` build the executable into a temporary folder.

``ResourceFS()`` returns an ``fs.FS`` over the folders. ``embed.FS`` can be passed as fallback:

.. code-block:: go

   //go:embed resources
   var embedded embed.FS

   configDirs := configdir.New("vendor-name", "application-name")
   configDirs.ResourcePath = os.Getenv("APP_RESOURCE_PATH") // optional
   resources, _ := fs.Sub(embedded, "resources")
   data, err := fs.ReadFile(configDirs.ResourceFS(resources), "template.html")

Write Configuration
~~~~~~~~~~~~~~~~~~~~~~

When storing configuration, get configuration folder by using ``configdir.ConfigDir.QueryFolders()`` method.

.. code-block:: go

   configDirs := configdir.New("vendor-name", "application-name")

   var config Config
   data, _ := json.Marshal(&config)

   // Stores to local folder
   folders := configDirs.QueryFolders(configdir.Local)
   folders[0].WriteFile("setting.json", data)

   // Stores to user folder
   folders = configDirs.QueryFolders(configdir.Global)
   folders[0].WriteFile("setting.json", data)

   // Stores to system folder
   folders = configDirs.QueryFolders(configdir.System)
   folders[0].WriteFile("setting.json", data)

Writing from snap
~~~~~~~~~~~~~~~~~~~~~~

``QueryFolders(configdir.Global)`` returns the real home folder first and the confined folder next. Fall back to the next folder when writing fails:

.. code-block:: go

   for _, folder := range configDirs.QueryFolders(configdir.Global) {
       if err := folder.WriteFile("setting.json", data); err == nil {
           break
       }
   }

File Permission
~~~~~~~~~~~~~~~~~~~~~~

Configuration files sometimes contain secrets. ``FileMode`` and ``DirMode`` (default: ``0644`` and ``0755``) are used by ``Create``, ``WriteFile``, ``MkdirAll`` and so on.
When ``FileMode`` is set, the permission of an existing file is also changed on writing.

.. code-block:: go

   configDirs := configdir.New("vendor-name", "application-name")
   configDirs.FileMode = 0600
   configDirs.DirMode = 0700

   folder := configDirs.QueryFolders(configdir.Global)[0]
   folder.WriteFile("credential.json", data)

   // check and fix permission of files created by other tools
   if ok, _ := folder.CheckPermission("credential.json"); !ok {
       folder.FixPermission("credential.json")
   }

Getting Cache Folder
~~~~~~~~~~~~~~~~~~~~~~

It is similar to the above example, but returns cache folder.

.. code-block:: go

   configDirs := configdir.New("vendor-name", "application-name")
   cache := configDirs.QueryCacheFolder()

   resp, err := http.Get("http://examples.com/sdk.zip")
   if err != nil {
       log.Fatal(err)
   }
   defer resp.Body.Close()
   body, err := io.ReadAll(resp.Body)

   cache.WriteFile("sdk.zip", body)

Document
------------

https://pkg.go.dev/github.com/shibukawa/configdir

License
------------

MIT

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	Path string
	Type ConfigType
	// FileMode is the permission of files created by Create and WriteFile (default: 0644).
	// If it is set, the permission of existing files is also changed when writing.
	FileMode fs.FileMode
	// DirMode is the permission of folders created by this package (default: 0755).
	DirMode fs.FileMode
}

Config represents each folder

func (Config) CheckPermission

func (c Config) CheckPermission(fileName string) (bool, error)

CheckPermission reports whether fileName and folders between c.Path and fileName have the permission of FileMode and DirMode.

func (Config) Create

func (c Config) Create(fileName string) (*os.File, error)

func (Config) CreateParentDir

func (c Config) CreateParentDir(fileName string) error

CreateParentDir creates the parent directory of fileName inside c. fileName is a relative path inside c, containing zero or more path separators.

func (Config) Exists

func (c Config) Exists(fileName string) bool

func (Config) FS

func (c Config) FS() fs.FS

FS returns an fs.FS rooted at the folder.

func (Config) FixPermission

func (c Config) FixPermission(fileName string) error

FixPermission changes the permission of fileName and folders between c.Path and fileName to FileMode and DirMode.

func (Config) MkdirAll

func (c Config) MkdirAll() error

func (Config) Open

func (c Config) Open(fileName string) (*os.File, error)

func (Config) ReadFile

func (c Config) ReadFile(fileName string) ([]byte, error)

func (Config) WriteFile

func (c Config) WriteFile(fileName string, data []byte) error

type ConfigDir

type ConfigDir struct {
	VendorName      string
	ApplicationName string
	LocalPath       string
	// ResourcePath is a resource folder that has the highest priority (e.g. for development
	// with go run). See QueryResourceFolders.
	ResourcePath string
	// XDG uses XDG Base Directory Specification on macOS too
	// (${XDG_CONFIG_HOME}, ${XDG_CONFIG_DIRS}, ${XDG_CACHE_HOME} and ${XDG_DATA_DIRS}).
	// It is always used on Linux/BSDs and ignored on Windows.
	XDG bool
	// FileMode and DirMode are passed to Config. See Config.
	FileMode fs.FileMode
	DirMode  fs.FileMode
}

ConfigDir keeps setting for querying folders.

Example

Sample for reading configuration

package main

import (
	"encoding/json"
	"path/filepath"

	"github.com/shibukawa/configdir"
)

type Config struct {
	UserName string `json:"user-name"`
}

var DefaultConfig = Config{
	UserName: "baron",
}

func main() {

	var config Config

	configDirs := configdir.New("vendor-name", "application-name")
	// optional: local path has the highest priority
	configDirs.LocalPath, _ = filepath.Abs(".")
	folder := configDirs.QueryFolderContainsFile("setting.json")
	if folder != nil {
		data, _ := folder.ReadFile("setting.json")
		json.Unmarshal(data, &config)
	} else {
		config = DefaultConfig
	}
}

func New

func New(vendorName, applicationName string) ConfigDir

func (ConfigDir) FS

func (c ConfigDir) FS() fs.FS

FS returns a read-only fs.FS that looks up files in the same order as QueryFolderContainsFile (local, user, system). Each name is resolved in the first folder that contains it; directory listings are not merged.

Example

Sample for reading configuration via fs.FS

package main

import (
	"encoding/json"
	"io/fs"
	"path/filepath"

	"github.com/shibukawa/configdir"
)

type Config struct {
	UserName string `json:"user-name"`
}

var DefaultConfig = Config{
	UserName: "baron",
}

func main() {
	var config Config

	configDirs := configdir.New("vendor-name", "application-name")
	configDirs.LocalPath, _ = filepath.Abs(".")
	data, err := fs.ReadFile(configDirs.FS(), "setting.json")
	if err == nil {
		json.Unmarshal(data, &config)
	} else {
		config = DefaultConfig
	}
}

func (ConfigDir) QueryCacheFolder

func (c ConfigDir) QueryCacheFolder() *Config
Example

Sample for getting cache folder

package main

import (
	"io"
	"log"
	"net/http"

	"github.com/shibukawa/configdir"
)

func main() {
	configDirs := configdir.New("vendor-name", "application-name")
	cache := configDirs.QueryCacheFolder()

	resp, err := http.Get("http://examples.com/sdk.zip")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)

	cache.WriteFile("sdk.zip", body)
}

func (ConfigDir) QueryFolderContainsFile

func (c ConfigDir) QueryFolderContainsFile(fileName string) *Config

func (ConfigDir) QueryFolders

func (c ConfigDir) QueryFolders(configType ConfigType) []*Config

QueryFolders returns folders in priority order (local, global, confined, system).

Global returns the global folder and the confined folder (if running in a sandbox like snap). Existing returns folders that exist and are accessible.

Example

Sample for reading configuration

package main

import (
	"encoding/json"

	"github.com/shibukawa/configdir"
)

type Config struct {
	UserName string `json:"user-name"`
}

func main() {
	configDirs := configdir.New("vendor-name", "application-name")

	var config Config
	data, _ := json.Marshal(&config)

	// Stores to local folder
	folders := configDirs.QueryFolders(configdir.Local)
	folders[0].WriteFile("setting.json", data)

	// Stores to user folder
	folders = configDirs.QueryFolders(configdir.Global)
	folders[0].WriteFile("setting.json", data)

	// Stores to system folder
	folders = configDirs.QueryFolders(configdir.System)
	folders[0].WriteFile("setting.json", data)
}

func (ConfigDir) QueryResourceFolders added in v1.1.0

func (c ConfigDir) QueryResourceFolders() []*Config

QueryResourceFolders returns existing folders that contain files installed with the application (e.g. default settings, templates and assets) in priority order:

  1. ResourcePath (if set)
  2. The folder relative to the executable (symbolic links are resolved)
  3. ${XDG_DATA_DIRS}/<ApplicationName> (/usr/local/share and /usr/share by default) on Linux/BSDs, and on macOS when XDG is true

The folder relative to the executable is:

  • macOS app bundle: X.app/Contents/MacOS/app → X.app/Contents/Resources
  • Windows: the folder of app.exe
  • Others: <prefix>/bin/app → <prefix>/share/<ApplicationName> (/usr, /usr/local, Homebrew, snap, Flatpak, AppImage and so on)

Unlike other folders, VendorName is not used because packages are installed to <prefix>/share/<package name> by convention. The folders are read-only. Don't write files to them.

func (ConfigDir) ResourceFS added in v1.1.0

func (c ConfigDir) ResourceFS(fallbacks ...fs.FS) fs.FS

ResourceFS returns a read-only fs.FS that resolves each name in the order of QueryResourceFolders, and then fallbacks (e.g. embed.FS bundled in the executable).

Example

Sample for reading files installed with the application

package main

import (
	"io/fs"
	"os"
	"testing/fstest"

	"github.com/shibukawa/configdir"
)

func main() {
	configDirs := configdir.New("vendor-name", "application-name")
	// optional: highest priority (e.g. for go run)
	configDirs.ResourcePath = os.Getenv("APP_RESOURCE_PATH")

	// embedded files can be passed as fallback (e.g. embed.FS)
	embedded := fstest.MapFS{"template.html": {Data: []byte("<html></html>")}}
	data, _ := fs.ReadFile(configDirs.ResourceFS(embedded), "template.html")
	_ = data
}

type ConfigType

type ConfigType int
const (
	System ConfigType = iota
	Global
	All
	Existing
	Local
	Cache
	// Confined is a user level folder inside a sandbox (e.g. snap's $HOME/snap/<name>/<revision>/.config).
	// It is returned after Global when the application runs confined.
	Confined
	// Resource is a read-only folder that contains files installed with the application.
	// See ConfigDir.QueryResourceFolders.
	Resource
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL