fujin-cli 0.6.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

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.

fujin/__main__.py CHANGED
@@ -1,3 +1,7 @@
1
+ from gevent import monkey
2
+
3
+ monkey.patch_all()
4
+
1
5
  import shlex
2
6
  import sys
3
7
  from pathlib import Path
@@ -16,6 +20,7 @@ from fujin.commands.redeploy import Redeploy
16
20
  from fujin.commands.rollback import Rollback
17
21
  from fujin.commands.server import Server
18
22
  from fujin.commands.up import Up
23
+ from fujin.commands.printenv import Printenv
19
24
 
20
25
  if sys.version_info >= (3, 11):
21
26
  import tomllib
@@ -38,6 +43,7 @@ class Fujin:
38
43
  | Down
39
44
  | Rollback
40
45
  | Prune
46
+ | Printenv
41
47
  ]
42
48
 
43
49
 
fujin/commands/deploy.py CHANGED
@@ -7,6 +7,7 @@ import cappa
7
7
 
8
8
  from fujin.commands import BaseCommand
9
9
  from fujin.config import InstallationMode
10
+ from fujin.secrets import resolve_secrets
10
11
  from fujin.connection import Connection
11
12
 
12
13
 
@@ -15,14 +16,16 @@ from fujin.connection import Connection
15
16
  )
16
17
  class Deploy(BaseCommand):
17
18
  def __call__(self):
19
+ parsed_env = self.parse_envfile()
18
20
  self.build_app()
19
21
 
20
22
  with self.connection() as conn:
21
23
  process_manager = self.create_process_manager(conn)
22
24
  conn.run(f"mkdir -p {self.app_dir}")
25
+ conn.run(f"mkdir -p {self.versioned_assets_dir}")
23
26
  with conn.cd(self.app_dir):
24
27
  self.create_hook_manager(conn).pre_deploy()
25
- self.transfer_files(conn)
28
+ self.transfer_files(conn, env=parsed_env)
26
29
  self.install_project(conn)
27
30
  with self.app_environment() as app_conn:
28
31
  self.release(app_conn)
@@ -48,16 +51,31 @@ class Deploy(BaseCommand):
48
51
  def versioned_assets_dir(self) -> str:
49
52
  return f"{self.app_dir}/v{self.config.version}"
50
53
 
51
- def transfer_files(self, conn: Connection):
54
+ def parse_envfile(self) -> str:
52
55
  if not self.config.host.envfile.exists():
53
56
  raise cappa.Exit(f"{self.config.host.envfile} not found", code=1)
54
- conn.put(str(self.config.host.envfile), f"{self.app_dir}/.env")
55
- conn.run(f"mkdir -p {self.versioned_assets_dir}")
57
+ if self.config.secret_config:
58
+ self.stdout.output("[blue]Reading secrets....[/blue]")
59
+ return resolve_secrets(self.config.host.envfile, self.config.secret_config)
60
+ return self.config.host.envfile.read_text()
61
+
62
+ def transfer_files(
63
+ self, conn: Connection, env: str, skip_requirements: bool = False
64
+ ):
65
+ conn.run(f"echo '{env}' > {self.app_dir}/.env")
56
66
  distfile_path = self.config.get_distfile_path()
57
67
  conn.put(
58
68
  str(distfile_path),
59
69
  f"{self.versioned_assets_dir}/{distfile_path.name}",
60
70
  )
71
+ if not skip_requirements and self.config.requirements:
72
+ requirements = Path(self.config.requirements)
73
+ if not requirements.exists():
74
+ raise cappa.Exit(f"{self.config.requirements} not found", code=1)
75
+ conn.put(
76
+ Path(self.config.requirements).resolve(),
77
+ f"{self.versioned_assets_dir}/requirements.txt",
78
+ )
61
79
 
