fujin-cli 0.7.1__py3-none-any.whl → 0.8.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/commands/_base.py CHANGED
@@ -33,6 +33,14 @@ class BaseCommand:
33
33
  def app_dir(self) -> str:
34
34
  return self.config.host.get_app_dir(app_name=self.config.app_name)
35
35
 
36
+ @cached_property
37
+ def hook_manager(self) -> HookManager:
38
+ return HookManager(
39
+ hooks=self.config.hooks,
40
+ app_name=self.config.app_name,
41
+ local_config_dir=self.config.local_config_dir,
42
+ )
43
+
36
44
  @contextmanager
37
45
  def connection(self):
38
46
  with host_connection(host=self.config.host) as conn:
@@ -70,8 +78,3 @@ class BaseCommand:
70
78
 
71
79
  def create_process_manager(self, conn: Connection) -> ProcessManager:
72
80
  return self.process_manager_class.create(conn=conn, config=self.config)
73
-
74
- def create_hook_manager(self, conn: Connection) -> HookManager:
75
- return HookManager(
76
- conn=conn, hooks=self.config.hooks, app_name=self.config.app_name
77
- )
fujin/commands/deploy.py CHANGED
@@ -16,15 +16,15 @@ from fujin.secrets import resolve_secrets
16
16
  )
17
17
  class Deploy(BaseCommand):
18
18
  def __call__(self):
19
+ self.hook_manager.pre_build()
19
20
  parsed_env = self.parse_envfile()
20
21
  self.build_app()
21
-
22
+ self.hook_manager.pre_deploy()
22
23
  with self.connection() as conn:
23
24
  process_manager = self.create_process_manager(conn)
24
25
  conn.run(f"mkdir -p {self.app_dir}")
25
26
  conn.run(f"mkdir -p {self.versioned_assets_dir}")
26
27
  with conn.cd(self.app_dir):
27
- self.create_hook_manager(conn).pre_deploy()
28
28
  self.transfer_files(conn, env=parsed_env)
29
29
  self.install_project(conn)
30
30
  with self.app_environment() as app_conn:
@@ -35,7 +35,7 @@ class Deploy(BaseCommand):
35
35
  self.create_web_proxy(app_conn).setup()
36
36
  self.update_version_history(app_conn)
37
37
  self.prune_assets(app_conn)
38
- self.create_hook_manager(conn).post_deploy()
38
+ self.hook_manager.post_deploy()
39
39
  self.stdout.output("[green]Project deployment completed successfully![/green]")
40
40
  self.stdout.output(
41
41
  f"[blue]Access the deployed project at: https://{self.config.host.domain_name}[/blue]"
@@ -60,7 +60,7 @@ class Deploy(BaseCommand):
60
60
  return self.config.host.envfile.read_text()
61
61
 
62
62
  def transfer_files(
63
- self, conn: Connection, env: str, skip_requirements: bool = False
63
+ self, conn: Connection, env: str, skip_requirements: bool = False
64
64
  ):
65
65
  conn.run(f"echo '{env}' > {self.app_dir}/.env")
66
66
  distfile_path = self.config.get_distfile_path()
@@ -78,7 +78,7 @@ class Deploy(BaseCommand):
78
78
  )
79
79
 
80
80
  def install_project(
81
- self, conn: Connection, version: str | None = None, *, skip_setup: bool = False
81
+ self, conn: Connection, version: str | None = None, *, skip_setup: bool = False
82
82
  ):
83
83
  version = version or self.config.version
84
84
  if self.config.installation_mode == InstallationMode.PY_PACKAGE:
@@ -87,7 +87,7 @@ class Deploy(BaseCommand):
87
87
  self._install_binary(conn, version)
88
88
 
89
89
  def _install_python_package(
90
- self, conn: Connection, version: str, skip_setup: bool = False
90
+ self, conn: Connection, version: str, skip_setup: bool = False
91
91
  ):
