fujin-cli 0.9.0__tar.gz → 0.9.1__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.
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/CHANGELOG.md +14 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/PKG-INFO +1 -1
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/pyproject.toml +2 -2
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/_base.py +2 -12
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/config.py +5 -1
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/proxies/caddy.py +2 -1
- {fujin_cli-0.9.0/src/fujin/process_managers → fujin_cli-0.9.1/src/fujin}/systemd.py +59 -24
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/uv.lock +1 -1
- fujin_cli-0.9.0/src/fujin/process_managers/__init__.py +0 -40
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/.github/workflows/publish.yml +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/.gitignore +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/.pre-commit-config.yaml +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/.readthedocs.yaml +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/LICENSE.txt +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/README.md +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/Vagrantfile +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/changelog.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/app.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/config.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/deploy.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/docs.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/down.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/index.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/init.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/printenv.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/proxy.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/prune.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/redeploy.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/rollback.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/server.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/commands/up.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/conf.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/configuration.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/hooks.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/index.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/installation.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/requirements.txt +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/secrets.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/docs/tutorial.rst +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/django/bookstore/README.md +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/django/bookstore/bookstore/__init__.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/django/bookstore/bookstore/__main__.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/django/bookstore/bookstore/asgi.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/django/bookstore/bookstore/settings.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/django/bookstore/bookstore/urls.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/django/bookstore/bookstore/wsgi.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/django/bookstore/fujin.toml +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/django/bookstore/manage.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/django/bookstore/pyproject.toml +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/django/bookstore/requirements.txt +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/golang/pocketbase/.env.prod +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/examples/golang/pocketbase/fujin.toml +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/justfile +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/__init__.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/__main__.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/__init__.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/app.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/config.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/deploy.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/docs.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/down.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/init.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/printenv.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/proxy.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/prune.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/redeploy.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/rollback.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/server.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/commands/up.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/connection.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/errors.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/hooks.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/proxies/__init__.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/proxies/dummy.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/proxies/nginx.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/secrets/__init__.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/secrets/bitwarden.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/secrets/onepassword.py +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/templates/simple.service +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/templates/web.service +0 -0
- {fujin_cli-0.9.0 → fujin_cli-0.9.1}/src/fujin/templates/web.socket +0 -0
|
@@ -4,6 +4,20 @@ 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.9.1] - 2024-11-23
|
|
8
|
+
|
|
9
|
+
### 🚜 Refactor
|
|
10
|
+
|
|
11
|
+
- Drop configurable proxy manager
|
|
12
|
+
|
|
13
|
+
### 📚 Documentation
|
|
14
|
+
|
|
15
|
+
- Add links to template systemd service files
|
|
16
|
+
|
|
17
|
+
### ⚡ Performance
|
|
18
|
+
|
|
19
|
+
- Run systemd commands concurrently using gevent
|
|
20
|
+
|
|
7
21
|
## [0.9.0] - 2024-11-23
|
|
8
22
|
|
|
9
23
|
### 🚀 Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: fujin-cli
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.1
|
|
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 +7,7 @@ requires = [
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "fujin-cli"
|
|
10
|
-
version = "0.9.
|
|
10
|
+
version = "0.9.1"
|
|
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 = [
|
|
@@ -154,7 +154,7 @@ lint.isort.required-imports = [
|
|
|
154
154
|
lint.pyupgrade.keep-runtime-typing = true
|
|
155
155
|
|
|
156
156
|
[tool.bumpversion]
|
|
157
|
-
current_version = "0.9.
|
|
157
|
+
current_version = "0.9.1"
|
|
158
158
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
159
159
|
serialize = [
|
|
160
160
|
"{major}.{minor}.{patch}",
|
|
@@ -10,8 +10,8 @@ from fujin.connection import Connection
|
|
|
10
10
|
from fujin.connection import host_connection
|
|
11
11
|
from fujin.errors import ImproperlyConfiguredError
|
|
12
12
|
from fujin.hooks import HookManager
|
|
13
|
-
from fujin.process_managers import ProcessManager
|
|
14
13
|
from fujin.proxies import WebProxy
|
|
14
|
+
from fujin.systemd import ProcessManager
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@dataclass
|
|
@@ -66,15 +66,5 @@ class BaseCommand:
|
|
|
66
66
|
def create_web_proxy(self, conn: Connection) -> WebProxy:
|
|
67
67
|
return self.web_proxy_class.create(conn=conn, config=self.config)
|
|
68
68
|
|
|
69
|
-
@cached_property
|
|
70
|
-
def process_manager_class(self) -> type[ProcessManager]:
|
|
71
|
-
module = importlib.import_module(self.config.process_manager)
|
|
72
|
-
try:
|
|
73
|
-
return getattr(module, "ProcessManager")
|
|
74
|
-
except KeyError as e:
|
|
75
|
-
raise ImproperlyConfiguredError(
|
|
76
|
-
f"Missing ProcessManager class in {self.config.process_manager}"
|
|
77
|
-
) from e
|
|
78
|
-
|
|
79
69
|
def create_process_manager(self, conn: Connection) -> ProcessManager:
|
|
80
|
-
return
|
|
70
|
+
return ProcessManager.create(conn=conn, config=self.config)
|
|
@@ -116,6 +116,11 @@ Example:
|
|
|
116
116
|
.. note::
|
|
117
117
|
|
|
118
118
|
Commands are relative to your ``app_dir``. When generating systemd service files, the full path is automatically constructed.
|
|
119
|
+
Here are the templates for the service files:
|
|
120
|
+
|
|
121
|
+
- `web.service <https://github.com/falcopackages/fujin/blob/main/src/fujin/templates/web.service>`_
|
|
122
|
+
- `web.socket <https://github.com/falcopackages/fujin/blob/main/src/fujin/templates/web.socket>`_
|
|
123
|
+
- `simple.service <https://github.com/falcopackages/fujin/blob/main/src/fujin/templates/simple.service>`_ (for all additional processes)
|
|
119
124
|
|
|
120
125
|
Host Configuration
|
|
121
126
|
-------------------
|
|
@@ -235,7 +240,6 @@ class Config(msgspec.Struct, kw_only=True):
|
|
|
235
240
|
aliases: dict[str, str] = msgspec.field(default_factory=dict)
|
|
236
241
|
host: HostConfig
|
|
237
242
|
processes: dict[str, str] = msgspec.field(default_factory=dict)
|
|
238
|
-
process_manager: str = "fujin.process_managers.systemd"
|
|
239
243
|
webserver: Webserver
|
|
240
244
|
requirements: str | None = None
|
|
241
245
|
hooks: HooksDict = msgspec.field(default_factory=dict)
|
|
@@ -166,7 +166,8 @@ class WebProxy(msgspec.Struct):
|
|
|
166
166
|
new_routes = [r for r in existing_routes if r.get("group") != self.app_name]
|
|
167
167
|
current_config["routes"] = new_routes
|
|
168
168
|
self.conn.run(
|
|
169
|
-
f"curl localhost:2019/config/apps/http/servers/srv0 -H 'Content-Type: application/json' -d '{json.dumps(current_config)}'"
|
|
169
|
+
f"curl localhost:2019/config/apps/http/servers/srv0 -H 'Content-Type: application/json' -d '{json.dumps(current_config)}'",
|
|
170
|
+
hide="out",
|
|
170
171
|
)
|
|
171
172
|
|
|
172
173
|
def start(self) -> None:
|
|
@@ -4,6 +4,8 @@ import importlib.util
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
import gevent
|
|
8
|
+
|
|
7
9
|
from fujin.config import Config
|
|
8
10
|
from fujin.connection import Connection
|
|
9
11
|
|
|
@@ -54,11 +56,23 @@ class ProcessManager:
|
|
|
54
56
|
hide="out",
|
|
55
57
|
)
|
|
56
58
|
|
|
59
|
+
threads = []
|
|
57
60
|
for name in self.processes:
|
|
58
61
|
if name == "web" and self.is_using_unix_socket:
|
|
59
|
-
|
|
62
|
+
threads.append(
|
|
63
|
+
gevent.spawn(
|
|
64
|
+
self.run_pty,
|
|
65
|
+
f"sudo systemctl enable --now {self.app_name}.socket",
|
|
66
|
+
)
|
|
67
|
+
)
|
|
60
68
|
else:
|
|
61
|
-
|
|
69
|
+
threads.append(
|
|
70
|
+
gevent.spawn(
|
|
71
|
+
self.run_pty,
|
|
72
|
+
f"sudo systemctl enable {self.get_service_name(name)}",
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
gevent.joinall(threads)
|
|
62
76
|
|
|
63
77
|
def get_configuration_files(
|
|
64
78
|
self, ignore_local: bool = False
|
|
@@ -105,47 +119,68 @@ class ProcessManager:
|
|
|
105
119
|
|
|
106
120
|
def uninstall_services(self) -> None:
|
|
107
121
|
self.stop_services()
|
|
108
|
-
|
|
109
|
-
self.run_pty
|
|
110
|
-
|
|
122
|
+
threads = [
|
|
123
|
+
gevent.spawn(self.run_pty, f"sudo systemctl disable {name}", warn=True)
|
|
124
|
+
for name in self.service_names
|
|
125
|
+
]
|
|
126
|
+
gevent.joinall(threads)
|
|
127
|
+
files_to_delete = [f"/etc/systemd/system/{name}" for name in self.service_names]
|
|
128
|
+
self.run_pty(f"sudo rm {' '.join(files_to_delete)}", warn=True)
|
|
111
129
|
|
|
112
130
|
def start_services(self, *names) -> None:
|
|
113
131
|
names = names or self.service_names
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
threads = [
|
|
133
|
+
gevent.spawn(self.run_pty, f"sudo systemctl start {name}")
|
|
134
|
+
for name in names
|
|
135
|
+
if name in self.service_names
|
|
136
|
+
]
|
|
137
|
+
gevent.joinall(threads)
|
|
117
138
|
|
|
118
139
|
def restart_services(self, *names) -> None:
|
|
119
140
|
names = names or self.service_names
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
threads = [
|
|
142
|
+
gevent.spawn(self.run_pty, f"sudo systemctl restart {name}")
|
|
143
|
+
for name in names
|
|
144
|
+
if name in self.service_names
|
|
145
|
+
]
|
|
146
|
+
gevent.joinall(threads)
|
|
123
147
|
|
|
124
148
|
def stop_services(self, *names) -> None:
|
|
125
149
|
names = names or self.service_names
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
150
|
+
threads = [
|
|
151
|
+
gevent.spawn(self.run_pty, f"sudo systemctl stop {name}")
|
|
152
|
+
for name in names
|
|
153
|
+
if name in self.service_names
|
|
154
|
+
]
|
|
155
|
+
gevent.joinall(threads)
|
|
129
156
|
|
|
130
157
|
def is_enabled(self, *names) -> dict[str, bool]:
|
|
131
158
|
names = names or self.service_names
|
|
132
|
-
|
|
133
|
-
name:
|
|
134
|
-
f"sudo systemctl is-enabled {name}", warn=True, hide=True
|
|
135
|
-
)
|
|
136
|
-
== "enabled"
|
|
159
|
+
threads = {
|
|
160
|
+
name: gevent.spawn(
|
|
161
|
+
self.run_pty, f"sudo systemctl is-enabled {name}", warn=True, hide=True
|
|
162
|
+
)
|
|
137
163
|
for name in names
|
|
138
164
|
}
|
|
165
|
+
gevent.joinall(threads.values())
|
|
166
|
+
return {
|
|
167
|
+
name: thread.value.stdout.strip() == "enabled"
|
|
168
|
+
for name, thread in threads.items()
|
|
169
|
+
}
|
|
139
170
|
|
|
140
171
|
def is_active(self, *names) -> dict[str, bool]:
|
|
141
172
|
names = names or self.service_names
|
|
142
|
-
|
|
143
|
-
name:
|
|
144
|
-
f"sudo systemctl is-active {name}", warn=True, hide=True
|
|
145
|
-
)
|
|
146
|
-
== "active"
|
|
173
|
+
threads = {
|
|
174
|
+
name: gevent.spawn(
|
|
175
|
+
self.run_pty, f"sudo systemctl is-active {name}", warn=True, hide=True
|
|
176
|
+
)
|
|
147
177
|
for name in names
|
|
148
178
|
}
|
|
179
|
+
gevent.joinall(threads.values())
|
|
180
|
+
return {
|
|
181
|
+
name: thread.value.stdout.strip() == "active"
|
|
182
|
+
for name, thread in threads.items()
|
|
183
|
+
}
|
|
149
184
|
|
|
150
185
|
def service_logs(self, name: str, follow: bool = False):
|
|
151
186
|
# TODO: add more options here
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Protocol
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
|
-
|
|
6
|
-
from fujin.connection import Connection
|
|
7
|
-
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from fujin.config import Config
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ProcessManager(Protocol):
|
|
13
|
-
service_names: list[str]
|
|
14
|
-
|
|
15
|
-
@classmethod
|
|
16
|
-
def create(cls, config: Config, conn: Connection) -> ProcessManager: ...
|
|
17
|
-
|
|
18
|
-
def get_service_name(self, process_name: str): ...
|
|
19
|
-
|
|
20
|
-
def install_services(self) -> None: ...
|
|
21
|
-
|
|
22
|
-
def uninstall_services(self) -> None: ...
|
|
23
|
-
|
|
24
|
-
def start_services(self, *names) -> None: ...
|
|
25
|
-
|
|
26
|
-
def restart_services(self, *names) -> None: ...
|
|
27
|
-
|
|
28
|
-
def stop_services(self, *names) -> None: ...
|
|
29
|
-
|
|
30
|
-
def is_enabled(self, *names) -> dict[str, bool]: ...
|
|
31
|
-
|
|
32
|
-
def is_active(self, *names) -> dict[str, bool]: ...
|
|
33
|
-
|
|
34
|
-
def service_logs(self, name: str, follow: bool = False): ...
|
|
35
|
-
|
|
36
|
-
def reload_configuration(self) -> None: ...
|
|
37
|
-
|
|
38
|
-
def get_configuration_files(
|
|
39
|
-
self, ignore_local: bool = False
|
|
40
|
-
) -> list[tuple[str, str]]: ...
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|