ominfra 0.0.0.dev190__py3-none-any.whl → 0.0.0.dev191__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.
- ominfra/manage/deploy/apps.py +72 -3
- ominfra/manage/deploy/conf/manager.py +64 -7
- ominfra/manage/deploy/deploy.py +119 -31
- ominfra/manage/deploy/specs.py +9 -0
- ominfra/manage/deploy/systemd.py +48 -27
- ominfra/manage/deploy/tags.py +1 -1
- ominfra/manage/deploy/venvs.py +8 -4
- ominfra/scripts/manage.py +308 -72
- ominfra/systemd.py +49 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/METADATA +4 -4
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/RECORD +15 -14
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/top_level.txt +0 -0
ominfra/manage/deploy/apps.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
import dataclasses as dc
|
3
|
+
import json
|
3
4
|
import os.path
|
4
5
|
import typing as ta
|
5
6
|
|
@@ -13,6 +14,7 @@ from .git import DeployGitManager
|
|
13
14
|
from .paths.owners import DeployPathOwner
|
14
15
|
from .paths.paths import DeployPath
|
15
16
|
from .specs import DeployAppSpec
|
17
|
+
from .tags import DeployAppRev
|
16
18
|
from .tags import DeployTagMap
|
17
19
|
from .types import DeployHome
|
18
20
|
from .venvs import DeployVenvManager
|
@@ -57,9 +59,21 @@ class DeployAppManager(DeployPathOwner):
|
|
57
59
|
|
58
60
|
#
|
59
61
|
|
62
|
+
def _make_tags(self, spec: DeployAppSpec) -> DeployTagMap:
|
63
|
+
return DeployTagMap(
|
64
|
+
spec.app,
|
65
|
+
spec.key(),
|
66
|
+
DeployAppRev(spec.git.rev),
|
67
|
+
)
|
68
|
+
|
69
|
+
#
|
70
|
+
|
60
71
|
@dc.dataclass(frozen=True)
|
61
72
|
class PreparedApp:
|
62
|
-
|
73
|
+
spec: DeployAppSpec
|
74
|
+
tags: DeployTagMap
|
75
|
+
|
76
|
+
dir: str
|
63
77
|
|
64
78
|
git_dir: ta.Optional[str] = None
|
65
79
|
venv_dir: ta.Optional[str] = None
|
@@ -75,16 +89,23 @@ class DeployAppManager(DeployPathOwner):
|
|
75
89
|
|
76
90
|
#
|
77
91
|
|
92
|
+
app_tags = tags.add(*self._make_tags(spec))
|
93
|
+
|
94
|
+
#
|
95
|
+
|
78
96
|
check.non_empty_str(home)
|
79
97
|
|
80
|
-
app_dir = os.path.join(home, self.APP_DIR.render(
|
98
|
+
app_dir = os.path.join(home, self.APP_DIR.render(app_tags))
|
81
99
|
|
82
100
|
os.makedirs(app_dir, exist_ok=True)
|
83
101
|
|
84
102
|
#
|
85
103
|
|
86
104
|
rkw: ta.Dict[str, ta.Any] = dict(
|
87
|
-
|
105
|
+
spec=spec,
|
106
|
+
tags=app_tags,
|
107
|
+
|
108
|
+
dir=app_dir,
|
88
109
|
)
|
89
110
|
|
90
111
|
#
|
@@ -127,3 +148,51 @@ class DeployAppManager(DeployPathOwner):
|
|
127
148
|
#
|
128
149
|
|
129
150
|
return DeployAppManager.PreparedApp(**rkw)
|
151
|
+
|
152
|
+
async def prepare_app_link(
|
153
|
+
self,
|
154
|
+
tags: DeployTagMap,
|
155
|
+
app_dir: str,
|
156
|
+
) -> PreparedApp:
|
157
|
+
spec_file = os.path.join(app_dir, 'spec.json')
|
158
|
+
with open(spec_file) as f: # noqa
|
159
|
+
spec_json = f.read()
|
160
|
+
|
161
|
+
spec: DeployAppSpec = self._msh.unmarshal_obj(json.loads(spec_json), DeployAppSpec)
|
162
|
+
|
163
|
+
#
|
164
|
+
|
165
|
+
app_tags = tags.add(*self._make_tags(spec))
|
166
|
+
|
167
|
+
#
|
168
|
+
|
169
|
+
rkw: ta.Dict[str, ta.Any] = dict(
|
170
|
+
spec=spec,
|
171
|
+
tags=app_tags,
|
172
|
+
|
173
|
+
dir=app_dir,
|
174
|
+
)
|
175
|
+
|
176
|
+
#
|
177
|
+
|
178
|
+
git_dir = os.path.join(app_dir, 'git')
|
179
|
+
check.state(os.path.isdir(git_dir))
|
180
|
+
rkw.update(git_dir=git_dir)
|
181
|
+
|
182
|
+
#
|
183
|
+
|
184
|
+
if spec.venv is not None:
|
185
|
+
venv_dir = os.path.join(app_dir, 'venv')
|
186
|
+
check.state(os.path.isdir(venv_dir))
|
187
|
+
rkw.update(venv_dir=venv_dir)
|
188
|
+
|
189
|
+
#
|
190
|
+
|
191
|
+
if spec.conf is not None:
|
192
|
+
conf_dir = os.path.join(app_dir, 'conf')
|
193
|
+
check.state(os.path.isdir(conf_dir))
|
194
|
+
rkw.update(conf_dir=conf_dir)
|
195
|
+
|
196
|
+
#
|
197
|
+
|
198
|
+
return DeployAppManager.PreparedApp(**rkw)
|
@@ -16,6 +16,8 @@ TODO:
|
|
16
16
|
- some things (venvs) cannot be moved, thus the /deploy/venvs dir
|
17
17
|
- ** ensure (enforce) equivalent relpath nesting
|
18
18
|
"""
|
19
|
+
import collections.abc
|
20
|
+
import functools
|
19
21
|
import os.path
|
20
22
|
import typing as ta
|
21
23
|
|
@@ -43,20 +45,66 @@ from .specs import NginxDeployAppConfContent
|
|
43
45
|
from .specs import RawDeployAppConfContent
|
44
46
|
|
45
47
|
|
48
|
+
T = ta.TypeVar('T')
|
49
|
+
|
50
|
+
|
51
|
+
##
|
52
|
+
|
53
|
+
|
46
54
|
class DeployConfManager:
|
47
|
-
def
|
55
|
+
def _process_conf_content(
|
56
|
+
self,
|
57
|
+
content: T,
|
58
|
+
*,
|
59
|
+
str_processor: ta.Optional[ta.Callable[[str], str]] = None,
|
60
|
+
) -> T:
|
61
|
+
def rec(o):
|
62
|
+
if isinstance(o, str):
|
63
|
+
if str_processor is not None:
|
64
|
+
return type(o)(str_processor(o))
|
65
|
+
|
66
|
+
elif isinstance(o, collections.abc.Mapping):
|
67
|
+
return type(o)([ # type: ignore
|
68
|
+
(rec(k), rec(v))
|
69
|
+
for k, v in o.items()
|
70
|
+
])
|
71
|
+
|
72
|
+
elif isinstance(o, collections.abc.Iterable):
|
73
|
+
return type(o)([ # type: ignore
|
74
|
+
rec(e) for e in o
|
75
|
+
])
|
76
|
+
|
77
|
+
return o
|
78
|
+
|
79
|
+
return rec(content)
|
80
|
+
|
81
|
+
#
|
82
|
+
|
83
|
+
def _render_app_conf_content(
|
84
|
+
self,
|
85
|
+
ac: DeployAppConfContent,
|
86
|
+
*,
|
87
|
+
str_processor: ta.Optional[ta.Callable[[str], str]] = None,
|
88
|
+
) -> str:
|
89
|
+
pcc = functools.partial(
|
90
|
+
self._process_conf_content,
|
91
|
+
str_processor=str_processor,
|
92
|
+
)
|
93
|
+
|
48
94
|
if isinstance(ac, RawDeployAppConfContent):
|
49
|
-
return ac.body
|
95
|
+
return pcc(ac.body)
|
50
96
|
|
51
97
|
elif isinstance(ac, JsonDeployAppConfContent):
|
52
|
-
|
98
|
+
json_obj = pcc(ac.obj)
|
99
|
+
return strip_with_newline(json_dumps_pretty(json_obj))
|
53
100
|
|
54
101
|
elif isinstance(ac, IniDeployAppConfContent):
|
55
|
-
|
102
|
+
ini_sections = pcc(ac.sections)
|
103
|
+
return strip_with_newline(render_ini_config(ini_sections))
|
56
104
|
|
57
105
|
elif isinstance(ac, NginxDeployAppConfContent):
|
58
|
-
|
59
|
-
return strip_with_newline(render_nginx_config_str(
|
106
|
+
nginx_items = NginxConfigItems.of(pcc(ac.items))
|
107
|
+
return strip_with_newline(render_nginx_config_str(nginx_items))
|
60
108
|
|
61
109
|
else:
|
62
110
|
raise TypeError(ac)
|
@@ -65,11 +113,16 @@ class DeployConfManager:
|
|
65
113
|
self,
|
66
114
|
acf: DeployAppConfFile,
|
67
115
|
app_conf_dir: str,
|
116
|
+
*,
|
117
|
+
str_processor: ta.Optional[ta.Callable[[str], str]] = None,
|
68
118
|
) -> None:
|
69
119
|
conf_file = os.path.join(app_conf_dir, acf.path)
|
70
120
|
check.arg(is_path_in_dir(app_conf_dir, conf_file))
|
71
121
|
|
72
|
-
body = self._render_app_conf_content(
|
122
|
+
body = self._render_app_conf_content(
|
123
|
+
acf.content,
|
124
|
+
str_processor=str_processor,
|
125
|
+
)
|
73
126
|
|
74
127
|
os.makedirs(os.path.dirname(conf_file), exist_ok=True)
|
75
128
|
|
@@ -81,10 +134,14 @@ class DeployConfManager:
|
|
81
134
|
spec: DeployAppConfSpec,
|
82
135
|
app_conf_dir: str,
|
83
136
|
) -> None:
|
137
|
+
def process_str(s: str) -> str:
|
138
|
+
return s
|
139
|
+
|
84
140
|
for acf in spec.files or []:
|
85
141
|
await self._write_app_conf_file(
|
86
142
|
acf,
|
87
143
|
app_conf_dir,
|
144
|
+
str_processor=process_str,
|
88
145
|
)
|
89
146
|
|
90
147
|
#
|
ominfra/manage/deploy/deploy.py
CHANGED
@@ -9,6 +9,7 @@ from omlish.lite.json import json_dumps_pretty
|
|
9
9
|
from omlish.lite.marshal import ObjMarshalerManager
|
10
10
|
from omlish.lite.typing import Func0
|
11
11
|
from omlish.lite.typing import Func1
|
12
|
+
from omlish.os.paths import abs_real_path
|
12
13
|
from omlish.os.paths import relative_symlink
|
13
14
|
|
14
15
|
from .apps import DeployAppManager
|
@@ -19,9 +20,10 @@ from .paths.paths import DeployPath
|
|
19
20
|
from .specs import DeployAppSpec
|
20
21
|
from .specs import DeploySpec
|
21
22
|
from .systemd import DeploySystemdManager
|
22
|
-
from .tags import
|
23
|
+
from .tags import DeployApp
|
23
24
|
from .tags import DeployTagMap
|
24
25
|
from .tags import DeployTime
|
26
|
+
from .tmp import DeployHomeAtomics
|
25
27
|
from .types import DeployHome
|
26
28
|
|
27
29
|
|
@@ -38,16 +40,24 @@ class DeployManager(DeployPathOwner):
|
|
38
40
|
def __init__(
|
39
41
|
self,
|
40
42
|
*,
|
43
|
+
atomics: DeployHomeAtomics,
|
44
|
+
|
41
45
|
utc_clock: ta.Optional[DeployManagerUtcClock] = None,
|
42
46
|
):
|
43
47
|
super().__init__()
|
44
48
|
|
49
|
+
self._atomics = atomics
|
50
|
+
|
45
51
|
self._utc_clock = utc_clock
|
46
52
|
|
47
53
|
#
|
48
54
|
|
55
|
+
# Home current link just points to CURRENT_DEPLOY_LINK, and is intended for user convenience.
|
56
|
+
HOME_CURRENT_LINK = DeployPath.parse('current')
|
57
|
+
|
49
58
|
DEPLOYS_DIR = DeployPath.parse('deploys/')
|
50
59
|
|
60
|
+
# Authoritative current symlink is not in deploy-home, just to prevent accidental corruption.
|
51
61
|
CURRENT_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}current')
|
52
62
|
DEPLOYING_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}deploying')
|
53
63
|
|
@@ -80,6 +90,11 @@ class DeployManager(DeployPathOwner):
|
|
80
90
|
|
81
91
|
#
|
82
92
|
|
93
|
+
def render_path(self, home: DeployHome, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
|
94
|
+
return os.path.join(check.non_empty_str(home), pth.render(tags))
|
95
|
+
|
96
|
+
#
|
97
|
+
|
83
98
|
def _utc_now(self) -> datetime.datetime:
|
84
99
|
if self._utc_clock is not None:
|
85
100
|
return self._utc_clock() # noqa
|
@@ -89,6 +104,22 @@ class DeployManager(DeployPathOwner):
|
|
89
104
|
def make_deploy_time(self) -> DeployTime:
|
90
105
|
return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
|
91
106
|
|
107
|
+
#
|
108
|
+
|
109
|
+
def make_home_current_link(self, home: DeployHome) -> None:
|
110
|
+
home_current_link = os.path.join(check.non_empty_str(home), self.HOME_CURRENT_LINK.render())
|
111
|
+
current_deploy_link = os.path.join(check.non_empty_str(home), self.CURRENT_DEPLOY_LINK.render())
|
112
|
+
with self._atomics(home).begin_atomic_path_swap( # noqa
|
113
|
+
'file',
|
114
|
+
home_current_link,
|
115
|
+
auto_commit=True,
|
116
|
+
) as dst_swap:
|
117
|
+
os.unlink(dst_swap.tmp_path)
|
118
|
+
os.symlink(
|
119
|
+
os.path.relpath(current_deploy_link, os.path.dirname(dst_swap.dst_path)),
|
120
|
+
dst_swap.tmp_path,
|
121
|
+
)
|
122
|
+
|
92
123
|
|
93
124
|
##
|
94
125
|
|
@@ -130,18 +161,18 @@ class DeployDriver:
|
|
130
161
|
#
|
131
162
|
|
132
163
|
@property
|
133
|
-
def
|
164
|
+
def tags(self) -> DeployTagMap:
|
134
165
|
return DeployTagMap(
|
135
166
|
self._time,
|
136
167
|
self._spec.key(),
|
137
168
|
)
|
138
169
|
|
139
|
-
def
|
140
|
-
return os.path.join(self._home, pth.render(tags if tags is not None else self.
|
170
|
+
def render_path(self, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
|
171
|
+
return os.path.join(self._home, pth.render(tags if tags is not None else self.tags))
|
141
172
|
|
142
173
|
@property
|
143
|
-
def
|
144
|
-
return self.
|
174
|
+
def dir(self) -> str:
|
175
|
+
return self.render_path(self._deploys.DEPLOY_DIR)
|
145
176
|
|
146
177
|
#
|
147
178
|
|
@@ -150,25 +181,36 @@ class DeployDriver:
|
|
150
181
|
|
151
182
|
#
|
152
183
|
|
184
|
+
das: ta.Set[DeployApp] = {a.app for a in self._spec.apps}
|
185
|
+
las: ta.Set[DeployApp] = set(self._spec.app_links.apps)
|
186
|
+
if (ras := das & las):
|
187
|
+
raise RuntimeError(f'Must not specify apps as both deploy and link: {sorted(a.s for a in ras)}')
|
188
|
+
|
189
|
+
#
|
190
|
+
|
153
191
|
self._paths.validate_deploy_paths()
|
154
192
|
|
155
193
|
#
|
156
194
|
|
157
|
-
os.makedirs(self.
|
195
|
+
os.makedirs(self.dir)
|
158
196
|
|
159
197
|
#
|
160
198
|
|
161
|
-
spec_file = self.
|
199
|
+
spec_file = self.render_path(self._deploys.DEPLOY_SPEC_FILE)
|
162
200
|
with open(spec_file, 'w') as f: # noqa
|
163
201
|
f.write(spec_json)
|
164
202
|
|
165
203
|
#
|
166
204
|
|
167
|
-
deploying_link = self.
|
205
|
+
deploying_link = self.render_path(self._deploys.DEPLOYING_DEPLOY_LINK)
|
206
|
+
current_link = self.render_path(self._deploys.CURRENT_DEPLOY_LINK)
|
207
|
+
|
208
|
+
#
|
209
|
+
|
168
210
|
if os.path.exists(deploying_link):
|
169
211
|
os.unlink(deploying_link)
|
170
212
|
relative_symlink(
|
171
|
-
self.
|
213
|
+
self.dir,
|
172
214
|
deploying_link,
|
173
215
|
target_is_directory=True,
|
174
216
|
make_dirs=True,
|
@@ -180,16 +222,30 @@ class DeployDriver:
|
|
180
222
|
self._deploys.APPS_DEPLOY_DIR,
|
181
223
|
self._deploys.CONFS_DEPLOY_DIR,
|
182
224
|
]:
|
183
|
-
os.makedirs(self.
|
225
|
+
os.makedirs(self.render_path(md))
|
184
226
|
|
185
227
|
#
|
186
228
|
|
229
|
+
if not self._spec.app_links.exclude_unspecified:
|
230
|
+
cad = abs_real_path(os.path.join(current_link, 'apps'))
|
231
|
+
if os.path.exists(cad):
|
232
|
+
for d in os.listdir(cad):
|
233
|
+
if (da := DeployApp(d)) not in das:
|
234
|
+
las.add(da)
|
235
|
+
|
236
|
+
for la in self._spec.app_links.apps:
|
237
|
+
await self._drive_app_link(
|
238
|
+
la,
|
239
|
+
current_link,
|
240
|
+
)
|
241
|
+
|
187
242
|
for app in self._spec.apps:
|
188
|
-
await self.
|
243
|
+
await self._drive_app_deploy(
|
244
|
+
app,
|
245
|
+
)
|
189
246
|
|
190
247
|
#
|
191
248
|
|
192
|
-
current_link = self.render_deploy_path(self._deploys.CURRENT_DEPLOY_LINK)
|
193
249
|
os.replace(deploying_link, current_link)
|
194
250
|
|
195
251
|
#
|
@@ -197,31 +253,27 @@ class DeployDriver:
|
|
197
253
|
await self._systemd.sync_systemd(
|
198
254
|
self._spec.systemd,
|
199
255
|
self._home,
|
200
|
-
os.path.join(self.
|
256
|
+
os.path.join(self.dir, 'conf', 'systemd'), # FIXME
|
201
257
|
)
|
202
258
|
|
203
|
-
|
259
|
+
#
|
204
260
|
|
205
|
-
|
206
|
-
app_tags = self.deploy_tags.add(
|
207
|
-
app.app,
|
208
|
-
app.key(),
|
209
|
-
DeployAppRev(app.git.rev),
|
210
|
-
)
|
261
|
+
self._deploys.make_home_current_link(self._home)
|
211
262
|
|
212
|
-
|
263
|
+
#
|
213
264
|
|
214
|
-
|
265
|
+
async def _drive_app_deploy(self, app: DeployAppSpec) -> None:
|
266
|
+
pa = await self._apps.prepare_app(
|
215
267
|
app,
|
216
268
|
self._home,
|
217
|
-
|
269
|
+
self.tags,
|
218
270
|
)
|
219
271
|
|
220
272
|
#
|
221
273
|
|
222
|
-
app_link = self.
|
274
|
+
app_link = self.render_path(self._deploys.APP_DEPLOY_LINK, pa.tags)
|
223
275
|
relative_symlink(
|
224
|
-
|
276
|
+
pa.dir,
|
225
277
|
app_link,
|
226
278
|
target_is_directory=True,
|
227
279
|
make_dirs=True,
|
@@ -229,11 +281,47 @@ class DeployDriver:
|
|
229
281
|
|
230
282
|
#
|
231
283
|
|
232
|
-
|
233
|
-
|
284
|
+
await self._drive_app_configure(pa)
|
285
|
+
|
286
|
+
async def _drive_app_link(
|
287
|
+
self,
|
288
|
+
app: DeployApp,
|
289
|
+
current_link: str,
|
290
|
+
) -> None:
|
291
|
+
app_link = os.path.join(abs_real_path(current_link), 'apps', app.s)
|
292
|
+
check.state(os.path.islink(app_link))
|
293
|
+
|
294
|
+
app_dir = abs_real_path(app_link)
|
295
|
+
check.state(os.path.isdir(app_dir))
|
296
|
+
|
297
|
+
#
|
298
|
+
|
299
|
+
pa = await self._apps.prepare_app_link(
|
300
|
+
self.tags,
|
301
|
+
app_dir,
|
302
|
+
)
|
303
|
+
|
304
|
+
#
|
305
|
+
|
306
|
+
relative_symlink(
|
307
|
+
app_dir,
|
308
|
+
os.path.join(self.dir, 'apps', app.s),
|
309
|
+
target_is_directory=True,
|
310
|
+
)
|
311
|
+
|
312
|
+
#
|
313
|
+
|
314
|
+
await self._drive_app_configure(pa)
|
315
|
+
|
316
|
+
async def _drive_app_configure(
|
317
|
+
self,
|
318
|
+
pa: DeployAppManager.PreparedApp,
|
319
|
+
) -> None:
|
320
|
+
deploy_conf_dir = self.render_path(self._deploys.CONFS_DEPLOY_DIR)
|
321
|
+
if pa.spec.conf is not None:
|
234
322
|
await self._conf.link_app_conf(
|
235
|
-
|
236
|
-
|
237
|
-
check.non_empty_str(
|
323
|
+
pa.spec.conf,
|
324
|
+
pa.tags,
|
325
|
+
check.non_empty_str(pa.conf_dir),
|
238
326
|
deploy_conf_dir,
|
239
327
|
)
|
ominfra/manage/deploy/specs.py
CHANGED
@@ -91,6 +91,13 @@ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
|
|
91
91
|
return DeployAppKey(self._key_str())
|
92
92
|
|
93
93
|
|
94
|
+
@dc.dataclass(frozen=True)
|
95
|
+
class DeployAppLinksSpec:
|
96
|
+
apps: ta.Sequence[DeployApp] = ()
|
97
|
+
|
98
|
+
exclude_unspecified: bool = False
|
99
|
+
|
100
|
+
|
94
101
|
##
|
95
102
|
|
96
103
|
|
@@ -109,6 +116,8 @@ class DeploySpec(DeploySpecKeyed[DeployKey]):
|
|
109
116
|
|
110
117
|
apps: ta.Sequence[DeployAppSpec] = ()
|
111
118
|
|
119
|
+
app_links: DeployAppLinksSpec = DeployAppLinksSpec()
|
120
|
+
|
112
121
|
systemd: ta.Optional[DeploySystemdSpec] = None
|
113
122
|
|
114
123
|
def __post_init__(self) -> None:
|
ominfra/manage/deploy/systemd.py
CHANGED
@@ -1,34 +1,17 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
"""
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
cat ~/.config/systemd/user/sleep-infinity.service
|
10
|
-
[Unit]
|
11
|
-
Description=User-specific service to run 'sleep infinity'
|
12
|
-
After=default.target
|
13
|
-
|
14
|
-
[Service]
|
15
|
-
ExecStart=/bin/sleep infinity
|
16
|
-
Restart=always
|
17
|
-
RestartSec=5
|
18
|
-
|
19
|
-
[Install]
|
20
|
-
WantedBy=default.target
|
21
|
-
|
22
|
-
systemctl --user daemon-reload
|
23
|
-
|
24
|
-
systemctl --user enable sleep-infinity.service
|
25
|
-
systemctl --user start sleep-infinity.service
|
26
|
-
|
27
|
-
systemctl --user status sleep-infinity.service
|
3
|
+
TODO:
|
4
|
+
- verify - systemd-analyze
|
5
|
+
- sudo loginctl enable-linger "$USER"
|
6
|
+
- idemp kill services that shouldn't be running, start ones that should
|
7
|
+
- ideally only those defined by links to deploy home
|
8
|
+
- ominfra.systemd / x.sd_orphans
|
28
9
|
"""
|
29
10
|
import os.path
|
11
|
+
import sys
|
30
12
|
import typing as ta
|
31
13
|
|
14
|
+
from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
|
32
15
|
from omlish.lite.check import check
|
33
16
|
from omlish.os.paths import abs_real_path
|
34
17
|
from omlish.os.paths import is_path_in_dir
|
@@ -73,6 +56,8 @@ class DeploySystemdManager:
|
|
73
56
|
if not spec:
|
74
57
|
return
|
75
58
|
|
59
|
+
#
|
60
|
+
|
76
61
|
if not (ud := spec.unit_dir):
|
77
62
|
return
|
78
63
|
|
@@ -80,6 +65,8 @@ class DeploySystemdManager:
|
|
80
65
|
|
81
66
|
os.makedirs(ud, exist_ok=True)
|
82
67
|
|
68
|
+
#
|
69
|
+
|
83
70
|
uld = {
|
84
71
|
n: p
|
85
72
|
for n, p in self._scan_link_dir(ud).items()
|
@@ -91,8 +78,11 @@ class DeploySystemdManager:
|
|
91
78
|
else:
|
92
79
|
cld = {}
|
93
80
|
|
94
|
-
|
95
|
-
|
81
|
+
#
|
82
|
+
|
83
|
+
ns = sorted(set(uld) | set(cld))
|
84
|
+
|
85
|
+
for n in ns:
|
96
86
|
cl = cld.get(n)
|
97
87
|
if cl is None:
|
98
88
|
os.unlink(os.path.join(ud, n))
|
@@ -108,3 +98,34 @@ class DeploySystemdManager:
|
|
108
98
|
os.path.relpath(cl, os.path.dirname(dst_swap.dst_path)),
|
109
99
|
dst_swap.tmp_path,
|
110
100
|
)
|
101
|
+
|
102
|
+
#
|
103
|
+
|
104
|
+
if sys.platform == 'linux':
|
105
|
+
async def reload() -> None:
|
106
|
+
await asyncio_subprocesses.check_call('systemctl', '--user', 'daemon-reload')
|
107
|
+
|
108
|
+
await reload()
|
109
|
+
|
110
|
+
num_deleted = 0
|
111
|
+
for n in ns:
|
112
|
+
if n.endswith('.service'):
|
113
|
+
cl = cld.get(n)
|
114
|
+
ul = uld.get(n)
|
115
|
+
if cl is not None:
|
116
|
+
if ul is None:
|
117
|
+
cs = ['enable', 'start']
|
118
|
+
else:
|
119
|
+
cs = ['restart']
|
120
|
+
else: # noqa
|
121
|
+
if ul is not None:
|
122
|
+
cs = ['stop']
|
123
|
+
num_deleted += 1
|
124
|
+
else:
|
125
|
+
cs = []
|
126
|
+
|
127
|
+
for c in cs:
|
128
|
+
await asyncio_subprocesses.check_call('systemctl', '--user', c, n)
|
129
|
+
|
130
|
+
if num_deleted:
|
131
|
+
await reload()
|
ominfra/manage/deploy/tags.py
CHANGED
ominfra/manage/deploy/venvs.py
CHANGED
@@ -5,6 +5,7 @@ TODO:
|
|
5
5
|
- share more code with pyproject?
|
6
6
|
"""
|
7
7
|
import os.path
|
8
|
+
import shutil
|
8
9
|
|
9
10
|
from omdev.interp.default import get_default_interp_resolver
|
10
11
|
from omdev.interp.types import InterpSpecifier
|
@@ -44,9 +45,12 @@ class DeployVenvManager:
|
|
44
45
|
|
45
46
|
if os.path.isfile(reqs_txt):
|
46
47
|
if spec.use_uv:
|
47
|
-
|
48
|
-
|
48
|
+
if shutil.which('uv') is not None:
|
49
|
+
pip_cmd = ['uv', 'pip']
|
50
|
+
else:
|
51
|
+
await asyncio_subprocesses.check_call(venv_exe, '-m', 'pip', 'install', 'uv')
|
52
|
+
pip_cmd = [venv_exe, '-m', 'uv', 'pip']
|
49
53
|
else:
|
50
|
-
pip_cmd = ['-m', 'pip']
|
54
|
+
pip_cmd = [venv_exe, '-m', 'pip']
|
51
55
|
|
52
|
-
await asyncio_subprocesses.check_call(
|
56
|
+
await asyncio_subprocesses.check_call(*pip_cmd, 'install', '-r', reqs_txt, cwd=venv_dir)
|