fujin-cli 0.10.0__tar.gz → 0.11.4__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.

Potentially problematic release.


This version of fujin-cli might be problematic. Click here for more details.

Files changed (83) hide show
  1. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/CHANGELOG.md +42 -0
  2. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/PKG-INFO +3 -2
  3. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/secrets.rst +30 -9
  4. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/tutorial.rst +48 -37
  5. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/pyproject.toml +8 -5
  6. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/__main__.py +2 -2
  7. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/init.py +1 -1
  8. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/config.py +46 -41
  9. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/secrets/__init__.py +7 -1
  10. fujin_cli-0.11.4/src/fujin/secrets/system.py +19 -0
  11. fujin_cli-0.11.4/src/fujin/templates/granian/web.service +22 -0
  12. {fujin_cli-0.10.0/src/fujin/templates → fujin_cli-0.11.4/src/fujin/templates/gunicorn}/web.service +2 -2
  13. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/templates/simple.service +2 -1
  14. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/uv.lock +4 -3
  15. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/.github/workflows/publish.yml +0 -0
  16. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/.gitignore +0 -0
  17. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/.pre-commit-config.yaml +0 -0
  18. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/.readthedocs.yaml +0 -0
  19. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/LICENSE.txt +0 -0
  20. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/README.md +0 -0
  21. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/Vagrantfile +0 -0
  22. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/changelog.rst +0 -0
  23. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/app.rst +0 -0
  24. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/config.rst +0 -0
  25. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/deploy.rst +0 -0
  26. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/docs.rst +0 -0
  27. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/down.rst +0 -0
  28. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/index.rst +0 -0
  29. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/init.rst +0 -0
  30. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/printenv.rst +0 -0
  31. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/proxy.rst +0 -0
  32. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/prune.rst +0 -0
  33. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/redeploy.rst +0 -0
  34. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/rollback.rst +0 -0
  35. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/server.rst +0 -0
  36. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/commands/up.rst +0 -0
  37. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/conf.py +0 -0
  38. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/configuration.rst +0 -0
  39. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/hooks.rst +0 -0
  40. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/index.rst +0 -0
  41. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/installation.rst +0 -0
  42. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/docs/requirements.txt +0 -0
  43. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/django/bookstore/README.md +0 -0
  44. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/django/bookstore/bookstore/__init__.py +0 -0
  45. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/django/bookstore/bookstore/__main__.py +0 -0
  46. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/django/bookstore/bookstore/asgi.py +0 -0
  47. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/django/bookstore/bookstore/settings.py +0 -0
  48. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/django/bookstore/bookstore/urls.py +0 -0
  49. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/django/bookstore/bookstore/wsgi.py +0 -0
  50. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/django/bookstore/fujin.toml +0 -0
  51. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/django/bookstore/manage.py +0 -0
  52. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/django/bookstore/pyproject.toml +0 -0
  53. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/django/bookstore/requirements.txt +0 -0
  54. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/golang/pocketbase/.env.prod +0 -0
  55. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/examples/golang/pocketbase/fujin.toml +0 -0
  56. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/justfile +0 -0
  57. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/__init__.py +0 -0
  58. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/__init__.py +0 -0
  59. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/_base.py +0 -0
  60. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/app.py +0 -0
  61. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/config.py +0 -0
  62. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/deploy.py +0 -0
  63. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/docs.py +0 -0
  64. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/down.py +0 -0
  65. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/printenv.py +0 -0
  66. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/proxy.py +0 -0
  67. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/prune.py +0 -0
  68. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/redeploy.py +0 -0
  69. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/rollback.py +0 -0
  70. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/server.py +0 -0
  71. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/commands/up.py +0 -0
  72. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/connection.py +0 -0
  73. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/errors.py +0 -0
  74. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/hooks.py +0 -0
  75. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/proxies/__init__.py +0 -0
  76. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/proxies/caddy.py +0 -0
  77. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/proxies/dummy.py +0 -0
  78. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/proxies/nginx.py +0 -0
  79. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/secrets/bitwarden.py +0 -0
  80. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/secrets/dopppler.py +0 -0
  81. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/secrets/onepassword.py +0 -0
  82. {fujin_cli-0.10.0 → fujin_cli-0.11.4}/src/fujin/systemd.py +0 -0
  83. {fujin_cli-0.10.0/src/fujin/templates → fujin_cli-0.11.4/src/fujin/templates/gunicorn}/web.socket +0 -0
@@ -4,6 +4,48 @@ 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.11.4] - 2025-03-08
8
+
9
+ ### 🐛 Bug Fixes
10
+
11
+ - No mutable default
12
+
13
+ ## [0.11.3] - 2025-03-08
14
+
15
+ ### Cfeat
16
+
17
+ - Add fujin version info
18
+
19
+ ## [0.11.2] - 2025-03-08
20
+
21
+ ### 🚀 Features
22
+
23
+ - Set system as the default secrets adapter
24
+
25
+ ## [0.11.1] - 2025-03-08
26
+
27
+ ### 🐛 Bug Fixes
28
+
29
+ - Env content parse logic
30
+
31
+ ## [0.11.0] - 2025-03-08
32
+
33
+ ### 🚀 Features
34
+
35
+ - Add system secret reader
36
+
37
+ ### 🚜 Refactor
38
+
39
+ - Rename env_content to env
40
+
41
+ ### 📚 Documentation
42
+
43
+ - Apply a more consitent writing style
44
+
45
+ ### ⚙️ Miscellaneous Tasks
46
+
47
+ - Specify source package to avoid failing build backend
48
+
7
49
  ## [0.10.0] - 2024-11-24
