{"id":15712342,"url":"https://github.com/razor-x/config-curator","last_synced_at":"2025-05-07T13:46:33.459Z","repository":{"id":42360968,"uuid":"111950109","full_name":"razor-x/config-curator","owner":"razor-x","description":"Config curator is CLI tool for installing static configuration files.","archived":false,"fork":false,"pushed_at":"2023-01-07T22:14:14.000Z","size":263,"stargazers_count":30,"open_issues_count":1,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-03T11:47:02.062Z","etag":null,"topics":["config","config-management","devlopment","dotfiles","infrastructure-as-code"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/config-curator","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/razor-x.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-11-24T19:31:27.000Z","updated_at":"2023-11-07T12:47:15.000Z","dependencies_parsed_at":"2023-02-08T02:47:55.553Z","dependency_job_id":null,"html_url":"https://github.com/razor-x/config-curator","commit_stats":null,"previous_names":["rxrc/curator"],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razor-x%2Fconfig-curator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razor-x%2Fconfig-curator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razor-x%2Fconfig-curator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razor-x%2Fconfig-curator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/razor-x","download_url":"https://codeload.github.com/razor-x/config-curator/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252890096,"owners_count":21820312,"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":["config","config-management","devlopment","dotfiles","infrastructure-as-code"],"created_at":"2024-10-03T21:15:41.900Z","updated_at":"2025-05-07T13:46:33.434Z","avatar_url":"https://github.com/razor-x.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# ☄️ Config Curator\n\n[![npm](https://img.shields.io/npm/v/config-curator.svg)](https://www.npmjs.com/package/config-curator)\n[![main](https://github.com/razor-x/config-curator/actions/workflows/main.yml/badge.svg)](https://github.com/razor-x/config-curator/actions/workflows/main.yml)\n\n**CLI tool for installing static configuration or dotfiles.**\n\n## Description\n\n- 🦉 **Idempotent:** syncs directories, copies files,\n  creates system links, deletes paths, and sets access permissions to ensure\n  the system will be in a consistent state after each run.\n- 🐬 **Declarative**: all operations are defined\n  in a manifest file with a simple syntax.\n- 🐍 **Flexible**: operations may be limited only to specific hosts\n  or only when specific packages are installed,\n  additionally, since the manifest is written in JavaScript,\n  it may include arbitrary logic.\n- 🐹 **Minimal:** written in \u0026lt; 500 lines of code\n  using only the Node.js standard library and a few system calls.\n- 🐡 **Secure:** no additional third party dependencies (except `rsync`):\n  safe to run with `sudo` to install system files.\n- 🦅 **Fast:** uses maximal concurrency\n  and allows custom ordering of groups of operations.\n\n\n#### Try it out\n\n1. Clone this repo.\n2. Run `npm install`.\n3. Run `npm test`:\n   This will install the configuration\n   defined in `test/manifest.js` to `test/dest`.\n\n## Requirements\n\n- Linux or macOS with [rsync] installed.\n- [Node.js] version 8 or above.\n- For conditional configuration based on installed packages,\n  the following package managers are supported:\n  [Pacman], [Homebrew], [dpkg], or [pkgng].\n\n[dpkg]: https://help.ubuntu.com/lts/serverguide/dpkg.html\n[Homebrew]: https://brew.sh/\n[Node.js]: https://nodejs.org/\n[Pacman]: https://www.archlinux.org/pacman/\n[pkgng]: https://wiki.freebsd.org/pkgng\n[rsync]: https://rsync.samba.org/\n\n## Installation\n\n1. Add this as a development dependency to your project using [npm] with\n\n    ```\n    $ npm install --save-dev config-curator\n    ```\n\n2. Add a [script][npm scripts] to your `package.json` with `\"curator\": \"curator\"`\n   so you may run this with\n\n    ```\n    $ npm run curator\n    ```\n\n[npm]: https://www.npmjs.com/\n[npm scripts]: https://docs.npmjs.com/misc/scripts\n\n## Usage\n\nCreate a `manifest.js` file to define the configuration\nand run the `curator` command to install the configuration.\n\n- The manifest configuration is defined below.\n  Copy the example manifest from [`manifest.example.js`](./manifest.example.js).\n- The manifest should be the default export and may be an object, function,\n  promise, or async function.\n- The location of the manifest file may be passed as the first argument,\n  otherwise it looks for `manifest.js` in the current working directory.\n- The environment variables `CURATOR_IO` and `CURATOR_PKG` may be set\n  to override the `ioType` and `pkgType` values from the manifest.\n\n### Manifest\n\n#### Minimal example\n\n```js\n/* manifest.js */\n\nimport os from 'os'\n\nconst targetRoot = os.homedir()\n\nconst unlinks = [\n  {\n    src: 'old.conf'\n  }\n]\n\nconst directories = [\n  {\n    src: '.config/envs'\n  },\n  {\n    src: 'vim',\n    dst: '.vim'\n  },\n  {\n    src: 'private',\n    dmode: '0700',\n    fmode: '0600',\n    user: 'root',\n    group: 'wheel'\n  }\n]\n\nconst files = [\n  {\n    src: '.zshrc'\n  },\n  {\n    src: `keys/${os.hostname()}`,\n    dst: '.ssh/id_rsa',\n    fmode: '0600',\n    pkgs: ['openssh']\n  }\n]\n\nconst symlinks = [\n  {\n    src: '.config/env.conf',\n    dst: '.config/envs.${os.hostname()}.conf',\n    hosts: ['alpha', 'delta']\n  }\n]\n\nexport default {\n  targetRoot,\n  unlinks,\n  directories,\n  files,\n  symlinks\n}\n```\n\n#### Complete manifest API\n\n```js\n/* manifest.js */\n\nimport os from 'os'\n\n/* Prefix for all source paths\n * except for unlinks and symlinks which use targetRoot below.\n *\n * Use process.cwd() for the current working directory.\n *\n * Default: the current working directory.\n */\nconst originRoot = os.homedir()\n\n/* Prefix for all destination paths.\n * For unlinks and symlinks, the source is also prefixed.\n *\n * Use process.cwd() for the current working directory.\n *\n * Default: a ./dest folder under the current working directory.\n */\nconst targetRoot = os.homedir()\n\n/* Package lookup backend to use:\n * pacman, dpkg, homebrew, pkgng, or noop.\n *\n * The special value noop will assume all packages are installed.\n *\n * Default: attempt to autodetect, fallback to noop.\n */\nconst pkgType = 'homebrew'\n\n/* I/O backend to use:\n * linux, macos, or noop.\n *\n * The special value noop will not perform any modifications\n * and only log what actions would be taken.\n *\n * Default: attempt to autodetect, fallback to noop.\n */\nconst ioType = 'macos'\n\n/* Defaults to use for each operation.\n *\n * Default: shown below.\n */\nconst defaults = {\n  order: 100, // all operations start with this order value\n  dmode: '0750', // files have user write, group read, other no access\n  fmode: '0640', // directories have user write, group read, other no access\n  user: process.getuid(), // current user\n  group: process.getgid() // current group\n}\n\n/* Operations to perform by type.\n *\n * Operations always happen in this order: unlinks, directories, files, and symlinks.\n *\n * Each type of operations waits until the previous type has completed successfully.\n *\n * Operations of each type with equal order value are always done in parallel,\n * but operations with a later order do not start until earlier ones complete.\n *\n * Specifying an array of hostnames will restrict that\n * operation to matching hosts (case insensitive).\n *\n * Specifying an array of packages will restrict that\n * operation to hosts with all packages installed (case insensitive).\n *\n * All operation types will default to an empty array if unset.\n */\n\n/* Unlink (unconditionally remove) the directory, file, or symlink at src.\n *\n * The src is relative to the global targetRoot option.\n */\nconst unlinks = [\n  {\n    // Remove ~/intruders on all hosts.\n    src: 'intruders'\n  },\n  {\n    // Remove ~/warpcore on host enterprise, if the eject package is installed.\n    src: 'warpcore',\n    hosts: ['enterprise'],\n    pkgs: ['eject']\n  }\n]\n\n/* Synchronize the contents of the directory at src to dst\n * and sets the directory and file permissions.\n *\n * The src is relative to the global originRoot option.\n * The dst is relative to the global targetRoot option.\n *\n * NOTE: this WILL remove files in dst that are not in src.\n *\n * If dst is not given, will use src as the subpath.\n */\nconst directories = [\n  {\n    // Synchronize ./holodeck to ~/holodeck on all hosts.\n    src: 'holodeck'\n  },\n  {\n    // Synchronize ./panels/exploding to ~/bridge/panels\n    // on hosts enterprise and defiant,\n    // if the turbolift and transporter packages are installed,\n    // and set specific user, group, and access permissions.\n    src: 'panels/exploding',\n    dst: 'bridge/panels',\n    user: 'numberone',\n    group: 'officers',\n    dmode: '0755',\n    fmode: '0644',\n    hosts: ['enterprise', 'defiant'],\n    pkgs: ['turbolift', 'transporter']\n  },\n  {\n    // Install sickbay first, then install the beds and meds.\n    src: 'decks/sickbay',\n    dst: 'sickbay',\n    order: 10\n  },\n  {\n    src: 'beds',\n    dst: 'sickbay/beds',\n    order: 11\n  },\n  {\n    src: 'meds',\n    dst: 'sickbay/meds',\n    order: 11\n  }\n]\n\n/* Copy the file at src to dst\n * and sets the file permissions.\n *\n * The src is relative to the global originRoot option.\n * The dst is relative to the global targetRoot option.\n *\n * NOTE: this WILL replace the file at dst.\n *\n * If dst is not given, will use src as the subpath.\n */\nconst files = [\n  {\n    // Copy ./bay/torpedo to ~/bay/torpedo on all hosts.\n    src: 'bay/torpedo'\n  },\n  {\n    // Copy ./phaser to ~/brig/phaser\n    // on host defiant, if the stun package is installed,\n    // and set the user, group, and access permissions.\n    src: 'phaser',\n    dst: 'brig/phaser',\n    user: 'warf',\n    group: 'security',\n    fmode: '0600',\n    hosts: ['defiant'],\n    pkgs: ['stun']\n  }\n]\n\n/* Create a system link (symlink) at src pointing to dst.\n *\n * The src and dst are relative to the global targetRoot option.\n *\n * NOTE: this WILL replace the file at src.\n */\nconst symlinks = [\n  {\n    // Create a symlink from ~/drink to ~/tea/earlgray/hot on all hosts.\n    src: 'drink',\n    dst: 'tea/earlgray/hot'\n  },\n  {\n    // Create a symlink from ~/hypospray to ~/hyposprays/norepinephrine\n    // on host enterprise if the sickbay package is installed.\n    src: 'hypospray',\n    dst: 'hyposprays/norepinephrine',\n    hosts: ['enterprise'],\n    pkgs: ['sickbay']\n  }\n]\n\n/* Export each value set above.\n * The export may be a plain object as below, or a function, promise,\n * or async function that returns the plain object.\n * Simply do not export an option to use the default.\n */\nexport default {\n  unlinks,\n  directories,\n  files,\n  symlinks,\n  originRoot,\n  targetRoot,\n  ioType,\n  pkgType,\n  defaults\n}\n```\n\n## Similar Software\n\nThis is the successor to my original configuration tool written in Ruby:\nhttps://github.com/razor-x/config_curator.\n\nGitHub maintains an unofficial guide to dotfiles: https://dotfiles.github.io/.\n\n## Users\n\n_If you are using Config Curator, add a link here and open a pull request._\n\n- [My Arch Linux configuration.](https://github.com/rxrc/archrc)\n- [My dotfiles.](https://github.com/rxrc/dotfiles)\n- [krlanguet's dotfiles.](https://github.com/krlanguet/dotfiles)\n\n## Contributing\n\nPlease submit and comment on bug reports and feature requests.\n\nTo submit a patch:\n\n1. Fork it (https://github.com/razor-x/config-curator/fork).\n2. Create your feature branch (`git checkout -b my-new-feature`).\n3. Make changes.\n4. Commit your changes (`git commit -am 'Add some feature'`).\n5. Push to the branch (`git push origin my-new-feature`).\n6. Create a new Pull Request.\n\n## License\n\nThis npm package is licensed under the MIT license.\n\n## Warranty\n\nThis software is provided by the copyright holders and contributors \"as is\" and\nany express or implied warranties, including, but not limited to, the implied\nwarranties of merchantability and fitness for a particular purpose are\ndisclaimed. In no event shall the copyright holder or contributors be liable for\nany direct, indirect, incidental, special, exemplary, or consequential damages\n(including, but not limited to, procurement of substitute goods or services;\nloss of use, data, or profits; or business interruption) however caused and on\nany theory of liability, whether in contract, strict liability, or tort\n(including negligence or otherwise) arising in any way out of the use of this\nsoftware, even if advised of the possibility of such damage.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frazor-x%2Fconfig-curator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frazor-x%2Fconfig-curator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frazor-x%2Fconfig-curator/lists"}