fmtr.tools 1.1.1__py3-none-any.whl → 1.4.37__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.
- fmtr/tools/__init__.py +86 -52
- fmtr/tools/ai_tools/__init__.py +2 -2
- fmtr/tools/ai_tools/agentic_tools.py +151 -32
- fmtr/tools/ai_tools/inference_tools.py +2 -1
- fmtr/tools/api_tools.py +73 -12
- fmtr/tools/async_tools.py +4 -0
- fmtr/tools/av_tools.py +7 -0
- fmtr/tools/caching_tools.py +101 -3
- fmtr/tools/constants.py +41 -0
- fmtr/tools/context_tools.py +23 -0
- fmtr/tools/data_modelling_tools.py +227 -14
- fmtr/tools/database_tools/__init__.py +6 -0
- fmtr/tools/database_tools/document.py +51 -0
- fmtr/tools/datatype_tools.py +22 -2
- fmtr/tools/datetime_tools.py +12 -0
- fmtr/tools/debugging_tools.py +60 -1
- fmtr/tools/dns_tools/__init__.py +7 -0
- fmtr/tools/dns_tools/client.py +97 -0
- fmtr/tools/dns_tools/dm.py +257 -0
- fmtr/tools/dns_tools/proxy.py +66 -0
- fmtr/tools/dns_tools/server.py +138 -0
- fmtr/tools/docker_tools/__init__.py +6 -0
- fmtr/tools/entrypoints/__init__.py +0 -0
- fmtr/tools/entrypoints/cache_hfh.py +3 -0
- fmtr/tools/entrypoints/ep_test.py +2 -0
- fmtr/tools/entrypoints/install_yamlscript.py +8 -0
- fmtr/tools/{console_script_tools.py → entrypoints/remote_debug_test.py} +1 -6
- fmtr/tools/entrypoints/shell_debug.py +8 -0
- fmtr/tools/environment_tools.py +3 -2
- fmtr/tools/function_tools.py +77 -1
- fmtr/tools/google_api_tools.py +15 -4
- fmtr/tools/ha_tools/__init__.py +8 -0
- fmtr/tools/ha_tools/constants.py +9 -0
- fmtr/tools/ha_tools/core.py +16 -0
- fmtr/tools/ha_tools/supervisor.py +16 -0
- fmtr/tools/ha_tools/utils.py +46 -0
- fmtr/tools/http_tools.py +52 -0
- fmtr/tools/inherit_tools.py +27 -0
- fmtr/tools/interface_tools/__init__.py +8 -0
- fmtr/tools/interface_tools/context.py +13 -0
- fmtr/tools/interface_tools/controls.py +354 -0
- fmtr/tools/interface_tools/interface_tools.py +189 -0
- fmtr/tools/iterator_tools.py +122 -1
- fmtr/tools/logging_tools.py +99 -18
- fmtr/tools/mqtt_tools.py +89 -0
- fmtr/tools/networking_tools.py +73 -0
- fmtr/tools/packaging_tools.py +14 -0
- fmtr/tools/path_tools/__init__.py +12 -0
- fmtr/tools/path_tools/app_path_tools.py +40 -0
- fmtr/tools/{path_tools.py → path_tools/path_tools.py} +217 -14
- fmtr/tools/path_tools/type_path_tools.py +3 -0
- fmtr/tools/pattern_tools.py +277 -0
- fmtr/tools/pdf_tools.py +39 -1
- fmtr/tools/settings_tools.py +27 -6
- fmtr/tools/setup_tools/__init__.py +8 -0
- fmtr/tools/setup_tools/setup_tools.py +481 -0
- fmtr/tools/string_tools.py +92 -13
- fmtr/tools/tabular_tools.py +61 -0
- fmtr/tools/tools.py +27 -2
- fmtr/tools/version +1 -1
- fmtr/tools/version_tools/__init__.py +12 -0
- fmtr/tools/version_tools/version_tools.py +51 -0
- fmtr/tools/webhook_tools.py +17 -0
- fmtr/tools/yaml_tools.py +64 -5
- fmtr/tools/youtube_tools.py +128 -0
- fmtr_tools-1.4.37.data/scripts/add-service +14 -0
- fmtr_tools-1.4.37.data/scripts/add-user-path +8 -0
- fmtr_tools-1.4.37.data/scripts/apt-headless +23 -0
- fmtr_tools-1.4.37.data/scripts/compose-update +10 -0
- fmtr_tools-1.4.37.data/scripts/docker-sandbox +43 -0
- fmtr_tools-1.4.37.data/scripts/docker-sandbox-init +23 -0
- fmtr_tools-1.4.37.data/scripts/docs-deploy +6 -0
- fmtr_tools-1.4.37.data/scripts/docs-serve +5 -0
- fmtr_tools-1.4.37.data/scripts/download +9 -0
- fmtr_tools-1.4.37.data/scripts/fmtr-test-script +3 -0
- fmtr_tools-1.4.37.data/scripts/ftu +3 -0
- fmtr_tools-1.4.37.data/scripts/ha-addon-launch +16 -0
- fmtr_tools-1.4.37.data/scripts/install-browser +8 -0
- fmtr_tools-1.4.37.data/scripts/parse-args +43 -0
- fmtr_tools-1.4.37.data/scripts/set-password +5 -0
- fmtr_tools-1.4.37.data/scripts/snips-install +14 -0
- fmtr_tools-1.4.37.data/scripts/ssh-auth +28 -0
- fmtr_tools-1.4.37.data/scripts/ssh-serve +15 -0
- fmtr_tools-1.4.37.data/scripts/vlc-tn +10 -0
- fmtr_tools-1.4.37.data/scripts/vm-launch +17 -0
- {fmtr_tools-1.1.1.dist-info → fmtr_tools-1.4.37.dist-info}/METADATA +178 -54
- fmtr_tools-1.4.37.dist-info/RECORD +122 -0
- {fmtr_tools-1.1.1.dist-info → fmtr_tools-1.4.37.dist-info}/WHEEL +1 -1
- fmtr_tools-1.4.37.dist-info/entry_points.txt +6 -0
- fmtr_tools-1.4.37.dist-info/top_level.txt +1 -0
- fmtr/tools/docker_tools.py +0 -30
- fmtr/tools/interface_tools.py +0 -64
- fmtr/tools/version_tools.py +0 -62
- fmtr_tools-1.1.1.dist-info/RECORD +0 -65
- fmtr_tools-1.1.1.dist-info/entry_points.txt +0 -3
- fmtr_tools-1.1.1.dist-info/top_level.txt +0 -2
- {fmtr_tools-1.1.1.dist-info → fmtr_tools-1.4.37.dist-info}/licenses/LICENSE +0 -0
fmtr/tools/tools.py
CHANGED
|
@@ -27,7 +27,15 @@ def identity(x: Any) -> Any:
|
|
|
27
27
|
return x
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class
|
|
30
|
+
class Special:
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
Classes to differentiate special arguments from primitive arguments.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Empty(Special):
|
|
31
39
|
"""
|
|
32
40
|
|
|
33
41
|
Class to denote an unspecified object (e.g. argument) when `None` cannot be used.
|
|
@@ -35,7 +43,7 @@ class Empty:
|
|
|
35
43
|
"""
|
|
36
44
|
|
|
37
45
|
|
|
38
|
-
class Raise:
|
|
46
|
+
class Raise(Special):
|
|
39
47
|
"""
|
|
40
48
|
|
|
41
49
|
Class to denote when a function should raise instead of e.g. returning a default.
|
|
@@ -43,4 +51,21 @@ class Raise:
|
|
|
43
51
|
"""
|
|
44
52
|
|
|
45
53
|
|
|
54
|
+
class Auto(Special):
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
Class to denote when an argument should be inferred.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Required(Special):
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
Class to denote when an argument is required.
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
46
71
|
EMPTY = Empty()
|
fmtr/tools/version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.4.37
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from fmtr.tools.import_tools import MissingExtraMockModule
|
|
2
|
+
|
|
3
|
+
from fmtr.tools.version_tools.version_tools import read, read_path, get
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
import semver
|
|
7
|
+
|
|
8
|
+
semver = semver
|
|
9
|
+
parse = semver.VersionInfo.parse
|
|
10
|
+
|
|
11
|
+
except ModuleNotFoundError as exception:
|
|
12
|
+
semver = parse = MissingExtraMockModule('version.dev', exception)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from fmtr.tools import environment_tools as env
|
|
2
|
+
from fmtr.tools.constants import Constants
|
|
3
|
+
from fmtr.tools.inspection_tools import get_call_path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def read() -> str:
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
Read a generic version file from the calling package path.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
path = get_call_path(offset=2).parent / Constants.FILENAME_VERSION
|
|
14
|
+
return read_path(path)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def read_path(path) -> str:
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
Read in version from specified path
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
from fmtr.tools.tools import Constants
|
|
24
|
+
text = path.read_text(encoding=Constants.ENCODING).strip()
|
|
25
|
+
|
|
26
|
+
text = get(text)
|
|
27
|
+
return text
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get(text) -> str:
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
Optionally add dev build info to raw version string.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
if not env.IS_DEV:
|
|
38
|
+
return text
|
|
39
|
+
|
|
40
|
+
import datetime
|
|
41
|
+
from fmtr.tools.tools import Constants
|
|
42
|
+
from fmtr.tools.version_tools import parse
|
|
43
|
+
|
|
44
|
+
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime(Constants.DATETIME_SEMVER_BUILD_FORMAT)
|
|
45
|
+
|
|
46
|
+
version = parse(text)
|
|
47
|
+
version = version.bump_patch()
|
|
48
|
+
version = version.replace(prerelease=Constants.DEVELOPMENT, build=timestamp)
|
|
49
|
+
text = str(version)
|
|
50
|
+
|
|
51
|
+
return text
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from fmtr.tools import environment_tools, Constants
|
|
2
|
+
from fmtr.tools.http_tools import client
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def notify(title, body, url=None):
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
Send simple debug notification
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
url = url or environment_tools.get(Constants.WEBHOOK_URL_NOTIFY_KEY)
|
|
12
|
+
client.post(url, json=dict(title=title, body=body))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if __name__ == '__main__':
|
|
16
|
+
notify('Title', 'Body')
|
|
17
|
+
notify
|
fmtr/tools/yaml_tools.py
CHANGED
|
@@ -1,12 +1,63 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
1
2
|
from typing import Any
|
|
2
|
-
from yaml import
|
|
3
|
-
from yaml import
|
|
3
|
+
from yaml import CDumper as Dumper
|
|
4
|
+
from yaml import dump
|
|
4
5
|
|
|
6
|
+
try:
|
|
7
|
+
import yamlscript
|
|
8
|
+
except ImportError:
|
|
9
|
+
raise # Raise if even the package isn't installed, to trigger the regular missing extra exception.
|
|
10
|
+
except Exception as exception:
|
|
11
|
+
pass # Allow missing binary, so we can install on-demand
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def install():
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
Installs the YAML Script runtime binary of the specified version.
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
import subprocess
|
|
21
|
+
from fmtr.tools import logger, packaging
|
|
22
|
+
|
|
23
|
+
version = packaging.get_version('yamlscript')
|
|
24
|
+
logger.warning(f"Installing YAML Script runtime binary version {version}...")
|
|
25
|
+
result = subprocess.run(f"curl https://yamlscript.org/install | VERSION={version} LIB=1 bash", shell=True, check=True)
|
|
26
|
+
return result
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@lru_cache
|
|
30
|
+
def get_module(is_auto=True):
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
Get the YAML Script runtime module, installing the runtime if specified
|
|
5
34
|
|
|
6
|
-
def to_yaml(obj: Any) -> str:
|
|
7
35
|
"""
|
|
36
|
+
try:
|
|
37
|
+
import yamlscript
|
|
38
|
+
except Exception as exception:
|
|
39
|
+
if not is_auto:
|
|
40
|
+
raise ImportError(f'YAML Script runtime missing and {is_auto=}. Set to {True} to install.') from exception
|
|
41
|
+
install()
|
|
42
|
+
import yamlscript
|
|
43
|
+
return yamlscript
|
|
8
44
|
|
|
9
45
|
|
|
46
|
+
@lru_cache
|
|
47
|
+
def get_interpreter():
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
Fetches and returns a cached instance of the YAMLScript interpreter.
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
module = get_module()
|
|
54
|
+
interpreter = module.YAMLScript()
|
|
55
|
+
return interpreter
|
|
56
|
+
|
|
57
|
+
def to_yaml(obj: Any) -> str:
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
Serialize to YAML
|
|
10
61
|
|
|
11
62
|
"""
|
|
12
63
|
yaml_str = dump(obj, allow_unicode=True, Dumper=Dumper)
|
|
@@ -16,8 +67,16 @@ def to_yaml(obj: Any) -> str:
|
|
|
16
67
|
def from_yaml(yaml_str: str) -> Any:
|
|
17
68
|
"""
|
|
18
69
|
|
|
19
|
-
|
|
70
|
+
Deserialize from YAML
|
|
20
71
|
|
|
21
72
|
"""
|
|
22
|
-
obj = load(yaml_str
|
|
73
|
+
obj = get_interpreter().load(yaml_str)
|
|
23
74
|
return obj
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == '__main__':
|
|
78
|
+
from fmtr.tools import Path
|
|
79
|
+
|
|
80
|
+
py = Path('hw.yml')
|
|
81
|
+
data = py.read_yaml()
|
|
82
|
+
data
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from pytubefix import YouTube, Stream, extract, request
|
|
4
|
+
from pytubefix.exceptions import RegexMatchError
|
|
5
|
+
from typing import AsyncIterator, Iterator
|
|
6
|
+
from urllib.error import HTTPError
|
|
7
|
+
|
|
8
|
+
from fmtr.tools.path_tools.path_tools import Path
|
|
9
|
+
|
|
10
|
+
Stream = Stream
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Video(YouTube):
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
Video stub
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AudioStreamDownloadError(Exception):
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
Error downloading audio stream
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class AudioStreamData:
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
Audio stream download data and progress information
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
message: str | None = None
|
|
38
|
+
chunk: bytes | None = None
|
|
39
|
+
percentage: int | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AudioStreamDownloader:
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
Download the highest-bitrate audio stream and write to temp directory.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, url_or_id: str):
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
Initialise with URL or video ID
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
self.id = extract.video_id(url_or_id)
|
|
58
|
+
except RegexMatchError:
|
|
59
|
+
self.id = url_or_id
|
|
60
|
+
|
|
61
|
+
self.path = None
|
|
62
|
+
|
|
63
|
+
@cached_property
|
|
64
|
+
def url(self) -> str:
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
Get URL from ID
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
return f'https://youtube.com/watch?v={self.id}'
|
|
71
|
+
|
|
72
|
+
async def download(self) -> AsyncIterator[AudioStreamData]:
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
Download the audio stream and yield chunks and progress information
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
yield AudioStreamData(message='Fetching video metadata...')
|
|
80
|
+
video = Video(self.url)
|
|
81
|
+
|
|
82
|
+
yield AudioStreamData('Finding audio streams...')
|
|
83
|
+
|
|
84
|
+
audio_streams = video.streams.filter(only_audio=True).order_by('bitrate')
|
|
85
|
+
if not audio_streams:
|
|
86
|
+
raise AudioStreamDownloadError(f'Error downloading: no audio streams found in "{video.title}"')
|
|
87
|
+
|
|
88
|
+
stream = audio_streams.last()
|
|
89
|
+
yield AudioStreamData(f'Found highest-bitrate audio stream: {stream.audio_codec}/{stream.subtype}@{stream.abr}')
|
|
90
|
+
|
|
91
|
+
self.path = Path.temp() / stream.default_filename
|
|
92
|
+
if self.path.exists():
|
|
93
|
+
self.path.unlink()
|
|
94
|
+
|
|
95
|
+
if stream.filesize == 0:
|
|
96
|
+
raise AudioStreamDownloadError(f'Error downloading: empty audio stream found in "{video.title}"')
|
|
97
|
+
|
|
98
|
+
yield AudioStreamData('Downloading...')
|
|
99
|
+
|
|
100
|
+
with self.path.open('wb') as out_file:
|
|
101
|
+
for data in self.iter_data(stream):
|
|
102
|
+
out_file.write(data.chunk)
|
|
103
|
+
yield data
|
|
104
|
+
|
|
105
|
+
def iter_data(self, stream: Stream, chunk_size: int | None = None) -> Iterator[AudioStreamData]:
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
Iterate over chunks of the specified size
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
bytes_total = bytes_remaining = stream.filesize
|
|
112
|
+
|
|
113
|
+
if chunk_size:
|
|
114
|
+
request.default_range_size = chunk_size
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
stream = request.stream(stream.url)
|
|
118
|
+
except HTTPError as e:
|
|
119
|
+
if e.code != 404:
|
|
120
|
+
raise
|
|
121
|
+
stream = request.seq_stream(stream.url)
|
|
122
|
+
|
|
123
|
+
for chunk in stream:
|
|
124
|
+
bytes_remaining -= len(chunk)
|
|
125
|
+
percentage = round(((bytes_total - bytes_remaining) / bytes_total) * 100)
|
|
126
|
+
|
|
127
|
+
data = AudioStreamData(chunk=chunk, percentage=percentage)
|
|
128
|
+
yield data
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
. parse-args "$@"
|
|
4
|
+
|
|
5
|
+
loginctl enable-linger $USER
|
|
6
|
+
|
|
7
|
+
NAME=$(basename "$FILE")
|
|
8
|
+
|
|
9
|
+
mkdir -p ~/.config/systemd/user
|
|
10
|
+
cp "$FILE" ~/.config/systemd/user/
|
|
11
|
+
systemctl --user daemon-reload
|
|
12
|
+
systemctl --user enable "$NAME"
|
|
13
|
+
systemctl --user start "$NAME"
|
|
14
|
+
systemctl --user status -l -n 50 "$NAME"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
source $SCRIPT_DIR/parse-args "$@"
|
|
7
|
+
|
|
8
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
9
|
+
apt update --yes
|
|
10
|
+
|
|
11
|
+
# if --full was passed, run full-upgrade
|
|
12
|
+
if [ "${FULL}" = "1" ]; then
|
|
13
|
+
apt --yes full-upgrade
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# if ARGS is not empty, install the packages
|
|
17
|
+
if [ ${#ARGS[@]} -gt 0 ]; then
|
|
18
|
+
apt install --yes --no-install-recommends "${ARGS[@]}"
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
apt autoremove --yes
|
|
22
|
+
apt clean
|
|
23
|
+
rm -rf /var/lib/apt/lists/*
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
. parse-args "$@"
|
|
4
|
+
|
|
5
|
+
PROJECT_DIR=/opt/dev/repo/infrastructure/${CONTEXT-gex}/docker/${SERVICE}
|
|
6
|
+
CONTEXT=${CONTEXT-gex}
|
|
7
|
+
|
|
8
|
+
docker --context "$CONTEXT" compose --project-directory "$PROJECT_DIR" pull
|
|
9
|
+
docker --context "$CONTEXT" compose --project-directory "$PROJECT_DIR" up --detach
|
|
10
|
+
docker --context "$CONTEXT" compose --project-directory "$PROJECT_DIR" logs --follow
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
. parse-args "$@"
|
|
4
|
+
|
|
5
|
+
#Spin up the container in the background with sleep infinity
|
|
6
|
+
CONTAINER_ID=$(docker run -d \
|
|
7
|
+
--hostname=sandbox \
|
|
8
|
+
--user=user \
|
|
9
|
+
--volume=/opt/dev/:/opt/dev/ \
|
|
10
|
+
--publish=${PORT-9000}:${PORT-9000} \
|
|
11
|
+
${IMAGE-fmtr/python} \
|
|
12
|
+
bash -c "sleep infinity")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Capture Docker's default name
|
|
16
|
+
DEFAULT_NAME=$(docker inspect --format '{{.Name}}' $CONTAINER_ID | cut -c2-)
|
|
17
|
+
|
|
18
|
+
# Modify it dynamically (prefix or suffix)
|
|
19
|
+
MODIFIED_NAME="sandbox_${DEFAULT_NAME}"
|
|
20
|
+
docker rename $CONTAINER_ID $MODIFIED_NAME
|
|
21
|
+
|
|
22
|
+
echo "Started container $MODIFIED_NAME."
|
|
23
|
+
|
|
24
|
+
#Run the sandbox-init script as root (interactive)
|
|
25
|
+
docker exec \
|
|
26
|
+
--interactive=true \
|
|
27
|
+
--tty=true \
|
|
28
|
+
--user=root \
|
|
29
|
+
$CONTAINER_ID \
|
|
30
|
+
/opt/dev/repo/fmtr.tools/scripts/docker-sandbox-init --tools
|
|
31
|
+
|
|
32
|
+
#Start an interactive bash session as user
|
|
33
|
+
docker exec \
|
|
34
|
+
--interactive=true \
|
|
35
|
+
--tty=true \
|
|
36
|
+
--user=user \
|
|
37
|
+
--env USER=user \
|
|
38
|
+
$CONTAINER_ID \
|
|
39
|
+
bash
|
|
40
|
+
|
|
41
|
+
# Cleanup
|
|
42
|
+
docker rm -f $MODIFIED_NAME
|
|
43
|
+
echo "Container $MODIFIED_NAME removed."
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
SECURE_PATH=$PATH
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source $SCRIPT_DIR/add-user-path --username=user --new=$SCRIPT_DIR
|
|
9
|
+
|
|
10
|
+
. parse-args "$@"
|
|
11
|
+
|
|
12
|
+
echo "Setting up sandbox..."
|
|
13
|
+
|
|
14
|
+
set-password
|
|
15
|
+
set-password --username=user
|
|
16
|
+
|
|
17
|
+
echo "user ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/sandbox
|
|
18
|
+
echo "Defaults secure_path=\"${SECURE_PATH}\"" >> /etc/sudoers.d/sandbox
|
|
19
|
+
chmod 440 /etc/sudoers.d/sandbox
|
|
20
|
+
|
|
21
|
+
if [ "$TOOLS" == "1" ]; then
|
|
22
|
+
sudo -u user -H pip install --user fmtr.tools --upgrade
|
|
23
|
+
fi
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/with-contenv bashio
|
|
2
|
+
|
|
3
|
+
. parse-args "$@"
|
|
4
|
+
|
|
5
|
+
bashio::log.info "${NAME} container started."
|
|
6
|
+
|
|
7
|
+
export FMTR_DEV="$(bashio::config 'fmtr_dev')"
|
|
8
|
+
|
|
9
|
+
if bashio::var.true "${FMTR_DEV}"; then
|
|
10
|
+
bashio::log.info "Starting ${NAME} SSH development server"
|
|
11
|
+
printenv > /addon.env
|
|
12
|
+
echo "root:password" | chpasswd
|
|
13
|
+
/usr/sbin/sshd -D -o Port=22 -o PermitRootLogin=yes -o PasswordAuthentication=yes -o AllowTcpForwarding=yes -o LogLevel=VERBOSE
|
|
14
|
+
else
|
|
15
|
+
${NAME}
|
|
16
|
+
fi
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
sudo -u user /opt/dev/venv/fmtr.tools/bin/python -m pip install fmtr.tools[browsers] --upgrade
|
|
7
|
+
/opt/dev/venv/fmtr.tools/bin/python -m playwright install-deps
|
|
8
|
+
sudo -u user /opt/dev/venv/fmtr.tools/bin/python -m playwright install chromium
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
ARGS=() # positional arguments
|
|
4
|
+
|
|
5
|
+
while [ $# -gt 0 ]; do
|
|
6
|
+
case "$1" in
|
|
7
|
+
# --key=value or -k=value
|
|
8
|
+
--*=* | -*=*)
|
|
9
|
+
key=${1%%=*}
|
|
10
|
+
key=${key#--}
|
|
11
|
+
key=${key#-}
|
|
12
|
+
key=$(printf '%s' "$key" | tr 'a-z-' 'A-Z_')
|
|
13
|
+
|
|
14
|
+
val=${1#*=}
|
|
15
|
+
export "$key=$val"
|
|
16
|
+
|
|
17
|
+
unset key val # remove temporary variables
|
|
18
|
+
shift
|
|
19
|
+
;;
|
|
20
|
+
# --key value or -k value, or boolean flag
|
|
21
|
+
--* | -*)
|
|
22
|
+
key=${1#--}
|
|
23
|
+
key=${key#-}
|
|
24
|
+
key=$(printf '%s' "$key" | tr 'a-z-' 'A-Z_')
|
|
25
|
+
|
|
26
|
+
if [ -n "$2" ] && [[ ! "$2" =~ ^- ]]; then
|
|
27
|
+
val="$2"
|
|
28
|
+
export "$key=$val"
|
|
29
|
+
shift 2
|
|
30
|
+
else
|
|
31
|
+
export "$key=1"
|
|
32
|
+
shift
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
unset key val # remove temporary variables
|
|
36
|
+
;;
|
|
37
|
+
# positional arguments
|
|
38
|
+
*)
|
|
39
|
+
ARGS+=("$1")
|
|
40
|
+
shift
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
43
|
+
done
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
. parse-args "$@"
|
|
4
|
+
|
|
5
|
+
DIR="/home/$(whoami)/.local/bin/"
|
|
6
|
+
REPO="snips"
|
|
7
|
+
BRANCH="main"
|
|
8
|
+
URL="https://github.com/${ORG}/${REPO}/archive/refs/heads/${BRANCH}.zip"
|
|
9
|
+
|
|
10
|
+
TMPDIR=$(mktemp -d)
|
|
11
|
+
download --url="$URL" --file=$TMPDIR/snips.zip
|
|
12
|
+
unzip $TMPDIR/snips.zip -d $TMPDIR
|
|
13
|
+
mv "$TMPDIR/${REPO}-${BRANCH}/"* "$DIR"
|
|
14
|
+
rm -rf "$TMPDIR"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
. parse-args "$@"
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
if [ -z "${USERNAME}" ]; then
|
|
8
|
+
echo "Error: --username argument is not set" >&2
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
SSH_DIR="$HOME/.ssh"
|
|
13
|
+
AUTHORIZED_KEYS="$SSH_DIR/authorized_keys"
|
|
14
|
+
|
|
15
|
+
mkdir -p "$SSH_DIR"
|
|
16
|
+
chmod 700 "$SSH_DIR"
|
|
17
|
+
|
|
18
|
+
TMP_KEYS=$(mktemp)
|
|
19
|
+
curl -fsSL "https://github.com/${USERNAME}.keys" > "$TMP_KEYS"
|
|
20
|
+
|
|
21
|
+
touch "$AUTHORIZED_KEYS"
|
|
22
|
+
chmod 600 "$AUTHORIZED_KEYS"
|
|
23
|
+
|
|
24
|
+
grep -Fvx -f "$AUTHORIZED_KEYS" "$TMP_KEYS" >> "$AUTHORIZED_KEYS"
|
|
25
|
+
|
|
26
|
+
rm "$TMP_KEYS"
|
|
27
|
+
|
|
28
|
+
echo "SSH keys from '${USERNAME}' added to '${AUTHORIZED_KEYS}'"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
FMTR_DEV_SSH_PORT=${FMTR_DEV_SSH_PORT:-22}
|
|
6
|
+
|
|
7
|
+
[ -f /home/user/.ssh/ssh_host ] || ssh-keygen -f /home/user/.ssh/ssh_host -N '' -t rsa
|
|
8
|
+
|
|
9
|
+
exec /usr/sbin/sshd -D -e -p "$FMTR_DEV_SSH_PORT" \
|
|
10
|
+
-h /home/user/.ssh/ssh_host \
|
|
11
|
+
-o PasswordAuthentication=no \
|
|
12
|
+
-o PermitRootLogin=prohibit-password \
|
|
13
|
+
-o PubkeyAuthentication=yes \
|
|
14
|
+
-o AuthorizedKeysFile=/home/user/.ssh/authorized_keys \
|
|
15
|
+
-o LogLevel=VERBOSE
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
. parse-args "$@"
|
|
4
|
+
|
|
5
|
+
if ! command -v vlc >/dev/null 2>&1; then
|
|
6
|
+
echo "VLC not found. Installing dependencies..."
|
|
7
|
+
sudo apt-headless vlc-bin vlc-plugin-base
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
vlc -I telnet --telnet-password ${PASSWORD-password} --telnet-host ${HOST-0.0.0.0}:${PORT-4212}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
. parse-args "$@"
|
|
4
|
+
|
|
5
|
+
sudo qemu-system-x86_64 \
|
|
6
|
+
-enable-kvm \
|
|
7
|
+
-m ${GB-12}G \
|
|
8
|
+
-smp ${CPUS-4} \
|
|
9
|
+
-cpu host \
|
|
10
|
+
-machine q35 \
|
|
11
|
+
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
|
|
12
|
+
-drive if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS_4M.fd \
|
|
13
|
+
-drive file=/opt/dev/vm/${NAME-default}/disk.qcow2,format=qcow2,if=virtio,cache=writeback,aio=threads \
|
|
14
|
+
-device virtio-net-pci,netdev=net0 \
|
|
15
|
+
-netdev bridge,id=net0,br=${BR-br0} \
|
|
16
|
+
-display ${DISPLAY_VM-gtk} \
|
|
17
|
+
-serial mon:stdio
|