8
50
 
9
51
  ### 🚀 Features
@@ -1,11 +1,12 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: fujin-cli
3
- Version: 0.10.0
3
+ Version: 0.11.4
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/falcopackages/fujin#readme
6
6
  Project-URL: Issues, https://github.com/falcopackages/fujin/issues
7
7
  Project-URL: Source, https://github.com/falcopackages/fujin
8
8
  Author-email: Tobi DEGNON <tobidegnon@proton.me>
9
+ License-File: LICENSE.txt
9
10
  Keywords: caddy,deployment,django,fastapi,litestar,python,systemd
10
11
  Classifier: Development Status :: 3 - Alpha
11
12
  Classifier: Intended Audience :: Developers
@@ -1,24 +1,43 @@
1
1
  Secrets
2
2
  =======
3
3
 
4
+ System
5
+ ------
6
+
7
+ The system adapter uses environment variables directly from your system. This is the simplest adapter and requires no additional setup.
8
+ Update your fujin.toml file with the following configuration:
9
+
10
+ [secrets]
11
+ adapter = "system"
12
+
13
+ .. code-block:: text
14
+ :caption: Example of an environment file with system environment variables
15
+
16
+ DEBUG=False
17
+ AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
18
+ AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
19
+
20
+ The system adapter will look for environment variables with the same name as the value after the $ sign.
21
+
4
22
  Bitwarden
5
23
  ---------
6
24
 
7
25
  First, download and install the `Bitwarden CLI <https://bitwarden.com/help/cli/#download-and-install>`_. Make sure to log in to your account.
8
26
  You should be able to run ``bw get password <name_of_secret>`` and get the value for the secret. This is the command that will be executed when pulling your secrets.
9
27
 
10
- Add the following to your ``fujin.toml`` file:
28
+ Add the following to your **fujin.toml** file:
11
29
 
12
- .. code-block:: toml
30
+ .. code-block:: toml
31
+ :caption: fujin.toml
13
32
 
14
33
  [secrets]
15
34
  adapter = "bitwarden"
16
35
  password_env = "BW_PASSWORD"
17
36
 
18
- To unlock the Bitwarden vault, the password is required. You can set the ``BW_PASSWORD`` environment variable in your shell, which will be used to unlock the vault.
19
- When Fujin signs in, it will always sync the vault first.
37
+ To unlock the Bitwarden vault, the password is required. Set the *BW_PASSWORD* environment variable in your shell.
38
+ When ``fujin`` signs in, it will always sync the vault first.
20
39
 
21
- 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.
40
+ 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.
22
41
 
23
42
  .. code-block:: text
24
43
  :caption: Example of an environment file with Bitwarden secrets
@@ -27,7 +46,7 @@ Alternatively, you can set the ``BW_SESSION`` environment variable. If ``BW_SESS
27
46
  AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
28
47
  AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
29
48
 
30
- Note the ``$`` sign, which indicates to ``fujin`` that it is a secret.
49
+ Note the *$* sign, which indicates to ``fujin`` that it is a secret.
31
50
 
32
51
  1Password
33
52
  ---------
@@ -35,9 +54,10 @@ Note the ``$`` sign, which indicates to ``fujin`` that it is a secret.
35
54
  Download and install the `1Password CLI <https://developer.1password.com/docs/cli>`_, and sign in to your account.
36
55
  You need to be actively signed in for Fujin to work with 1Password.
37
56
 
38
- Update your ``fujin.toml`` file with the following configuration:
57
+ Update your **fujin.toml** file with the following configuration:
39
58
 
40
- .. code-block:: toml
59
+ .. code-block:: toml
60
+ :caption: fujin.toml
41
61
 
42
62
  [secrets]
43
63
  adapter = "1password"
@@ -55,9 +75,10 @@ Doppler
55
75
  Download and install the `Doppler CLI <https://docs.doppler.com/docs/cli>`_, and sign in to your account.
56
76
  Move to your project root directory and run ``doppler setup`` to configure your project.
57
77
 
58
- Update your ``fujin.toml`` file with the following configuration:
78
+ Update your **fujin.toml** file with the following configuration:
59
79
 
60
80
  .. code-block:: toml
81
+ :caption: fujin.toml
61
82
 
62
83
  [secrets]
63
84
  adapter = "doppler"
@@ -1,8 +1,9 @@
1
1
  Tutorial
2
2
  ========
3
3
 
4
+ In this tutorial, you will deploy a Django project or a PocketBase binary (in binary mode) to a Linux server running Ubuntu using the ``fujin`` CLI.
5
+
4
6
  First, make sure you follow the `installation </installation.html>`_ instructions and have the ``fujin`` command available globally in your shell.
5
- ``fujin`` allows you to interact with your remote server and your deployed apps from your local shell.
6
7
 
7
8
  Prerequisites
8
9
  -------------