62
80
  def install_project(
63
81
  self, conn: Connection, version: str | None = None, *, skip_setup: bool = False
@@ -71,14 +89,6 @@ class Deploy(BaseCommand):
71
89
  def _install_python_package(
72
90
  self, conn: Connection, version: str, skip_setup: bool = False
73
91
  ):
74
- if not skip_setup and self.config.requirements:
75
- requirements = Path(self.config.requirements)
76
- if not requirements.exists():
77
- raise cappa.Exit(f"{self.config.requirements} not found", code=1)
78
- conn.put(
79
- Path(self.config.requirements).resolve(),
80
- f"{self.versioned_assets_dir}/requirements.txt",
81
- )
82
92
  appenv = f"""
83
93
  set -a # Automatically export all variables
84
94
  source .env
@@ -0,0 +1,16 @@
1
+ import cappa
2
+
3
+ from fujin.commands import BaseCommand
4
+ from fujin.secrets import resolve_secrets
5
+
6
+
7
+ @cappa.command(
8
+ help="Print the content of the envfile with extracted secrets (for debugging)"
9
+ )
10
+ class Printenv(BaseCommand):
11
+ def __call__(self):
12
+ if self.config.secret_config:
13
+ result = resolve_secrets(self.config.host.envfile, self.config.secret_config)
14
+ else:
15
+ result = self.config.host.envfile.read_text()
16
+ self.stdout.output(result)
@@ -15,13 +15,17 @@ from fujin.connection import Connection
15
15
  class Redeploy(BaseCommand):
16
16
  def __call__(self):
17
17
  deploy = Deploy()
18
+ parsed_env = deploy.parse_envfile()
18
19
  deploy.build_app()
19
20
 
20
21
  with self.app_environment() as conn:
21
22
  hook_manager = self.create_hook_manager(conn)
22
23
  hook_manager.pre_deploy()
23
- deploy.transfer_files(conn)
24
+ conn.run(f"mkdir -p {deploy.versioned_assets_dir}")
24
25
  requirements_copied = self._copy_requirements_if_needed(conn)
26
+ deploy.transfer_files(
27
+ conn, env=parsed_env, skip_requirements=requirements_copied
28
+ )
25
29
  deploy.install_project(conn, skip_setup=requirements_copied)
26
30
  deploy.release(conn)
27
31
  self.create_process_manager(conn).restart_services()
fujin/config.py CHANGED
@@ -43,6 +43,23 @@ release_command
43
43
  ---------------
44
44
  Optional command to run at the end of deployment (e.g., database migrations).
45
45
 
46
+ secrets
47
+ -------
48
+
49
+ Optional secrets configuration. If set, Fujin will load secrets from the specified secret management service.
50
+ Check out the `secrets </secrets.html>`_ page for more information.
51
+
52
+ adapter
53
+ ~~~~~~~
54
+ The secret management service to use. Available options:
55
+
56
+ - ``bitwarden``
57
+ - ``1password``
58
+
59
+ password_env
60
+ ~~~~~~~~~~~~
61
+ Environment variable containing the password for the service account. This is only required for certain adapters.
62
+
46
63
  Webserver
47
64
  ---------
48
65
 
@@ -61,6 +78,10 @@ The address where your web application listens for requests. Supports any value
61
78
  - HTTP address (e.g., ``localhost:8000``)
62
79
  - Unix socket (e.g., ``unix//run/project.sock``)
63
80
 
81
+ certbot_email
82
+ ~~~~~~~~~~~~~
83
+ Required when Nginx is used as a proxy, to obtain SSL certificates.
84
+
64
85
  statics
65
86
  ~~~~~~~
66
87
 
@@ -162,7 +183,6 @@ from __future__ import annotations
162
183
 
163
184
  import os
164
185
  import sys
165
- from functools import cached_property
166
186
  from pathlib import Path
167
187
 
168
188
  import msgspec
@@ -182,6 +202,16 @@ class InstallationMode(StrEnum):
182
202
  BINARY = "binary"
183
203
 
184
204
 
205
+ class SecretAdapter(StrEnum):
206
+ BITWARDEN = "bitwarden"
207
+ ONE_PASSWORD = "1password"
208
+
209
+
210
+ class SecretConfig(msgspec.Struct):
211
+ adapter: SecretAdapter
212
+ password_env: str | None = None
213
+
214
+
185
215
  class Config(msgspec.Struct, kw_only=True):
186
216
  app_name: str = msgspec.field(name="app")
187
217
  version: str = msgspec.field(default_factory=lambda: read_version_from_pyproject())
@@ -199,6 +229,7 @@ class Config(msgspec.Struct, kw_only=True):
199
229
  requirements: str | None = None
200
230
  hooks: HooksDict = msgspec.field(default_factory=dict)
201
231
  local_config_dir: Path = Path(".fujin")
232
+ secret_config: SecretConfig | None = msgspec.field(name="secrets", default=None)
202
233
 
203
234
  def __post_init__(self):
204
235
  if self.installation_mode == InstallationMode.PY_PACKAGE:
@@ -281,6 +312,7 @@ class HostConfig(msgspec.Struct, kw_only=True):
281
312
  class Webserver(msgspec.Struct):
282
313
  upstream: str
283
314
  type: str = "fujin.proxies.caddy"
315
+ certbot_email: str | None = None
284
316
  statics: dict[str, str] = msgspec.field(default_factory=dict)
285
317
 
286
318
 
fujin/proxies/nginx.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
4
3
  from pathlib import Path
5
4
 
6
5
  import msgspec
@@ -8,8 +7,6 @@ import msgspec
8
7
  from fujin.config import Config
9
8
  from fujin.connection import Connection
10
9
 
11
- CERTBOT_EMAIL = os.getenv("CERTBOT_EMAIL")
12
-
13
10
 
14
11
  class WebProxy(msgspec.Struct):
15
12
  conn: Connection
@@ -18,6 +15,7 @@ class WebProxy(msgspec.Struct):
18
15
  upstream: str
19
16
  statics: dict[str, str]
20
17
  local_config_dir: Path
18
+ certbot_email: str | None = None
21
19
 
22
20
  @property
23
21
  def config_file(self) -> Path:
@@ -32,6 +30,7 @@ class WebProxy(msgspec.Struct):
32
30
  app_name=config.app_name,
33
31
  local_config_dir=config.local_config_dir,
34
32
  statics=config.webserver.statics,
33
+ certbot_email=config.webserver.certbot_email,
35
34
  )
36
35
 
37
36
  def run_pty(self, *args, **kwargs):
@@ -63,13 +62,13 @@ class WebProxy(msgspec.Struct):
63
62
  self.run_pty(
64
63
  f"sudo ln -sf /etc/nginx/sites-available/{self.app_name}.conf /etc/nginx/sites-enabled/{self.app_name}.conf",
65
64
  )
66
- if CERTBOT_EMAIL:
65
+ if self.certbot_email:
67
66
  cert_path = f"/etc/letsencrypt/live/{self.domain_name}/fullchain.pem"
68
67
  cert_exists = self.run_pty(f"sudo test -f {cert_path}", warn=True).ok
69
68
 
70
69
  if not cert_exists:
71
70
  self.run_pty(
72
- f"sudo certbot --nginx -d {self.domain_name} --non-interactive --agree-tos --email {CERTBOT_EMAIL} --redirect"
71
+ f"sudo certbot --nginx -d {self.domain_name} --non-interactive --agree-tos --email {self.certbot_email} --redirect"
73
72
  )
74
73
  self.config_file.parent.mkdir(exist_ok=True)
75
74
  self.conn.get(
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import gevent
5
+ from dotenv import dotenv_values
6
+ from typing import Callable
7
+
8
+ from fujin.config import SecretConfig, SecretAdapter
9
+ from .bitwarden import bitwarden
10
+ from .onepassword import one_password
11
+
12
+
13
+ secret_reader = Callable[[str], str]
14
+ secret_adapter_context = Callable[[SecretConfig], secret_reader]
15
+
16
+ adapter_to_context: dict[SecretAdapter, secret_adapter_context] = {
17
+ SecretAdapter.BITWARDEN: bitwarden,
18
+ SecretAdapter.ONE_PASSWORD: one_password,
19
+ }
20
+
21
+
22
+ def resolve_secrets(envfile: Path, secret_config: SecretConfig) -> str:
23
+ env_dict = dotenv_values(envfile)
24
+ secrets = {key: value for key, value in env_dict.items() if value.startswith("$")}
25
+ adapter_context = adapter_to_context[secret_config.adapter]
26
+ parsed_secrets = {}
27
+ with adapter_context(secret_config) as secret_reader:
28
+ for key, secret in secrets.items():
29
+ parsed_secrets[key] = gevent.spawn(secret_reader, secret[1:])
30
+ gevent.joinall(parsed_secrets.values())
31
+ env_dict.update({key: thread.value for key, thread in parsed_secrets.items()})
32
+ return "\n".join(f'{key}="{value}"' for key, value in env_dict.items())
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+ import cappa
3
+ import os
4
+
5
+
6
+ from fujin.config import SecretConfig
7
+ import subprocess
8
+ from contextlib import contextmanager
9
+ from typing import Generator, TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from . import secret_reader
13
+
14
+
15
+ @contextmanager
16
+ def bitwarden(secret_config: SecretConfig) -> Generator[secret_reader, None, None]:
17
+ session = os.getenv("BW_SESSION")
18
+ if not session:
19
+ if not secret_config.password_env:
20
+ raise cappa.Exit(
21
+ "You need to set the password_env to use the bitwarden adapter or set the BW_SESSION environment variable",
22
+ code=1,
23
+ )
24
+ session = _signin(secret_config.password_env)
25
+
26
+ def read_secret(name: str) -> str:
27
+ result = subprocess.run(
28
+ ["bw", "get", "password", name, "--raw", "--session", session, "--nointeraction"],
29
+ capture_output=True,
30
+ text=True,
31
+ )
32
+ if result.returncode != 0:
33
+ raise cappa.Exit(f"Password not found for {name}")
34
+ return result.stdout.strip()
35
+
36
+ try:
37
+ yield read_secret
38
+ finally:
39
+ pass
40
+ # subprocess.run(["bw", "lock"], capture_output=True)
41
+
42
+
43
+ def _signin(password_env) -> str:
44
+ sync_result = subprocess.run(["bw", "sync"], capture_output=True, text=True)
45
+ if sync_result.returncode != 0:
46
+ raise cappa.Exit(f"Bitwarden sync failed: {sync_result.stdout}", code=1)
47
+ unlock_result = subprocess.run(
48
+ [
49
+ "bw",
50
+ "unlock",
51
+ "--nointeraction",
52
+ "--passwordenv",
53
+ password_env,
54
+ "--raw",
55
+ ],
56
+ capture_output=True,
57
+ text=True,
58
+ )
59
+ if unlock_result.returncode != 0:
60
+ raise cappa.Exit(f"Bitwarden unlock failed {unlock_result.stderr}", code=1)
61
+
62
+ return unlock_result.stdout.strip()
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+ import cappa
3
+
4
+
5
+ from fujin.config import SecretConfig
6
+ import subprocess
7
+
8
+ from contextlib import contextmanager
9
+ from typing import Generator, TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from . import secret_reader
13
+
14
+
15
+ @contextmanager
16
+ def one_password(_: SecretConfig) -> Generator[secret_reader, None, None]:
17
+ def read_secret(name: str) -> str:
18
+ result = subprocess.run(["op", "read", name], capture_output=True, text=True)
19
+ if result.returncode != 0:
20
+ raise cappa.Exit(result.stderr)
21
+ return result.stdout.strip()
22
+
23
+ try:
24
+ yield read_secret
25
+ finally:
26
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fujin-cli
3
- Version: 0.6.0
3
+ Version: 0.7.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/falcopackages/fujin#readme
6
6
  Project-URL: Issues, https://github.com/falcopackages/fujin/issues
@@ -21,36 +21,48 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
21
21
  Requires-Python: >=3.10
22
22
  Requires-Dist: cappa>=0.24
23
23
  Requires-Dist: fabric>=3.2.2
24
+ Requires-Dist: gevent[recommended]>=24.11.1
24
25
  Requires-Dist: msgspec[toml]>=0.18.6
26
+ Requires-Dist: python-dotenv>=1.0.1
25
27
  Requires-Dist: rich>=13.9.2
26
28
  Description-Content-Type: text/markdown
27
29
 
28
30
  # fujin
29
31
 
32
+ > [!IMPORTANT]
33
+ > This tool is currently contains minimal features and is a work-in-progress
34
+
35
+ <!-- content:start -->
36
+
37
+ `fujin` is a simple deployment tool that helps you get your project up and running on a VPS in minutes. It manages your app processes using [systemd](https://systemd.io) and runs your apps behind [caddy](https://caddyserver.com).
38
+
39
+ [![Publish Package](https://github.com/falcopackages/fujin/actions/workflows/publish.yml/badge.svg)](https://github.com/falcopackages/fujin/actions/workflows/publish.yml)
30
40
  [![PyPI - Version](https://img.shields.io/pypi/v/fujin-cli.svg)](https://pypi.org/project/fujin-cli)
31
41
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/fujin-cli.svg)](https://pypi.org/project/fujin-cli)
32
42
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/falcopackages/fujin/blob/main/LICENSE.txt)
33
43
  [![Status](https://img.shields.io/pypi/status/fujin-cli.svg)](https://pypi.org/project/fujin-cli)
34
- -----
35
44
 
36
- > [!IMPORTANT]
37
- > This package currently contains minimal features and is a work-in-progress
38
-
39
- <!-- content:start -->
45
+ ## Features
40
46
 
41
- `fujin` is a simple deployment tool that helps you get your project up and running on a VPS in a few minutes. It manages your app processes using `systemd` and runs your apps behind [caddy](https://caddyserver.com/). For Python projects,
42
- it expects your app to be a packaged Python application ideally with a CLI entry point defined. For other languages, you need to provide a self-contained single executable file with all necessary dependencies.
43
- The main job of `fujin` is to bootstrap your server (installing caddy, etc.), copy the files onto the server with a structure that supports rollback, and automatically generate configs for systemd and caddy that you can manually edit if needed.
47
+ - 🚀 One-command server bootstrap
48
+ - 🔄 Rollback broken deployments
49
+ - 🔐 Zero configuration SSL certificates
50
+ - 🔁 Swappable proxy ([caddy](https://caddyserver.com), [nginx](https://nginx.org/en/) and `dummy` to disable proxy)
51
+ - 🛠️ Secrets injection from password managers ([Bitwarden](https://bitwarden.com/), [1Password](https://1password.com), etc.)
52
+ - 📝 Easily customizable `systemd` and `proxy` configurations
53
+ - 👨‍💻 Remote application management and log streaming
54
+ - 🐍 Supports packaged python apps and self-contained binaries
44
55
 
45
- Check out the [documentation📚](https://fujin.oluwatobi.dev/en/latest/) for installation, features, and usage guides.
56
+ For more details, check out the [documentation📚](https://fujin.oluwatobi.dev/en/latest/).
46
57
 
47
58
  ## Why?
48
59
 
49
60
  I wanted [kamal](https://kamal-deploy.org/) but without Docker, and I thought the idea was fun. At its core, this project automates versions of this [tutorial](https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu). If you've been a Django beginner
50
- trying to get your app in production, you probably went through this. I'm using caddy instead of nginx because it's configurable via an API and it's is a no-brainer for SSL certificates. Systemd is the default on most Linux distributions and does a good enough job.
61
+ trying to get your app in production, you probably went through this.
62
+
63
+ I'm using `caddy` here by default instead of `nginx` because it's configurable via an API and it's is a no-brainer for SSL certificates. `Systemd` is the default on most Linux distributions and does a good enough job.
51
64
 
52
- Fujin was initially planned to be a Python-only project, but the core concepts can be applied to any language that can produce a single distributable file (e.g., Go, Rust). I wanted to recreate kamal's nice local-to-remote app management API, but I'm skipping Docker to keep things simple.
53
- I'm currently rocking SQLite in production for my side projects and ths setup is enough for my use case.
65
+ Fujin was initially planned to be a Python-only project, but the core concepts can be applied to any language that can produce a single distributable file (e.g., Go, Rust).
54
66
 
55
67
  The goal is to automate deployment while leaving you in full control of your Linux box. It's not a CLI PaaS - it's simple and expects you to be able to SSH into your server and troubleshoot if necessary. For beginners, it makes the initial deployment easier while you get your hands dirty with Linux.
56
68
  If you need a never-break, worry-free, set-it-and-forget-it setup that auto-scales and does all the magic, fujin probably isn't for you.
@@ -66,4 +78,4 @@ Fujin draws inspiration from the following tools for their developer experience.
66
78
 
67
79
  `fujin` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
68
80
 
69
- <!-- content:end -->
81
+ <!-- content:end -->
@@ -1,6 +1,6 @@
1
1
  fujin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- fujin/__main__.py,sha256=St0VnEWhRRw_ukAddAwDGFliLqQT3xlone-9JIONlDI,1702
3
- fujin/config.py,sha256=GecU6XklYNXjjR7bkRORBcX1-AYlIaTMw1mmYKSCSnA,9595
2
+ fujin/__main__.py,sha256=VJMBzuQuxkQaAKNEySktGnms944bkRsrIAjj-XeaWR8,1813
3
+ fujin/config.py,sha256=pdWK4fM3BITpz0MAv_ydCXRcQbgC3eLz--APdOpehiQ,10412
4
4
  fujin/connection.py,sha256=ZkYaNykRFj9Yr-K-vOrZtVVGUDurDm6W7OQrgct71CA,2428
5
5
  fujin/errors.py,sha256=74Rh-Sgql1YspPdR_akQ2G3xZ48zecyafYCptpaFo1A,73
6
6
  fujin/hooks.py,sha256=QHIqxLxujG2U70UkN1BpUplE6tTqn7pFJP5oHde1tUQ,1350
@@ -8,15 +8,15 @@ fujin/commands/__init__.py,sha256=uIGGXt8YofL5RZn8KIy153ioWGoCl32ffHtqOhB-6ZM,78
8
8
  fujin/commands/_base.py,sha256=o3R4-c3XeFWTIW3stiUdrcCPwdjzfjUVIpZy2L1-gZ4,2525
9
9
  fujin/commands/app.py,sha256=mazb4dCTdR5juh79bL3a9b68Nd6O8u_nR9IgYqQlqWE,5279
10
10
  fujin/commands/config.py,sha256=xdfd1OZLxw2YZldiAbW5rq5EBXEaXbUC-I7FKLRfzIQ,2387
11
- fujin/commands/deploy.py,sha256=oAOLUrtHjHkmIA3D0AiaJRWEsfVk51_I29kRC_FhjYo,5463
11
+ fujin/commands/deploy.py,sha256=SUI_aMqTmaWHVtyX03rjrbWMYESoysAykcvILsEtsWk,5897
12
12
  fujin/commands/docs.py,sha256=b5FZ8AgoAfn4q4BueEQvM2w5HCuh8-rwBqv_CRFVU8E,349
13
13
  fujin/commands/down.py,sha256=v1lAq70ApktjeHRB_1sCzjmKH8t6EXqyL4RTt7OE-f0,1716
14
14
  fujin/commands/init.py,sha256=t8uwwOi4SBqHjV8px_SkTHAeZIiIUJnFN-lf7DK6HhE,3959
15
+ fujin/commands/printenv.py,sha256=1xz80UCKpz64wsR9rYStro0K6pkKndNoNQvusEMYRuQ,486
15
16
  fujin/commands/proxy.py,sha256=ajXwboS0gDDiMWW7b9rtWU6WPF1h7JYYeycDyU-hQfg,3053
16
17
  fujin/commands/prune.py,sha256=C2aAN6AUS84jgRg1eiCroyiuZyaZDmf5yvGAQY9xkcg,1517
17
- fujin/commands/redeploy.py,sha256=JvCJBZBcCKkUw1efZwRPJMLUAV8oqBAZeSbUBLHyn3k,2185
18
+ fujin/commands/redeploy.py,sha256=qYPM_Klj_2G1Up4ICu-8rklcqIbU-uohddmAzAA5sy0,2378
18
19
  fujin/commands/rollback.py,sha256=BN9vOTEBcSSpFIfck9nzWvMVO7asVC20lQbcNrxRchg,2009
19
- fujin/commands/secrets.py,sha256=1xZQVkvbopsAcWUocLstxPKxsvmGoE2jWip5hdTrP50,162
20
20
  fujin/commands/server.py,sha256=0N_P_Luj31t56riZ8GfgRqW3vRHiw0cDrlp3PFoyWn8,3453
21
21
  fujin/commands/up.py,sha256=DgDN-1mc_mMHJRCIvcB947Cd5a7phunu9NpXloGK0UU,419
22
22
  fujin/process_managers/__init__.py,sha256=MhhfTBhm64zWRAKgjvsZRIToOUJus60vGScbAjqpQ6Y,994
@@ -24,12 +24,15 @@ fujin/process_managers/systemd.py,sha256=qG_4Ew8SEWtaTFOAW_XZXsMO2WjFWZ4dp5nBwAP
24
24
  fujin/proxies/__init__.py,sha256=UuWYU175tkdaz1WWRCDDpQgGfFVYYNR9PBxA3lTCNr0,695
25
25
  fujin/proxies/caddy.py,sha256=dzLD8s664_kIK-1hCE3y50JIwBd8kK9yS1LynUDRVSE,7908
26
26
  fujin/proxies/dummy.py,sha256=qBKSn8XNEA9SVwB7GzRNX2l9Iw6tUjo2CFqZjWi0FjY,465
27
- fujin/proxies/nginx.py,sha256=S2-tBaytGtehqMyeZZMPSPoXjV1GVv7S63eMtfhkGNM,4100
27
+ fujin/proxies/nginx.py,sha256=BNJNLxLLRVAmBIGVCk8pb16iiSJsOI9jXOZhdSQGtX8,4151
28
+ fujin/secrets/__init__.py,sha256=WFFe81fB6R3dzdvYsdPT9x-fE2LwpyrfF0nCuWrQNRw,1192
29
+ fujin/secrets/bitwarden.py,sha256=rJ_n7CQT02TwwqN9lHQkh56g5BETKhZBtjWi3bOA8rQ,1846
30
+ fujin/secrets/onepassword.py,sha256=2ySDY7fNW_sOqH-I89rfz3uMiBu0mJRHot4yKlTRbrE,636
28
31
  fujin/templates/simple.service,sha256=-lyKjmSyfHGucP4O_vRQE1NNaHq0Qjsc0twdwoRLgI0,321
29
32
  fujin/templates/web.service,sha256=NZ7ZeaFvV_MZTBn8QqRQeu8PIrWHf3aWYWNzjOQeqCw,685
30
33
  fujin/templates/web.socket,sha256=2lJsiOHlMJL0YlN7YBLLnr5zqsytPEt81yP34nk0dmc,173
31
- fujin_cli-0.6.0.dist-info/METADATA,sha256=_rYue3Q7z_StHy_Z9DVdaU0RWijde8eWFlm_gwxN4GY,4452
32
- fujin_cli-0.6.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
33
- fujin_cli-0.6.0.dist-info/entry_points.txt,sha256=Y_TBtKt3j11qhwquMexZR5yqnDEqOBDACtresqQFE-s,46
34
- fujin_cli-0.6.0.dist-info/licenses/LICENSE.txt,sha256=0QF8XfuH0zkIHhSet6teXfiCze6JSdr8inRkmLLTDyo,1099
35
- fujin_cli-0.6.0.dist-info/RECORD,,
34
+ fujin_cli-0.7.0.dist-info/METADATA,sha256=0H7E8Re_jONc6BsoAdATLKM3r8QIrA_lT1oRgG3gWIw,4576
35
+ fujin_cli-0.7.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
36
+ fujin_cli-0.7.0.dist-info/entry_points.txt,sha256=Y_TBtKt3j11qhwquMexZR5yqnDEqOBDACtresqQFE-s,46
37
+ fujin_cli-0.7.0.dist-info/licenses/LICENSE.txt,sha256=0QF8XfuH0zkIHhSet6teXfiCze6JSdr8inRkmLLTDyo,1099
38
+ fujin_cli-0.7.0.dist-info/RECORD,,
fujin/commands/secrets.py DELETED
@@ -1,11 +0,0 @@
1
- from dataclasses import dataclass
2
-
3
- import cappa
4
-
5
- from fujin.commands import BaseCommand
6
-
7
-
8
- @cappa.command(help="")
9
- @dataclass
10
- class Secrets(BaseCommand):
11
- pass