Configuration¶
You should not have to retype a flag you have already decided on. CLI
preferences (icons, aliases, the layer stack) and non-secret provider settings
(bucket, base, region, ...) live in the same config files the library reads, so
sx and your code load them through one loader and cannot drift.
Start here:
sx config init # a commented starter file
sx config show --effective # what a session would actually use, and from where
Which source wins¶
Three canonical files are read. For any value, the strongest source wins:
- command-line flags and
--set - the selected profile's stage overlay (
--env) - the selected profile (
--profile) STORIX_*(provider settings) /STORIX_CLI_*(preferences) environment variables- a project
.env(provider settings) - the nearest project config, searching upward from the current directory:
storix.toml>.storix.toml(a compatibility alias) >pyproject.toml([tool.storix]) - your personal defaults:
~/.config/storix/config.toml - built-in defaults
A profile sits above the environment on purpose: selecting one is an explicit
act, and a stale exported variable must not quietly redirect it. The same chain
applies to the library, with get_storage() keywords in place of flags; see
Configure from settings.
The three files¶
~/.config/storix/config.toml # user scope on Linux and macOS
%APPDATA%\storix\config.toml # user scope on Windows
storix.toml # project scope, standalone
pyproject.toml -> [tool.storix] # project scope, namespaced
XDG_CONFIG_HOME overrides the user location on every platform, including
Windows: a user who exports it means it.
icons = true
provider = 'azure' # which backend sx opens by default
# every sx session gets the read-through cache - handy against cloud
# providers, where a repeated ls or du is a real round trip
layers = [{ name = "cache", ttl = 300 }]
[azure] # non-secret connection coordinates live here too
account_name = "myaccount"
container = "media"
credential = "env:AZURE_CREDENTIAL" # secrets via env: refs, not literals
# (resolved from the environment,
# then the project .env)
[alias]
lt = "tree"
la = "ls -a"
Non-secret connection coordinates (bucket, container, account name, region, endpoint, base) are project facts, so the same provider sections the library reads live here too. Only secrets stay out of a committed file.
provider picks the default backend for sx. Setting STORIX_PROVIDER would
drag your application's library sessions onto the same backend, so it is a CLI
habit here, not a shared one. The library also never auto-applies a layer
stack: in code you opt in explicitly with with_layer().
A complete annotated example of every key lives in the repository as
storix.toml.example.
Seeing and editing it: sx config¶
sx config path # which files are read, and whether they exist
sx config sources # what was found, and the precedence order
sx config show # everything as storix reads it, secrets redacted
sx config show --effective # what a session would actually use, and from where
sx config get s3.bucket # one value, and the file that supplies it
sx config get --effective azure.read_prefetch_size
# the value actually in force, and where it came
# from - defaults included
sx config profiles # every profile as a table, stages as rows
sx config set s3.bucket media # writes the project file
sx config set azure.credential X --user # --user == --scope user
sx config unset s3.region
sx config init # a commented starter file; --force to overwrite
sx config validate # load every file the way storix does
sx config edit # $VISUAL, else $EDITOR
get without --effective answers "what does a file say", and says so when no
file says anything. get --effective answers "what will storix do", which is
usually the real question:
$ sx config get azure.read_prefetch_size
sx: azure.read_prefetch_size is not set in any config file; try
`sx config get --effective azure.read_prefetch_size` for the value in force
$ sx config get --effective azure.read_prefetch_size
8388608 (8.0MiB) <- default
Profiles print as a table rather than as dotted keys, so a stage is a row under
its profile and * marks what this invocation would use:
$ sx --profile media --env prod config profiles
profiles /home/you/.config/storix/config.toml
profile provider stage settings
media * azure container = 'raw'
dev account_name = 'acctdev'
credential = '***'
prod * account_name = 'acctprod'
credential = '***'
* what this invocation would use
Every view follows the selection. sx --profile media config show shows
media and names the others rather than printing them, because they are not
what that command would use. Listing and explaining never resolve a credential,
so sx config profiles and sx doctor still work when an env: reference
points at a variable you have not exported - which is exactly when you reach
for them.
How writes behave¶
Writes go through a round-trip TOML editor, so your comments and layout survive; they are validated against the same models a loaded file gets, so an invalid edit is refused before the file changes; and they land atomically (temp file, then rename), so an interrupted write cannot leave a half-file.
Project scope writes the nearest existing storix.toml, else a pyproject.toml
already carrying [tool.storix], else it creates ./storix.toml. The target is
always printed. --user is shorthand for --scope user on every write,
including sx config edit --user to open your global config.
Settings for one invocation¶
Non-secret provider coordinates can be set for one command, without touching the environment or a file. Direct flags cover the common ones:
sx -p local --base . # local base at the current directory
sx -p s3 --bucket media --region auto ls # bucket + region
sx -p azure --account-name acct --container media --kind adls ls
sx -p s3 --endpoint https://ACCOUNT.r2.cloudflarestorage.com --bucket media ls
--base (local), --bucket, --container, --account-name, --region,
--endpoint, --root, and --kind are validated against the effective
provider: a flag that does not belong to it exits naming the provider and the
flags it does accept, so -p local --bucket x never silently does nothing.
For any other field, --set provider.field=value is the repeatable escape
hatch (values are parsed type-aware through the same models):
A relative --base (or --set) path resolves against the directory you run
sx from, exactly like ls.
Secrets¶
Secrets are refused on the command line (--set s3.access_key_id=... exits
pointing at the environment), because a flag lands in shell history. They are
redacted in every read command, including inside profiles. Setting one in
project scope is refused, naming the environment variable and the env: form
instead; user-scope files are created mode 600.
That contract is enforced, not assumed. An unknown key or table, or a literal secret, exits with the fix named rather than being silently ignored:
$ sx ls
sx: /home/you/proj/storix.toml: unknown table 'databse'; known: alias,
aliases, azure, cli, gcs, local, provider, s3
$ sx ls
sx: /home/you/proj/storix.toml: credential is a secret and project files are
committed; use env:VAR, the STORIX_* environment, or the user config
(~/.config/storix/config.toml)
Profiles¶
A profile is a named connection, selected by name instead of spelled out:
sx --profile media ls / # the profile's own settings
sx --profile media --env prod ls / # with the prod overlay on top
STORIX_PROFILE=media sx ls / # the same, for a whole shell session
Pin one in a config file and no flag is needed at all. The same profiles drive
get_storage(profile=..., environment=...), so a profile a pipeline runs on is
one you can stand inside and inspect. Written up in full, with the selection
order and the stage rules, in Profiles and stages.