@@ -48,9 +49,10 @@ Let's start by installing and initializing a simple Django project.
48
49
  The ``uv init --package`` command makes your project mostly ready to be used with ``fujin``. It initializes a `packaged application <https://docs.astral.sh/uv/concepts/projects/#packaged-applications>`_ using uv,
49
50
  meaning the app can be packaged and distributed (e.g: via PyPI) and defines an entry point, which are the two requirements of ``fujin``.
50
51
 
51
- This is the content you'll get in the ``pyproject.toml`` file, with the relevant parts highlighted.
52
+ This is the content you'll get in the **pyproject.toml** file, with the relevant parts highlighted.
52
53
 
53
54
  .. code-block:: toml
55
+ :caption: fujin.toml
54
56
  :linenos:
55
57
  :emphasize-lines: 15-16,18-20
56
58
 
@@ -75,38 +77,39 @@ This is the content you'll get in the ``pyproject.toml`` file, with the relevant
75
77
  requires = ["hatchling"]
76
78
  build-backend = "hatchling.build"
77
79
 
78
- The ``build-system`` section is what allows us to build our project into a wheel file (Python package format), and the ``project.scripts`` defines a CLI entry point for our app.
80
+ The *build-system* section is what allows us to build our project into a wheel file (Python package format), and the *project.scripts* defines a CLI entry point for our app.
79
81
  This means that if our app is installed (either with ``pip install`` or ``uv tool install``, for example), there will be a ``bookstore`` command available globally on our system to run the project.
80
82
 
81
83
  .. note::
82
84
 
83
- If you are installing it in a virtual environment, then there will be a file ``.venv/bin/bookstore`` that will run this CLI entry point. This is what ``fujin`` expects internally.
84
- When it deploys your Python project, it sets up and installs a virtual environment in the app directory in a ``.venv`` folder and expects this entry point to be able to run
85
+ If you are installing it in a virtual environment, then there will be a file **.venv/bin/bookstore** that will run this CLI entry point. This is what ``fujin`` expects internally.
86
+ When it deploys your Python project, it sets up and installs a virtual environment in the app directory in a **.venv** folder and expects this entry point to be able to run
85
87
  commands with the ``fujin app exec <command>`` command.
86
88
 
87
- Currently, our entry point will run the main function in the ``src/bookstore/__init__.py`` file. Let's change that.
89
+ Currently, our entry point will run the main function in the **src/bookstore/__init__.py** file. Let's change that.
88
90
 
89
91
  .. code-block:: shell
90
92
 
91
93
  rm -r src
92
94
  mv manage.py bookstore/__main__.py
93
95
 
94
- We first remove the ``src`` folder, as we won't use that since our Django project will reside in the top-level ``bookstore`` folder. I also recommend keeping all
96
+ We first remove the **src** folder, as we won't use that since our Django project will reside in the top-level **bookstore** folder. I also recommend keeping all
95
97
  your Django code in that folder, including new apps, as this makes things easier for packaging purposes.
96
- Then we move the ``manage.py`` file to the ``bookstore`` folder and rename it to ``__main__.py``. This enables us to do this:
98
+ Then we move the **manage.py** file to the **bookstore** folder and rename it to **__main__.py**. This enables us to do this:
97
99
 
98
100
  .. code-block:: shell
99
101
 
100
102
  uv run bookstore migrate # equivalent to python manage.py migrate if we kept the manage.py file
101
103
 
102
- Now to finish, update the ``scripts`` section in your ``pyproject.toml`` file.
104
+ Now to finish, update the *scripts* section in your **pyproject.toml** file.
103
105
 
104
106
  .. code-block:: toml
107
+ :caption: fujin.toml
105
108
 
106
109
  [project.scripts]
107
110
  bookstore = "bookstore.__main__:main"
108
111
 
109
- Now the CLI that will be installed with your project will do the job of the ``manage.py`` file. To test this out, run the following commands:
112
+ Now the CLI that will be installed with your project will do the job of the **manage.py** file. To test this out, run the following commands:
110
113
 
111
114
  .. code-block:: shell
112
115
 
@@ -134,6 +137,7 @@ Now that our project is ready, run ``fujin init`` at the root of it.
134
137
  Here's what you'll get:
135
138
 
136
139
  .. code-block:: toml
140
+ :caption: fujin.toml
137
141
 
138
142
  app = "bookstore"
139
143
  build_command = "uv build && uv pip compile pyproject.toml -o requirements.txt"
@@ -160,6 +164,7 @@ Here's what you'll get:
160
164
  Update the host section; it should look something like this, but with your server IP:
161
165
 
162
166
  .. code-block:: toml
167
+ :caption: fujin.toml
163
168
 
164
169
  [host]
165
170
  domain_name = "SERVER_IP.sslip.io"
@@ -170,10 +175,11 @@ Update the host section; it should look something like this, but with your serve
170
175
 
171
176
  Make sure to replace ``SERVER_IP`` with the actual IP address of your server.
172
177
 
173
- Create a ``.env.prod`` file at the root of your project; it can be an empty file for now. The only requirement is that the file should exist.
174
- Update your ``bookstore/settings.py`` with the changes below:
178
+ Create a **.env.prod** file at the root of your project; it can be an empty file for now. The only requirement is that the file should exist.
179
+ Update your **bookstore/settings.py** with the changes below:
175
180
 
176
181
  .. code-block:: python
182
+ :caption: settings.py
177
183
 
