fujin-cli 0.22.2__tar.gz → 0.24.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/.github/workflows/publish.yml +3 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/CHANGELOG.md +17 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/PKG-INFO +1 -1
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-1password/pyproject.toml +2 -2
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-bitwarden/README.md +3 -1
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-bitwarden/pyproject.toml +2 -2
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-bitwarden/src/fujin_secrets_bitwarden/__init__.py +4 -3
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-doppler/pyproject.toml +2 -2
- fujin_cli-0.24.0/plugins/fujin-secrets-env/README.md +86 -0
- fujin_cli-0.24.0/plugins/fujin-secrets-env/pyproject.toml +33 -0
- fujin_cli-0.24.0/plugins/fujin-secrets-env/src/fujin_secrets_env/__init__.py +95 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/pyproject.toml +4 -2
- fujin_cli-0.24.0/src/fujin/__init__.py +1 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/_installer.py +0 -8
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/deploy.py +49 -4
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/config.py +1 -1
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/uv.lock +20 -4
- fujin_cli-0.22.2/src/fujin/__init__.py +0 -1
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/.github/FUNDING.yml +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/.github/workflows/test.yml +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/.gitignore +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/.pre-commit-config.yaml +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/.readthedocs.yaml +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/CLAUDE.md +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/LICENSE.txt +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/README.md +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/Vagrantfile +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/app-cat-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/app-exec-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/app-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/app-logs-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/app-restart-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/app-scale-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/app-shell-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/app-start-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/app-status-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/app-stop-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/audit-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/deploy-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/down-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/fa-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/fujin-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/init-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/migrate-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/new-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/prune-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/rollback-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/server-bootstrap-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/server-create-user-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/server-exec-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/server-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/server-setup-ssh-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/server-status-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/_static/images/help/up-help.png +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/changelog.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/app.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/audit.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/deploy.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/down.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/index.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/init.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/migrate.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/new.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/prune.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/rollback.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/server.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/commands/up.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/conf.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/configuration.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/guides/django-complete.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/guides/index.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/guides/templates.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/howtos/binary.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/howtos/django.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/howtos/index.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/index.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/installation.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/integrations.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/requirements.txt +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/secrets.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/docs/troubleshooting.rst +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/.fujin/Caddyfile +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/.fujin/systemd/health.service +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/.fujin/systemd/health.timer +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/.fujin/systemd/web.service +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/.fujin/systemd/worker@.service +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/README.md +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/bookstore/__init__.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/bookstore/__main__.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/bookstore/asgi.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/bookstore/management/commands/health.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/bookstore/settings.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/bookstore/urls.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/bookstore/wsgi.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/fujin.toml +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/manage.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/pyproject.toml +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/requirements.txt +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/justfile +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-1password/README.md +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-1password/src/fujin_secrets_1password/__init__.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-1password/src/fujin_secrets_1password/py.typed +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-bitwarden/src/fujin_secrets_bitwarden/py.typed +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-doppler/README.md +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-doppler/src/fujin_secrets_doppler/__init__.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/plugins/fujin-secrets-doppler/src/fujin_secrets_doppler/py.typed +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/__main__.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/audit.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/caddy.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/__init__.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/_base.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/app.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/audit.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/down.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/init.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/migrate.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/new.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/prune.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/rollback.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/server.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/showenv.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/commands/up.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/connection.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/discovery.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/errors.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/fa.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/formatting.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/secrets.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/src/fujin/templates.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/__init__.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/conftest.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/integration/Dockerfile +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/integration/__init__.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/integration/conftest.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/integration/helpers.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/integration/test_app_management.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/integration/test_full_deploy.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/integration/test_installation.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/integration/test_server_bootstrap.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_app.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_audit.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_caddy_domain.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_config.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_connection.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_deploy.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_discovery.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_down.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_init.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_new.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_prune.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_rollback.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_scale.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_secrets.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_server.py +0 -0
- {fujin_cli-0.22.2 → fujin_cli-0.24.0}/tests/test_up.py +0 -0
|
@@ -35,6 +35,7 @@ jobs:
|
|
|
35
35
|
- bitwarden
|
|
36
36
|
- 1password
|
|
37
37
|
- doppler
|
|
38
|
+
- env
|
|
38
39
|
steps:
|
|
39
40
|
- name: Checkout code
|
|
40
41
|
uses: actions/checkout@v4
|
|
@@ -169,6 +170,8 @@ jobs:
|
|
|
169
170
|
artifact: wheels-plugin-1password
|
|
170
171
|
- name: fujin-secrets-doppler
|
|
171
172
|
artifact: wheels-plugin-doppler
|
|
173
|
+
- name: fujin-secrets-env
|
|
174
|
+
artifact: wheels-plugin-env
|
|
172
175
|
steps:
|
|
173
176
|
- name: Download artifacts for ${{ matrix.package.name }}
|
|
174
177
|
uses: actions/download-artifact@v4
|
|
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.24.0] - 2026-03-21
|
|
8
|
+
|
|
9
|
+
### 🚀 Features
|
|
10
|
+
|
|
11
|
+
- Add env secrets adapter and refactor adapter config
|
|
12
|
+
|
|
13
|
+
### ⚙️ Miscellaneous Tasks
|
|
14
|
+
|
|
15
|
+
- Add fujin-secrets-env to publish workflow
|
|
16
|
+
|
|
17
|
+
## [0.23.0] - 2026-03-21
|
|
18
|
+
|
|
19
|
+
### 🚀 Features
|
|
20
|
+
|
|
21
|
+
- Stream .env directly to server instead of bundling
|
|
22
|
+
- Add --bundle-dir option to preserve deployment bundle
|
|
23
|
+
|
|
7
24
|
## [0.22.2] - 2026-03-21
|
|
8
25
|
|
|
9
26
|
### 🐛 Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fujin-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.0
|
|
4
4
|
Summary: Get your project up and running in a few minutes on your own vps.
|
|
5
5
|
Project-URL: Documentation, https://github.com/Tobi-De/fujin#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/Tobi-De/fujin/issues
|
|
@@ -4,7 +4,7 @@ requires = [ "uv-build>=0.9.18,<0.10" ]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fujin-secrets-1password"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.24.0"
|
|
8
8
|
description = "1Password secret adapter for Fujin"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
"Programming Language :: Python :: 3.14",
|
|
21
21
|
]
|
|
22
22
|
dependencies = [
|
|
23
|
-
"fujin-cli>=0.
|
|
23
|
+
"fujin-cli>=0.24",
|
|
24
24
|
"python-dotenv>=1.0.1",
|
|
25
25
|
]
|
|
26
26
|
|
|
@@ -27,12 +27,14 @@ Add the following to your `fujin.toml` file:
|
|
|
27
27
|
```toml
|
|
28
28
|
[secrets]
|
|
29
29
|
adapter = "bitwarden"
|
|
30
|
+
|
|
31
|
+
[secrets.options]
|
|
30
32
|
password_env = "BW_PASSWORD"
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
To unlock the Bitwarden vault, the password is required. Set the `BW_PASSWORD` environment variable in your shell. When Fujin signs in, it will always sync the vault first.
|
|
34
36
|
|
|
35
|
-
Alternatively, you can set the `BW_SESSION` environment variable. If `BW_SESSION` is present, Fujin will use it directly without signing in or syncing the vault. In this case, the `password_env` configuration is not required.
|
|
37
|
+
Alternatively, you can set the `BW_SESSION` environment variable. If `BW_SESSION` is present, Fujin will use it directly without signing in or syncing the vault. In this case, the `options.password_env` configuration is not required.
|
|
36
38
|
|
|
37
39
|
## Usage
|
|
38
40
|
|
|
@@ -4,7 +4,7 @@ requires = [ "uv-build>=0.9.18,<0.10" ]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fujin-secrets-bitwarden"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.24.0"
|
|
8
8
|
description = "Bitwarden secret adapter for Fujin"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
"Programming Language :: Python :: 3.14",
|
|
21
21
|
]
|
|
22
22
|
dependencies = [
|
|
23
|
-
"fujin-cli>=0.
|
|
23
|
+
"fujin-cli>=0.24",
|
|
24
24
|
"python-dotenv>=1.0.1",
|
|
25
25
|
]
|
|
26
26
|
|
|
@@ -54,13 +54,14 @@ def bitwarden(env_content: str, secret_config: SecretConfig) -> str:
|
|
|
54
54
|
# Authenticate with Bitwarden
|
|
55
55
|
session = os.getenv("BW_SESSION")
|
|
56
56
|
if not session:
|
|
57
|
-
|
|
57
|
+
password_env = secret_config.options.get("password_env")
|
|
58
|
+
if not password_env:
|
|
58
59
|
raise SecretResolutionError(
|
|
59
|
-
"You need to set
|
|
60
|
+
"You need to set options.password_env to use the bitwarden adapter "
|
|
60
61
|
"or set the BW_SESSION environment variable",
|
|
61
62
|
adapter="bitwarden",
|
|
62
63
|
)
|
|
63
|
-
session = _signin(
|
|
64
|
+
session = _signin(password_env)
|
|
64
65
|
|
|
65
66
|
# Resolve secrets concurrently
|
|
66
67
|
resolved_secrets = {}
|
|
@@ -4,7 +4,7 @@ requires = [ "uv-build>=0.9.18,<0.10" ]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fujin-secrets-doppler"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.24.0"
|
|
8
8
|
description = "Doppler secret adapter for Fujin"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
"Programming Language :: Python :: 3.14",
|
|
21
21
|
]
|
|
22
22
|
dependencies = [
|
|
23
|
-
"fujin-cli>=0.
|
|
23
|
+
"fujin-cli>=0.24",
|
|
24
24
|
"python-dotenv>=1.0.1",
|
|
25
25
|
]
|
|
26
26
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Fujin Secrets - Environment Variable
|
|
2
|
+
|
|
3
|
+
Environment variable secret adapter for Fujin deployment tool. Reads secrets from a JSON-formatted environment variable, making it ideal for CI systems like GitHub Actions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install fujin-secrets-env
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with uv:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv pip install fujin-secrets-env
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
Add the following to your `fujin.toml` file:
|
|
20
|
+
|
|
21
|
+
```toml
|
|
22
|
+
[secrets]
|
|
23
|
+
adapter = "env"
|
|
24
|
+
|
|
25
|
+
[secrets.options]
|
|
26
|
+
source = "FUJIN_SECRETS"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The `source` option specifies the name of the environment variable containing the JSON-formatted secrets.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### GitHub Actions
|
|
34
|
+
|
|
35
|
+
In your workflow file, pass all secrets as JSON using `toJSON(secrets)`:
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
- name: Deploy
|
|
39
|
+
run: uvx --from fujin-cli --with fujin-secrets-env fujin deploy
|
|
40
|
+
env:
|
|
41
|
+
FUJIN_SECRETS: ${{ toJSON(secrets) }}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Environment File
|
|
45
|
+
|
|
46
|
+
In your environment configuration (via `env` in `fujin.toml`), prefix secret values with `$`:
|
|
47
|
+
|
|
48
|
+
```env
|
|
49
|
+
DEBUG=False
|
|
50
|
+
SECRET_KEY=$SECRET_KEY
|
|
51
|
+
DATABASE_URL=$DATABASE_URL
|
|
52
|
+
AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The `$` prefix indicates to Fujin that the value should be resolved from the secrets source.
|
|
56
|
+
|
|
57
|
+
## How it Works
|
|
58
|
+
|
|
59
|
+
The adapter:
|
|
60
|
+
1. Reads the JSON string from the configured environment variable
|
|
61
|
+
2. Parses the JSON into a dictionary
|
|
62
|
+
3. For each secret reference (prefixed with `$`), looks up the value in the parsed JSON
|
|
63
|
+
4. Returns the resolved environment variables
|
|
64
|
+
|
|
65
|
+
## Example
|
|
66
|
+
|
|
67
|
+
Given this GitHub Actions secret setup:
|
|
68
|
+
- `SECRET_KEY`: `my-secret-key`
|
|
69
|
+
- `DATABASE_URL`: `postgres://...`
|
|
70
|
+
|
|
71
|
+
And this fujin.toml env configuration:
|
|
72
|
+
```toml
|
|
73
|
+
[[hosts]]
|
|
74
|
+
env = """
|
|
75
|
+
DEBUG=False
|
|
76
|
+
SECRET_KEY=$SECRET_KEY
|
|
77
|
+
DATABASE_URL=$DATABASE_URL
|
|
78
|
+
"""
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The adapter will resolve `$SECRET_KEY` and `$DATABASE_URL` from the JSON passed via `FUJIN_SECRETS`.
|
|
82
|
+
|
|
83
|
+
## Related
|
|
84
|
+
|
|
85
|
+
- [Fujin Documentation](https://github.com/Tobi-De/fujin)
|
|
86
|
+
- [GitHub Actions Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "uv_build"
|
|
3
|
+
requires = [ "uv-build>=0.9.18,<0.10" ]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fujin-secrets-env"
|
|
7
|
+
version = "0.23.0"
|
|
8
|
+
description = "Environment variable secret adapter for Fujin"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Tobi", email = "tobidegnon@proton.me" },
|
|
12
|
+
]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"fujin-cli>=0.23",
|
|
24
|
+
"python-dotenv>=1.0.1",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
urls.Homepage = "https://github.com/Tobi-De/fujin"
|
|
28
|
+
urls.Issues = "https://github.com/Tobi-De/fujin/issues"
|
|
29
|
+
urls.Source = "https://github.com/Tobi-De/fujin"
|
|
30
|
+
entry-points."fujin.secrets".env = "fujin_secrets_env:env"
|
|
31
|
+
|
|
32
|
+
[tool.uv.sources]
|
|
33
|
+
fujin-cli = { workspace = true }
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Environment variable secret adapter for Fujin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from contextlib import closing
|
|
8
|
+
from io import StringIO
|
|
9
|
+
|
|
10
|
+
from dotenv import dotenv_values
|
|
11
|
+
|
|
12
|
+
from fujin.config import SecretConfig
|
|
13
|
+
from fujin.errors import SecretResolutionError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def env(env_content: str, secret_config: SecretConfig) -> str:
|
|
17
|
+
"""Environment variable secret adapter.
|
|
18
|
+
|
|
19
|
+
Reads secrets from a JSON-formatted environment variable. Useful for CI
|
|
20
|
+
systems like GitHub Actions where secrets can be passed as JSON.
|
|
21
|
+
|
|
22
|
+
Configuration:
|
|
23
|
+
[secrets]
|
|
24
|
+
adapter = "env"
|
|
25
|
+
source = "FUJIN_SECRETS" # Name of env var containing JSON secrets
|
|
26
|
+
|
|
27
|
+
Example GitHub Actions usage:
|
|
28
|
+
env:
|
|
29
|
+
FUJIN_SECRETS: ${{ toJSON(secrets) }}
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
env_content: Raw environment file content
|
|
33
|
+
secret_config: Secret configuration with adapter settings
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Resolved environment content with secrets replaced
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
SecretResolutionError: If source env var not set or invalid JSON
|
|
40
|
+
"""
|
|
41
|
+
source = secret_config.options.get("source")
|
|
42
|
+
if not source:
|
|
43
|
+
raise SecretResolutionError(
|
|
44
|
+
"The 'options.source' parameter is required for the env adapter. "
|
|
45
|
+
"Set it to the name of the environment variable containing secrets JSON.",
|
|
46
|
+
adapter="env",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Read JSON from source env var
|
|
50
|
+
secrets_json = os.getenv(source)
|
|
51
|
+
if not secrets_json:
|
|
52
|
+
raise SecretResolutionError(
|
|
53
|
+
f"Environment variable '{source}' is not set or empty.",
|
|
54
|
+
adapter="env",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
secrets = json.loads(secrets_json)
|
|
59
|
+
except json.JSONDecodeError as e:
|
|
60
|
+
raise SecretResolutionError(
|
|
61
|
+
f"Invalid JSON in '{source}': {e}",
|
|
62
|
+
adapter="env",
|
|
63
|
+
) from e
|
|
64
|
+
|
|
65
|
+
if not isinstance(secrets, dict):
|
|
66
|
+
raise SecretResolutionError(
|
|
67
|
+
f"'{source}' must contain a JSON object, got {type(secrets).__name__}",
|
|
68
|
+
adapter="env",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Parse env file
|
|
72
|
+
with closing(StringIO(env_content)) as buffer:
|
|
73
|
+
env_dict = dotenv_values(stream=buffer)
|
|
74
|
+
|
|
75
|
+
# Identify secrets (values starting with $)
|
|
76
|
+
secret_refs = {
|
|
77
|
+
key: value[1:] # Strip the $ prefix
|
|
78
|
+
for key, value in env_dict.items()
|
|
79
|
+
if value and value.startswith("$")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if not secret_refs:
|
|
83
|
+
return env_content
|
|
84
|
+
|
|
85
|
+
# Resolve secrets
|
|
86
|
+
for key, secret_name in secret_refs.items():
|
|
87
|
+
if secret_name not in secrets:
|
|
88
|
+
raise SecretResolutionError(
|
|
89
|
+
f"Secret '{secret_name}' not found in '{source}'",
|
|
90
|
+
adapter="env",
|
|
91
|
+
key=key,
|
|
92
|
+
)
|
|
93
|
+
env_dict[key] = secrets[secret_name]
|
|
94
|
+
|
|
95
|
+
return "\n".join(f'{key}="{value}"' for key, value in env_dict.items())
|
|
@@ -5,7 +5,7 @@ requires = [ "hatchling" ]
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "fujin-cli"
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.24.0"
|
|
9
9
|
description = "Get your project up and running in a few minutes on your own vps."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
keywords = [
|
|
@@ -74,10 +74,12 @@ members = [
|
|
|
74
74
|
"plugins/fujin-secrets-bitwarden",
|
|
75
75
|
"plugins/fujin-secrets-1password",
|
|
76
76
|
"plugins/fujin-secrets-doppler",
|
|
77
|
+
"plugins/fujin-secrets-env",
|
|
77
78
|
]
|
|
78
79
|
|
|
79
80
|
[tool.uv.sources]
|
|
80
81
|
fujin-secrets-bitwarden = { workspace = true }
|
|
82
|
+
fujin-secrets-env = { workspace = true }
|
|
81
83
|
|
|
82
84
|
[tool.ruff]
|
|
83
85
|
# Assume Python {{ python_version }}
|
|
@@ -149,7 +151,7 @@ markers = [
|
|
|
149
151
|
]
|
|
150
152
|
|
|
151
153
|
[tool.bumpversion]
|
|
152
|
-
current_version = "0.
|
|
154
|
+
current_version = "0.24.0"
|
|
153
155
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
154
156
|
serialize = [ "{major}.{minor}.{patch}" ]
|
|
155
157
|
search = "{current_version}"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.24.0"
|
|
@@ -135,13 +135,6 @@ def install(
|
|
|
135
135
|
install_dir = app_dir / ".install"
|
|
136
136
|
install_dir.mkdir(exist_ok=True)
|
|
137
137
|
|
|
138
|
-
# Move .env file to .install/
|
|
139
|
-
env_file = bundle_dir / ".env"
|
|
140
|
-
if env_file.exists():
|
|
141
|
-
shutil.move(env_file, install_dir / ".env")
|
|
142
|
-
env_file = install_dir / ".env"
|
|
143
|
-
logger.debug("Moved .env to %s", env_file)
|
|
144
|
-
|
|
145
138
|
# ==========================================================================
|
|
146
139
|
# PHASE 2: INSTALLATION
|
|
147
140
|
# ==========================================================================
|
|
@@ -221,7 +214,6 @@ export -f {config.app_name}
|
|
|
221
214
|
run(f"chown -R {config.deploy_user}:{config.app_user} {install_dir}")
|
|
222
215
|
# Make .install directory group-writable (deploy user can update, app user can read)
|
|
223
216
|
install_dir.chmod(0o775)
|
|
224
|
-
env_file.chmod(0o640)
|
|
225
217
|
|
|
226
218
|
# .venv permissions: readable/executable by group, writable by owner
|
|
227
219
|
# Use chmod -R with symbolic modes to traverse once instead of 3 find commands
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import base64
|
|
3
4
|
import json
|
|
4
5
|
import logging
|
|
5
6
|
import shlex
|
|
@@ -8,9 +9,10 @@ import hashlib
|
|
|
8
9
|
import subprocess
|
|
9
10
|
import tempfile
|
|
10
11
|
import zipapp
|
|
12
|
+
from contextlib import contextmanager
|
|
11
13
|
from dataclasses import dataclass
|
|
12
14
|
from pathlib import Path
|
|
13
|
-
from typing import Annotated
|
|
15
|
+
from typing import Annotated, Generator
|
|
14
16
|
|
|
15
17
|
import cappa
|
|
16
18
|
from rich.console import Console
|
|
@@ -61,6 +63,13 @@ class Deploy(BaseCommand):
|
|
|
61
63
|
help="Disable automatic rollback on deployment failure",
|
|
62
64
|
),
|
|
63
65
|
] = False
|
|
66
|
+
bundle_dir: Annotated[
|
|
67
|
+
Path | None,
|
|
68
|
+
cappa.Arg(
|
|
69
|
+
long="--bundle-dir",
|
|
70
|
+
help="Directory to create bundle in (preserved after deploy, fails if exists)",
|
|
71
|
+
),
|
|
72
|
+
] = None
|
|
64
73
|
|
|
65
74
|
def __call__(self):
|
|
66
75
|
logger.debug("Starting deployment for %s", self.config.app_name)
|
|
@@ -124,7 +133,7 @@ class Deploy(BaseCommand):
|
|
|
124
133
|
if not self.config.deployed_units:
|
|
125
134
|
raise DeploymentError("No systemd units found, nothing to deploy")
|
|
126
135
|
|
|
127
|
-
with
|
|
136
|
+
with self._bundle_directory() as tmpdir:
|
|
128
137
|
self.output.info("Preparing deployment bundle...")
|
|
129
138
|
zipapp_dir = Path(tmpdir) / "zipapp_source"
|
|
130
139
|
zipapp_dir.mkdir()
|
|
@@ -162,10 +171,9 @@ class Deploy(BaseCommand):
|
|
|
162
171
|
# Track unresolved variables across all files
|
|
163
172
|
all_unresolved = set()
|
|
164
173
|
|
|
165
|
-
#
|
|
174
|
+
# Resolve env file (uploaded separately, not bundled)
|
|
166
175
|
resolved_env, unresolved = safe_format(parsed_env, **context)
|
|
167
176
|
all_unresolved.update(unresolved)
|
|
168
|
-
(zipapp_dir / ".env").write_text(resolved_env)
|
|
169
177
|
|
|
170
178
|
logger.debug("Validating and resolving systemd units")
|
|
171
179
|
systemd_dir = zipapp_dir / "systemd"
|
|
@@ -351,6 +359,23 @@ class Deploy(BaseCommand):
|
|
|
351
359
|
conn.put(str(zipapp_path), remote_bundle_path_q, verify=True)
|
|
352
360
|
|
|
353
361
|
self.output.success("Bundle uploaded successfully.")
|
|
362
|
+
|
|
363
|
+
# Write .env file directly (not bundled for security)
|
|
364
|
+
remote_env_path = f"{self.config.install_dir}/.env"
|
|
365
|
+
remote_env_path_q = shlex.quote(remote_env_path)
|
|
366
|
+
install_dir_q = shlex.quote(self.config.install_dir)
|
|
367
|
+
logger.debug("Writing .env to %s", remote_env_path)
|
|
368
|
+
|
|
369
|
+
# Use base64 encoding to safely transfer content with special chars
|
|
370
|
+
encoded_env = base64.b64encode(resolved_env.encode()).decode()
|
|
371
|
+
conn.run(
|
|
372
|
+
f"mkdir -p {install_dir_q} && "
|
|
373
|
+
f"echo {shlex.quote(encoded_env)} | base64 -d > {remote_env_path_q} && "
|
|
374
|
+
f"chmod 640 {remote_env_path_q} && "
|
|
375
|
+
f"chown {self.selected_host.user}:{self.config.app_user} {remote_env_path_q}",
|
|
376
|
+
hide=True,
|
|
377
|
+
)
|
|
378
|
+
|
|
354
379
|
self.output.info("Executing remote installation...")
|
|
355
380
|
|
|
356
381
|
rollback_ran = False
|
|
@@ -425,6 +450,26 @@ class Deploy(BaseCommand):
|
|
|
425
450
|
url = f"https://{domain}"
|
|
426
451
|
self.output.info(f"Application is available at: {url}")
|
|
427
452
|
|
|
453
|
+
@contextmanager
|
|
454
|
+
def _bundle_directory(self) -> Generator[Path, None, None]:
|
|
455
|
+
"""Context manager for bundle directory.
|
|
456
|
+
|
|
457
|
+
If bundle_dir is specified, uses that directory (fails if exists).
|
|
458
|
+
Otherwise, creates a temporary directory that is cleaned up on exit.
|
|
459
|
+
"""
|
|
460
|
+
if self.bundle_dir:
|
|
461
|
+
if self.bundle_dir.exists():
|
|
462
|
+
raise DeploymentError(
|
|
463
|
+
f"Bundle directory already exists: {self.bundle_dir}"
|
|
464
|
+
)
|
|
465
|
+
self.bundle_dir.mkdir(parents=True)
|
|
466
|
+
logger.debug("Using bundle directory: %s", self.bundle_dir)
|
|
467
|
+
yield self.bundle_dir
|
|
468
|
+
self.output.info(f"Bundle preserved in: {self.bundle_dir}")
|
|
469
|
+
else:
|
|
470
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
471
|
+
yield Path(tmpdir)
|
|
472
|
+
|
|
428
473
|
def _show_deployment_summary(self, bundle_size: int, bundle_version: str):
|
|
429
474
|
console = Console()
|
|
430
475
|
|
|
@@ -35,7 +35,7 @@ class InstallationMode(StrEnum):
|
|
|
35
35
|
|
|
36
36
|
class SecretConfig(msgspec.Struct):
|
|
37
37
|
adapter: str
|
|
38
|
-
|
|
38
|
+
options: dict[str, str] = msgspec.field(default_factory=dict)
|
|
39
39
|
|
|
40
40
|
def __post_init__(self):
|
|
41
41
|
if not re.match(r"^[a-z0-9_-]+$", self.adapter):
|
|
@@ -15,6 +15,7 @@ members = [
|
|
|
15
15
|
"fujin-secrets-1password",
|
|
16
16
|
"fujin-secrets-bitwarden",
|
|
17
17
|
"fujin-secrets-doppler",
|
|
18
|
+
"fujin-secrets-env",
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
[[package]]
|
|
@@ -345,7 +346,7 @@ wheels = [
|
|
|
345
346
|
|
|
346
347
|
[[package]]
|
|
347
348
|
name = "fujin-cli"
|
|
348
|
-
version = "0.
|
|
349
|
+
version = "0.23.0"
|
|
349
350
|
source = { editable = "." }
|
|
350
351
|
dependencies = [
|
|
351
352
|
{ name = "cappa" },
|
|
@@ -401,7 +402,7 @@ docs = [
|
|
|
401
402
|
|
|
402
403
|
[[package]]
|
|
403
404
|
name = "fujin-secrets-1password"
|
|
404
|
-
version = "0.
|
|
405
|
+
version = "0.23.0"
|
|
405
406
|
source = { editable = "plugins/fujin-secrets-1password" }
|
|
406
407
|
dependencies = [
|
|
407
408
|
{ name = "fujin-cli" },
|
|
@@ -416,7 +417,7 @@ requires-dist = [
|
|
|
416
417
|
|
|
417
418
|
[[package]]
|
|
418
419
|
name = "fujin-secrets-bitwarden"
|
|
419
|
-
version = "0.
|
|
420
|
+
version = "0.23.0"
|
|
420
421
|
source = { editable = "plugins/fujin-secrets-bitwarden" }
|
|
421
422
|
dependencies = [
|
|
422
423
|
{ name = "fujin-cli" },
|
|
@@ -431,7 +432,7 @@ requires-dist = [
|
|
|
431
432
|
|
|
432
433
|
[[package]]
|
|
433
434
|
name = "fujin-secrets-doppler"
|
|
434
|
-
version = "0.
|
|
435
|
+
version = "0.23.0"
|
|
435
436
|
source = { editable = "plugins/fujin-secrets-doppler" }
|
|
436
437
|
dependencies = [
|
|
437
438
|
{ name = "fujin-cli" },
|
|
@@ -444,6 +445,21 @@ requires-dist = [
|
|
|
444
445
|
{ name = "python-dotenv", specifier = ">=1.0.1" },
|
|
445
446
|
]
|
|
446
447
|
|
|
448
|
+
[[package]]
|
|
449
|
+
name = "fujin-secrets-env"
|
|
450
|
+
version = "0.23.0"
|
|
451
|
+
source = { editable = "plugins/fujin-secrets-env" }
|
|
452
|
+
dependencies = [
|
|
453
|
+
{ name = "fujin-cli" },
|
|
454
|
+
{ name = "python-dotenv" },
|
|
455
|
+
]
|
|
456
|
+
|
|
457
|
+
[package.metadata]
|
|
458
|
+
requires-dist = [
|
|
459
|
+
{ name = "fujin-cli", editable = "." },
|
|
460
|
+
{ name = "python-dotenv", specifier = ">=1.0.1" },
|
|
461
|
+
]
|
|
462
|
+
|
|
447
463
|
[[package]]
|
|
448
464
|
name = "gunicorn"
|
|
449
465
|
version = "25.1.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.22.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/.fujin/systemd/health.service
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fujin_cli-0.22.2 → fujin_cli-0.24.0}/examples/django/bookstore/.fujin/systemd/worker@.service
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|