92
92
  appenv = f"""
93
93
  set -a # Automatically export all variables
fujin/commands/down.py CHANGED
@@ -33,8 +33,6 @@ class Down(BaseCommand):
33
33
  if not confirm:
34
34
  return
35
35
  with self.connection() as conn:
36
- hook_manager = self.create_hook_manager(conn)
37
- hook_manager.pre_teardown()
38
36
  process_manager = self.create_process_manager(conn)
39
37
  conn.run(f"rm -rf {self.app_dir}")
40
38
  self.create_web_proxy(conn).teardown()
@@ -42,7 +40,6 @@ class Down(BaseCommand):
42
40
  process_manager.reload_configuration()
43
41
  if self.full:
44
42
  self.create_web_proxy(conn).uninstall()
45
- hook_manager.post_teardown()
46
43
  self.stdout.output(
47
44
  "[green]Project teardown completed successfully![/green]"
48
45
  )
@@ -5,22 +5,21 @@ from pathlib import Path
5
5
 
6
6
  import cappa
7
7
 
8
- from .deploy import Deploy
9
8
  from fujin.commands import BaseCommand
10
9
  from fujin.config import InstallationMode
11
10
  from fujin.connection import Connection
11
+ from .deploy import Deploy
12
12
 
13
13
 
14
14
  @cappa.command(help="Redeploy the application to apply code and environment changes")
15
15
  class Redeploy(BaseCommand):
16
16
  def __call__(self):
17
17
  deploy = Deploy()
18
+ self.hook_manager.pre_build()
18
19
  parsed_env = deploy.parse_envfile()
19
20
  deploy.build_app()
20
-
21
+ self.hook_manager.pre_deploy()
21
22
  with self.app_environment() as conn:
22
- hook_manager = self.create_hook_manager(conn)
23
- hook_manager.pre_deploy()
24
23
  conn.run(f"mkdir -p {deploy.versioned_assets_dir}")
25
24
  requirements_copied = self._copy_requirements_if_needed(conn)
26
25
  deploy.transfer_files(
@@ -30,13 +29,13 @@ class Redeploy(BaseCommand):
30
29
  deploy.release(conn)
31
30
  self.create_process_manager(conn).restart_services()
32
31
  deploy.update_version_history(conn)
33
- hook_manager.post_deploy()
34
- self.stdout.output("[green]Redeployment completed successfully![/green]")
32
+ self.hook_manager.post_deploy()
33
+ self.stdout.output("[green]Redeployment completed successfully![/green]")
35
34
 
36
35
  def _copy_requirements_if_needed(self, conn: Connection) -> bool:
37
36
  if (
38
- not self.config.requirements
39
- or self.config.installation_mode == InstallationMode.BINARY
37
+ not self.config.requirements
38
+ or self.config.installation_mode == InstallationMode.BINARY
40
39
  ):
41
40
  return False
42
41
  local_requirements = hashlib.md5(
fujin/commands/server.py CHANGED
@@ -23,8 +23,6 @@ class Server(BaseCommand):
23
23
  @cappa.command(help="Setup uv, web proxy, and install necessary dependencies")
24
24
  def bootstrap(self):
25
25
  with self.connection() as conn:
26
- hook_manager = self.create_hook_manager(conn)
27
- hook_manager.pre_bootstrap()
28
26
  conn.run("sudo apt update && sudo apt upgrade -y", pty=True)
29
27
  conn.run("sudo apt install -y sqlite3 curl rsync", pty=True)
30
28
  result = conn.run("command -v uv", warn=True)
@@ -33,7 +31,6 @@ class Server(BaseCommand):
33
31
  conn.run("uv tool update-shell")
34
32
  conn.run("uv tool install fastfetch-bin-edge")
35
33
  self.create_web_proxy(conn).install()
36
- hook_manager.post_bootstrap()
37
34
  self.stdout.output(
38
35
  "[green]Server bootstrap completed successfully![/green]"
39
36
  )
fujin/config.py CHANGED
@@ -176,6 +176,9 @@ Example:
176
176
  dbconsole = "app exec -i dbshell" # open an interactive django database shell
177
177
  shell = "server exec --appenv -i bash" # SSH into the project directory with environment variables loaded
178
178
 
179
+ hooks
180
+ -----
181
+ Run custom scripts at specific points with hooks. Check out the `secrets </hooks.html>`_ page for more information.
179
182
 
180
183
  """
181
184
 
fujin/hooks.py CHANGED
@@ -1,9 +1,10 @@
1
+ import subprocess
1
2
  from dataclasses import dataclass
3
+ from pathlib import Path
2
4
 
5
+ import cappa
3
6
  from rich import print as rich_print
4
7
 
5
- from fujin.connection import Connection
6
-
7
8
  try:
8
9
  from enum import StrEnum
9
10
  except ImportError:
@@ -14,43 +15,45 @@ except ImportError:
14
15
 
15
16
 
16
17
  class Hook(StrEnum):
18
+ PRE_BUILD = "pre_build"
17
19
  PRE_DEPLOY = "pre_deploy"
18
20
  POST_DEPLOY = "post_deploy"
