Guide for contributors and developers working on dotbak.
The dotbak-dev script sources modules directly without building:
# Edit any module
vim src/commands/status.sh
# Test immediately (no build!)
./dotbak-dev status
# When ready for production
./build.sh
./install.shdotbak/
├── src/
│ ├── core/ # Core utilities (colors, paths, config)
│ ├── utils/ # Utilities (tree, ignore, symlink, common)
│ ├── commands/ # Command implementations (11 commands)
│ │ ├── init.sh, add.sh, remove.sh, list.sh, status.sh
│ │ ├── snapshot.sh # Snapshot management
│ │ ├── symlink-sync.sh # Symlink detection
│ │ ├── deploy.sh # Machine deployment
│ │ └── archive.sh, ignored.sh, version.sh, update.sh
│ └── main.sh # Entry point and routing
├── test/ # bats-core test suite
│ ├── bats/ # bats-core (pinned to v1.14.0)
│ ├── test_helper/ # bats helpers (pinned versions)
│ ├── unit/ # Unit tests
│ └── integration/ # Integration tests
├── tools/shc/ # Vendored shc compiler (GPLv3) for native binary build
├── dotbak-dev # Development runner (sources src/ live)
├── build.sh # Compile to native binary via shc (--script for plain mode)
├── install.sh # Installation script
└── update-test-deps.sh # Test dependency updater
Pinned versions (tested and stable):
- bats-core: v1.14.0
- bats-support: v0.3.0
- bats-assert: v2.2.4
These are pinned in .gitmodules to ensure stability. We don't automatically update to avoid breaking changes.
bats-core v1.14.0 notes (from upstream changelog):
runnow honorsset -einside functions (intentional behavior change).- Empty suites now fail unless
--allow-empty-suiteis passed. - Helpers stay on latest releases; bats-support master has unreleased license work — stay on the tag.
Step-by-step process for maintainers to cut a release.
- You have push access to the repository
- All changes for the release are merged to
main - CI is green on
main
1. Decide the version number
We use Semantic Versioning: MAJOR.MINOR.PATCH.
- PATCH (
0.1.0→0.1.1): bug fixes, documentation - MINOR (
0.1.0→0.2.0): new features, backward-compatible - MAJOR (
0.1.0→1.0.0): breaking changes
2. Update the VERSION file
echo "0.2.0" > VERSIONThis is the single source of truth — build.sh, dotbak-dev, CI, and the release workflow all read from it.
3. Update CHANGELOG.md
Move items from [Unreleased] (if present) into a new version section:
## [0.2.0] - 2026-08-01
### Added
- ...
### Fixed
- ...4. Build and verify locally
./build.sh
./dotbak --version # should show the new version
./test.sh # all tests must pass5. Commit the release
git add VERSION CHANGELOG.md
git commit -m "release: v0.2.0"6. Create an annotated tag
git tag -a v0.2.0 -m "Release v0.2.0"The tag must match the VERSION file content (the release workflow verifies this and fails on mismatch).
7. Push the commit and tag
git push origin main
git push origin v0.2.0Pushing the tag triggers the release workflow (.github/workflows/release.yml) which:
- Runs the full test suite
- Verifies
VERSIONfile matches the git tag - Builds native binaries (
dotbak-linux-x86_64,dotbak-darwin-arm64) - Builds a portable script (
dotbak-portable) - Generates
SHA256SUMSfor all artifacts - Extracts release notes from CHANGELOG.md
- Creates a GitHub Release with all artifacts attached
8. Verify the release
- Check the Actions tab — the release workflow should complete green
- Check the Releases page — the new release should appear with all artifacts
- Test the update path:
dotbak update --checkfrom an older version
For beta/RC releases, use a hyphenated tag (e.g., v0.2.0-beta.1). The release workflow automatically marks tags containing - as pre-releases on GitHub.
The release workflow produces these artifacts:
| Artifact | Description |
|---|---|
dotbak-linux-x86_64 |
Native binary for Linux (x86_64) |
dotbak-darwin-arm64 |
Native binary for macOS (Apple Silicon) |
dotbak-portable |
Plain shell script (works on any platform with Bash 4.0+) |
SHA256SUMS |
Checksums for all artifacts (used by dotbak update for verification) |
Release workflow failed with "VERSION file does not match tag":
The VERSION file content must exactly match the tag (without the v prefix). Fix the VERSION file, amend the commit, delete and re-push the tag.
Self-update downloads wrong artifact:
dotbak update auto-detects the platform and downloads the matching artifact. It falls back to dotbak-portable for unrecognized platforms.
# Check status
dotbak status
# Remove and re-add
dotbak remove ~/.zshrc
dotbak add ~/.zshrc# Check disk space and zip availability
df -h ~/.dotbak
which zip
# Retry
dotbak archive test-run# Check internet
curl -I https://github.com
# Manual update (pick your platform)
curl -fsSL https://github.com/darshithedpara/dotbak/releases/latest/download/dotbak-linux-x86_64 -o /tmp/dotbak
sudo mv /tmp/dotbak /usr/local/bin/dotbak
sudo chmod +x /usr/local/bin/dotbakIf installed to ~/.local/bin, add to your PATH:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc# Remove tracked files (optional)
dotbak remove ~/.zshrc
dotbak remove ~/.config/nvim
# ... for each tracked file
# Or just remove dotbak
sudo rm /usr/local/bin/dotbak
# Optional: remove backup directory
rm -rf ~/.dotbakMIT License - see LICENSE file for details
dotbak includes comprehensive test coverage using bats-core.
For safe testing without risking your real dotfiles, see the Sandbox Testing Guide — covers quick local sandboxes, Docker, VMs, and CI.
Setup (first time only):
# Clone with submodules
git clone --recurse-submodules https://github.com/darshithedpara/dotbak
# Or if already cloned
git submodule update --init --recursiveRunning tests:
# All tests (148 total)
./test.sh
# Quick tests (unit only, ~5 seconds)
./test.sh unit
# Integration tests only (~30 seconds)
./test.sh integration
# Specific test file
./test.sh test/unit/test_paths.batsTest coverage:
-
Unit tests (
test/unit/): Test individual functions and modulestest_colors.bats- Color output functionstest_paths.bats- Path manipulation utilitiestest_config.bats- Configuration managementtest_ignore.bats- Ignore pattern matchingtest_symlink.bats- Symlink operations
-
Integration tests (
test/integration/): Test full commands end-to-endtest_init.bats- Initializationtest_add.bats- Adding filestest_list.bats- Listing filestest_status.bats- Status reportingtest_remove.bats- Removing filestest_deploy.bats- Deploying files to a new machinetest_snapshot.bats- Snapshot managementtest_archive.bats- Creating archivestest_sync.bats- Symlink-sync detectiontest_ignored.bats- Ignore management
Before submitting PR:
- Run
./test.sh- all tests must pass - Add tests for new features
- Update tests for bug fixes
Important: Test dependencies (bats-core and helpers) are pinned to specific versions to prevent breaking changes.
Current pinned versions:
- bats-core: v1.14.0
- bats-support: v0.3.0
- bats-assert: v2.2.4
Checking for updates:
# Check if updates are available
./update-test-deps.shUpdate process (maintainers only):
# 1. Check release notes for breaking changes
# https://github.com/bats-core/bats-core/releases
# https://github.com/bats-core/bats-support/releases
# https://github.com/bats-core/bats-assert/releases
# 2. Update submodules to new versions
cd test/bats && git checkout v1.14.0 && cd ../..
cd test/test_helper/bats-support && git checkout v0.4.0 && cd ../../..
cd test/test_helper/bats-assert && git checkout v2.3.0 && cd ../../..
# 3. Run full test suite
./test.sh
# 4. If tests pass, update configuration
# - Edit .gitmodules (update branch values)
# - Edit update-test-deps.sh (update version variables)
# 5. Commit changes
git add test/ .gitmodules update-test-deps.sh
git commit -m "chore: update test dependencies to vX.X.X"Why pin versions?
- ✅ Prevents surprise breaking changes
- ✅ Reproducible builds
- ✅ Controlled updates with testing
- ✅ Stability for users and CI/CD
When to update:
- Major bats-core releases (test thoroughly!)
- Security fixes
- Bug fixes we need
- When adding new features that require new bats features
# Edit source files in src/
vim src/commands/add.sh
# Test immediately (no rebuild needed!)
./dotbak-dev add ~/.testfile
# Run tests
./test.sh
# Build native binary (requires gcc)
./build.sh
# Or build plain script (no compiler needed)
./build.sh --scriptSee CONTRIBUTING.md for the full contributor guide — dev setup, coding conventions, commit messages, and PR workflow.