docker-app-launcher 0.1.0__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.
- docker_app_launcher-0.1.0/LICENSE +21 -0
- docker_app_launcher-0.1.0/PKG-INFO +141 -0
- docker_app_launcher-0.1.0/README.md +107 -0
- docker_app_launcher-0.1.0/pyproject.toml +121 -0
- docker_app_launcher-0.1.0/src/docker_app_launcher/__init__.py +45 -0
- docker_app_launcher-0.1.0/src/docker_app_launcher/__main__.py +122 -0
- docker_app_launcher-0.1.0/src/docker_app_launcher/actions.py +1046 -0
- docker_app_launcher-0.1.0/src/docker_app_launcher/config.py +197 -0
- docker_app_launcher-0.1.0/src/docker_app_launcher/gui.py +342 -0
- docker_app_launcher-0.1.0/src/docker_app_launcher/i18n.py +231 -0
- docker_app_launcher-0.1.0/src/docker_app_launcher/py.typed +0 -0
- docker_app_launcher-0.1.0/src/docker_app_launcher/tray.py +190 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Asterios Raptis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: docker-app-launcher
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Configurable desktop launcher for Docker-based applications
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: docker,launcher,desktop,tkinter,gui
|
|
8
|
+
Author: Asterios Raptis
|
|
9
|
+
Author-email: aster.raptis@gmail.com
|
|
10
|
+
Requires-Python: >=3.10,<4.0
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: X11 Applications
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
22
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Provides-Extra: tray
|
|
25
|
+
Requires-Dist: Pillow (>=10.0) ; extra == "tray"
|
|
26
|
+
Requires-Dist: pystray (>=0.19) ; extra == "tray"
|
|
27
|
+
Project-URL: Changelog, https://github.com/astrapi69/docker-app-launcher/blob/main/CHANGELOG.md
|
|
28
|
+
Project-URL: Documentation, https://github.com/astrapi69/docker-app-launcher#readme
|
|
29
|
+
Project-URL: Homepage, https://github.com/astrapi69/docker-app-launcher
|
|
30
|
+
Project-URL: Issue Tracker, https://github.com/astrapi69/docker-app-launcher/issues
|
|
31
|
+
Project-URL: Repository, https://github.com/astrapi69/docker-app-launcher
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# docker-app-launcher
|
|
35
|
+
|
|
36
|
+
Configurable desktop launcher for Docker-based applications.
|
|
37
|
+
**One persistent window.** It opens, shows progress, and never closes itself —
|
|
38
|
+
no dialog chains.
|
|
39
|
+
|
|
40
|
+
[](https://github.com/astrapi69/docker-app-launcher/actions/workflows/ci.yml)
|
|
41
|
+
[](https://pypi.org/project/docker-app-launcher/)
|
|
42
|
+
[](https://pypi.org/project/docker-app-launcher/)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
|
|
45
|
+
> 🇩🇪 [Deutsche Version](README-de.md)
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install docker-app-launcher
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Python API (3 lines)
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from docker_app_launcher import LauncherConfig, launch
|
|
57
|
+
|
|
58
|
+
launch(LauncherConfig(
|
|
59
|
+
app_name="My App",
|
|
60
|
+
container_name="my-app",
|
|
61
|
+
default_port=8080,
|
|
62
|
+
))
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### CLI
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
docker-app-launcher --config launcher.json # open the window
|
|
69
|
+
docker-app-launcher --status # print state and exit
|
|
70
|
+
docker-app-launcher --install --port 9000 # build + start headless
|
|
71
|
+
docker-app-launcher --check # is Docker running?
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### launcher.json
|
|
75
|
+
|
|
76
|
+
Everything is configurable. Only `app_name` is required — the rest is derived
|
|
77
|
+
(slug, container/image names, compose project, config dir) or defaulted.
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"app_name": "My App",
|
|
82
|
+
"container_name": "my-app",
|
|
83
|
+
"default_port": 8080,
|
|
84
|
+
"compose_file": "docker-compose.prod.yml",
|
|
85
|
+
"install_dir": "/opt/my-app",
|
|
86
|
+
"health_check_path": "/api/health",
|
|
87
|
+
"health_check_key": "status",
|
|
88
|
+
"health_check_value": "ok",
|
|
89
|
+
"repo_url": "https://github.com/owner/repo",
|
|
90
|
+
"locale": "en"
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Features
|
|
95
|
+
|
|
96
|
+
- **One persistent window** — never closes itself; only the X closes it.
|
|
97
|
+
- **Docker check on startup** — distinguishes *not installed* / *running* / *stopped* / *no Docker*.
|
|
98
|
+
- **Live build progress** — the Docker build is streamed line-by-line into the window.
|
|
99
|
+
- **Configurable port** — editable in the GUI and via `--port`, validated and persisted (to `launcher.json` and `.env`).
|
|
100
|
+
- **Verified actions** — install runs a health check; uninstall re-lists the containers to confirm they are gone.
|
|
101
|
+
- **Install manifest + startup cleanup** — finds and offers to remove stale leftovers of older installs.
|
|
102
|
+
- **Verbose uninstall / cleanup** — every step reported with a ✓ / ✗ result.
|
|
103
|
+
- **System tray** (optional) — `pip install docker-app-launcher[tray]`; the window minimizes to the tray while running.
|
|
104
|
+
- **DE / EN i18n** — with per-app custom-string overrides via `custom_strings`.
|
|
105
|
+
- **Actions architecture** — all business logic is GUI-free and unit-tested without a display.
|
|
106
|
+
- **CLI ↔ GUI parity** — both call the exact same actions layer.
|
|
107
|
+
|
|
108
|
+
## Architecture
|
|
109
|
+
|
|
110
|
+
| Module | Responsibility |
|
|
111
|
+
|---------------|-------------------------------------------------------------|
|
|
112
|
+
| `config.py` | `LauncherConfig` dataclass — the single source of truth. |
|
|
113
|
+
| `actions.py` | All business logic. No `tkinter`. Fully testable. |
|
|
114
|
+
| `gui.py` | `LauncherApp(tk.Tk)` — a thin window over the actions. |
|
|
115
|
+
| `tray.py` | Optional system tray (pystray + Pillow). |
|
|
116
|
+
| `i18n.py` | DE/EN strings with custom-string overrides. |
|
|
117
|
+
| `__main__.py` | CLI entry point + GUI router. |
|
|
118
|
+
|
|
119
|
+
## Configuration reference
|
|
120
|
+
|
|
121
|
+
See [LauncherConfig](src/docker_app_launcher/config.py) for the full field list
|
|
122
|
+
(app identity, network/health, Docker timeouts, paths, GUI, links, cleanup,
|
|
123
|
+
tray, i18n, and lifecycle callbacks).
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
poetry install --with dev --all-extras
|
|
129
|
+
make ci # lint + format-check + typecheck + tests
|
|
130
|
+
make test # tests with coverage
|
|
131
|
+
make fix # auto-fix lint + format
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Used by
|
|
135
|
+
|
|
136
|
+
- [Adaptive Learner](https://github.com/astrapi69/adaptive-learner)
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
[MIT](LICENSE) © Asterios Raptis
|
|
141
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# docker-app-launcher
|
|
2
|
+
|
|
3
|
+
Configurable desktop launcher for Docker-based applications.
|
|
4
|
+
**One persistent window.** It opens, shows progress, and never closes itself —
|
|
5
|
+
no dialog chains.
|
|
6
|
+
|
|
7
|
+
[](https://github.com/astrapi69/docker-app-launcher/actions/workflows/ci.yml)
|
|
8
|
+
[](https://pypi.org/project/docker-app-launcher/)
|
|
9
|
+
[](https://pypi.org/project/docker-app-launcher/)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
|
|
12
|
+
> 🇩🇪 [Deutsche Version](README-de.md)
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install docker-app-launcher
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Python API (3 lines)
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from docker_app_launcher import LauncherConfig, launch
|
|
24
|
+
|
|
25
|
+
launch(LauncherConfig(
|
|
26
|
+
app_name="My App",
|
|
27
|
+
container_name="my-app",
|
|
28
|
+
default_port=8080,
|
|
29
|
+
))
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### CLI
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
docker-app-launcher --config launcher.json # open the window
|
|
36
|
+
docker-app-launcher --status # print state and exit
|
|
37
|
+
docker-app-launcher --install --port 9000 # build + start headless
|
|
38
|
+
docker-app-launcher --check # is Docker running?
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### launcher.json
|
|
42
|
+
|
|
43
|
+
Everything is configurable. Only `app_name` is required — the rest is derived
|
|
44
|
+
(slug, container/image names, compose project, config dir) or defaulted.
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"app_name": "My App",
|
|
49
|
+
"container_name": "my-app",
|
|
50
|
+
"default_port": 8080,
|
|
51
|
+
"compose_file": "docker-compose.prod.yml",
|
|
52
|
+
"install_dir": "/opt/my-app",
|
|
53
|
+
"health_check_path": "/api/health",
|
|
54
|
+
"health_check_key": "status",
|
|
55
|
+
"health_check_value": "ok",
|
|
56
|
+
"repo_url": "https://github.com/owner/repo",
|
|
57
|
+
"locale": "en"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- **One persistent window** — never closes itself; only the X closes it.
|
|
64
|
+
- **Docker check on startup** — distinguishes *not installed* / *running* / *stopped* / *no Docker*.
|
|
65
|
+
- **Live build progress** — the Docker build is streamed line-by-line into the window.
|
|
66
|
+
- **Configurable port** — editable in the GUI and via `--port`, validated and persisted (to `launcher.json` and `.env`).
|
|
67
|
+
- **Verified actions** — install runs a health check; uninstall re-lists the containers to confirm they are gone.
|
|
68
|
+
- **Install manifest + startup cleanup** — finds and offers to remove stale leftovers of older installs.
|
|
69
|
+
- **Verbose uninstall / cleanup** — every step reported with a ✓ / ✗ result.
|
|
70
|
+
- **System tray** (optional) — `pip install docker-app-launcher[tray]`; the window minimizes to the tray while running.
|
|
71
|
+
- **DE / EN i18n** — with per-app custom-string overrides via `custom_strings`.
|
|
72
|
+
- **Actions architecture** — all business logic is GUI-free and unit-tested without a display.
|
|
73
|
+
- **CLI ↔ GUI parity** — both call the exact same actions layer.
|
|
74
|
+
|
|
75
|
+
## Architecture
|
|
76
|
+
|
|
77
|
+
| Module | Responsibility |
|
|
78
|
+
|---------------|-------------------------------------------------------------|
|
|
79
|
+
| `config.py` | `LauncherConfig` dataclass — the single source of truth. |
|
|
80
|
+
| `actions.py` | All business logic. No `tkinter`. Fully testable. |
|
|
81
|
+
| `gui.py` | `LauncherApp(tk.Tk)` — a thin window over the actions. |
|
|
82
|
+
| `tray.py` | Optional system tray (pystray + Pillow). |
|
|
83
|
+
| `i18n.py` | DE/EN strings with custom-string overrides. |
|
|
84
|
+
| `__main__.py` | CLI entry point + GUI router. |
|
|
85
|
+
|
|
86
|
+
## Configuration reference
|
|
87
|
+
|
|
88
|
+
See [LauncherConfig](src/docker_app_launcher/config.py) for the full field list
|
|
89
|
+
(app identity, network/health, Docker timeouts, paths, GUI, links, cleanup,
|
|
90
|
+
tray, i18n, and lifecycle callbacks).
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
poetry install --with dev --all-extras
|
|
96
|
+
make ci # lint + format-check + typecheck + tests
|
|
97
|
+
make test # tests with coverage
|
|
98
|
+
make fix # auto-fix lint + format
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Used by
|
|
102
|
+
|
|
103
|
+
- [Adaptive Learner](https://github.com/astrapi69/adaptive-learner)
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
[MIT](LICENSE) © Asterios Raptis
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "docker-app-launcher"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Configurable desktop launcher for Docker-based applications"
|
|
5
|
+
authors = [{ name = "Asterios Raptis", email = "aster.raptis@gmail.com" }]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
requires-python = ">=3.10,<4.0"
|
|
9
|
+
keywords = ["docker", "launcher", "desktop", "tkinter", "gui"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Environment :: X11 Applications",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"Topic :: Software Development :: Build Tools",
|
|
22
|
+
"Topic :: System :: Installation/Setup",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = []
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
tray = ["pystray>=0.19", "Pillow>=10.0"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/astrapi69/docker-app-launcher"
|
|
32
|
+
Repository = "https://github.com/astrapi69/docker-app-launcher"
|
|
33
|
+
Documentation = "https://github.com/astrapi69/docker-app-launcher#readme"
|
|
34
|
+
Changelog = "https://github.com/astrapi69/docker-app-launcher/blob/main/CHANGELOG.md"
|
|
35
|
+
"Issue Tracker" = "https://github.com/astrapi69/docker-app-launcher/issues"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
docker-app-launcher = "docker_app_launcher.__main__:main"
|
|
39
|
+
|
|
40
|
+
[tool.poetry]
|
|
41
|
+
packages = [{ include = "docker_app_launcher", from = "src" }]
|
|
42
|
+
|
|
43
|
+
[tool.poetry.group.dev.dependencies]
|
|
44
|
+
ruff = "^0.15.0"
|
|
45
|
+
mypy = "^2.1.0"
|
|
46
|
+
pre-commit = "^4.6.0"
|
|
47
|
+
pytest = "^9.1.0"
|
|
48
|
+
pytest-cov = "^7.1.0"
|
|
49
|
+
pytest-mock = "^3.15.0"
|
|
50
|
+
codespell = "^2.4.0"
|
|
51
|
+
twine = "^6.2.0"
|
|
52
|
+
|
|
53
|
+
[build-system]
|
|
54
|
+
requires = ["poetry-core>=2.0"]
|
|
55
|
+
build-backend = "poetry.core.masonry.api"
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# Tool Configuration
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
target-version = "py310"
|
|
63
|
+
line-length = 120
|
|
64
|
+
src = ["src", "tests"]
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
select = [
|
|
68
|
+
"E", # pycodestyle errors
|
|
69
|
+
"W", # pycodestyle warnings
|
|
70
|
+
"F", # pyflakes
|
|
71
|
+
"I", # isort
|
|
72
|
+
"B", # flake8-bugbear
|
|
73
|
+
"C4", # flake8-comprehensions
|
|
74
|
+
"UP", # pyupgrade
|
|
75
|
+
"SIM", # flake8-simplify
|
|
76
|
+
"BLE", # flake8-blind-except
|
|
77
|
+
"RUF", # ruff-specific rules
|
|
78
|
+
]
|
|
79
|
+
ignore = []
|
|
80
|
+
|
|
81
|
+
[tool.ruff.lint.isort]
|
|
82
|
+
known-first-party = ["docker_app_launcher"]
|
|
83
|
+
|
|
84
|
+
[tool.ruff.format]
|
|
85
|
+
docstring-code-format = true
|
|
86
|
+
|
|
87
|
+
[tool.mypy]
|
|
88
|
+
python_version = "3.10"
|
|
89
|
+
strict = true
|
|
90
|
+
warn_unused_configs = true
|
|
91
|
+
warn_return_any = true
|
|
92
|
+
ignore_missing_imports = true
|
|
93
|
+
files = ["src", "tests"]
|
|
94
|
+
|
|
95
|
+
# Tests stay readable: pytest fixtures are injected untyped, so don't require
|
|
96
|
+
# annotations on every test function/parameter. Bodies are still type-checked.
|
|
97
|
+
[[tool.mypy.overrides]]
|
|
98
|
+
module = "tests.*"
|
|
99
|
+
disallow_untyped_defs = false
|
|
100
|
+
disallow_incomplete_defs = false
|
|
101
|
+
|
|
102
|
+
[tool.pytest.ini_options]
|
|
103
|
+
testpaths = ["tests"]
|
|
104
|
+
addopts = "--cov=docker_app_launcher --cov-report=term-missing --strict-markers"
|
|
105
|
+
markers = [
|
|
106
|
+
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
[tool.coverage.run]
|
|
110
|
+
source = ["docker_app_launcher"]
|
|
111
|
+
branch = true
|
|
112
|
+
omit = ["tests/*"]
|
|
113
|
+
|
|
114
|
+
[tool.coverage.report]
|
|
115
|
+
show_missing = true
|
|
116
|
+
skip_covered = false
|
|
117
|
+
|
|
118
|
+
[tool.codespell]
|
|
119
|
+
# i18n.py is a bilingual (EN/DE) string catalog; German words are not typos.
|
|
120
|
+
skip = "poetry.lock,.git,src/docker_app_launcher/i18n.py"
|
|
121
|
+
ignore-words-list = "oeffnen,uebersprungen,ueberspringen"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Configurable desktop launcher for Docker-based applications.
|
|
2
|
+
|
|
3
|
+
Public API::
|
|
4
|
+
|
|
5
|
+
from docker_app_launcher import LauncherConfig, launch
|
|
6
|
+
|
|
7
|
+
launch(
|
|
8
|
+
LauncherConfig(
|
|
9
|
+
app_name="My App",
|
|
10
|
+
container_name="my-app",
|
|
11
|
+
default_port=8080,
|
|
12
|
+
)
|
|
13
|
+
)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
19
|
+
|
|
20
|
+
from docker_app_launcher.config import LauncherConfig
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
__version__ = version("docker-app-launcher")
|
|
24
|
+
except PackageNotFoundError: # pragma: no cover - package not installed
|
|
25
|
+
__version__ = "0.1.0"
|
|
26
|
+
|
|
27
|
+
__all__ = ["LauncherConfig", "__version__", "launch"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def launch(config: LauncherConfig | None = None, **kwargs: object) -> int:
|
|
31
|
+
"""Launch the GUI with the given config (or one built from ``kwargs``).
|
|
32
|
+
|
|
33
|
+
Usage::
|
|
34
|
+
|
|
35
|
+
launch(LauncherConfig(app_name="My App", default_port=8080))
|
|
36
|
+
# or
|
|
37
|
+
launch(app_name="My App", default_port=8080)
|
|
38
|
+
"""
|
|
39
|
+
if config is None:
|
|
40
|
+
config = LauncherConfig(**kwargs) # type: ignore[arg-type]
|
|
41
|
+
config.resolve()
|
|
42
|
+
|
|
43
|
+
from docker_app_launcher.gui import run
|
|
44
|
+
|
|
45
|
+
return run(config)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""CLI entry point + GUI router.
|
|
2
|
+
|
|
3
|
+
With no action flag the persistent window opens. With an action flag
|
|
4
|
+
(``--install`` / ``--status`` / ...) the request routes straight through the
|
|
5
|
+
:mod:`actions` layer and exits - same code path the GUI uses, so the CLI and
|
|
6
|
+
GUI stay in lockstep (CLI<->GUI parity).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import logging
|
|
13
|
+
import sys
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
|
|
16
|
+
from docker_app_launcher import __version__, actions
|
|
17
|
+
from docker_app_launcher.config import LauncherConfig
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("docker_app_launcher")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
23
|
+
"""Build the command-line argument parser."""
|
|
24
|
+
parser = argparse.ArgumentParser(
|
|
25
|
+
prog="docker-app-launcher",
|
|
26
|
+
description="Configurable desktop launcher for Docker-based applications.",
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--config", default="launcher.json", help="Path to the launcher config JSON (default: launcher.json)."
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument("--port", type=int, default=None, help="Host port for the app (1024-65535).")
|
|
32
|
+
parser.add_argument("--debug", action="store_true", help="Verbose logging to stdout.")
|
|
33
|
+
parser.add_argument("--version", action="store_true", help="Print the launcher version and exit.")
|
|
34
|
+
# Headless action flags (CLI<->GUI parity).
|
|
35
|
+
parser.add_argument("--check", action="store_true", help="Check Docker status and exit.")
|
|
36
|
+
parser.add_argument("--status", action="store_true", help="Print the app state and exit.")
|
|
37
|
+
parser.add_argument("--install", action="store_true", help="Build + start the app and exit.")
|
|
38
|
+
parser.add_argument("--start", action="store_true", help="Start the stopped app and exit.")
|
|
39
|
+
parser.add_argument("--stop", action="store_true", help="Stop the running app and exit.")
|
|
40
|
+
parser.add_argument("--uninstall", action="store_true", help="Remove the app containers/images and exit.")
|
|
41
|
+
parser.add_argument("--cleanup", action="store_true", help="Remove stale leftovers and exit.")
|
|
42
|
+
parser.add_argument("--open", action="store_true", help="Open the app in the browser and exit.")
|
|
43
|
+
return parser
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _setup_logging(*, debug: bool) -> None:
|
|
47
|
+
logging.basicConfig(
|
|
48
|
+
level=logging.DEBUG if debug else logging.INFO,
|
|
49
|
+
format="%(asctime)s %(levelname)s %(name)s %(message)s",
|
|
50
|
+
stream=sys.stdout,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run_cli_action(args: argparse.Namespace, config: LauncherConfig) -> int | None:
|
|
55
|
+
"""Route a headless CLI action through the actions layer.
|
|
56
|
+
|
|
57
|
+
Returns an exit code when an action flag was handled, or ``None`` when no
|
|
58
|
+
action flag was present (the caller then launches the GUI).
|
|
59
|
+
"""
|
|
60
|
+
if args.check:
|
|
61
|
+
ok, msg = actions.check_docker()
|
|
62
|
+
print(msg)
|
|
63
|
+
return 0 if ok else 1
|
|
64
|
+
if args.status:
|
|
65
|
+
print(f"Status: {actions.get_state(config)}")
|
|
66
|
+
return 0
|
|
67
|
+
if args.install:
|
|
68
|
+
ok, msg = actions.install(config, on_step=print, on_output=print)
|
|
69
|
+
print(msg)
|
|
70
|
+
return 0 if ok else 1
|
|
71
|
+
if args.start:
|
|
72
|
+
ok, msg = actions.start(config, on_step=print, on_output=print)
|
|
73
|
+
print(msg)
|
|
74
|
+
return 0 if ok else 1
|
|
75
|
+
if args.stop:
|
|
76
|
+
ok, msg = actions.stop(config)
|
|
77
|
+
print(msg)
|
|
78
|
+
return 0 if ok else 1
|
|
79
|
+
if args.uninstall:
|
|
80
|
+
ok, msg = actions.uninstall(config, on_step=print)
|
|
81
|
+
print(msg)
|
|
82
|
+
return 0 if ok else 1
|
|
83
|
+
if args.cleanup:
|
|
84
|
+
stale = actions.find_stale_artifacts(config)
|
|
85
|
+
ok, msg = actions.cleanup_stale(config, stale, on_step=print)
|
|
86
|
+
print(msg)
|
|
87
|
+
return 0 if ok else 1
|
|
88
|
+
if args.open:
|
|
89
|
+
actions.open_browser(config)
|
|
90
|
+
return 0
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
95
|
+
"""CLI entry point. Returns a process exit code."""
|
|
96
|
+
args = build_parser().parse_args(argv)
|
|
97
|
+
|
|
98
|
+
if args.version:
|
|
99
|
+
print(f"docker-app-launcher {__version__}")
|
|
100
|
+
return 0
|
|
101
|
+
|
|
102
|
+
_setup_logging(debug=args.debug)
|
|
103
|
+
config = LauncherConfig.from_json(args.config)
|
|
104
|
+
|
|
105
|
+
if args.port is not None:
|
|
106
|
+
ok, msg = actions.set_port(config, args.port)
|
|
107
|
+
if not ok:
|
|
108
|
+
print(msg, file=sys.stderr)
|
|
109
|
+
return 2
|
|
110
|
+
|
|
111
|
+
action_rc = run_cli_action(args, config)
|
|
112
|
+
if action_rc is not None:
|
|
113
|
+
return action_rc
|
|
114
|
+
|
|
115
|
+
# No action flag -> open the persistent window.
|
|
116
|
+
from docker_app_launcher.gui import run
|
|
117
|
+
|
|
118
|
+
return run(config, debug=args.debug)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
raise SystemExit(main())
|