Cookbook
Bootstrap a Go project
One pack for the files every new Go repo starts with:
files:
editorconfig:
filename: .editorconfig
url: https://raw.githubusercontent.com/example/dotfiles/main/.editorconfig
gitignore:
filename: .gitignore
url: https://raw.githubusercontent.com/example/dotfiles/main/go.gitignore
license:
filename: LICENSE
url: https://raw.githubusercontent.com/example/dotfiles/main/MIT
packs:
go-project: [editorconfig, gitignore, license]mkdir my-project && obtain -d my-project pack go-projectA license with your name and the current year
Host a template that uses placeholders:
MIT License
Copyright (c) {{ obtain.std.YEAR }} {{ obtain.NAME }}Set your name once at the top level of the config:
vars:
NAME: Ada Lovelace
files:
license:
filename: LICENSE
url: https://example.com/templates/MITobtain file licenseOverride a variable for one run
obtain -f -v NAME="Grace Hopper" file license-f is needed because LICENSE already exists.
Name a project after its directory
DIRNAME is the name of the directory being saved into, which is handy for READMEs and module files:
# {{ obtain.std.DIRNAME }}obtain -d ./my-project file readmeCI workflows that contain ${{ }}
GitHub Actions syntax like ${{ secrets.TOKEN }} doesn't use the obtain. namespace, so it passes through untouched. No raw needed:
files:
ci:
filename: .github/workflows/ci.yaml
url: https://example.com/templates/ci.yamlUse a project-local config
obtain -c ./obtain.yaml list
export OBTAIN_CONFIG=./obtain.yaml # or set it for the whole shellScaffold a whole repository in one command
obtain creates the destination directory, and any parent directories of a file's filename, so you can start from nothing. A service pack that covers the usual starting files:
vars:
NAME: Ada Lovelace
files:
readme:
filename: README.md
url: https://example.com/templates/README.md # starts with "# {{ obtain.std.DIRNAME }}"
license:
filename: LICENSE
url: https://example.com/templates/MIT # "Copyright (c) {{ obtain.std.YEAR }} {{ obtain.NAME }}"
gitignore:
filename: .gitignore
url: https://example.com/templates/go.gitignore
ci:
filename: .github/workflows/ci.yaml
url: https://example.com/templates/ci.yaml
packs:
service: [readme, license, gitignore, ci]obtain -d billing-api pack servicebilling-api/ is created, the README gets its title from the directory name, and .github/workflows/ appears without a mkdir.
Wrap it in a shell function
Because -d creates the directory and DIRNAME follows it, a tiny function gives you a "new project" command:
# bash / zsh
newgo() { obtain -d "$1" pack service && cd "$1" && go mod init "example.com/$1"; }# fish
function newgo
obtain -d $argv[1] pack service; and cd $argv[1]; and go mod init example.com/$argv[1]
endPin a template to a commit
A URL pointing at main changes whenever the template repository does. To get the same file every time, pin the URL to a commit or tag:
files:
ci:
filename: .github/workflows/ci.yaml
url: https://raw.githubusercontent.com/example/templates/3f9c2ab/ci.yamlBump the hash when you want to adopt a newer version.
One template, several files
Two entries can share a URL and differ only in filename and vars. The template is downloaded once per entry and rendered with each entry's own variables:
files:
dockerfile-api:
filename: services/api/Dockerfile
url: https://example.com/templates/Dockerfile
vars:
SERVICE: api
PORT: "8080"
dockerfile-worker:
filename: services/worker/Dockerfile
url: https://example.com/templates/Dockerfile
vars:
SERVICE: worker
PORT: "9090"
packs:
services: [dockerfile-api, dockerfile-worker]Values must be strings, so quote numbers like "8080".
Date-stamped notes
DATE is handy for files that record when they were created, such as an architecture decision record:
# ADR: <title>
Date: {{ obtain.std.DATE }}
Author: {{ obtain.std.USER }}
Status: proposedobtain -d docs/adr file adrRefresh a file across many repositories
Each obtain run is independent, so a shell loop updates a shared file everywhere. --force is required to overwrite the existing copy:
for repo in ~/code/*/; do
obtain -d "$repo" -f file ci
doneReview the result with git diff in each repository before committing. A failure in one repository doesn't stop the loop.
Try a pack without touching your project
Save into a scratch directory to see what a pack produces, including how variables render:
obtain -d "$(mktemp -d)" pack serviceThe command prints where each file went.
Keep separate configs for work and personal use
Point OBTAIN_CONFIG at a different file per context. For example with direnv, in a work directory's .envrc:
export OBTAIN_CONFIG=$HOME/.config/obtain/work.yamlEverything under that directory now uses the work config, and the default config stays untouched elsewhere.
Version your config and check it in CI
The config is plain YAML, so it fits in a dotfiles or templates repository. obtain doctor exits non-zero on any problem, which makes it a one-line CI check:
- run: obtain -c obtain.yaml doctorIt catches typos in keys, pack entries that point at missing files and malformed vars before they reach a machine.