{"id":14967916,"url":"https://github.com/nikstur/userborn","last_synced_at":"2026-02-01T01:16:42.272Z","repository":{"id":251766196,"uuid":"832322476","full_name":"nikstur/userborn","owner":"nikstur","description":"Declaratively bear (manage) Linux users and groups","archived":false,"fork":false,"pushed_at":"2026-01-26T17:22:13.000Z","size":110,"stargazers_count":119,"open_issues_count":4,"forks_count":5,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-01-27T05:23:09.286Z","etag":null,"topics":["declarative","groups","linux","nix","nixos","users"],"latest_commit_sha":null,"homepage":"","language":"Rust","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/nikstur.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-07-22T19:33:25.000Z","updated_at":"2026-01-26T17:23:04.000Z","dependencies_parsed_at":"2026-01-26T19:09:19.489Z","dependency_job_id":null,"html_url":"https://github.com/nikstur/userborn","commit_stats":{"total_commits":23,"total_committers":2,"mean_commits":11.5,"dds":0.08695652173913049,"last_synced_commit":"955575c38961617908ed2e63cdb8bb24af0f9041"},"previous_names":["nikstur/userborn"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/nikstur/userborn","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikstur%2Fuserborn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikstur%2Fuserborn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikstur%2Fuserborn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikstur%2Fuserborn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nikstur","download_url":"https://codeload.github.com/nikstur/userborn/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikstur%2Fuserborn/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28963431,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T00:42:38.011Z","status":"ssl_error","status_checked_at":"2026-02-01T00:42:35.920Z","response_time":128,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["declarative","groups","linux","nix","nixos","users"],"created_at":"2024-09-24T13:38:52.352Z","updated_at":"2026-02-01T01:16:42.267Z","avatar_url":"https://github.com/nikstur.png","language":"Rust","funding_links":[],"categories":["NixOS Modules","Rust"],"sub_categories":[],"readme":"# Userborn\n\nDeclaratively bear (manage) Linux users and groups.\n\n## Features\n\n- Create system (UID \u003c 1000) and normal (UID \u003e= 1000) users.\n- Update user (password, description (gecos), home directory,\n  shell) and group (members) information.\n- Prohibit UID/GID re-use.\n- Simple JSON config format.\n- Create per-user groups if no explicit primary group is provided.\n- Warn about insecure password hashing schemes.\n\n### Where does it run?\n\nIt is undeniable that Userborn finds its origin in NixOS. However, Userborn\nhas been designed to work on any distro. It is effectively distro-agnostic. It\nwill run on any Linux.\n\n## Getting Started\n\n### NixOS\n\nUserborn is available in Nixpkgs (nixos-unstable). To enable it:\n\n```nix\nservices.userborn.enable = true;\n```\n\n## Nondestructivity\n\n- Never deletes a user or group, only disables them when they are not present\n  in the config anymore.\n- Never changes the UID of an existing user or the GID of an existing group.\n\nThis prohibits UID/GID re-use which is a security issue. The danger of UID/GID\nre-use is best illustrated by an example. Imagine the following scenario:\n\n- A new user with the UID 1000 is created. The user creates all sorts of files\n  owned by them (via their UID).\n- This user is deleted, their UID is freed up.\n- A new user (with a different username) is created and gets allocated a new\n  UID. The allocation algorithm doesn't know that previously a user with the\n  UID 1000 existed so it allocates UID 1000 to the new user.\n- This user can now access files from a previously existing user because their\n  UIDs are the same.\n\n### Limitations to Nondestructivity\n\n- Userborn can handle comments in the password database files but it will\n  silently discard them.\n- Userborn will sort the password database files by GID/UID. This influences\n  only the representation inside the text files but doesn't change the way\n  group/user resolution works.\n- Userborn will discard entries in the shadow database that are not present in\n  the passwd database. It will warn about these inconsistent entries.\n\n## Mutable Users\n\nUserborn support mutable users. This feature allows you to manage some users\nvia Userborn and manage other users imperatively with tools like `useradd`,\n`usermod`, etc.\n\nWhen the mutable user feature is enabled by setting `USERBORN_MUTABLE_USERS =\ntrue`, only users and groups that were already in the previous config are\ndisabled and drained. All other users/groups remain enabled.\n\nUserborn will still \"re-take\" control of users/groups that were imperatively created\nand later added to the Userborn config. For example:\n\n- You imperatively create a user with `useradd` called `normalo`.\n- Userborn will keep it enabled as long as the user is not in the Userborn\n  config.\n- You now add a config for the user `normalo` to your Userborn config setting a\n  different password.\n- On the next invocation, Userborn will update the password of the user\n  `normalo` and take full ownership of the entire user.\n- When you now remove the user from the config, Userborn will disable the user\n  on the next invocation.\n\n## Configuration\n\nYou can configure Userborn during runtime via the provided config file and via\nenvironment variables.\n\n### Environment Variables\n\n- `USERBORN_NO_LOGIN_PATH`: Set this to the path of the `nologin` binary on\n  your system. This path is used when the user config doesn't specify a\n  `shell`. If this environment variable is set, its value overrides\n  `USERBORN_NO_LOGIN_DEFAULT_PATH`.\n- `USERBORN_MUTABLE_USERS`: Set this to the string `true` if you want to enable\n  mutable users.\n- `USERBORN_PREVIOUS_CONFIG`: Set this to the path of the previous Userborn\n  config. This is necessary when you enable mutable users. Otherwise, this\n  variable is ignored. It is your responsibility to update this on each change\n  of the Userborn config.\n\n## Building Userborn\n\nRuntime dependencies:\n\n- `libxcrypt`\n\n### Build-Time Parameters\n\nYou can configure Userborn via compile-time environment variables:\n\n- `USERBORN_NO_LOGIN_DEFAULT_PATH`: Set this to the default path of the\n  `nologin` binary in your distro or system. If this is not set, the value\n  `/run/current-system/sw/bin/nologin` is used which will only make sense on\n  NixOS.\n\n## Comparison With Other Tools for Declarative User Management\n\n### systemd-sysusers\n\nUserborn follows the same spirit as systemd-sysusers and indeed can be viewed\nas an adaptation of sysusers to a more specialized system where the service\ntakes full ownership of the user database (i.e. also changes certain fields of\nentries).\n\nUserborn has two key differences from systemd-sysusers:\n\n1. Does not only create system users (UID \u003c 1000) but also normal users. In the\n   systemd world, \"normal\" users wouldn't have an entry in\n   `/etc/{group,passwd,shadow}`. Userborn, however affords them one of these\n   entries, not because the systemd way is wrong or bad but because this way is\n   easier and fully backwards compatible.\n2. Takes full ownership of the password database and thus also (destructively)\n   changes user entries. For example, it can change passwords, home\n   directories, default shell, etc. Please see the [Nondestructivity\n   section](#Nondestructivity) for details of what Userborn can change and what it\n   will never change.\n\n### NixOS `update-users-groups.pl`\n\nUserborn:\n\n1. Doesn't use perl.\n2. Runs as a systemd service, not as an activation script.\n3. Doesn't rely on a hidden database to track state over the lifetime of a\n   system.\n4. Supports mounting `/etc` via an (immutable, read-only) overlay.\n\n### Limitations\n\n- Currently doesn't support group passwords (and thus also doesn't support `/etc/gshadow`).\n- Doesn't handle SUBUID/SUBGIDs.\n\n## Replacing `system.activationScripts`\n\nOn NixOS, Userborn is not run as an activation script unlike\n`update-users-groups.pl`. This means that scripts that relied on running after\nusers are created need to be replaced when using Userborn. There are, however,\nmore reasons to replace activation scripts and I personally believe that all of\nthem should be replaced.\n\nThe following describes effective strategies to replace activation scripts in\nthe order you should consider them.\n\n### [systemd-tmpfiles](https://www.freedesktop.org/software/systemd/man/latest/tmpfiles.d.html)\n\nSimple activation scripts that only create files, move them, change\npermissions, etc. can usually be converted to systemd-tmpfiles configs via\n[`systemd.tmpfiles.settings`](https://search.nixos.org/options?channel=unstable\u0026query=systemd.tmpfiles.settings).\n\nTo create a cache directory for `some-service` for example:\n\n```nix\nsystemd.tmpfiles.settings.\"some-service\" = {\n  \"/var/cache/some-service\".d = {\n    mode = \"0750\";\n    user = \"some-user\";\n    group = \"some-group\";\n  };\n};\n```\n\n### [ExecStartPre=](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#ExecStartPre=)\n\nThere are some more complex scenarios where activation scripts are used to\nprepare the system for some other service. These scripts can usually be run\ndirectly before the systemd service in question is started instead of as an\nactivation script via a command or full script in `ExecStartPre=`.\n\nTo run `my-script` right before `some-service` is started, for example:\n\n\n```nix\nsystemd.service.\"some-service\".serviceConfig.ExecStartPre = [\n  \"${pkgs.myScript}/bin/my-script\"\n];\n```\n\n### Dedicated systemd service\n\nFor the very rare activation scripts that are very complicated, you can write\nan entire systemd service to execute the script. This service can then be\nordered via the full systemd capabilities.\n\nTo run `my-service` after all users and groups have been created:\n\n```nix\nsystemd.service.\"my-service\".after = [ \"userborn.service\" ];\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikstur%2Fuserborn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnikstur%2Fuserborn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikstur%2Fuserborn/lists"}