fujin-cli 0.7.1__py3-none-any.whl → 0.9.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 +8 -5
- fujin/commands/config.py +10 -2
- fujin/commands/deploy.py +7 -7
- fujin/commands/down.py +0 -3
- fujin/commands/init.py +2 -2
- fujin/commands/printenv.py +2 -2
- fujin/commands/redeploy.py +5 -6
- fujin/commands/server.py +0 -3
- fujin/config.py +25 -15
- fujin/hooks.py +28 -25
- fujin/secrets/__init__.py +13 -10
- {fujin_cli-0.7.1.dist-info → fujin_cli-0.9.0.dist-info}/METADATA +1 -1
- {fujin_cli-0.7.1.dist-info → fujin_cli-0.9.0.dist-info}/RECORD +16 -16
- {fujin_cli-0.7.1.dist-info → fujin_cli-0.9.0.dist-info}/WHEEL +0 -0
- {fujin_cli-0.7.1.dist-info → fujin_cli-0.9.0.dist-info}/entry_points.txt +0 -0
- {fujin_cli-0.7.1.dist-info → fujin_cli-0.9.0.dist-info}/licenses/LICENSE.txt +0 -0
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/config.py
CHANGED
|
@@ -37,9 +37,17 @@ class ConfigCMD(BaseCommand):
|
|
|
37
37
|
)
|
|
38
38
|
)
|
|
39
39
|
|
|
40
|
+
host_config = {
|
|
41
|
+
f: getattr(self.config.host, f) for f in self.config.host.__struct_fields__
|
|
42
|
+
}
|
|
43
|
+
host_config.pop("_key_filename")
|
|
44
|
+
host_config.pop("_env_file")
|
|
45
|
+
host_config.pop("env_content")
|
|
46
|
+
if self.config.host.key_filename:
|
|
47
|
+
host_config["key_filename"] = self.config.host.key_filename
|
|
48
|
+
host_config["env_content"] = self.config.host.env_content
|
|
40
49
|
host_config_text = "\n".join(
|
|
41
|
-
f"[dim]{key}:[/dim] {value}"
|
|
42
|
-
for key, value in self.config.host.to_dict().items()
|
|
50
|
+
f"[dim]{key}:[/dim] {value}" for key, value in host_config.items()
|
|
43
51
|
)
|
|
44
52
|
console.print(
|
|
45
53
|
Panel(
|
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
|
-
|
|
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]"
|
|
@@ -52,12 +52,12 @@ class Deploy(BaseCommand):
|
|
|
52
52
|
return f"{self.app_dir}/v{self.config.version}"
|
|
53
53
|
|
|
54
54
|
def parse_envfile(self) -> str:
|
|
55
|
-
if not self.config.host.envfile.exists():
|
|
56
|
-
raise cappa.Exit(f"{self.config.host.envfile} not found", code=1)
|
|
57
55
|
if self.config.secret_config:
|
|
58
56
|
self.stdout.output("[blue]Reading secrets....[/blue]")
|
|
59
|
-
return resolve_secrets(
|
|
60
|
-
|
|
57
|
+
return resolve_secrets(
|
|
58
|
+
self.config.host.env_content, self.config.secret_config
|
|
59
|
+
)
|
|
60
|
+
return self.config.host.env_content
|
|
61
61
|
|
|
62
62
|
def transfer_files(
|
|
63
63
|
self, conn: Connection, env: str, skip_requirements: bool = False
|
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
|
)
|
fujin/commands/init.py
CHANGED
|
@@ -31,7 +31,7 @@ class Init(BaseCommand):
|
|
|
31
31
|
}
|
|
32
32
|
app_name = Path().resolve().stem.replace("-", "_").replace(" ", "_").lower()
|
|
33
33
|
config = profile_to_func[self.profile](app_name)
|
|
34
|
-
fujin_toml.write_text(tomli_w.dumps(config))
|
|
34
|
+
fujin_toml.write_text(tomli_w.dumps(config, multiline_strings=True))
|
|
35
35
|
self.stdout.output(
|
|
36
36
|
"[green]Sample configuration file generated successfully![/green]"
|
|
37
37
|
)
|
|
@@ -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
|
-
"
|
|
60
|
+
"env_content": f"DEBUG=False\nALLOWED_HOSTS={app_name}.com\n",
|
|
61
61
|
},
|
|
62
62
|
}
|
|
63
63
|
if not Path(".python-version").exists():
|
fujin/commands/printenv.py
CHANGED
|
@@ -11,8 +11,8 @@ class Printenv(BaseCommand):
|
|
|
11
11
|
def __call__(self):
|
|
12
12
|
if self.config.secret_config:
|
|
13
13
|
result = resolve_secrets(
|
|
14
|
-
self.config.host.
|
|
14
|
+
self.config.host.env_content, self.config.secret_config
|
|
15
15
|
)
|
|
16
16
|
else:
|
|
17
|
-
result = self.config.host.
|
|
17
|
+
result = self.config.host.env_content
|
|
18
18
|
self.stdout.output(result)
|
fujin/commands/redeploy.py
CHANGED
|
@@ -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,8 +29,8 @@ 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
|
-
|
|
34
|
-
|
|
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 (
|
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
|
@@ -136,10 +136,18 @@ The login user for running remote tasks. Should have passwordless sudo access fo
|
|
|
136
136
|
|
|
137
137
|
You can create a user with these requirements using the ``fujin server create-user`` command.
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
env_file
|
|
140
|
+
~~~~~~~~
|
|
141
141
|
Path to the production environment file that will be copied to the host.
|
|
142
142
|
|
|
143
|
+
env_content
|
|
144
|
+
~~~~~~~~~~~
|
|
145
|
+
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.
|
|
146
|
+
|
|
147
|
+
.. important::
|
|
148
|
+
|
|
149
|
+
``env_file`` and ``env_content`` are mutually exclusive—you can define only one.
|
|
150
|
+
|
|
143
151
|
apps_dir
|
|
144
152
|
~~~~~~~~
|
|
145
153
|
|
|
@@ -176,6 +184,9 @@ Example:
|
|
|
176
184
|
dbconsole = "app exec -i dbshell" # open an interactive django database shell
|
|
177
185
|
shell = "server exec --appenv -i bash" # SSH into the project directory with environment variables loaded
|
|
178
186
|
|
|
187
|
+
hooks
|
|
188
|
+
-----
|
|
189
|
+
Run custom scripts at specific points with hooks. Check out the `hooks </hooks.html>`_ page for more information.
|
|
179
190
|
|
|
180
191
|
"""
|
|
181
192
|
|
|
@@ -268,28 +279,27 @@ class HostConfig(msgspec.Struct, kw_only=True):
|
|
|
268
279
|
ip: str | None = None
|
|
269
280
|
domain_name: str
|
|
270
281
|
user: str
|
|
271
|
-
|
|
282
|
+
_env_file: str = msgspec.field(name="envfile", default="")
|
|
283
|
+
env_content: str = ""
|
|
272
284
|
apps_dir: str = ".local/share/fujin"
|
|
273
285
|
password_env: str | None = None
|
|
274
286
|
ssh_port: int = 22
|
|
275
287
|
_key_filename: str | None = msgspec.field(name="key_filename", default=None)
|
|
276
288
|
|
|
277
289
|
def __post_init__(self):
|
|
290
|
+
if self._env_file and self.env_content:
|
|
291
|
+
raise ImproperlyConfiguredError(
|
|
292
|
+
"Cannot set both 'env_content' and 'env_file' properties."
|
|
293
|
+
)
|
|
294
|
+
if not self.env_content:
|
|
295
|
+
envfile = Path(self._env_file)
|
|
296
|
+
if not envfile.exists():
|
|
297
|
+
raise ImproperlyConfiguredError(f"{self._env_file} not found")
|
|
298
|
+
self.env_content = envfile.read_text()
|
|
299
|
+
self.env_content = self.env_content.strip()
|
|
278
300
|
self.apps_dir = f"/home/{self.user}/{self.apps_dir}"
|
|
279
301
|
self.ip = self.ip or self.domain_name
|
|
280
302
|
|
|
281
|
-
def to_dict(self):
|
|
282
|
-
d = {f: getattr(self, f) for f in self.__struct_fields__}
|
|
283
|
-
d.pop("_key_filename")
|
|
284
|
-
d.pop("_envfile")
|
|
285
|
-
d["key_filename"] = self.key_filename
|
|
286
|
-
d["envfile"] = self.envfile
|
|
287
|
-
return d
|
|
288
|
-
|
|
289
|
-
@property
|
|
290
|
-
def envfile(self) -> Path:
|
|
291
|
-
return Path(self._envfile)
|
|
292
|
-
|
|
293
303
|
@property
|
|
294
304
|
def key_filename(self) -> Path | None:
|
|
295
305
|
if self._key_filename:
|
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,
|
|
23
|
+
HooksDict = dict[Hook, str]
|
|
26
24
|
|
|
27
25
|
|
|
28
|
-
@dataclass(
|
|
26
|
+
@dataclass(slots=True)
|
|
29
27
|
class HookManager:
|
|
30
28
|
app_name: str
|
|
31
29
|
hooks: HooksDict
|
|
32
|
-
|
|
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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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)
|
fujin/secrets/__init__.py
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from
|
|
3
|
+
from contextlib import closing
|
|
4
|
+
from io import StringIO
|
|
5
|
+
from typing import Callable, ContextManager
|
|
5
6
|
|
|
6
7
|
import gevent
|
|
7
8
|
from dotenv import dotenv_values
|
|
8
9
|
|
|
9
|
-
from .bitwarden import bitwarden
|
|
10
|
-
from .onepassword import one_password
|
|
11
10
|
from fujin.config import SecretAdapter
|
|
12
11
|
from fujin.config import SecretConfig
|
|
13
|
-
|
|
12
|
+
from .bitwarden import bitwarden
|
|
13
|
+
from .onepassword import one_password
|
|
14
14
|
|
|
15
15
|
secret_reader = Callable[[str], str]
|
|
16
|
-
secret_adapter_context = Callable[[SecretConfig], secret_reader]
|
|
16
|
+
secret_adapter_context = Callable[[SecretConfig], ContextManager[secret_reader]]
|
|
17
17
|
|
|
18
18
|
adapter_to_context: dict[SecretAdapter, secret_adapter_context] = {
|
|
19
19
|
SecretAdapter.BITWARDEN: bitwarden,
|
|
@@ -21,14 +21,17 @@ adapter_to_context: dict[SecretAdapter, secret_adapter_context] = {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def resolve_secrets(
|
|
25
|
-
|
|
24
|
+
def resolve_secrets(env_content: str, secret_config: SecretConfig) -> str:
|
|
25
|
+
with closing(StringIO(env_content)) as buffer:
|
|
26
|
+
env_dict = dotenv_values(stream=buffer)
|
|
26
27
|
secrets = {key: value for key, value in env_dict.items() if value.startswith("$")}
|
|
28
|
+
if not secrets:
|
|
29
|
+
return env_content
|
|
27
30
|
adapter_context = adapter_to_context[secret_config.adapter]
|
|
28
31
|
parsed_secrets = {}
|
|
29
|
-
with adapter_context(secret_config) as
|
|
32
|
+
with adapter_context(secret_config) as reader:
|
|
30
33
|
for key, secret in secrets.items():
|
|
31
|
-
parsed_secrets[key] = gevent.spawn(
|
|
34
|
+
parsed_secrets[key] = gevent.spawn(reader, secret[1:])
|
|
32
35
|
gevent.joinall(parsed_secrets.values())
|
|
33
36
|
env_dict.update({key: thread.value for key, thread in parsed_secrets.items()})
|
|
34
37
|
return "\n".join(f'{key}="{value}"' for key, value in env_dict.items())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: fujin-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.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=
|
|
3
|
+
fujin/config.py,sha256=LjgOTBumgOQTHkgXLJ0Rb2ERz_WBO-8rDj1hTYo-z0s,11027
|
|
4
4
|
fujin/connection.py,sha256=LL7LhX9p0X9FmiGdlSroD3Ht216QY0Kd51xkSrXmM3s,2479
|
|
5
5
|
fujin/errors.py,sha256=74Rh-Sgql1YspPdR_akQ2G3xZ48zecyafYCptpaFo1A,73
|
|
6
|
-
fujin/hooks.py,sha256=
|
|
6
|
+
fujin/hooks.py,sha256=EDVYNozlDJ5kc1xHtZrXgtuKplUMEMPTp65TLMP02Ek,1449
|
|
7
7
|
fujin/commands/__init__.py,sha256=g0b13vzidPUbxne_Zo_Wv5jpEmwCiSK4AokCinH9uo4,39
|
|
8
|
-
fujin/commands/_base.py,sha256=
|
|
8
|
+
fujin/commands/_base.py,sha256=Rtf7kcO_Pg2JxPNKsXEx0UdhqfzYU6g7IiezC8cqEPQ,2610
|
|
9
9
|
fujin/commands/app.py,sha256=mazb4dCTdR5juh79bL3a9b68Nd6O8u_nR9IgYqQlqWE,5279
|
|
10
|
-
fujin/commands/config.py,sha256=
|
|
11
|
-
fujin/commands/deploy.py,sha256=
|
|
10
|
+
fujin/commands/config.py,sha256=WymGla-H2yduhLcYE1Nb6IJaFIj0S0KIhV9fBDCKsAw,2779
|
|
11
|
+
fujin/commands/deploy.py,sha256=AylcVNX47ekf-AFfQrYcSh86I3qltADEgjPtV9lQAfE,5831
|
|
12
12
|
fujin/commands/docs.py,sha256=b5FZ8AgoAfn4q4BueEQvM2w5HCuh8-rwBqv_CRFVU8E,349
|
|
13
|
-
fujin/commands/down.py,sha256=
|
|
14
|
-
fujin/commands/init.py,sha256=
|
|
15
|
-
fujin/commands/printenv.py,sha256=
|
|
13
|
+
fujin/commands/down.py,sha256=aw_mxl_TMC66plKTXwlYP1W2XQBmHeROltQqOpQssyE,1577
|
|
14
|
+
fujin/commands/init.py,sha256=AHAZeYkSk-zShF288Zs4rCfVwalCUqeMXWR6GTks2v0,4046
|
|
15
|
+
fujin/commands/printenv.py,sha256=bS2mIgk7zd_w3yDhZLTa1PkHIzuSbBWjnYyM8CUjX0k,523
|
|
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=
|
|
18
|
+
fujin/commands/redeploy.py,sha256=491Mzz0qiaHqcI7BFUtZq-M34WQkiBd2ZgQbLRLp8T8,2355
|
|
19
19
|
fujin/commands/rollback.py,sha256=JsocJzQcdQelSnYD94klhjBh8UKkkdiRD9shfUfo4FI,2032
|
|
20
|
-
fujin/commands/server.py,sha256
|
|
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
|
|
@@ -25,14 +25,14 @@ 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
27
|
fujin/proxies/nginx.py,sha256=BNJNLxLLRVAmBIGVCk8pb16iiSJsOI9jXOZhdSQGtX8,4151
|
|
28
|
-
fujin/secrets/__init__.py,sha256=
|
|
28
|
+
fujin/secrets/__init__.py,sha256=hiNZIBtOwM0WQbvAF3GhuMMCsQCjXjiSr5gTLAGN-VI,1375
|
|
29
29
|
fujin/secrets/bitwarden.py,sha256=01GZL5hYwZzL6yXy5ab3L3kgBFBeOT8i3Yg9GC8YwFU,2008
|
|
30
30
|
fujin/secrets/onepassword.py,sha256=6Xj3XWttKfcjMbcoMZvXVpJW1KHxlD785DysmX_mqvk,654
|
|
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.
|
|
35
|
-
fujin_cli-0.
|
|
36
|
-
fujin_cli-0.
|
|
37
|
-
fujin_cli-0.
|
|
38
|
-
fujin_cli-0.
|
|
34
|
+
fujin_cli-0.9.0.dist-info/METADATA,sha256=0OCkg_YffO4PLhTp1AKrk7OmncaCbZOO3IyTYkjGXxI,4576
|
|
35
|
+
fujin_cli-0.9.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
36
|
+
fujin_cli-0.9.0.dist-info/entry_points.txt,sha256=Y_TBtKt3j11qhwquMexZR5yqnDEqOBDACtresqQFE-s,46
|
|
37
|
+
fujin_cli-0.9.0.dist-info/licenses/LICENSE.txt,sha256=0QF8XfuH0zkIHhSet6teXfiCze6JSdr8inRkmLLTDyo,1099
|
|
38
|
+
fujin_cli-0.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|