mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 10:52:22 +00:00
i18n: complete OCI runtime and maintenance Slovak diagnostics
This commit is contained in:
@@ -93,8 +93,8 @@ def main() -> None:
|
||||
rootfs = Path(sys.argv[1])
|
||||
configuration = json.loads(sys.argv[2])
|
||||
path, changed = update_encoding(rootfs, configuration)
|
||||
state = "updated" if changed else "already applied"
|
||||
print(f"Jellyfin configuration {state}: /{path.relative_to(rootfs.resolve())}")
|
||||
state = translate("Jellyfin configuration updated:") if changed else translate("Jellyfin configuration already applied:")
|
||||
print(f"{state} /{path.relative_to(rootfs.resolve())}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -20,6 +20,7 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from oci_ui import translate
|
||||
|
||||
CONFIGS = Path('/etc/pve/lxc')
|
||||
HOOKS = Path('/var/lib/lxc')
|
||||
@@ -121,12 +122,13 @@ def check(vmid, settle=0):
|
||||
except BlockingIOError:
|
||||
return
|
||||
for name, version in repair(vmid, settle):
|
||||
print(f'CT {vmid}: DHCP client of {name} (IPv{version}) started again', flush=True)
|
||||
print(translate('CT {vmid}: DHCP client of {name} (IPv{version}) started again').format(
|
||||
vmid=vmid, name=name, version=version), flush=True)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2 or not (sys.argv[1].isdigit() or sys.argv[1] == '--all'):
|
||||
print('usage: oci_dhcp_lease.py <vmid> | --all', file=sys.stderr)
|
||||
print(translate('Usage:') + ' oci_dhcp_lease.py <vmid> | --all', file=sys.stderr)
|
||||
return 2
|
||||
LOCKS.mkdir(parents=True, exist_ok=True)
|
||||
if sys.argv[1] == '--all':
|
||||
|
||||
@@ -10,6 +10,7 @@ import sys
|
||||
import time
|
||||
|
||||
import oci_instances as instances
|
||||
from oci_ui import translate
|
||||
|
||||
# Names given by the OCI installers and by updates; other archives are never touched.
|
||||
NAME = re.compile(r'(?:proxmenux-update-[a-z0-9]+-[0-9a-f]{64}'
|
||||
@@ -82,10 +83,10 @@ def main(arguments):
|
||||
command = arguments[0] if arguments else 'prune'
|
||||
if command == 'prune':
|
||||
for path, size in prune():
|
||||
print(f'removed unused image archive: {path} ({size} bytes)')
|
||||
print(translate('removed unused image archive: {path} ({size} bytes)').format(path=path, size=size))
|
||||
return 0
|
||||
if command not in ('list', 'remove') or not all(a.isdigit() for a in arguments[1:]):
|
||||
print('usage: oci_image_cache.py [prune | list VMID... | remove VMID...]', file=sys.stderr)
|
||||
print(translate('Usage:') + ' oci_image_cache.py [prune | list VMID... | remove VMID...]', file=sys.stderr)
|
||||
return 2
|
||||
with instances.locked(instances.ROOT):
|
||||
archives = archives_of([int(a) for a in arguments[1:]])
|
||||
@@ -103,5 +104,5 @@ if __name__ == '__main__':
|
||||
try:
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
except (OSError, ValueError, BlockingIOError) as error:
|
||||
print(f'image cache: {error}', file=sys.stderr)
|
||||
print(f"{translate('Image cache')}: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -284,7 +284,7 @@ def change(root, primary_id, acceleration, render_device=None, gfx_override=None
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('vmid', type=int, help='main container of the application')
|
||||
parser.add_argument('vmid', type=int, help=translate('main container of the application'))
|
||||
parser.add_argument('--acceleration', required=True, choices=PROFILES)
|
||||
parser.add_argument('--render-device')
|
||||
parser.add_argument('--gfx-override')
|
||||
|
||||
@@ -301,7 +301,7 @@ def main(argv=None):
|
||||
diagnose = sub.add_parser('diagnose')
|
||||
diagnose.add_argument('vmid', type=int)
|
||||
diagnose.add_argument('--check-registry', action='store_true')
|
||||
record = sub.add_parser('record', help='Internal installer operation; not legacy adoption')
|
||||
record = sub.add_parser('record', help=translate('Internal installer operation; not legacy adoption'))
|
||||
record.add_argument('vmid', type=int)
|
||||
for flag in ('template', 'deployment', 'archive', 'digest'):
|
||||
record.add_argument('--' + flag, required=True)
|
||||
|
||||
@@ -1029,9 +1029,9 @@ def main():
|
||||
parser.add_argument('--root', type=Path, default=instances.ROOT)
|
||||
parser.add_argument('--start', action='store_true')
|
||||
parser.add_argument('--host-firewall', type=int, action='append', default=[], metavar='VMID',
|
||||
help='Add the host firewall rule of this container; one option for each confirmed rule')
|
||||
help=translate('Add the host firewall rule of this container; one option for each confirmed rule'))
|
||||
parser.add_argument('--automatic', action='store_true',
|
||||
help='Only the applications that need no question: the ones the cluster keeps a record of')
|
||||
help=translate('Only the applications that need no question: the ones the cluster keeps a record of'))
|
||||
args = parser.parse_args()
|
||||
if os.geteuid() != 0:
|
||||
parser.error(translate('Root privileges are required'))
|
||||
|
||||
@@ -12,6 +12,7 @@ import argparse
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from oci_ui import translate
|
||||
|
||||
# A model of five operators (convolution, pooling and a dense layer), enough
|
||||
# to make ROCm compile and run on the GPU without downloading anything.
|
||||
@@ -72,7 +73,8 @@ def main():
|
||||
try:
|
||||
check(args.vmid)
|
||||
except RuntimeError as error:
|
||||
print(error, file=sys.stderr)
|
||||
message = translate('the GPU did not answer in time') if str(error) == 'the GPU did not answer in time' else str(error)
|
||||
print(message, file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import time
|
||||
|
||||
import oci_instances as instances
|
||||
from oci_installation_state import parse_config
|
||||
from oci_ui import translate
|
||||
|
||||
APPS = Path('/etc/proxmenux/apps')
|
||||
SOCKET = Path('/run/proxmenux-monitor/oci-check.sock')
|
||||
@@ -206,7 +207,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--root', type=Path, default=instances.ROOT)
|
||||
parser.add_argument('--configs', type=Path, default=Path('/etc/pve/lxc'))
|
||||
parser.add_argument('--progress', action='store_true', help='Emit numeric progress for the OCI menu')
|
||||
parser.add_argument('--progress', action='store_true', help=translate('Emit numeric progress for the OCI menu'))
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
if args.progress:
|
||||
|
||||
@@ -19,6 +19,7 @@ import time
|
||||
|
||||
import oci_instances as instances
|
||||
import oci_operation_notice
|
||||
from oci_ui import translate
|
||||
|
||||
UNIT = 'proxmenux-oci-watchdog.service'
|
||||
UNIT_FILE = Path('/etc/systemd/system') / UNIT
|
||||
@@ -191,7 +192,8 @@ def look(states, now):
|
||||
lambda seen: stop_requested(read_tasks(), vmid, seen))
|
||||
data = {'app_name': name, 'vmid': vmid, 'containers': f'CT {vmid}'}
|
||||
if action == 'start':
|
||||
print(f'CT {vmid} ({name}) stopped on its own; starting it again (attempt {state["attempts"]})', flush=True)
|
||||
print(translate('CT {vmid} ({name}) stopped on its own; starting it again (attempt {attempt})').format(
|
||||
vmid=vmid, name=name, attempt=state['attempts']), flush=True)
|
||||
try:
|
||||
started = start(vmid)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
@@ -199,7 +201,8 @@ def look(states, now):
|
||||
if started and notice_due(state, now):
|
||||
oci_operation_notice.notify('oci_watchdog_restarted', data)
|
||||
elif action == 'give-up':
|
||||
print(f'CT {vmid} ({name}) could not be kept running; it is left stopped', flush=True)
|
||||
print(translate('CT {vmid} ({name}) could not be kept running; it is left stopped').format(
|
||||
vmid=vmid, name=name), flush=True)
|
||||
oci_operation_notice.notify('oci_watchdog_failed', data)
|
||||
|
||||
|
||||
@@ -290,7 +293,7 @@ def main():
|
||||
try:
|
||||
vmids = set_watchdog(instances.ROOT, args.vmid, args.state == 'on')
|
||||
except BlockingIOError:
|
||||
print('Another OCI operation is using the instance registry.', file=sys.stderr)
|
||||
print(translate('Another OCI operation is using the instance registry.'), file=sys.stderr)
|
||||
return 3
|
||||
except (OSError, ValueError, KeyError) as error:
|
||||
print(str(error) or type(error).__name__, file=sys.stderr)
|
||||
|
||||
@@ -4,9 +4,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _messages() -> dict:
|
||||
# This helper is also installed alone in libexec: importing oci_ui would
|
||||
# break that existing caller. Read the same catalog using stdlib only.
|
||||
base = Path(os.environ.get('PMX_BASE_DIR', '/usr/local/share/proxmenux'))
|
||||
try:
|
||||
language = json.loads((base / 'config.json').read_text(encoding='utf-8')).get('language', 'en')
|
||||
if language == 'en' or not isinstance(language, str) or not language:
|
||||
return {}
|
||||
data = json.loads((base / 'lang' / (language + '.json')).read_text(encoding='utf-8'))
|
||||
return data if isinstance(data, dict) else {}
|
||||
except (OSError, ValueError, AttributeError):
|
||||
return {}
|
||||
|
||||
|
||||
def translate(text: str) -> str:
|
||||
value = _messages().get(text)
|
||||
return value if isinstance(value, str) and value else text
|
||||
|
||||
|
||||
AT_FDCWD = -100
|
||||
@@ -41,14 +64,16 @@ def fail(step: str) -> None:
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 5 or sys.argv[4] not in {"rw", "ro"}:
|
||||
print(f"usage: {sys.argv[0]} PID SOURCE TARGET rw|ro", file=sys.stderr)
|
||||
print(f"{translate('Usage:')} {sys.argv[0]} PID SOURCE TARGET rw|ro", file=sys.stderr)
|
||||
return 2
|
||||
machine = platform.machine().lower()
|
||||
if machine not in SYSCALLS:
|
||||
print(f"unsupported host architecture: {machine}", file=sys.stderr)
|
||||
print(f"{translate('Unsupported host architecture:')} {machine}", file=sys.stderr)
|
||||
return 2
|
||||
open_tree_nr, move_mount_nr, mount_setattr_nr = SYSCALLS[machine]
|
||||
pid, source, target, mode = sys.argv[1:]
|
||||
# The host catalog is no longer reachable after chroot into the container.
|
||||
_messages()
|
||||
libc = ctypes.CDLL(None, use_errno=True)
|
||||
libc.syscall.restype = ctypes.c_long
|
||||
libc.setns.argtypes = (ctypes.c_int, ctypes.c_int)
|
||||
@@ -60,7 +85,7 @@ def main() -> int:
|
||||
ct_root = os.open(f"/proc/{pid}/root", os.O_PATH | os.O_DIRECTORY | os.O_CLOEXEC)
|
||||
try:
|
||||
if libc.setns(ct_ns, CLONE_NEWNS) != 0:
|
||||
fail("enter container namespace")
|
||||
fail(translate("enter container namespace"))
|
||||
os.fchdir(ct_root)
|
||||
os.chroot(".")
|
||||
os.chdir("/")
|
||||
@@ -71,7 +96,7 @@ def main() -> int:
|
||||
OPEN_TREE_CLONE | os.O_CLOEXEC,
|
||||
)
|
||||
if tree < 0:
|
||||
fail("clone source mount tree")
|
||||
fail(translate("clone source mount tree"))
|
||||
try:
|
||||
if mode == "ro":
|
||||
attributes = MountAttr(attr_set=MOUNT_ATTR_RDONLY)
|
||||
@@ -84,9 +109,9 @@ def main() -> int:
|
||||
ctypes.sizeof(attributes),
|
||||
)
|
||||
if result != 0:
|
||||
fail("make cloned mount tree read-only")
|
||||
fail(translate("make cloned mount tree read-only"))
|
||||
if libc.setns(host_ns, CLONE_NEWNS) != 0:
|
||||
fail("return to host namespace")
|
||||
fail(translate("return to host namespace"))
|
||||
os.fchdir(host_root)
|
||||
os.chroot(".")
|
||||
os.chdir("/")
|
||||
@@ -99,7 +124,7 @@ def main() -> int:
|
||||
MOVE_MOUNT_F_EMPTY_PATH,
|
||||
)
|
||||
if result != 0:
|
||||
fail("publish mount tree")
|
||||
fail(translate("publish mount tree"))
|
||||
finally:
|
||||
os.close(tree)
|
||||
finally:
|
||||
|
||||
@@ -164,7 +164,7 @@ def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("archive", type=Path)
|
||||
parser.add_argument("--extracted-mib", action="store_true",
|
||||
help="print the estimated unpacked size in MiB (0 when unknown)")
|
||||
help=translate("print the estimated unpacked size in MiB (0 when unknown)"))
|
||||
args = parser.parse_args()
|
||||
if args.extracted_mib:
|
||||
try:
|
||||
|
||||
@@ -472,7 +472,7 @@ def _rclone_mount(catalog: Catalog, ui) -> None:
|
||||
if result:
|
||||
from .management import carry_records
|
||||
carry_records(PROJECT_ROOT, shown=True)
|
||||
console.msg_ok(f"Remote: {result['remote']}:")
|
||||
console.msg_ok(f"{translate('Rclone remote')}: {result['remote']}:")
|
||||
console.msg_ok(f"{translate('Read/write')}: {result['read_write_path']}")
|
||||
console.msg_ok(f"{translate('Read-only')}: {result['read_only_path']}")
|
||||
console.wait_for_enter(translate("Press Enter to return to the menu..."))
|
||||
@@ -623,52 +623,52 @@ def _template_summary_text(template: dict[str, Any]) -> str:
|
||||
contract = template["container_contract"]
|
||||
lines = [
|
||||
source_text(template["catalog_ui"]["title"]),
|
||||
f"Image: {contract['image']['reference']}",
|
||||
f"Status: {template['status']}",
|
||||
"Ports: " + (", ".join(f"{p['container_port']}/{p['protocol']}" for p in contract["ports"]) or "none"),
|
||||
"Volumes: " + (", ".join(v["container_path"] for v in contract["volumes"]) or "none"),
|
||||
f"{translate('Image')}: {contract['image']['reference']}",
|
||||
f"{translate('Status')}: {template['status']}",
|
||||
translate("Ports") + ": " + (", ".join(f"{p['container_port']}/{p['protocol']}" for p in contract["ports"]) or translate("none")),
|
||||
translate("Volumes") + ": " + (", ".join(v["container_path"] for v in contract["volumes"]) or translate("none")),
|
||||
]
|
||||
blockers = template["compatibility"]["untranslated_blockers"]
|
||||
if blockers:
|
||||
lines.append(f"Blockers: {', '.join(blockers)}")
|
||||
lines.append(f"{translate('Blockers')}: {', '.join(blockers)}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(prog="oci_manager_apps.sh",
|
||||
description="ProxMenux OCI manager Apps (Technology Preview)")
|
||||
description=translate("ProxMenux OCI manager Apps (Technology Preview)"))
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
subparsers.add_parser("sync", help="Refresh the index from the image sources")
|
||||
list_parser = subparsers.add_parser("list", help="List applications")
|
||||
subparsers.add_parser("sync", help=translate("Refresh the index from the image sources"))
|
||||
list_parser = subparsers.add_parser("list", help=translate("List applications"))
|
||||
list_parser.add_argument("--filter", default="")
|
||||
generate_parser = subparsers.add_parser("generate", help="Regenerate the template of one application")
|
||||
generate_parser = subparsers.add_parser("generate", help=translate("Regenerate the template of one application"))
|
||||
generate_parser.add_argument("app")
|
||||
generate_all_parser = subparsers.add_parser("generate-all", help="Regenerate the catalog templates")
|
||||
generate_all_parser = subparsers.add_parser("generate-all", help=translate("Regenerate the catalog templates"))
|
||||
generate_all_parser.add_argument("--provider", choices=["all", "linuxserver.io", "imported", "curated"],
|
||||
default="all")
|
||||
subparsers.add_parser("apply-icons", help="Resolve catalog icons against the jsdelivr icon sets")
|
||||
show_parser = subparsers.add_parser("show", help="Show the summary of a template")
|
||||
subparsers.add_parser("apply-icons", help=translate("Resolve catalog icons against the jsdelivr icon sets"))
|
||||
show_parser = subparsers.add_parser("show", help=translate("Show the summary of a template"))
|
||||
show_parser.add_argument("app")
|
||||
open_parser = subparsers.add_parser("open", help="Open the page of an application in the menu")
|
||||
open_parser = subparsers.add_parser("open", help=translate("Open the page of an application in the menu"))
|
||||
open_parser.add_argument("app")
|
||||
install_parser = subparsers.add_parser("install", help="Configure and install an application")
|
||||
install_parser = subparsers.add_parser("install", help=translate("Configure and install an application"))
|
||||
install_parser.add_argument("app")
|
||||
install_parser.add_argument("--host", default="auto", help="'auto'/'local', or root@IP for development")
|
||||
install_parser.add_argument("--host", default="auto", help=translate("'auto'/'local', or root@IP for development"))
|
||||
install_parser.add_argument("--advanced", action="store_true")
|
||||
install_parser.add_argument("--dry-run", action="store_true")
|
||||
rclone_parser = subparsers.add_parser("rclone-mount", help="Enable a mount on an installed Rclone OCI")
|
||||
rclone_parser = subparsers.add_parser("rclone-mount", help=translate("Enable a mount on an installed Rclone OCI"))
|
||||
rclone_parser.add_argument("--host", default="auto")
|
||||
rclone_parser.add_argument("--dry-run", action="store_true")
|
||||
subparsers.add_parser("recover", help="Register again the OCI applications restored from a backup")
|
||||
manage_parser = subparsers.add_parser("manage", help="Update, modify or recreate one installed OCI instance")
|
||||
subparsers.add_parser("recover", help=translate("Register again the OCI applications restored from a backup"))
|
||||
manage_parser = subparsers.add_parser("manage", help=translate("Update, modify or recreate one installed OCI instance"))
|
||||
manage_parser.add_argument("vmid", type=int)
|
||||
manage_parser.add_argument("--action", choices=("update", "modify", "recreate"), required=True)
|
||||
manage_parser.add_argument("--keep-backup", metavar="STORAGE",
|
||||
help="Keep the backup taken before the update in this Proxmox storage")
|
||||
help=translate("Keep the backup taken before the update in this Proxmox storage"))
|
||||
manage_parser.add_argument("--unattended", action="store_true",
|
||||
help="Scheduled run: no questions; stops where a person has to decide")
|
||||
help=translate("Scheduled run: no questions; stops where a person has to decide"))
|
||||
manage_parser.add_argument("--acknowledge-external-data", action="store_true",
|
||||
help="Host directories are not reverted by the backup (confirmed beforehand)")
|
||||
help=translate("Host directories are not reverted by the backup (confirmed beforehand)"))
|
||||
manage_parser.add_argument("--min-image-age-days", type=int, default=0)
|
||||
return parser
|
||||
|
||||
@@ -681,15 +681,15 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return interactive(catalog)
|
||||
if args.command == "sync":
|
||||
payload = catalog.sync_index()
|
||||
print(f"Index updated: {len(payload['applications'])} candidate applications")
|
||||
print(translate('Index updated: {count} candidate applications').format(count=len(payload['applications'])))
|
||||
return 0
|
||||
if args.command == "list":
|
||||
query = args.filter.casefold()
|
||||
for item in catalog.load_index()["applications"]:
|
||||
if not item.get("hidden", False) and query in item["id"].casefold():
|
||||
candidate = item.get("automatic_install_candidate")
|
||||
status = ("installable" if candidate else "pending adaptation" if candidate is False
|
||||
else item.get("template_status") or "not generated")
|
||||
status = (translate("installable") if candidate else translate("pending adaptation") if candidate is False
|
||||
else item.get("template_status") or translate("not generated"))
|
||||
print(f"{item['id']:<30} {_display_architectures(item):<12} {publisher(item):<14} {status}")
|
||||
return 0
|
||||
if args.command == "generate":
|
||||
@@ -702,13 +702,12 @@ def main(argv: list[str] | None = None) -> int:
|
||||
print(f"\r[{current:3}/{total}] {marker:<5} {app_id:<32}", end="", flush=True)
|
||||
|
||||
report = catalog.generate_all(progress=progress, provider=args.provider)
|
||||
print(f"\nGenerated: {report['generated_count']}; failed: {report['failed_count']}; "
|
||||
f"family: {args.provider}")
|
||||
print('\n' + translate('Generated: {generated}; failed: {failed}; family: {provider}').format(
|
||||
generated=report['generated_count'], failed=report['failed_count'], provider=args.provider))
|
||||
return 0 if not report["failed"] else 2
|
||||
if args.command == "apply-icons":
|
||||
report = catalog.apply_icons()
|
||||
print(f"Icons resolved: {report['resolved']}; cleared: {report['cleared']}; "
|
||||
f"unchanged: {report['unchanged']}; with a theme variant: {report['themed']}")
|
||||
print(translate('Icons resolved: {resolved}; cleared: {cleared}; unchanged: {unchanged}; with a theme variant: {themed}').format(**report))
|
||||
return 0
|
||||
if args.command == "show":
|
||||
print(_template_summary_text(catalog.compose(args.app)))
|
||||
@@ -728,14 +727,14 @@ def main(argv: list[str] | None = None) -> int:
|
||||
if args.command == "install":
|
||||
template = catalog.compose(args.app)
|
||||
if not template["compatibility"]["automatic_install_candidate"]:
|
||||
raise InstallError("Installation blocked: " + ", ".join(template["compatibility"]["untranslated_blockers"]))
|
||||
raise InstallError(translate("Installation blocked:") + " " + ", ".join(template["compatibility"]["untranslated_blockers"]))
|
||||
notice = external_services_notice(template)
|
||||
if notice:
|
||||
print(notice + "\n")
|
||||
deployment = build_deployment(template, None, ADVANCED_MODE if args.advanced else DEFAULT_MODE)
|
||||
print(_deployment_summary_text(template, deployment))
|
||||
if not args.dry_run and input("\nInstall? [y/N]: ").strip().casefold() not in {"y", "yes"}:
|
||||
print("Installation cancelled.")
|
||||
if not args.dry_run and input('\n' + translate('Install? [y/N]:') + ' ').strip().casefold() not in {"y", "yes"}:
|
||||
print(translate("Installation cancelled."))
|
||||
return 0
|
||||
result = run_remote_install(PROJECT_ROOT, template, deployment, args.host, args.dry_run)
|
||||
if result:
|
||||
@@ -764,7 +763,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
print(json.dumps(deployment, ensure_ascii=False, indent=2))
|
||||
result = run_remote_rclone_mount(PROJECT_ROOT, template, deployment, args.host, args.dry_run)
|
||||
if result:
|
||||
print(f"Read/write: {result['read_write_path']}\nRead-only: {result['read_only_path']}")
|
||||
print(f"{translate('Read/write')}: {result['read_write_path']}\n{translate('Read-only')}: {result['read_only_path']}")
|
||||
return 0
|
||||
return 1
|
||||
except (KeyboardInterrupt, UserCancelled):
|
||||
|
||||
@@ -57,10 +57,55 @@ def diagnostic(error: Any) -> str:
|
||||
for pattern, source in (
|
||||
(r"No template is available for '(.*)'", N_("No template is available for '{app_id}'")),
|
||||
(r"The application '(.*)' is not in the index", N_("The application '{app_id}' is not in the index")),
|
||||
(r"The application '(.*)' does not come from LinuxServer", N_("The application '{app_id}' does not come from LinuxServer")),
|
||||
):
|
||||
match = re.fullmatch(pattern, text)
|
||||
if match:
|
||||
return translate(source).format(app_id=match[1])
|
||||
exact = {
|
||||
'The CasaOS Compose file has no services': N_('The Compose file has no services.'),
|
||||
'The Compose file has no services': N_('The Compose file has no services.'),
|
||||
'x-casaos.main no identifica un servicio valido': N_('x-casaos.main must name an existing service.'),
|
||||
'The main CasaOS service declares no image': N_('The main Compose service has no image.'),
|
||||
'El servicio principal no declara una imagen': N_('The main Compose service has no image.'),
|
||||
'El servicio no declara una imagen': N_('The service declares no image:'),
|
||||
'The CasaOS document is not a Compose mapping': N_('The Compose document must be a mapping.'),
|
||||
'The Compose file has no x-casaos metadata': N_('The Compose file has no x-casaos metadata.'),
|
||||
'environment must be a list or a mapping': N_('Compose environment must be a list or a mapping.'),
|
||||
'volumes must be a list': N_('Compose volumes must be a list.'),
|
||||
'ports must be a list': N_('Compose ports must be a list.'),
|
||||
'The README has no recognisable docker-compose section': N_('The README has no recognisable docker-compose section'),
|
||||
'La seccion docker-compose no contiene un bloque de codigo': N_('The docker-compose section contains no code block'),
|
||||
'The docker-compose block is not closed': N_('The docker-compose block is not closed'),
|
||||
'The block found does not look like a Docker Compose file': N_('The block found does not look like a Docker Compose file'),
|
||||
'No se puede identificar la imagen principal del Compose': N_('The main Compose service cannot be identified unambiguously'),
|
||||
'No se puede identificar de forma univoca el servicio principal del Compose': N_('The main Compose service cannot be identified unambiguously'),
|
||||
'python3-jsonschema is required to generate templates': N_('python3-jsonschema is required to generate templates'),
|
||||
'Bulk generation requires GITHUB_TOKEN to obtain an immutable revision per application': N_('Bulk generation requires GITHUB_TOKEN to obtain an immutable revision per application'),
|
||||
'The ProxMenux application catalogue is not a list': N_('The ProxMenux application catalogue is not a list'),
|
||||
'GitHub returned a truncated tree for the CasaOS catalogue': N_('GitHub returned a truncated tree for the CasaOS catalogue'),
|
||||
'No Compose files were found in the CasaOS catalogue': N_('No Compose files were found in the CasaOS catalogue'),
|
||||
}
|
||||
if text in exact:
|
||||
return translate(exact[text])
|
||||
for prefix, source in (
|
||||
('Cannot normalise the CasaOS identifier:', N_('Invalid application identifier:')),
|
||||
('Cannot normalise the identifier:', N_('Invalid application identifier:')),
|
||||
('Docker Compose CasaOS no valido:', N_('The Compose file is not valid YAML:')),
|
||||
('Docker Compose no valido:', N_('The Compose file is not valid YAML:')),
|
||||
('La plantilla generada no cumple el esquema:', N_('The generated template does not match the schema:')),
|
||||
):
|
||||
if text.startswith(prefix):
|
||||
return translate(source) + text[len(prefix):]
|
||||
match = re.fullmatch(r'GitHub returned HTTP 403 for (.+)\. Set GITHUB_TOKEN to raise the API rate limit\.', text)
|
||||
if match:
|
||||
return translate('GitHub returned HTTP 403 for {url}. Set GITHUB_TOKEN to raise the API rate limit.').format(url=match[1])
|
||||
match = re.fullmatch(r'GitHub returned HTTP ([0-9]{3}) (?:for|para) (.+)\.', text)
|
||||
if match:
|
||||
return translate('GitHub returned HTTP {status} for {url}.').format(status=match[1], url=match[2])
|
||||
match = re.fullmatch(r'No se pudo acceder a (.+) despues de 3 intentos: (.*)', text, re.S)
|
||||
if match:
|
||||
return translate('Could not reach {url} after 3 attempts: {reason}').format(url=match[1], reason=match[2])
|
||||
return text
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user