19
- PRE_BOOTSTRAP = "pre_bootstrap"
20
- POST_BOOTSTRAP = "post_bootstrap"
21
- PRE_TEARDOWN = "pre_teardown"
22
- POST_TEARDOWN = "post_teardown"
23
21
 
24
22
 
25
- HooksDict = dict[Hook, dict]
23
+ HooksDict = dict[Hook, str]
26
24
 
27
25
 
28
- @dataclass(frozen=True, slots=True)
26
+ @dataclass(slots=True)
29
27
  class HookManager:
30
28
  app_name: str
31
29
  hooks: HooksDict
32
- conn: Connection
30
+ local_config_dir: Path
31
+
32
+ def __post_init__(self):
33
+ if self.hooks:
34
+ return
35
+ hooks_folder = self.local_config_dir / "hooks"
36
+ if not hooks_folder.exists():
37
+ return
38
+ self.hooks = {
39
+ h.value: f"./{hooks_folder / h.value}" # noqa
40
+ for h in Hook
41
+ if (hooks_folder / h.value).exists() # noqa
42
+ }
33
43
 
34
44
  def _run_hook(self, type_: Hook) -> None:
35
- if hooks := self.hooks.get(type_):
36
- for name, command in hooks.items():
37
- rich_print(f"[blue]Running {type_} hook {name} [/blue]")
38
- self.conn.run(command, pty=True)
45
+ if cmd := self.hooks.get(type_):
46
+ rich_print(f"[blue]Running {type_} hook[/blue]")
47
+ result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
48
+ if result.returncode != 0:
49
+ raise cappa.Exit(result.stderr)
50
+ rich_print(result.stdout)
51
+
52
+ def pre_build(self) -> None:
53
+ self._run_hook(Hook.PRE_BUILD)
39
54
 
40
55
  def pre_deploy(self) -> None:
41
56
  self._run_hook(Hook.PRE_DEPLOY)
42
57
 
43
58
  def post_deploy(self) -> None:
44
59
  self._run_hook(Hook.POST_DEPLOY)
45
-
46
- def pre_bootstrap(self) -> None:
47
- self._run_hook(Hook.PRE_BOOTSTRAP)
48
-
49
- def post_bootstrap(self) -> None:
50
- self._run_hook(Hook.POST_BOOTSTRAP)
51
-
52
- def pre_teardown(self) -> None:
53
- self._run_hook(Hook.PRE_TEARDOWN)
54
-
55
- def post_teardown(self) -> None:
56
- self._run_hook(Hook.POST_TEARDOWN)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fujin-cli
3
- Version: 0.7.1
3
+ Version: 0.8.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
@@ -1,23 +1,23 @@
1
1
  fujin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  fujin/__main__.py,sha256=VJMBzuQuxkQaAKNEySktGnms944bkRsrIAjj-XeaWR8,1813
3
- fujin/config.py,sha256=pdWK4fM3BITpz0MAv_ydCXRcQbgC3eLz--APdOpehiQ,10412
3
+ fujin/config.py,sha256=FDHCkfAEt6sEjdK1szSCRqCvBfH7LMs_DQeAFHuR6wg,10540
4
4
  fujin/connection.py,sha256=LL7LhX9p0X9FmiGdlSroD3Ht216QY0Kd51xkSrXmM3s,2479
5
5
  fujin/errors.py,sha256=74Rh-Sgql1YspPdR_akQ2G3xZ48zecyafYCptpaFo1A,73
6
- fujin/hooks.py,sha256=nTn2PHpFuKbl_iAXUxagkrGcuwT2Exx8zIq4YfleX8A,1351
6
+ fujin/hooks.py,sha256=EDVYNozlDJ5kc1xHtZrXgtuKplUMEMPTp65TLMP02Ek,1449
7
7
  fujin/commands/__init__.py,sha256=g0b13vzidPUbxne_Zo_Wv5jpEmwCiSK4AokCinH9uo4,39
8
- fujin/commands/_base.py,sha256=NfBdRBx_l7tYnVg0L8A3tPFC_XmtuHq-toU66KT5CiE,2553
8
+ fujin/commands/_base.py,sha256=Rtf7kcO_Pg2JxPNKsXEx0UdhqfzYU6g7IiezC8cqEPQ,2610
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=PfEdLS-rkdF12BfjtLEyqbnn1K4dEYRh7h6CFtWb2zs,5934
11
+ fujin/commands/deploy.py,sha256=UFWraUDx2EmfD_riGq79fCWNGfytCJ9UR_eYKOfDYXY,5945
12
12
  fujin/commands/docs.py,sha256=b5FZ8AgoAfn4q4BueEQvM2w5HCuh8-rwBqv_CRFVU8E,349