178
184
  # SECURITY WARNING: don't run with debug turned on in production!
179
185
  DEBUG = False
@@ -182,9 +188,10 @@ Update your ``bookstore/settings.py`` with the changes below:
182
188
 
183
189
  With the current setup, we should already be able to deploy our app with the ``fujin up`` command, but static files won't work. Let's make some changes.
184
190
 
185
- Update ``bookstore/settings.py`` with the changes below:
191
+ Update **bookstore/settings.py** with the changes below:
186
192
 
187
193
  .. code-block:: python
194
+ :caption: settings.py
188
195
  :linenos:
189
196
  :lineno-start: 118
190
197
  :emphasize-lines: 119
@@ -194,7 +201,7 @@ Update ``bookstore/settings.py`` with the changes below:
194
201
 
195
202
  The last line means that when the ``collectstatic`` command is run, the files will be placed in a **staticfiles** directory in the current directory.
196
203
 
197
- Now let's update the ``fujin.toml`` file to run ``collectstatic`` before the app is started and move these files to the folder where our web server
204
+ Now let's update the **fujin.toml** file to run ``collectstatic`` before the app is started and move these files to the folder where our web server
198
205
  can read them:
199
206
 
200
207
  .. code-block:: toml
@@ -232,10 +239,11 @@ For this tutorial, we will use `pocketbase <https://github.com/pocketbase/pocket
232
239
  curl -LO https://github.com/pocketbase/pocketbase/releases/download/v0.22.26/pocketbase_0.22.26_linux_amd64.zip
233
240
  fujin init --profile binary
234
241
 
235
- With the instructions above, we will download a version of Pocketbase to run on Linux from their GitHub release and initialize a new fujin configuration in ``binary`` mode.
236
- Now update the ``fujin.toml`` file with the changes below:
242
+ With the instructions above, we will download a version of Pocketbase to run on Linux from their GitHub release and initialize a new fujin configuration in *binary* mode.
243
+ Now update the **fujin.toml** file with the changes below:
237
244
 
238
245
  .. code-block:: toml
246
+ :caption: fujin.toml
239
247
  :linenos:
240
248
  :emphasize-lines: 2-5,9,13,19-21
241
249
 
@@ -263,25 +271,26 @@ Now update the ``fujin.toml`` file with the changes below:
263
271
 
264
272
  .. caution::
265
273
 
266
- Make sure to replace ``SERVER_IP`` with the actual IP address of your server.
274
+ Make sure to replace *SERVER_IP* with the actual IP address of your server.
267
275
 
268
276
  Create User
269
277
  -----------
270
278
 
271
- Currently, we have the user set to **root** in our ``fujin.toml`` file and ``fujin`` might work with the root user, but I've noticed some issues with it, so I highly recommend creating a custom user.
279
+ Currently, we have the user set to **root** in our **fujin.toml** file and ``fujin`` might work with the root user, but I've noticed some issues with it, so I highly recommend creating a custom user.
272
280
  For that, you'll need the root user with SSH access set up on the server.
273
- Then you'll run the command ``fujin server create-user`` with the username you want to use. You can, for example, use **fujin** as the username.
274
- For example:
281
+ Then you'll run the command ``fujin server create-user`` with the username you want to use. You can, for example, use *fujin* as the username.
275
282
 
276
283
  .. code-block:: shell
284
+ :caption: create-user example
277
285
 
278
286
  fujin server create-user fujin
279
287
 
280
288
  This will create a new **fujin** user on your server, add it to the ``sudo`` group with the option to run all commands without having to type a password, and will
281
289
  copy the authorized key from the **root** to your new user so that the SSH setup you made for the root user still works with this new one.
282
- Now update the ``fujin.toml`` file with the new user:
290
+ Now update the **fujin.toml** file with the new user:
283
291
 
284
292
  .. code-block:: toml
293
+ :caption: fujin.toml
285
294
 
286
295
  [host]
287
296
  domain_name = "SERVER_IP.sslip.io"
@@ -298,31 +307,33 @@ Now that your project is ready, run the commands below to deploy for the first t
298
307
  fujin up
299
308
 
300
309
  The first time, the process can take a few minutes. At the end of it, you should have a link to your deployed app.
301
- A few notable commands:
302
310
 
303
- .. code-block:: shell
304
- :caption: Deploy an app on a host where fujin has already been set up
311
+ .. admonition:: A few notable commands
312
+ :class: note dropdown
305
313
 
306
- fujin deploy
314
+ .. code-block:: shell
315
+ :caption: Deploy an app on a host where ``fujin`` has already been set up
307
316
 
308
- You also use the ``deploy`` command when you have changed the fujin config or exported configs:
317
+ fujin deploy
309
318
 
310
- .. code-block:: shell
311
- :caption: Export the systemd config being used so that you can edit them
319
+ You also use the ``deploy`` command when you have changed the ``fujin`` config or exported configs:
312
320
 
313
- fujin app export-config
321
+ .. code-block:: shell
322
+ :caption: Export the systemd config being used so that you can edit them
314
323
 
315
- .. code-block:: shell
316
- :caption: Export the webserver config, in this case, caddy
324
+ fujin app export-config
317
325
 
318
- fujin proxy export-config
326
+ .. code-block:: shell
327
+ :caption: Export the webserver config, in this case, caddy
319
328
 
320
- and the command you'll probably be running the most:
329
+ fujin proxy export-config
321
330
 
322
- .. code-block:: shell
323
- :caption: When you've only made code and envfile related changes
331
+ and the command you'll probably be running the most:
332
+
333
+ .. code-block:: shell
334
+ :caption: When you've only made code and envfile related changes
324
335
 
325
- fujin redeploy
336
+ fujin redeploy
326
337
 
327
338
  FAQ
328
339
  ---
@@ -333,5 +344,5 @@ What about my database?
333
344
  I'm currently using SQLite for my side projects, so this isn't an issue for me at the moment. That's why ``fujin`` does not currently assist with databases.
334
345
  However, you can still SSH into your server and manually install PostgreSQL or any other database or services you need.
335
346
 
336
- I plan to add support for managing additional tools like Redis or databases by declaring containers via the ``fujin.toml`` file. These containers will be managed with ``podman``,
347
+ I plan to add support for managing additional tools like Redis or databases by declaring containers via the **fujin.toml** file. These containers will be managed with ``podman``,
337
348
  To follow the development of this feature, subscribe to this `issue <https://github.com/falcopackages/fujin/issues/17>`_.
