{"id":37074658,"url":"https://github.com/marckrenn/places-env","last_synced_at":"2026-01-14T08:47:23.967Z","repository":{"id":267674028,"uuid":"900483886","full_name":"marckrenn/places-env","owner":"marckrenn","description":"Secure version control of environment files","archived":false,"fork":false,"pushed_at":"2025-06-16T18:47:40.000Z","size":8156,"stargazers_count":8,"open_issues_count":4,"forks_count":0,"subscribers_count":3,"default_branch":"develop","last_synced_at":"2025-09-25T09:00:03.130Z","etag":null,"topics":["environment-variables","environments"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/places-env/","language":"Python","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/marckrenn.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2024-12-08T21:58:19.000Z","updated_at":"2025-06-22T21:14:54.000Z","dependencies_parsed_at":"2024-12-11T19:39:37.394Z","dependency_job_id":null,"html_url":"https://github.com/marckrenn/places-env","commit_stats":null,"previous_names":["marckrenn/places-env"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/marckrenn/places-env","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marckrenn%2Fplaces-env","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marckrenn%2Fplaces-env/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marckrenn%2Fplaces-env/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marckrenn%2Fplaces-env/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marckrenn","download_url":"https://codeload.github.com/marckrenn/places-env/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marckrenn%2Fplaces-env/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28414693,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T08:38:59.149Z","status":"ssl_error","status_checked_at":"2026-01-14T08:38:43.588Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":["environment-variables","environments"],"created_at":"2026-01-14T08:47:21.752Z","updated_at":"2026-01-14T08:47:23.937Z","avatar_url":"https://github.com/marckrenn.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Main test status](https://img.shields.io/github/actions/workflow/status/marckrenn/places-env/test.yaml?branch=main\u0026label=Test%20(main))\n![Develop test status](https://img.shields.io/github/actions/workflow/status/marckrenn/places-env/test.yaml?branch=develop\u0026label=Test%20(develop))\n[![PyPI - Version](https://img.shields.io/pypi/v/places-env)](https://pypi.org/project/places-env/)\n![GitHub License](https://img.shields.io/github/license/marckrenn/places-env)\n# places-env: secure version control of environment files\n\u003e **Note:**  \n\u003e _places-env_ is currently a proof of concept (PoC) and is **not ready for use in public projects or production environments**. Use it cautiously and only with private repositories.  \n\u003e If you appreciate the ideas behind _places-env_, consider contributing by submitting pull requests!\n\n## Motivation / The heck is _places-env_?\n\n![Schematic overview of places](https://raw.githubusercontent.com/marckrenn/places-env/5a99cc9245ca6c8ea9d3cb4adb67d2f2cee56c09/images/places-dark.svg?sanitize=true#gh-dark-mode-only)\n![Schematic overview of places](https://raw.githubusercontent.com/marckrenn/places-env/5a99cc9245ca6c8ea9d3cb4adb67d2f2cee56c09/images/places-light.svg?sanitize=true#gh-light-mode-only)\n\n- _places-env_ is a self-contained, completely free open-source (FOSS) alternative to [HashiCorp Vault](https://www.hashicorp.com/products/vault), [Infisical](https://infisical.com/), [dotenv-vault](https://github.com/dotenv-org/dotenv-vault) and [sops](https://github.com/getsops/sops).  \n- Leverages a single source of truth (SSOT) [`places.yaml`](#placesyaml) for deriving multiple environment files.\n- Similar to [sops](https://github.com/getsops/sops), _places-env_ encrypts only the values in [`places.yaml`](#placesyaml), resulting in [`places.enc.yaml`](#placesencyaml), which can be securely checked into git:  \n  - Congrats, your SSOT is now version-controlled 🎉\n  - Always synchronized with collaborators\n  - Fully in-sync with the rest of your code, branches and tags (try doing that with [Infisical](https://infisical.com/) \u0026 co. 😉) \n  - Changes remain 'human-trackable' — even when values are encrypted\n  - Contrary to [sops](https://github.com/getsops/sops), encryption keys can be assigned either per environment or on a per-value basis\n- Provides a [straightforward setup](#getting-started) with no dependency on external services or libraries.  \n- [`places watch start`](#watch-start) (persistently) tracks changes in [`places.yaml`](#placesyaml)/[`places.enc.yaml`](#placesencyaml) and automatically handles [encryption](#encrypt), [decryption](#decrypt), [keeps `.gitignore` up-to-date](#sync-gitignore), and [auto-updates](#generate-environment) environment files. So it's essentially _set and forget_.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eFallback Image (for Github Mobile users)\u003c/summary\u003e\n\n![Schematic overview of places](https://github.com/marckrenn/places-env/blob/develop/images/places-dark.png?raw=True)\n\n\u003c/details\u003e\n\n## Getting started\n\n1. **Install _places-env_:**\n\n- via [pypi](https://pypi.org/project/places-env/):\n\n    `pip install places-env`\n\n2. **Init project:** In terminal\n  - `cd` into your project  \n  - Run one of the following commands:  \n    - [`places init`](#init): Creates an empty [`places.yaml`](#placesyaml), generates a default crypto key at `.places/keys/default`\n    - [`places init --template min`](#init): Initializes with a minimal template ([view content](src/places/templates/min.yaml)).  \n    - [`places init --tutorial`](#init): Initializes with a tutorial template ([view content](src/places/templates/tutorial.yaml)).\n\n3. **Modify [`places.yaml`](#placesyaml)**:\n  - Use your preferred text editor  \n  - Or modify it using the [_places-env_ CLI](#places-cli-documentation)\n\n4. **Track changes:**\n  - Use [`places watch start (optionally: --daemon, --service)`](#watch-start) (recommended)  \n  - Alternatively, use [`places encrypt`](#encrypt) and [`places sync gitignore`](#sync-gitignore). This will automatically add all necessary entries to `.gitignore`.\n\n5. **Generate environment files:**\n  - If [`places watch start`](#watch-start) is already running, environments with property `watch: true` will be (re)generated whenever [`places.yaml`](#placesyaml) is updated.  \n  - Or use [`places generate environment --all`](#generate-environment) to manually regenerate all environment files.\n\n6. **Commit [`places.enc.yaml`](#placesencyaml)**\n\n7. **Decrypt after switching to another branch**:\n  - If [`places watch start`](#watch-start) is already running, [`places.enc.yaml`](#placesencyaml) will automatically be decrypted into [`places.yaml`](#placesyaml) after switching branches.  \n  - Otherwise, run [`places decrypt`](#decrypt) to manually derive [`places.yaml`](#placesyaml) from [`places.enc.yaml`](#placesencyaml).\n\n8. **Key exchange:**\n  - If you're working with collaborators, **securely** share your crypto keys located in `.places/keys` with them.\n  - Recommended methods include shared password managers like [Bitwarden](https://bitwarden.com/), secure one-time sharing services, or dedicated tools such as [Amazon KMS](https://aws.amazon.com/kms/).\n  - Collaborators without the necessary decryption keys can still add and edit new secrets but are restricted from reading existing ones.\n\n## Example / Demo\n\nA \"live\" example / demo project can be found [here](https://github.com/marckrenn/places-env-example).\n\n## CI/CD\n\n_places-env_ has a companion GitHub Action you can find on the GitHub Marketplace [here](https://github.com/marketplace/actions/places-env). It installs _places-env_, injects crypto keys and generates environment files so that they can be used downstream in your CI/CD workflow.\n\n## Documentation\n\n### `places.yaml`\n\n#### Examples\n\n1. [Minimal example](src/places/templates/min.yaml):\n```yaml\nkey: .places/keys/default\n\nenvironments:\n  local:\n    filepath: .env\n    watch: true\n\nvariables:\n  PROJECT_NAME: your-project-name\n```\n\n[`places generate environment local`](#generate-environment) or [`places watch start`](#watch-start) will generate this `.env` for environment `local`:\n\n```\nPROJECT_NAME=your-project-name\n```\n\n\n2. Closer-to-live example based on the [tutorial template](src/places/templates/tutorial.yaml):\n```yaml\n\nkeys:\n  default: .places/keys/default\n  prod: .places/keys/prod\n  dev: .places/keys/dev\n  test: .places/keys/test\n\nenvironments:\n\n  local:\n    filepath: .env\n    watch: true\n    key: default\n\n  development:\n    filepath: .env.dev\n    alias: [dev]\n    key: dev\n\n  production:\n    filepath: .env.prod\n    alias: [prod]\n    key: prod\n\nvariables:\n\n  PROJECT_NAME: your-project-name\n\n  HOST: localhost\n\n  PORT:\n    local: 8000\n    dev: 8001\n    prod:\n      value: 8002\n      unencrypted: true\n  \n  ADDRESS: ${HOST}:${PORT}\n\n  DOMAIN:\n    dev: ${PROJECT_NAME}.foo.dev\n    prod: ${PROJECT_NAME}.foo.com\n  \n  JSON_MULTILINE: |\n    {\n      \"key1\": \"value1\",\n      \"key2\": \"value2\"\n    }\n\n```\n\n[`places generate environment --all`](#generate-environment) or [`places watch start`](#watch-start) will generate\n\n* this `.env` for environment `local`:\n```\nPROJECT_NAME=your-project-name\nHOST=localhost\nPORT=8000\nADDRESS=localhost:8000\nJSON_MULTILINE='{\n  \"key1\": \"value1\",\n  \"key2\": \"value2\"\n}'\n```\n\n* this `.env.dev` for environment `development`:\n```\nPROJECT_NAME=your-project-name\nHOST=localhost\nPORT=8001\nADDRESS=localhost:8001\nDOMAIN=your-project-name.foo.dev\nJSON_MULTILINE='{\n  \"key1\": \"value1\",\n  \"key2\": \"value2\"\n}'\n```\n\n* and this `.env.prod` for environment `production`:\n```\nPROJECT_NAME=your-project-name\nHOST=localhost\nPORT=8002\nADDRESS=localhost:8002\nDOMAIN=your-project-name.foo.com\nJSON_MULTILINE='{\n  \"key1\": \"value1\",\n  \"key2\": \"value2\"\n}'\n```\n\u003cdetails\u003e\n\u003csummary\u003eCLI commands:\u003c/summary\u003e\n\n- Encrypt the values in [`places.yaml`](#placesyaml) and saves the encrypted data to [`.places/places.enc.yaml`](#placesencyaml):\n    \n    [`places encrypt`](#encrypt)\n\n\u003c/details\u003e\n\n#### Sections\n\nAll sections are case-sensitive!\n\n**Required sections:**\n- [`key` / `keys`](#key--keys)\n- [`environments`](#environments)\n- [`variables`](#variables)\n\n**Optional section:**\n- [`settings`](#settings)\n\n##### `key` / `keys`\n\nEncryption/decryption key or keys that can be referenced in [`environments`](#environments).  \n\nThe `default` key is required as it serves as a fallback when no other key is specified.\n\n**Examples:**\n\n```yaml\nkey: .places/keys/default # shorthand for keys: default: .places/keys/default\n```\n\n```yaml\nkeys:\n  default: .places/keys/default\n  dev: .places/keys/dev\n  prod: .places/keys/prod\n  topsecret: .places/keys/topsecret\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eCLI commands:\u003c/summary\u003e\n\n- Generate key, add it to `.places/keys/` and optionally add key to [`places.yaml`](#placesyaml):\n    \n    [`places generate key`](#generate-key)\n\n- Add a key from string to `.places/keys/` and optionally add the key to [`places.yaml`](#placesyaml):\n\n    [`places add key_from_string`](#add-key_from_string)\n\n- Add existing key to [`places.yaml`](#placesyaml):\n\n    [`places add key`](#add-key)\n\n\u003c/details\u003e\n\n##### `environments`\n\n`environments` define what environment file(s) should be generated.\n\n**Example:**\n```yaml\nenvironments:\n  local:\n    filepath: .env\n    watch: true\n  development:\n    filepath: .env.dev\n    watch: true\n    alias: [dev, stage]\n    key: dev\n  production:\n    filepath: .env.prod\n    watch: true\n    alias: [prod]\n    key: prod\n```\n\n**Options**:\n\n| Option | Type | Default | Required | Description |\n|--------|------|---------|:--------:|-------------|\n| `filepath` | `String` | `None` | ✅ | filepath of environment file to generate relative to root |\n| `key` | `Bool` | `default` | ❌ | Key to encrypt / decrypt variables of this environment. Refers to keys defined in [keys](#key--keys) |\n| `alias` | `[String]` | `None` | ❌ |Alias(es) that can be used for this environment|\n| `watch` | `Bool` | `false` | ❌ | If `true` and [`places watch start`](#watch-start) is running, this environment will be auto-(re)generated on filechange of [`places.yaml`](#placesyaml) |\n\n\u003cdetails\u003e\n\u003csummary\u003eCLI commands:\u003c/summary\u003e\n- Add or modify environment in [`places.yaml`](#placesyaml):\n\n    [`places add environment`](#add-environment)\n\u003c/details\u003e\n\n##### `variables`\n\nKey-value pairs to save to environment file(s). Keys should contain only uppercase alphanumerics and underscores; otherwise, a warning is printed.\n\n**Example:**\n\n```yaml\nvariables:\n\n  PROJECT_NAME: your-project-name\n\n  HOST: localhost\n\n  PORT:\n    local: 8000\n    dev: 8001\n    prod:\n      value: 8002\n      unencrypted: true\n  \n  ADDRESS: ${HOST}:${PORT}\n\n  DOMAIN:\n    dev: ${PROJECT_NAME}.foo.dev\n    prod: ${PROJECT_NAME}.foo.com\n  \n  JSON: |\n    {\n      'key1': 'value1',\n      'key2': 'value2'\n    }\n```\n\n**Syntax**:\n\n- Shorthand: Set a key-value for all [environments](#environments). **Note: This will encrypt the value separately with the keys of all environments. Any of these keys will be able to decrypt it!**\n\n    ```yaml\n    VARIABLE_NAME: value\n    ```\n\n- Set specific value per [environment](#environments)\n\n    ```yaml\n    PORT:\n        local: 8000\n        dev: 8001\n        prod: 8002\n    ```\n\n- Set specific encryption key per value [environment](#environments)\n\n    ```yaml\n    SECRET:\n        local:\n            value: This won't be encrypted # in places.enc.yaml\n            unencrypted: true\n        prod:\n            value: Dirty secret # will be encrypted with 'topsecret' key\n            key: topsecret # must be defined in keys section\n    ```\n\n- Multiline strings (must start with `|`):\n    ```yaml\n    JSON: |\n        {\n        'key1': 'value1',\n        'key2': 'value2'\n        }\n    ```\n- Single-line dicts must be explicitly wrapped into quotes:\n    ```yaml\n    JSON: \"{'key1': 'value1', 'key2': 'value2'}\"\n    ```\n\n- Value interpolation:\n\n    ```yaml\n    HOST: localhost\n\n    PORT:\n        local: 8000\n        dev: 8001\n        prod: 8002\n    \n    ADDRESS: ${HOST}:${PORT} # .env = localhost:8000, .env.dev = localhost:8001, etc.\n    ```\n\n- Lists/arrays with square brackets (**Note:** yaml-multiline arrays are currently NOT supported, see [Known Issues](#known-issues--limitations)!)\n\n    ```yaml\n    ARRAY: [1,2,3,4]\n    ```\n\n- Combination of all syntaxes above.\n\n**Options**:\n\n| Option | Type | Default | Required | Description |\n|--------|------|---------|:--------:|-------------|\n| `value` | `Any` | `None` | ✅ | value of Key |\n| `key` | `String` | `key set in` [environments](#environments) `\u003e default key` | ❌ | encryption / decryption key used for this particular value |\n| `unencrypted` | `Bool` | `False` | ❌ | If `true` explicitly not encrypt value |\n\n\u003cdetails\u003e\n\u003csummary\u003eCLI commands:\u003c/summary\u003e\n\n- Add variable to [`places.yaml`](#placesyaml):\n    \n    [`places add variable`](#add-variable)\n\n\u003c/details\u003e\n\n##### `settings`\n\nAllows for configuration of project parameters, primarily related to cryptography.\n\n**Examples:**\n```yaml\nsettings:\n    sync-gitingore: false\n    cryptography:\n        hash-function: sha265\n        iterations: 120000\n        dklen: 32\n        salt:\n            mode: from-file\n            filepath: version.txt\n```\n\n**Options:**\n\n| Option | Type | Default | Required | Description |\n|--------|------|---------|:--------:|-------------|\n| `sync-gitignore` | `Bool` | `True` | ❌ | If `true` makes sure that all `.envs`, [`places.yaml`](#placesyaml) and `.places` are in `.gitignore` |\n| `cryptography`:`hash-function` | `String` | `sha512` | ❌ | Hash function to encrypt / decrypt (`sha256` or `sha512`) |\n| `cryptography`:`iterations` | `Int` | `600000` (`sha265`), `210000` (`sha512`) | ❌ | Hash function to encrypt / decrypt (`sha256` or `sha512`) |\n| `cryptography`:`dklen` | `Int` | `32` | ❌ | Derived key length |\n| `cryptography`:`salt`:`mode` | `String` | `deterministic` | ❌ | Available modes: `deterministic`[^1], `custom`[^2], `from-file`[^3], `git-project`[^4], `git-branch`[^5], `git-project-branch`[^6] |\n\n[^1]: By default, _places-env_ intentionally uses a deterministic salt. While this allows for some statistical attacks, it enables tracking of value changes.\n[^2]: Set a custom salt using `cryptography`:`salt`:`value`.  \n[^3]: Use the content of `cryptography`:`salt`:`filepath` as the salt (e.g., salting with `version.txt`).\n[^4]: Use the Git project name as the salt.  \n[^5]: Use the Git branch as the salt (encrypted values will differ for each branch).  \n[^6]: Combine the Git project name and branch as the salt.\n\n\n\u003cdetails\u003e\n\u003csummary\u003eCLI commands:\u003c/summary\u003e\n\n- Add settings to [`places.yaml`](#placesyaml):\n    \n    [`places add setting`](#add-setting)\n\n\u003c/details\u003e\n\n### `places.enc.yaml`\n\nThe encrypted version of [`places.yaml`](#placesyaml), which is safe to check into Git.\n\n**Example:**\n```yaml\nkeys:\n  default: .places/keys/default\n  prod: .places/keys/prod\n  dev: .places/keys/dev\n  test: .places/keys/test\n\nenvironments:\n\n  local:\n    filepath: .env\n    watch: true\n    key: default\n\n  development:\n    filepath: .env.dev\n    alias: [dev]\n    key: dev\n\n  production:\n    filepath: .env.prod\n    alias: [prod]\n    key: prod\n\nvariables:\n\n  PROJECT_NAME: encrypted(default|dev|prod):kvvmBtvz6I8QadAG5hoDyEZ8kzbfJ2IrGwpNlqD70CWIpWfSlzR6TA==|ddts1k4JhTNmP9f9zrfCyfM6dcth5eP86y9UoCQwGvqmrCW02Y4jwg==|1037LUJgxus4CsF35VtwZ/FjFuioG/PGwzaMuJwGI4GRdKA+eiH0gQ==\n\n  HOST: encrypted(default|dev|prod):levmXeHNoZcRN6dHdvE5GZTG8TpBCqD8IxpjtA==|cstsjXQ3zCtnYaC8IPmbMqGVIeONE5EA4QIVyw==|0F37dnhej/M5VLY2xqHJWGrwGUBGg9KWVYPSXA==\n\n  PORT:\n    local: encrypted(default):uOieQPXb5MVQjSDnUF7EXkVfEKHRC2aJ\n    dev: encrypted(dev):X8gUkGAxiXkySxxyJeDZiABVBFr7JbGD\n    prod:\n      value: 8002\n      unencrypted: true\n  \n  ADDRESS: encrypted(default|dev|prod):kp+sUOvf4KwlR6tO2hk9z29S5A/pQX1DgBN1LLeFNKwB2DNSnVulEsGPSuE=|db8mgH4ljRBTEay18rT8ztoUAvJXg/yU2hEhXMxD1DlIKFauN2tO6uCKsNU=|1ymxe3JMzsgNJLo/2VhOYNhNYdGefeyuzEl4GkNBfe4rss/5PfZpdaUCf9Y=\n\n  DOMAIN:\n    dev: encrypted(dev):db8mgHgm/hRWObjIwqa1tu44ceVK+of43zRKE0pthsnU3U7da7gqjvX5ZbqKjOdHZHPAfA==\n    prod: encrypted(prod):1ymxe3RPvcwIDK5C6UoHGOxhEsaDBJfCyWwTVUA1GneBv+DzLbWmIphZPaAPZOd8xM6yYg==\n  \n  JSON_MULTILINE: encrypted(default|dev|prod):ktuwUPHZk4opXIIP9Scin0NF/DbfOGF6hAgNZjOVzfH5hckrOvVBaL80vB6mdBXPrfFFDYAbk7NXLdeQzHBuv9+lqoi4qetM|dfs6gGQj/jZfCoX03YrjnvYDGsth+uCt3gpZFmt98sXH6GOMmolif4Wj2Zz3KyUGhEiioMYmbHKq2o77duYEKxY+woyWEKFA|122te2hKve4BP5N+9mZRMPRaeeioBPCXyFIAUGElbnqq4KSiQIxsoqc6ZQpj1FexDm9Ya7iPKKkjOcl8JqtuUEtYmQWfu9uX\n\n```\n\u003cdetails\u003e\n\u003csummary\u003eCLI commands:\u003c/summary\u003e\n\n- Decrypts and derives [`places.yaml`](#placesyaml) from [`places.enc.yaml`](#placesencyaml):\n    \n    [`places decrypt`](#decrypt)\n\n\u003c/details\u003e\n\n## FAQ\n\n- **The hell is this? Do you have _any_ idea what you're doing?**  \n    \u003e No. Consider this a toy, a conversation starter. If this gains traction, those who truly know how things should be done will need to take over.  \n    \u003e This is my first public Python project/package, and it's full of firsts for me, so please keep that in mind. Also, I don't consider myself a professional programmer and have no formal education in this domain.\n\n- **Why?**  \n    \u003e This started as a Hackathon project, and I felt the urge to complete and release something for once. Additionally, I'm preparing a tech stack I’d like to work with, and I wasn’t satisfied with the existing workflows for managing and syncing secrets (see below).\n\n- **Is this for me/my project?**  \n    \u003e Again, consider this a toy. For now, use it only for private repositories and only with people you trust.\n\n- **What happens if a collaborator doesn't have all the crypto keys defined in [`places.yaml`](#placesyaml)?**\n\n  \u003e - **For per-environment values (e.g., `PORT: local: 8000`)**:  \n    If a collaborator lacks the required keys, [`places decrypt`](#decrypt) will fail to decrypt the encrypted value. In this case, the unencrypted value will remain in [`places.yaml`](#placesyaml) as-is. When re-encrypting with [`places encrypt`](#encrypt), the existing encrypted value will be written to [`places.enc.yaml`](#placesencyaml) unchanged.\n\n  \u003e - **For shorthand/compound values (e.g., `PROJECT_NAME: your-project-name`) that use multi/compound keys**:  \n    If the user possesses any of the required keys (e.g. `default` and `dev` out of `encrypted(default|dev|prod):kvvmBt…`), [`places decrypt`](#decrypt) will successfully decrypt the value. When encrypting with [`places encrypt`](#encrypt), all keys (e.g. `default` and `dev`) available to the user will be used to encrypt the value.\n\n  \u003e - **Important Consideration**:  \n    Compound values should only be used for non-sensitive information. For sensitive values, define them explicitly per environment.\n\n- **Is _places-env_ secure?**  \n    \u003e Arguably, yes—especially when used in private repositories and among trusted collaborators. In general, _places-env_ exposes encrypted data to others (collaborators or the public), meaning that with enough time, effort and ressources, encrypted values could eventually be cracked. However, _places-env_ was designed to make this unlikely within reasonable boundaries. For instance:  \n    \u003e - [`places sync gitignore`](#sync-gitignore) is executed automatically by default, which should help prevent unencrypted data from being committed.  \n    \u003e - [`places generate key`](#generate-key) generates cryptographic keys with appropriate length and entropy.\n    \u003e - Per default `AES-512-GCM` with 210,000 iterations (per [OWASP recommendations](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)) is used for cryptographic opersions (see [settings options](#settings) for more details).  \n    \u003e  \n    \u003e That said, some design decisions have been made that may weaken security:\n    \u003e - By default, a deterministic salt is used to allow for deterministic tracking of changes, which introduces some potential attack vectors. If security is critical, you can choose alternative salting strategies in [settings options](#settings).  \n    \u003e - The cryptographic key exchange between collaborators is manual, so it’s your responsibility to ensure it happens securely.  \n    \u003e - When using the shorthand to define a variable for multiple environment files, any encryption key can decrypt the encrypted value.  \n    \u003e - **If you identify any inherent security flaws in _places-env_, please let me know ASAP. Thank you!**\n\n- **Instead of _places-env_ why not just use …**\n    - … [sops](https://github.com/getsops/sops)?\n        \u003e To be honest, I was overwhelmed at first glance and didn’t even try it. It’s almost certainly better and more secure in every regard than _places-env_, but at the same time, it looks cumbersome to set up.  \n        \u003e Additionally, I didn’t like how it seems to require (or strongly encourage) the use of another (potentially overkill) service for key management. Also, it appears to focus on file-based encryption rather than allowing for easy value-based encryption.\n    - … [dotenv-vault](https://github.com/dotenv-org/dotenv-vault)?\n        \u003e Similar to [sops](https://github.com/getsops/sops), it looks great and might be a better solution for your use case. It’s also the closest alternative to _places-env_, so you may want to check it out.\n        \u003e What I prefer about _places-env_ is that it doesn't lock you into the [dotenv.org](https://www.dotenv.org/)-ecosystem and that multiple environment files are derived from a single source of truth ([`places.yaml`](#placesyaml)). Additionally, [`places watch start`](#watch-start) persistently tracks changes in [`places.yaml`](#placesyaml) and automatically manages [encryption](#encrypt), [decryption](#decrypt), and [`auto-updates`](#generate-environment) for your environment files.\n    - … [Infisical](https://infisical.com/)?\n      \u003e I genuinely wanted to like it, but their documentation is currently a mess. It took me over half an hour to locate their current Python library, which wasn’t even referenced in the documentation. I ultimately gave up, frustrated, when attempting to align secrets with my version tags.\n    - … [HashiCorp Vault](https://www.hashicorp.com/products/vault)?\n        \u003e Yeah, [no](https://www.hashicorp.com/products/vault/pricing).\n    - … git hooks?\n        \u003e Glad you asked! This project actually started as Git hooks, and you can find a very basic MVP in [places-mini](places-mini). It uses a single key to encrypt local environment files but lacks many of the convenient features of _places-env_. For example, you’ll need to manually ensure that all the appropriate entries are added to `.gitignore`, among other things. Also, it uses a naughty hack to track changes and force encryption. Don't use it.\n\n- **Why is the code so bad?**\n    \u003e As I mentioned above, I’m neither a professional coder nor experienced with the Python ecosystem. Additionally, I’ve made some questionable decisions along the way.\n\n- **Why can’t the generated environment files be styled, structured, or annotated?**\n  \u003e It's on the [roadmap](#roadmap) below.\n\n## Roadmap (unordered)\n\n* **Hombrew:** Distribute _places-env_ also via [Homebrew](https://brew.sh/)\n* **Comments in environment files**: Add `comment`property to variables\n* **Layouting in environment files**: Add \"meta-variables\" (eg. `places.section`) that add sections and linebreaks at gen-time.\n\n## Known issues / Limitations\n* _places-env_ does not adhere to the [YAML specifications](https://yaml.org/).\n* Only arrays/lists in square brackets are supported, block style arrays aren't (yet).\n* Single-line KV/JSON needs to be wrapped in quotes.\n\n***\n***\n\n# places CLI Documentation\n\n## add environment\n\nAdd a new environment configuration.\n\n```shell\nplaces add environment NAME [OPTIONS]\n```\n\n\u003cdetails\u003e\n\n***\n\n\u003csummary\u003eOptions \u0026 Arguments\u003c/summary\u003e\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-f` | `--filepath \u003cString\u003e` | Path to environment file. |\n| `-w` | `--watch \u003cBool\u003e` | Enable file watching. |\n| `-a` | `--alias \u003cString\u003e` | Environment aliases. |\n| `-k` | `--key \u003cString\u003e` | Key to use for encryption. |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `NAME` | ❌ |\n\n***\n\n\u003c/details\u003e\n\n## add key\n\nAdd an existing key file reference to places.yaml\n\n```shell\nplaces add key NAME [OPTIONS]\n```\n\n\u003cdetails\u003e\n\n***\n\n\u003csummary\u003eOptions \u0026 Arguments\u003c/summary\u003e\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-a` | `--add` | Add key reference to places.yaml |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `NAME` | ❌ |\n\n***\n\n\u003c/details\u003e\n\n## add key_from_string\n\nAdd a key from a provided string with the specified name.\n\n```shell\nplaces add key_from_string NAME KEY_STRING [OPTIONS]\n```\n\n\u003cdetails\u003e\n\n***\n\n\u003csummary\u003eOptions \u0026 Arguments\u003c/summary\u003e\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-a` | `--add` | Add key to places.yaml |\n| `-f` | `--force-overwrite` | Force overwrite without safety checks. |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `NAME` | ❌ |\n| `KEY_STRING` | ❌ |\n\n***\n\n\u003c/details\u003e\n\n## add setting\n\nAdd or update settings configuration.\n\n```shell\nplaces add setting [OPTIONS]\n```\n\n\u003cdetails\u003e\n\n***\n\n\u003csummary\u003eOptions\u003c/summary\u003e\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-sg` | `--sync-gitignore \u003cBool\u003e` | Enable/disable .gitignore sync. |\n| `-i` | `--iterations \u003cInt\u003e` | Number of iterations for cryptography. |\n| `-hf` | `--hash-function \u003cString\u003e` | Hash function for cryptography. |\n| `-sm` | `--salt-mode \u003cString\u003e` | Salt mode for cryptography. |\n| `-sf` | `--salt-filepath \u003cString\u003e` | Salt filepath for cryptography. |\n| `-sv` | `--salt-value \u003cString\u003e` | Salt value for cryptography. |\n\n***\n\n\u003c/details\u003e\n\n## add variable\n\nAdd a new variable configuration.\n\n```shell\nplaces add variable NAME [OPTIONS]\n```\n\n\u003cdetails\u003e\n\n***\n\n\u003csummary\u003eOptions \u0026 Arguments\u003c/summary\u003e\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-v` | `--value \u003cAny\u003e` | Value of variable / secret. |\n| `-k` | `--key \u003cString\u003e` | Key to use for encryption. |\n| `-u` | `--unencrypt \u003cBool\u003e` | Mark value as unencrypted. |\n| `-e` | `--environment \u003cString\u003e` | Target environment(s). |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `NAME` | ❌ |\n\n***\n\n\u003c/details\u003e\n\n## decrypt\n\nDecrypts `.places/places.enc.yaml` into `places.yaml` file.\n\n```shell\nplaces decrypt [OPTIONS]\n```\n\n## encrypt\n\nEncrypts `places.yaml` into `.places/places.enc.yaml` file.\n\n```shell\nplaces encrypt [OPTIONS]\n```\n\n## generate environment\n\nGenerate .env files for specified environments or all environments defined in `places.yaml`\n\nThis generally follows [https://dotenv-linter.github.io/](https://dotenv-linter.github.io/) rules, with the exception of alphabetical ordering.\n\n```shell\nplaces generate environment [ENVIRONMENT]... [OPTIONS]\n```\n\n\u003cdetails\u003e\n\n***\n\n\u003csummary\u003eOptions \u0026 Arguments\u003c/summary\u003e\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-a` | `--all` | Generate .env files for all environments. |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `ENVIRONMENT` | ❌ |\n\n***\n\n\u003c/details\u003e\n\n## generate key\n\nGenerate a new encryption key with the specified name.\n\n```shell\nplaces generate key [NAME] [OPTIONS]\n```\n\n\u003cdetails\u003e\n\n***\n\n\u003csummary\u003eOptions \u0026 Arguments\u003c/summary\u003e\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-l` | `--length \u003cInt\u003e` | Custom length for generated key in bytes. |\n| `-a` | `--add` | Add key to places.yaml |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `NAME` | ❌ |\n\n***\n\n\u003c/details\u003e\n\n## init\n\nInitialize a new places project.\n\nAlso generates a new default encryption key and adds it to `.places/keys/`.\n\n```shell\nplaces init [OPTIONS]\n```\n\n\u003cdetails\u003e\n\n***\n\n\u003csummary\u003eOptions\u003c/summary\u003e\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-t` | `--template \u003cString\u003e` | Template to use for initialization |\n| `--list-templates` | `--list-templates` | List available templates |\n\n***\n\n\u003c/details\u003e\n\n## run test\n\nRun tests.\n\nCurrently supported tests: e2e, cli.\n\nSpecify test names or use –all flag.\n\n```shell\nplaces run test [TESTS]... [OPTIONS]\n```\n\n\u003cdetails\u003e\n\n***\n\n\u003csummary\u003eOptions \u0026 Arguments\u003c/summary\u003e\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-a` | `--all` | Run all tests. |\n\n\n**Arguments**\n\n| Argument | Required |\n|----------|----------|\n| `TESTS` | ❌ |\n\n***\n\n\u003c/details\u003e\n\n## sync gitignore\n\nSync .gitignore with Places entries.\n\n```shell\nplaces sync gitignore [OPTIONS]\n```\n\n## watch start\n\nStart watching for changes.\n\n```shell\nplaces watch start [OPTIONS]\n```\n\n\u003cdetails\u003e\n\n***\n\n\u003csummary\u003eOptions\u003c/summary\u003e\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-s` | `--service` | Run watcher as a persistent system service. |\n| `-d` | `--daemon` | Run watcher as a background daemon. |\n\n***\n\n\u003c/details\u003e\n\n## watch stop\n\nStop watching for changes.\n\n```shell\nplaces watch stop [OPTIONS]\n```\n\n\u003cdetails\u003e\n\n***\n\n\u003csummary\u003eOptions\u003c/summary\u003e\n\n\n**Options**\n\n| Short | Long Option | Description |\n|-------|-------------|-------------|\n| `-s` | `--service` | Stop and remove persistent system service. |\n| `-d` | `--daemon` | Stop daemon process. |\n\n***\n\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarckrenn%2Fplaces-env","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarckrenn%2Fplaces-env","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarckrenn%2Fplaces-env/lists"}