{"id":13878030,"url":"https://github.com/consist-sh/consist","last_synced_at":"2026-03-08T13:32:58.476Z","repository":{"id":209693568,"uuid":"724359029","full_name":"consist-sh/consist","owner":"consist-sh","description":"The stone age one person framework server scaffolder","archived":false,"fork":false,"pushed_at":"2024-03-01T14:40:03.000Z","size":179,"stargazers_count":82,"open_issues_count":4,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2024-11-15T04:56:45.263Z","etag":null,"topics":["deploy","kamal","rails","server","setup"],"latest_commit_sha":null,"homepage":"https://consist.sh","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/consist-sh.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-27T23:13:20.000Z","updated_at":"2024-10-02T13:47:18.000Z","dependencies_parsed_at":"2024-08-22T19:52:34.284Z","dependency_job_id":null,"html_url":"https://github.com/consist-sh/consist","commit_stats":null,"previous_names":["consist-sh/consist"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/consist-sh%2Fconsist","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/consist-sh%2Fconsist/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/consist-sh%2Fconsist/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/consist-sh%2Fconsist/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/consist-sh","download_url":"https://codeload.github.com/consist-sh/consist/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226134226,"owners_count":17578778,"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":["deploy","kamal","rails","server","setup"],"created_at":"2024-08-06T08:01:37.972Z","updated_at":"2026-03-08T13:32:58.440Z","avatar_url":"https://github.com/consist-sh.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# consist\n\n[![Gem Version](https://img.shields.io/gem/v/consist)](https://rubygems.org/gems/consist)\n[![Gem Downloads](https://img.shields.io/gem/dt/consist)](https://www.ruby-toolbox.com/projects/consist)\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/consist-sh/consist/ci.yml)](https://github.com/consist-sh/consist/actions/workflows/ci.yml)\n[![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/consist-sh/consist)](https://codeclimate.com/github/consist-sh/consist)\n\n**THIS IS BETA SOFTWARE UNDER ACTIVE DEVELOPMENT. APIs AND FEATURES WILL CHANGE.**\n\n\u003e consist - (noun): a set of railroad vehicles forming a complete train.\n\n`consist` is the one person framework server scaffolder. It is stone age tech.\n\nYou can use it to quickly baseline a raw server using a given recipe provided\nby Consist. I use it to baseline new Droplets to be ready to run Kamal in\nsingle server setup for a Rails monolith. While Kamal will setup Docker\nfor you, it does not do anything else related to configuring the underlying\nserver, such as firewalls, general hardening, enabling swapfile etc.\n\n## Project Principles\n\n- Minimal tool specific language / knowledge required to use Consist\n- Procedural declaration execution - no converging, orchestration or\n  event driven operation\n- If you can shell script it, you can `consist` it directly\n- Encouraging sharing of portable `Consistfiles` across the community\n\n---\n\n- [Quick start](#quick-start)\n- [Support](#support)\n- [Rationale](#rationale)\n- [Key Concepts](#key-concepts)\n  - [Recipes](#recipes)\n  - [Steps](#steps)\n  - [Files](#files)\n  - [Consistfile](#consistfile)\n  - [Artifacts](#artifacts)\n  - [.consist directory](#.consist-directory)\n- [Community Consistfiles](#community-consistfiles)\n- [Is It Good?](#is-it-good%3F)\n- [License](#license)\n- [Code of conduct](#code-of-conduct)\n- [Contribution guide](#contribution-guide)\n\n## Quick start\n\nMake sure the `consist` gem is installed:\n\n```sh\ngem install consist\n```\n\nYou must be already authenticated with the server you want to scaffold.\n`consist` will use your account's SSH id to perform actions.\n\nThe main way of using `consist` is to go with a `Consistfile` in\nyour project root that describes the recipe and steps. Then you can say:\n\n```sh\nconsist up \u003cip_address\u003e [--consistfile=/path/to/consistfile] [--consistdir=/path/to/.consistdir]\n```\n\nAnd `consist` will do it's thing with that given IP address.\n\nTo create a blank `Consistfile` in your project, execute:\n\n```ruby\nconsist init\n```\n\n## Commands\n\nOther commands available:\n\n- `consist init [account/repo]` - initialize your project with a new Consist file. Optionally,\n  you can specify a Github `account/repo` path and that location will be used to clone down a\n  Consistfile, and any associated artifacts needed by the Consistfile.\n- `consist ping \u003cip_address\u003e` - checks you can connect and authenticate with the given IP\n\n## Features\n\n- Simple Ruby based DSL\n- ERB interpolation of config on shell commands and file contents\n- Small API surface area - quick to learn\n\n## Rationale\n\nI wanted a super-simple tool, that was baked in Ruby, for setting up\nrandom servers to specific configurations. This is the result.\n\nOn a scale of 1 to 10, with 10 being Terraform, this tool is basically\nas low-rent you can get to hand running scripts yourself, so about a 3\non the scale.\n\nIf you know how to shell script what you want, you can stick it in a step,\nand add it to a recipe.\n\nThe more I work in this industry, the less I see using other people's code\nand tools as a benefit, and more of a liability. I appreciate the paradox I'm\ncreating here for you 😅\n\n### Why not use Terraform / Ansible / Salt etc?\n\nI think they are bad tools for my needs. I wanted something simple\nI could hack on, grow only when needed, and will work specifically\nwithout ambiguity. For example, Ansible has a lot of nonsense with case sensitivity,\nTerraform does [weird unexpected things](https://github.com/hashicorp/terraform/issues/16330).\n\nI didn't want to keep maintaining specific knowledge of these infrastructure\nas code tools in my brain anymore, along with all of their peculiarities and oddities.\n\n**If you prefer those tools, go ahead and use them.**\n\nAin't nobody stopping you.\n\n## Key Concepts\n\nConsist leans on three primary ideas: recipes, steps and files. Recipes contain\none or more steps. Steps tend to be atomic and idempotent.\n\n### Recipes\n\nExample of a recipe:\n\n```ruby\nname \"Kamal Single Server\"\ndescription \"Sets up a single server to run Kamal\"\nuser :root\n\nsteps do\n  step :update_apt_packages\n  step :install_apt_packages\nend\n```\n\n### Steps\n\nExample of a step:\n\n```ruby\nname \"Install APT packages\"\nrequired_user :root\n\nshell \"Installing essential packages\" do\n  \u003c\u003c~EOS\n    apt-get -y remove systemd-timesyncd\n    timedatectl set-ntp no\n    apt-get -y install build-essential curl fail2ban git ntp vim\n    apt-get autoremove\n    apt-get autoclean\n  EOS\nend\n\nshell \"Start NTP and Fail2Ban\" do\n  \u003c\u003c~EOS\n    service ntp restart\n    service fail2ban restart\n  EOS\nend\n```\n\n### Files\n\nExample of a file:\n\n```ruby\nfile :hostname do\n  \u003c\u003c~EOS\n  \u003c%= hostname %\u003e\n  EOS\nend\n```\n\n### Consistfile\n\nA `Consistfile` is a portable giant file of a recipe and all its\nsteps. Something like (this is a _full_ example, in practice you\nwould reference some of Consists' built in steps):\n\n```ruby\n# This is a shortened non-complete example.\nconsist do\n  config :hostname, \"testexample.com\"\n  config :site_fqdn, \"textexample.com\"\n  config :admin_email, \"j@jmd.fm\"\n  config :swap_size, \"2G\"\n  config :swap_swappiness, \"60\"\n  config :timezone, \"UTC\"\n\n  file :apt_auto_upgrade do\n    \u003c\u003c~EOS\n      APT::Periodic::AutocleanInterval \"7\";\n      APT::Periodic::Update-Package-Lists \"1\";\n      APT::Periodic::Unattended-Upgrade \"1\";\n    EOS\n  end\n\n  file :hostname do\n    \u003c\u003c~EOS\n      \u003c%= hostname %\u003e\n    EOS\n  end\n\n  file :timezone do\n    \u003c\u003c~EOS\n      \u003c%= timezone %\u003e\n    EOS\n  end\n\n  recipe :kamal_single_server do\n    name \"Kamal Single Server Scaffold\"\n\n    steps do\n      step :set_hostname do\n        upload_file message: \"Setting hostname\",\n          local_file: :hostname,\n          remote_path: \"/etc/hostname\"\n\n        shell do\n          \u003c\u003c~EOS\n            hostname \u003c%= Consist.config[:hostname] %\u003e\n          EOS\n        end\n\n        mutate_file mode: :replace, target_file: \"/etc/hosts\", match: \"^127.0.0.1 localhost$\",\n          target_string: \"127.0.0.1 localhost \u003c%= hostname %\u003e\"\n      end\n\n      step :setup_timezone do\n        shell do\n          \u003c\u003c~EOS\n            rm /etc/localtime\n          EOS\n        end\n\n        upload_file message: \"Setting Timezone\",\n          local_file: :timezone,\n          remote_path: \"/etc/timezone\"\n\n        shell do\n          \u003c\u003c~EOS\n            chmod 0644 /etc/timezone\n            ln -s /usr/share/zoneinfo/\u003c%= timezone %\u003e /etc/localtime\n            chmod 0644 /etc/localtime\n            DEBIAN_FRONTEND=noninteractive dpkg-reconfigure -f noninteractive tzdata\n          EOS\n        end\n      end\n    end\n  end\nend\n\n# vim: filetype=ruby\n```\n\nGiven a `Consistfile` you could then say `consist up \u003cip_address\u003e` and\nit would just work.\n\n### Artifacts\n\nArtifacts allow you to split out your `Consistfile` into separate files.\n\nYou can create blocks in the `Consistfile` as shown above, but you can also only\nspecify an `id`, and that `id` will be used to try and attempt to load a file of that\nname in the `.consist/\u003ctype\u003e/\u003cid\u003e` directory. For example, referencing a file:\n\n```ruby\nfile :apt_auto_upgrade\n```\n\nWill attempt to load a file in `.consist/files/apt_auto_upgrade`. The same is\npossible for any of the main types: `files`, `steps`, and `recipes`\n\n### `.consist` directory\n\nThe `.consist` directory is assumed to be in the root of your project, and should\ncontain three subdirectories for each of the types: `files`, `steps`, `recipes`.\n\nYou can specify an alternate directory location by passing the `--consistdir` switch\nto the `up` command.\n\n## Community Consistfiles\n\nIf you create a Github repo, and all it contains is a `Consistfile` and any associated\nartifacts under a `.consist` directory, other people will be able to use it by\nexecuting `consist init \u003cgh_repo_path\u003e` in their project root.\n\nIf you create one, please open a PR to include it here:\n\n| Name                      | Repo                                                                                | Description                                                 |\n| ------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------- |\n| Kamal Single Server Setup | [consist-sh/kamal-single-server](https://github.com/consist-sh/kamal-single-server) | Setup a single server with good defaults ready to run Kamal |\n\n## Is it good?\n\nI think so. But I don't know, use your own brain or something. Don't listen to\nme.\n\n## Support\n\nIf you want to report a bug, or have ideas, feedback or questions about the gem,\n[let me know via GitHub issues](https://github.com/johnmcdowall/consist/issues/new)\nand I will do my best to provide a helpful answer.\n\n## License\n\nThe gem is available as open source under the terms of the [LGPLv3 License](LICENSE.txt).\n\n## Code of conduct\n\nEveryone interacting in this project’s codebases, issue trackers, chat\nrooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).\n\n## Contribution guide\n\nPull requests are welcome, but I want you to open an Issue first to discuss your\nideas. Thanks.\n\n## Development\n\n1. Clone the repo\n2. Run `bundle install`\n3. Run `bin/dev` to execute consist locally without having to build and install.\n\nMake sure any PRs have been formatted with `standard`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconsist-sh%2Fconsist","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconsist-sh%2Fconsist","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconsist-sh%2Fconsist/lists"}