13
- fujin/commands/down.py,sha256=v1lAq70ApktjeHRB_1sCzjmKH8t6EXqyL4RTt7OE-f0,1716
13
+ fujin/commands/down.py,sha256=aw_mxl_TMC66plKTXwlYP1W2XQBmHeROltQqOpQssyE,1577
14
14
  fujin/commands/init.py,sha256=zREfkIQyC6etqqQ6hgvDqpNNWQT4bk_8IOPBBT5-YUE,3983
15
15
  fujin/commands/printenv.py,sha256=bpGmOfc1t_dKWb8gy7EILYtwEyI9pIwhKg2XPKyJ9cQ,527
16
16
  fujin/commands/proxy.py,sha256=ajXwboS0gDDiMWW7b9rtWU6WPF1h7JYYeycDyU-hQfg,3053
17
17
  fujin/commands/prune.py,sha256=C2aAN6AUS84jgRg1eiCroyiuZyaZDmf5yvGAQY9xkcg,1517
18
- fujin/commands/redeploy.py,sha256=z1giY9SpINdJt8UagPlvUkJu30c8fgqapNqOxG4Jfuo,2378
18
+ fujin/commands/redeploy.py,sha256=PuXgrEc4fj_88WQXXBBvEtD46IeqzbPeMo-5pDlAuJQ,2363
19
19
  fujin/commands/rollback.py,sha256=JsocJzQcdQelSnYD94klhjBh8UKkkdiRD9shfUfo4FI,2032
20
- fujin/commands/server.py,sha256=0N_P_Luj31t56riZ8GfgRqW3vRHiw0cDrlp3PFoyWn8,3453
20
+ fujin/commands/server.py,sha256=-3-PyBNR0fGm-RYE3fz50kP-LSDYGU9BzUxrbGZEghc,3312
21
21
  fujin/commands/up.py,sha256=OEK_n-6-mnnIUffFpR7QtVunr1V1F04pxlAAS1U62BY,419
22
22
  fujin/process_managers/__init__.py,sha256=MhhfTBhm64zWRAKgjvsZRIToOUJus60vGScbAjqpQ6Y,994
23
23
  fujin/process_managers/systemd.py,sha256=qG_4Ew8SEWtaTFOAW_XZXsMO2WjFWZ4dp5nBwAPBObk,5603
@@ -31,8 +31,8 @@ fujin/secrets/onepassword.py,sha256=6Xj3XWttKfcjMbcoMZvXVpJW1KHxlD785DysmX_mqvk,
31
31
  fujin/templates/simple.service,sha256=-lyKjmSyfHGucP4O_vRQE1NNaHq0Qjsc0twdwoRLgI0,321
32
32
  fujin/templates/web.service,sha256=NZ7ZeaFvV_MZTBn8QqRQeu8PIrWHf3aWYWNzjOQeqCw,685
33
33
  fujin/templates/web.socket,sha256=2lJsiOHlMJL0YlN7YBLLnr5zqsytPEt81yP34nk0dmc,173
34
- fujin_cli-0.7.1.dist-info/METADATA,sha256=kqTIyfQ_EtbpdoaSkBmrF5Wy6y838HLaqLJHbLko3L4,4576
35
- fujin_cli-0.7.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
36
- fujin_cli-0.7.1.dist-info/entry_points.txt,sha256=Y_TBtKt3j11qhwquMexZR5yqnDEqOBDACtresqQFE-s,46
37
- fujin_cli-0.7.1.dist-info/licenses/LICENSE.txt,sha256=0QF8XfuH0zkIHhSet6teXfiCze6JSdr8inRkmLLTDyo,1099
38
- fujin_cli-0.7.1.dist-info/RECORD,,
34
+ fujin_cli-0.8.0.dist-info/METADATA,sha256=S-SNZujrKy0Omj5KlcHlWSCPynXWrrm-HtLc9fo17BA,4576
35
+ fujin_cli-0.8.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
36
+ fujin_cli-0.8.0.dist-info/entry_points.txt,sha256=Y_TBtKt3j11qhwquMexZR5yqnDEqOBDACtresqQFE-s,46
37
+ fujin_cli-0.8.0.dist-info/licenses/LICENSE.txt,sha256=0QF8XfuH0zkIHhSet6teXfiCze6JSdr8inRkmLLTDyo,1099
38
+ fujin_cli-0.8.0.dist-info/RECORD,,