{"id":18434342,"url":"https://github.com/borksh/bork","last_synced_at":"2025-06-25T19:03:21.051Z","repository":{"id":42687393,"uuid":"337438723","full_name":"borksh/bork","owner":"borksh","description":"The Bash-Operated Reconciling Kludge","archived":false,"fork":false,"pushed_at":"2025-03-30T15:54:12.000Z","size":805,"stargazers_count":26,"open_issues_count":12,"forks_count":8,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-11T14:36:15.602Z","etag":null,"topics":["bash","configuration","shell","unix-utilities"],"latest_commit_sha":null,"homepage":"https://bork.sh","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/borksh.png","metadata":{"files":{"readme":"Readme.md","changelog":"Changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"code_of_conduct.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"skylarmacdonald","custom":["https://monzo.me/skylarmacdonald","https://paypal.me/skylarmacdonald"]}},"created_at":"2021-02-09T14:54:19.000Z","updated_at":"2025-03-30T15:54:17.000Z","dependencies_parsed_at":"2024-12-30T19:31:01.552Z","dependency_job_id":"0beb2103-a988-404a-acc4-72609591f76f","html_url":"https://github.com/borksh/bork","commit_stats":{"total_commits":657,"total_committers":21,"mean_commits":"31.285714285714285","dds":0.4779299847792998,"last_synced_commit":"9d7561af1da27646b31ce73e5591d2db64327aeb"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/borksh/bork","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borksh%2Fbork","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borksh%2Fbork/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borksh%2Fbork/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borksh%2Fbork/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/borksh","download_url":"https://codeload.github.com/borksh/bork/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borksh%2Fbork/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261937020,"owners_count":23232843,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","configuration","shell","unix-utilities"],"created_at":"2024-11-06T06:03:18.828Z","updated_at":"2025-06-25T19:03:20.964Z","avatar_url":"https://github.com/borksh.png","language":"Shell","funding_links":["https://github.com/sponsors/skylarmacdonald","https://monzo.me/skylarmacdonald","https://paypal.me/skylarmacdonald"],"categories":[],"sub_categories":[],"readme":"# Bork - Skylar MacDonald's Bork Fork\n\nI still use [Bork](https://github.com/mattly/bork) in the year 2021, so I forked it to fix it.\n\n![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/borksh/bork)\n![Test status](https://github.com/borksh/bork/workflows/Test/badge.svg)\n![FreeBSD test status](https://img.shields.io/cirrus/github/borksh/bork?label=FreeBSD\u0026logo=freebsd)\n\nBork puts the 'sh' back into IT. [Bork Bork Bork](https://www.youtube.com/results?search_query=swedish+chef).\n\n## the Swedish Chef Puppet of Config Management\n\nBork is a bash DSL for making declarative assertions about the state of a system.\n\nBork is written against Bash 3.2 and common unix utilities such as sed, awk and\ngrep. It is designed to work on any UNIX-based system and maintain awareness of\nplatform differences between BSD and GPL versions of unix utilities.\n\n# Installation\n\n## From source\n\n1. Clone this repository:\n```bash\ngit clone https://github.com/borksh/bork /usr/local/src/bork\n```\n\n1. Symlink the bork binaries into your `$PATH`:\n```bash\n  ln -sf /usr/local/src/bork/bin/bork /usr/local/bin/bork\n```\n\n## via Homebrew (macOS)\n\n![homebrew](https://img.shields.io/homebrew/v/bork)\n\n1. Install via Homebrew:\n```bash\nbrew install bork\n```\n\n## via npm\n\n![npm](https://img.shields.io/npm/v/@borksh/bork)\n\n1. Install via npm:\n```bash\nnpm install -g @borksh/bork\n```\n\n## via pre-built package\n\nStarting with version 0.13.0, packages are available for a handful of operating\nsystems on the [GitHub releases page](https://github.com/borksh/bork/releases).\nThese are generally from CI and built unsigned, but SHA-1 hashes are always\navailable.\n\n## Updating\n\nBork can update itself as part of satisfying your config file. Your config file\nshould look something like this to update via git:\n\n```bash\nok github /usr/local/src/bork borksh/bork --branch=main\nok symlink /usr/local/bin/bork /usr/local/src/bork/bin/bork\n```\n\n(This example relies on you being able to write to `/usr/local`; if your Bork is\ninstalled elsewhere you should replace the paths above.)\n\nIf you have Homebrew available to you, you can do this instead:\n\n```bash\nok brew bork\n```\n\nYou can also specify the `--HEAD` option on the assertion to install Bork's\n`main` branch via Homebrew:\n\n```bash\nok brew bork --HEAD\n```\n\nThis will always keep the latest commit installed. Note that the latest commit\nwill contain unreleased code that might break, so take care when using it.\n\nUsing a package manager is the recommended way to install, as then you can\nensure you're only installing released versions of Bork, and rely on it to\nupdate Bork for you. If you prefer to use git, you can use `bork version` to\nshow the status of your local repo or installation. This command should be able\nto tell you how you installed Bork (e.g. via git or Homebrew), and therefore how\nyou should go about updating it.\n\n# Usage and Operations\n\nRunning bork without arguments will output some help:\n\n```\nbork usage:\n\nbork operation [config-file] [options]\n\nwhere \"operation\" is one of:\n\n- check:      perform 'status' for a single command\n    example:  bork check ok github skylarmacdonald/dotfiles\n- compile:    compile the config file to a self-contained script output to STDOUT\n    --conflicts=(y|yes|n|no)  If given, sets an automatic answer for conflict resolution.\n    example:  bork compile dotfiles.sh --conflicts=y \u003e install.sh\n- do:         perform 'satisfy' for a single command\n    example:  bork do ok github skylarmacdonald/dotfiles\n- satisfy:    satisfy the config file's conditions if possible\n- status:     determine if the config file's conditions are met\n- inspect:    output a Bork config file based on a type's current configuration\n- types:      list types and their usage information\n- docgen:     generates documentation under docs/_types for newly-added types\n- version:    get the currently installed version of bork\n```\n\nLet's explore these in more depth:\n\n## Assertions and Config Files\n\nAt the heart of bork is making **assertions** in a **declarative** manner via\nthe `ok` and `no` functions. That is, you tell it *what* you want the system to\nlook like instead of *how* to make it look like that. An assertion takes a\n**type** and a number of arguments. It invokes the type's handler function with\nan *action* such as `status`, `install`, or `upgrade`, which determines the\nimperative commands needed to test the assertion or bring it up to date. There\nare a number of included types in the `types` directory, and bork makes it easy\nto create your own. The `no` function works as an opposite to `ok` -- an `ok`\nassertion will require the *presence* of something, and a `no` assertion will\nrequire its absence.\n\nHere's a basic example:\n\n```bash\nok brew                                                # presence and updatedness of Homebrew\nok brew git                                            # presence and updatedness of Homebrew git package\nok directory $HOME/code                                # presence of the ~/code directory\nok github $HOME/code/dotfiles skylarmacdonald/dotfiles # presence, drift of git repository in ~/code/dotfiles\ncd $HOME\nfor file in $HOME/code/dotfiles/configs/.[!.]*\ndo                                            # for each file in ~/code/dotfiles/configs,\n  ok symlink \"$(basename $file)\" $file       # presense of a symlink to file in ~ with a leading dot\ndone\n```\n\nWhen run, bork will test each `ok`/`no` assertion and determine if it's met or\nnot. If not, bork can go ahead and *satisfy* the assertion by installing,\nupgrading, removing, or otherwise altering the configuration of the item to\nmatch the assertion. It will then test the assertion again. Declarations are\nidempotent -- if the assertion is already met, bork will not do anything.\n\nWhen you're happy with your config script, you can compile it to a standalone\nscript which does not require bork to run. The compiled script can be passed\naround via curl, scp or the like and run on completely new systems.\n\n## Assertion Types\n\nYou can run `bork types` from the command line to get a list of the assertion\ntypes and some basic information about their usage and options.\n\nIf adding features to Bork core, you can also use the command `bork docgen` to\ngenerate GitHub Pages-compatible Markdown files based on how a type responds to\nthe `desc` action.\n\n### Generic assertions\n```\n          check: runs a given command.  OK if returns 0, FAILED otherwise.\n```\n\n### File System\n```\n      directory: asserts presence of a directory\n           file: asserts the presence, checksum, owner and permissions of a file\n       download: asserts the presence of a file compared to an http(s) url\n        symlink: assert presence and target of a symlink\n```\n\n### Source Control\n```\n            git: asserts presence and state of a git repository\n         github: front-end for git type, uses github urls\n```\n\n### Language Package Managers\n```\n            gem: asserts the presence of a gem in the environment's ruby\n            npm: asserts the presence of a nodejs module in npm's global installation\n            pip: asserts presence of packages installed via pip\n           pip3: asserts presence of packages installed via pip3\n          pipsi: asserts presence of pipsi or packages installed via pipsi\n            apm: asserts the presence of an atom package\n         go-get: asserts the presence of a go package\n```\n\n### macOS specific\n```\n           brew: asserts presence of packages installed via Homebrew on macOS\n       brew-tap: asserts a Homebrew formula repository has been tapped; does NOT assert updatedness of a tap's formula. Use `ok brew` for that.\n           cask: asserts presence of apps installed via caskroom.io on macOS\n       defaults: asserts settings for macOS's 'defaults' system\n            mas: asserts a Mac app is installed and up-to-date from the App Store\n                 via the 'mas' utility https://github.com/argon/mas\n         scutil: verifies macOS machine name with scutil\n```\n\n### Linux specific:\n```\n            apt: asserts packages installed via apt-get on Debian or Ubuntu Linux\n            apk: asserts packages installed via apk (Alpine Linux)\n            yum: asserts packages installed via yum on CentOS or RedHat Linux\n         zypper: asserts packages installed via zypper (SUSE)\n```\n\n### User management (currently Linux-only)\n```\n          group: asserts presence of a unix group (Linux only, for now)\n           user: assert presence of a user on the system\n```\n\n### UNIX utilities\n```\n       iptables: asserts presence of iptables rule\n         shells: asserts presence of a shell in /etc/shells\n```\n\n## Runtime Operations\n\nPer the usage guide, bork has a few main modes of operation:\n\n- `status`: Reports on the status of the assertions in a config file.\n- `satisfy`: Checks the status of assertions in a config file, satisfying them\n  where needed.\n- `compile`: Compiles a config file to a standalone script.\n- `check`: Performs a status report on a single assertion.\n- `do`: Performs a satisfy operation on a single assertion.\n- `inspect`: Output a Bork-compatible config file based on the current state of\nthe system.\n\n### bork status myconfig.sh\n\nThe `status` command will confirm that assertions are met or not, and output\ntheir status. It will not take any action to satisfy those assertions. There are\na handful of statuses an assertion can return, and this since this mode is the\nclosest bork can do to a true 'dry run'\\*, you can use it to test a script\nagainst a pre-existing machine.\n\n\\* Some types, such as `git`, need to modify local state by talking to the\nnetwork (such as performing `git fetch`), without modifying the things the\nassertion aims to check.\n\nThe status command will give you output such as:\n\n```\noutdated: brew\nok: brew git\nmissing: brew zsh\nok: directory /Users/skylar/code\nconflict (upgradable): github skylarmacdonald/dotfiles\nlocal git repository has uncommitted changes\nok: symlink /Users/skylar/.gitignore /Users/skylar/code/dotfiles/configs/gitignore\nconflict (clobber required): symlink /Users/skylar/.lein /Users/skylar/code/dotfiles/configs/lein\nnot a symlink: /Users/skylar/.lein\nmismatch (upgradable): defaults com.apple.dock tilesize integer 36\nexpected type: integer\nreceived type: float\nexpected value: 36\nreceived value: 55\n```\n\nEach item reports its status like so:\n\n- `ok`: The assertion is met as best we can determine.\n- `no`: The assertion is met, because the item is absent from the system.\n- `missing`: The assertion is not met, and no trace of it ever being met was found.\n- `present`: The assertion is not met, as something is present on the system\n  that shouldn't be. It can be satisfied by removing the item.\n- `outdated`: The assertion is met, but can be upgraded to a newer version.\n- `mismatch (upgradable)`: The assertion is not met as specified, something is\n  different. It can be satisfied easily. An explanation will be given.\n- `conflict (upgradable)`: The assertion is not met as specified. It can be\n  satisfied easily, but doing so may result in data loss.\n- `conflict (clobber required)`: The assertion is not met as specified. Bork\n  cannot currently satisfy this assertion. In the future, it will be able to,\n  but doing so may result in data loss.\n\n### bork check ok github skylarmacdonald/dotfiles\n\nThe `check` command will take a single assertion on the command line and perform\na `status` check as above for it.\n\n### bork satisfy myconfig.sh\n\nThe `satisfy` command is where the real magic happens. For every assertion in\nthe config file, bork will check its status as described in the `status` command\nabove, and if it is not `ok` or `no` it will attempt to make it `ok` or `no`,\ntypically via *installing*, *upgrading* or *removing* something -- but sometimes\na *conflict* is detected which could lose data, such as a local git repository\nhaving uncommitted changes. In that case, bork will warn you about the problem\nand ask if you want to proceed. Sometimes conflicts are detected which bork does\nnot know how to resolve — it will warn you about the problem so you can fix it\nyourself.\n\n### bork do ok github skylarmacdonald/dotfiles\n\nThe `do` command will take a single assertion on the command line and perform a\n`satisfy` operation on it as above.\n\n### bork compile myconfig.sh\n\nThe `compile` command will output to STDOUT a standalone shell script that does\nnot require bork to run. You may pass this around as with any file via curl or\nscp or whatever you like and run it. Any sub-configs via `include` will be\nincluded in the output, and any type that needs to include resources to do what\nit does, such as the `file` type, will include their resources in the script as\nbase64 encoded data.\n\n### bork inspect brew\n\nThe `inspect` command will ask a type for a current inventory of how a system is\nconfigured, and output to STDOUT a Bork-compatible config file to configure the\nsame state. For example, when used with the `brew` type, this will list all\nformulae installed with Homebrew and output a config file to check for those\nsame formulae. **Not all types will work with this command.** Bork will exit\nwith code 1 if a type has not implemented `inspect`.\n\n## Custom Types\n\nWriting new types is pretty straightforward, and there is a guide to writing\nthem in the `docs/` directory. If you wish to use a type that is not in bork's\n`types` directory, you can let bork know about it with the `register`\ndeclaration:\n\n```bash\nregister etc/pgdb.sh\nok pgdb my_app_db\n```\n\n## Composing Config Files\n\nYou may compose config files into greater operations with the `include`\ndirective with a path to a script relative to the current script's directory.\n\n```bash\n# this is main.sh\ninclude databases.sh\ninclude etc/projects.sh\n```\n\n```bash\n# this is etc/projects.sh\ninclude project-one.sh\ninclude project-two.sh\n# these will be read from the etc/ directory\n```\n\n## Taking Further Action on Changes\n\nBork has two types of callback: before and after functions. These are only used\nwhen Bork is satisfying assertions (i.e. when running `bork satisfy`).\n\nUntil Bork starts processing an assertion made with `ok` or `no`, there's no way\nto know if anything will change. Therefore, Bork will look for and execute\nfunctions with known names while it processes an assertion, before making the\nchange.\n\nThe functions Bork expects are named:\n\n- `bork_will_change`: Bork will make any change at all to the system, i.e., the\n  assertion is not satisfied and Bork will change it.\n- `bork_will_install`: The assertion is completely missing, and Bork will\n  install something fresh to satisfy it.\n- `bork_will_upgrade`: The assertion is partially satisfied, but needs upgrading\n  (e.g. an outdated package, a file with the wrong permissions). Bork will\n  change it in-place to satisfy it fully.\n- `bork_will_remove`: The assertion specifies the removal of something that is\n  present on the system, and Bork will remove it to satisfy the assertion.\n\nEach of these will be unset by Bork after it has run them. You should only\ndefine these functions immediately before the assertion you wish to apply them\nto.\n\nBork will also call all of these functions with `_any` appended to the names\n(e.g. `bork_will_change_any`) -- these callbacks will not be unset, and will be\ncalled every time it applies.\n\nThese are used as follows:\n\n```bash\nbork_will_install () {\n  echo \"callback says hello world\"\n}\nok directory foo\n```\n\nBork will then output the following if (and only if) the directory `foo` has\nbeen newly created:\n\n```\nmissing: directory foo\ncallback says hello world\nverifying install: directory foo\n* success\n```\n\nIf the directory had already existed, the `bork_will_install` function would not\nhave been called. Bork would also not have called the function if it had\nupgraded the state of the system, e.g. if the directory had existed but had the\nincorrect permissions.\n\nAfter Bork has made a change, you may call a provided function in your script to\ndetermine the outcome of the change. These are used as follows:\n\n```bash\nok brew fish\nif did_install; then\n  ok shells $(brew --prefix)/bin/fish\n  chsh -s $(brew --prefix)/bin/fish\nfi\n```\nThere are five functions to help you take further actions after a change:\n\n- `did_install`: did the previous assertion result in the item being installed\n  from scratch?\n- `did_upgrade`: did the previous assertion result in the existing item being\n  upgraded?\n- `did_update`: did the previous assertion result in either the item being\n  installed or upgraded?\n- `did_remove`: did the previous assertion result in the existing item being\n  removed (e.g. deleted or uninstalled)?\n- `did_error`: did attempting to install or upgrade the previous assertion\n  result in an error?\n\nUnlike with before callbacks, Bork will not call any functions after making a\nchange. It is up to you to handle the logic however you wish. As with the before\ncallbacks, you are strongly advised to use these functions immediately after the\nassertion you wish to check.\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch: `git checkout -b feature/my-new-feature`\n3. Commit your changes: `git commit -am 'Add some feature'`\n4. Push to the branch: `git push origin feature/my-new-feature`\n5. Submit a pull request\n\n### Contribution Guidelines\n\n1. Prefer clarity of intent over brevity. Bash can be an obtuse language, but it\n   doesn't *have* to be. Many people have said bork has some of the clearest\n   bash code they've ever seen, and that's a standard to strive for.\n\n2. Favor helper abstractions over arbitrary platform-specific checks. See\n   [`md5cmd`](lib/helpers/md5cmd.sh), [`http`](lib/helpers/http.sh), and\n   [`permission_cmd`](lib/helpers/permission_cmd.sh), and look at how they're\n   used.\n\n3. Types are independent, stateless, and atomic. Do not attempt to maintain a\n   cache in a type file unless you're talking to the network. An assertion is\n   the *whole* of the assertion — don't attempt to create a multi-stage\n   assertion type that depends on maintaining state. Find a way to express the\n   whole of the assertion in one go.\n\n4. Leave Dependency Management to the user. Is a needed binary not installed for\n   a type? Return `$STATUS_FAILED_PRECONDITION` in your status check. Let the\n   user decide the best way to satisfy any dependencies.\n\n## Community\n\nDiscuss on [GitHub Discussions](https://github.com/borksh/bork/discussions)\n\n## Requirements / Dependencies\n\n* Bash 3.2\n\n## Version\n\n0.14.0\n\n## License\n\n[Apache License 2.0](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborksh%2Fbork","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fborksh%2Fbork","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborksh%2Fbork/lists"}