@@ -7,7 +7,7 @@ requires = [
7
7
 
8
8
  [project]
9
9
  name = "fujin-cli"
10
- version = "0.10.0"
10
+ version = "0.11.4"
11
11
  description = "Get your project up and running in a few minutes on your own vps."
12
12
  readme = "README.md"
13
13
  keywords = [
@@ -72,9 +72,7 @@ docs = [
72
72
  ]
73
73
 
74
74
  [tool.hatch.build.targets.wheel]
75
- packages = [
76
- "src/fujin",
77
- ]
75
+ packages = [ "src/fujin" ]
78
76
 
79
77
  [tool.ruff]
80
78
  # Assume Python {{ python_version }}
@@ -154,7 +152,7 @@ lint.isort.required-imports = [
154
152
  lint.pyupgrade.keep-runtime-typing = true
155
153
 
156
154
  [tool.bumpversion]
157
- current_version = "0.10.0"
155
+ current_version = "0.11.4"
158
156
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
159
157
  serialize = [
160
158
  "{major}.{minor}.{patch}",
@@ -178,6 +176,11 @@ filename = "pyproject.toml"
178
176
  search = 'version = "{current_version}"'
179
177
  replace = 'version = "{new_version}"'
180
178
 
179
+ [[tool.bumpversion.files]]
180
+ filename = "src/fujin/__main__.py"
181
+ replace = 'version="{new_version}"'
182
+ search = 'version="{current_version}"'
183
+
181
184
  [tool.mypy]
182
185
  check_untyped_defs = true
183
186
  exclude = [
@@ -50,9 +50,9 @@ class Fujin:
50
50
  def main():
51
51
  alias_cmd = _parse_aliases()
52
52
  if alias_cmd:
53
- cappa.invoke(Fujin, argv=alias_cmd)
53
+ cappa.invoke(Fujin, argv=alias_cmd, version="0.11.4")
54
54
  else:
55
- cappa.invoke(Fujin)
55
+ cappa.invoke(Fujin, version="0.11.4")
56
56
 
57
57
 
58
58
  def _parse_aliases() -> list[str] | None:
@@ -57,7 +57,7 @@ def simple_config(app_name) -> dict:
57
57
  "host": {
58
58
  "user": "root",
59
59
  "domain_name": f"{app_name}.com",
60
- "env_content": f"DEBUG=False\nALLOWED_HOSTS={app_name}.com\n",
60
+ "envfile": ".env.prod",
61
61
  },
62
62
  }
63
63
  if not Path(".python-version").exists():
@@ -1,5 +1,5 @@
1
1
  """
2
- Fujin uses a ``fujin.toml`` file at the root of your project for configuration. Below are all available configuration options.
2
+ Fujin uses a **fujin.toml** file at the root of your project for configuration. Below are all available configuration options.
3
3
 
4
4
  app
5
5
  ---
@@ -7,16 +7,16 @@ The name of your project or application. Must be a valid Python package name.
7
7
 
8
8
  version
9
9
  --------
10
- The version of your project to build and deploy. If not specified, automatically parsed from ``pyproject.toml`` under ``project.version``.
10
+ The version of your project to build and deploy. If not specified, automatically parsed from **pyproject.toml** under *project.version*.
11
11
 
12
12
  python_version
13
13
  --------------
14
- The Python version for your virtualenv. If not specified, automatically parsed from ``.python-version`` file. This is only
15
- required if the installation mode is set to ``python-package``
14
+ The Python version for your virtualenv. If not specified, automatically parsed from **.python-version** file. This is only
15
+ required if the installation mode is set to **python-package**
16
16
 
17
17
  requirements
18
18
  ------------
19
- Optional path to your requirements file. This will only be used when the installation mode is set to ``python-package``
19
+ Optional path to your requirements file. This will only be used when the installation mode is set to *python-package*
20
20
 
21
21
  versions_to_keep
22
22
  ----------------
@@ -30,32 +30,28 @@ The command to use to build your project's distribution file.
30
30
  distfile
31
31
  --------
32
32
  Path to your project's distribution file. This should be the main artifact containing everything needed to run your project on the server.
33
- Supports version placeholder, e.g., ``dist/app_name-{version}-py3-none-any.whl``
33
+ Supports version placeholder, e.g., **dist/app_name-{version}-py3-none-any.whl**
34
34
 
35
35
  installation_mode
36
36
  -----------------
37
37
 
38
- Indicates whether the ``distfile`` is a Python package or a self-contained executable. The possible values are ``python-package`` and ``binary``.
39
- The ``binary`` option disables specific Python-related features, such as virtual environment creation and requirements installation. ``fujin`` will assume the provided
40
- ``distfile`` already contains all the necessary dependencies to run your program.
38
+ Indicates whether the *distfile* is a Python package or a self-contained executable. The possible values are *python-package* and *binary*.
39
+ The *binary* option disables specific Python-related features, such as virtual environment creation and requirements installation. ``fujin`` will assume the provided
40
+ *distfile* already contains all the necessary dependencies to run your program.
41
41
 
42
42
  release_command
43
43
  ---------------
44
- Optional command to run at the end of deployment (e.g., database migrations).
44
+ Optional command to run at the end of deployment (e.g., database migrations) before your application is started.
45
45
 
46
46
  secrets
47
47
  -------
48
48
 
49
- Optional secrets configuration. If set, Fujin will load secrets from the specified secret management service.
49
+ Optional secrets configuration. If set, ``fujin`` will load secrets from the specified secret management service.
50
50
  Check out the `secrets </secrets.html>`_ page for more information.
51
51
 
52
52
  adapter
53
53
  ~~~~~~~
54
- The secret management service to use. Available options:
55
-
56
- - ``bitwarden``
57
- - ``1password``
58
- - ``doppler``
54
+ The secret management service to use. The currently available options are *bitwarden*, *1password*, *doppler*
59
55
 
60
56
  password_env
61
57
  ~~~~~~~~~~~~
@@ -64,35 +60,39 @@ Environment variable containing the password for the service account. This is on
64
60
  Webserver
65
61
  ---------
66
62
 
63
+ Web server configurations.
64
+
67
65
  type
68
66
  ~~~~
69
67
  The reverse proxy implementation to use. Available options:
70
68
 
71
- - ``fujin.proxies.caddy`` (default)
72
- - ``fujin.proxies.nginx``
73
- - ``fujin.proxies.dummy`` (disables proxy functionality)
69
+ - *fujin.proxies.caddy* (default)
70
+ - *fujin.proxies.nginx*
71
+ - *fujin.proxies.dummy* (disables proxy)
74
72
 
75
73
  upstream
76
74
  ~~~~~~~~
77
75
  The address where your web application listens for requests. Supports any value compatible with your chosen web proxy:
78
76
 
79
- - HTTP address (e.g., ``localhost:8000``)
80
- - Unix socket (e.g., ``unix//run/project.sock``)
77
+ - HTTP address (e.g., *localhost:8000* )
78
+ - Unix socket caddy (e.g., *unix//run/project.sock* )
79
+ - Unix socket nginx (e.g., *http://unix:/run/project.sock* )
81
80
 
82
81
  certbot_email
83
82
  ~~~~~~~~~~~~~
84
- Required when Nginx is used as a proxy, to obtain SSL certificates.
83
+ Required when Nginx is used as a proxy to obtain SSL certificates.
85
84
 
86
85
  statics
87
86
  ~~~~~~~
88
87
 
89
88
  Defines the mapping of URL paths to local directories for serving static files. The syntax and support for static
90
89
  file serving depend on the selected reverse proxy. The directories you map should be accessible by the web server, meaning
91
- with read permissions for the ``www-data`` group; a reliable choice is ``/var/www``.
90
+ with read permissions for the *www-data* group; a reliable choice is **/var/www**.
92
91
 
93
92
  Example:
94
93
 
95
94
  .. code-block:: toml
95
+ :caption: fujin.toml
96
96
 
97
97
  [webserver]
98
98
  upstream = "unix//run/project.sock"
@@ -103,12 +103,12 @@ processes
103
103
  ---------
104
104
 
105
105
  A mapping of process names to commands that will be managed by the process manager. Define as many processes as needed, but
106
- when using any proxy other than ``fujin.proxies.dummy``, a ``web`` process must be declared. Refer to the ``apps_dir``
107
- setting on the host to understand how ``app_dir`` is determined.
106
+ when using any proxy other than *fujin.proxies.dummy*, a *web* process must be declared.
108
107
 
109
108
  Example:
110
109
 
111
110
  .. code-block:: toml
111
+ :caption: fujin.toml
112
112
 
113
113
  [processes]
114
114
  web = ".venv/bin/gunicorn myproject.wsgi:application"
@@ -116,7 +116,8 @@ Example:
116
116
 
117
117
  .. note::
118
118
 
119
- Commands are relative to your ``app_dir``. When generating systemd service files, the full path is automatically constructed.
119
+ Commands are relative to your *app_dir*. When generating systemd service files, the full path is automatically constructed.
120
+ Refer to the *apps_dir* setting on the host to understand how *app_dir* is determined.
120
121
  Here are the templates for the service files:
121
122
 
122
123
  - `web.service <https://github.com/falcopackages/fujin/blob/main/src/fujin/templates/web.service>`_
@@ -128,7 +129,7 @@ Host Configuration
128
129
 
129
130
  ip
130
131
  ~~
131
- The IP address or anything that resolves to the remote host IP's. This is use to communicate via ssh with the server, if omitted it's value will default to the one of the ``domain_name``.
132
+ The IP address or anything that resolves to the remote host IP's. This is use to communicate via ssh with the server, if omitted it's value will default to the one of the *domain_name*.
132
133
 
133
134
  domain_name
134
135
  ~~~~~~~~~~~
@@ -142,23 +143,23 @@ The login user for running remote tasks. Should have passwordless sudo access fo
142
143
 
143
144
  You can create a user with these requirements using the ``fujin server create-user`` command.
144
145
 
145
- env_file
146
- ~~~~~~~~
146
+ envfile
147
+ ~~~~~~~
147
148
  Path to the production environment file that will be copied to the host.
148
149
 
149
- env_content
150
- ~~~~~~~~~~~
150
+ env
151
+ ~~~
151
152
  A string containing the production environment variables, ideal for scenarios where most variables are retrieved from secrets and you prefer not to use a separate file.
152
153
 
153
154
  .. important::
154
155
 
155
- ``env_file`` and ``env_content`` are mutually exclusive—you can define only one.
156
+ *envfile* and *env* are mutually exclusive—you can define only one.
156
157
 
157
158
  apps_dir
158
159
  ~~~~~~~~
159
160
 
160
161
  Base directory for project storage on the host. Path is relative to user's home directory.
161
- Default: ``.local/share/fujin``. This value determines your project's ``app_dir``, which is ``{apps_dir}/{app}``.
162
+ Default: **.local/share/fujin**. This value determines your project's **app_dir**, which is **{apps_dir}/{app}**.
162
163
 
163
164
  password_env
164
165
  ~~~~~~~~~~~~
@@ -168,8 +169,7 @@ Environment variable containing the user's password. Only needed if the user can
168
169
  ssh_port
169
170
  ~~~~~~~~
170
171
 
171
- SSH port for connecting to the host.
172
- Default: ``22``
172
+ SSH port for connecting to the host. Default to **22**.
173
173
 
174
174
  key_filename
175
175
  ~~~~~~~~~~~~
@@ -184,6 +184,7 @@ A mapping of shortcut names to Fujin commands. Allows you to create convenient s
184
184
  Example:
185
185
 
186
186
  .. code-block:: toml
187
+ :caption: fujin.toml
187
188
 
188
189
  [aliases]
189
190
  console = "app exec -i shell_plus" # open an interactive django shell
@@ -223,6 +224,7 @@ class SecretAdapter(StrEnum):
223
224
  BITWARDEN = "bitwarden"
224
225
  ONE_PASSWORD = "1password"
225
226
  DOPPLER = "doppler"
227
+ SYSTEM = "system"
226
228
 
227
229
 
228
230
  class SecretConfig(msgspec.Struct):
@@ -246,7 +248,10 @@ class Config(msgspec.Struct, kw_only=True):
246
248
  requirements: str | None = None
247
249
  hooks: HooksDict = msgspec.field(default_factory=dict)
248
250
  local_config_dir: Path = Path(".fujin")
249
- secret_config: SecretConfig | None = msgspec.field(name="secrets", default=None)
251
+ secret_config: SecretConfig | None = msgspec.field(
252
+ name="secrets",
253
+ default_factory=lambda: SecretConfig(adapter=SecretAdapter.SYSTEM),
254
+ )
250
255
 
251
256
  def __post_init__(self):
252
257
  if self.installation_mode == InstallationMode.PY_PACKAGE:
@@ -285,8 +290,8 @@ class HostConfig(msgspec.Struct, kw_only=True):
285
290
  ip: str | None = None
286
291
  domain_name: str
287
292
  user: str
288
- _env_file: str = msgspec.field(name="envfile", default="")
289
- env_content: str = ""
293
+ _env_file: str | None = msgspec.field(name="envfile", default=None)
294
+ env_content: str | None = msgspec.field(name="env", default=None)
290
295
  apps_dir: str = ".local/share/fujin"
291
296
  password_env: str | None = None
292
297
  ssh_port: int = 22
@@ -295,14 +300,14 @@ class HostConfig(msgspec.Struct, kw_only=True):
295
300
  def __post_init__(self):
296
301
  if self._env_file and self.env_content:
297
302
  raise ImproperlyConfiguredError(
298
- "Cannot set both 'env_content' and 'env_file' properties."
303
+ "Cannot set both 'env' and 'envfile' properties."
299
304
  )
300
- if not self.env_content:
305
+ if self._env_file:
301
306
  envfile = Path(self._env_file)
302
307
  if not envfile.exists():
303
308
  raise ImproperlyConfiguredError(f"{self._env_file} not found")
304
309
  self.env_content = envfile.read_text()
305
- self.env_content = self.env_content.strip()
310
+ self.env_content = self.env_content.strip() if self.env_content else ""
306
311
  self.apps_dir = f"/home/{self.user}/{self.apps_dir}"
307
312
  self.ip = self.ip or self.domain_name
308
313
 
@@ -12,11 +12,13 @@ from fujin.config import SecretConfig
12
12
  from .bitwarden import bitwarden
13
13
  from .dopppler import doppler
14
14
  from .onepassword import one_password
15
+ from .system import system
15
16
 
16
17
  secret_reader = Callable[[str], str]
17
18
  secret_adapter_context = Callable[[SecretConfig], ContextManager[secret_reader]]
18
19
 
19
20
  adapter_to_context: dict[SecretAdapter, secret_adapter_context] = {
21
+ SecretAdapter.SYSTEM: system,
20
22
  SecretAdapter.BITWARDEN: bitwarden,
21
23
  SecretAdapter.ONE_PASSWORD: one_password,
22
24
  SecretAdapter.DOPPLER: doppler,
@@ -24,6 +26,8 @@ adapter_to_context: dict[SecretAdapter, secret_adapter_context] = {
24
26
 
25
27
 
26
28
  def resolve_secrets(env_content: str, secret_config: SecretConfig) -> str:
29
+ if not env_content: # this is really for empty string
30
+ return ""
27
31
  with closing(StringIO(env_content)) as buffer:
28
32
  env_dict = dotenv_values(stream=buffer)
29
33
  secrets = {key: value for key, value in env_dict.items() if value.startswith("$")}
@@ -33,7 +37,9 @@ def resolve_secrets(env_content: str, secret_config: SecretConfig) -> str:
33
37
  parsed_secrets = {}
34
38
  with adapter_context(secret_config) as reader:
35
39
  for key, secret in secrets.items():
36
- parsed_secrets[key] = gevent.spawn(reader, secret[1:])
40
+ parsed_secrets[key] = gevent.spawn(
41
+ reader, secret[1:]
42
+ ) # remove the leading $
37
43
  gevent.joinall(parsed_secrets.values())
38
44
  env_dict.update({key: thread.value for key, thread in parsed_secrets.items()})
39
45
  return "\n".join(f'{key}="{value}"' for key, value in env_dict.items())
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from contextlib import contextmanager
5
+ from typing import Generator
6
+ from typing import TYPE_CHECKING
7
+
8
+ from fujin.config import SecretConfig
9
+
10
+ if TYPE_CHECKING:
11
+ from . import secret_reader
12
+
13
+
14
+ @contextmanager
15
+ def system(_: SecretConfig) -> Generator[secret_reader, None, None]:
16
+ try:
17
+ yield os.getenv
18
+ finally:
19
+ pass
@@ -0,0 +1,22 @@
1
+ # All options are documented here https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html
2
+ # Inspiration was taken from here https://docs.gunicorn.org/en/stable/deploy.html#systemd
3
+ [Unit]
4
+ Description={app_name} daemon
5
+ After=network.target
6
+
7
+ [Service]
8
+ User={user}
9
+ Group={user}
10
+ RuntimeDirectory={app_name}
11
+ WorkingDirectory={app_dir}
12
+ ExecStart={app_dir}/{command}
13
+ EnvironmentFile={app_dir}/.env
14
+ ExecReload=/bin/kill -s HUP $MAINPID
15
+ KillMode=mixed
16
+ TimeoutStopSec=5
17
+ PrivateTmp=true
18
+ # if your app does not need administrative capabilities, let systemd know
19
+ ProtectSystem=strict
20
+
21
+ [Install]
22
+ WantedBy=multi-user.target
@@ -6,8 +6,8 @@ Requires={app_name}.socket
6
6
  After=network.target
7
7
 
8
8
  [Service]
9
- #Type=notify
10
- #NotifyAccess=main
9
+ Type=notify
10
+ NotifyAccess=main
11
11
  User={user}
12
12
  Group={user}
13
13
  RuntimeDirectory={app_name}
@@ -1,6 +1,7 @@
1
1
  # All options are documented here https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html
2
2
  [Unit]
3
- Description={app_name} Worker
3
+ Description={app_name} {process_name}
4
+ After=network.target
4
5
 
5
6
  [Service]
6
7
  User={user}
@@ -1,4 +1,5 @@
1
1
  version = 1
2
+ revision = 1
2
3
  requires-python = ">=3.10"
3
4
 
4
5
  [manifest]
@@ -304,7 +305,7 @@ name = "click"
304
305
  version = "8.1.7"
305
306
  source = { registry = "https://pypi.org/simple" }
306
307
  dependencies = [
307
- { name = "colorama", marker = "platform_system == 'Windows'" },
308
+ { name = "colorama", marker = "sys_platform == 'win32'" },
308
309
  ]
309
310
  sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
310
311
  wheels = [
@@ -477,7 +478,7 @@ wheels = [
477
478
 
478
479
  [[package]]
479
480
  name = "fujin-cli"
480
- version = "0.10.0"
481
+ version = "0.11.4"
481
482
  source = { editable = "." }
482
483
  dependencies = [
483
484
  { name = "cappa" },
@@ -586,7 +587,7 @@ wheels = [
586
587
  [package.optional-dependencies]
587
588
  recommended = [
588
589
  { name = "cffi", marker = "platform_python_implementation == 'CPython'" },
589
- { name = "psutil", marker = "sys_platform != 'win32' or platform_python_implementation == 'CPython'" },
590
+ { name = "psutil", marker = "platform_python_implementation == 'CPython' or sys_platform != 'win32'" },
590
591
  ]
591
592
 
592
593
  [[package]]
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