ha-deploy-kit 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.
Files changed (57) hide show
  1. ha_deploy_kit-0.1.0/.coveragerc-ha +10 -0
  2. ha_deploy_kit-0.1.0/.github/workflows/release.yml +56 -0
  3. ha_deploy_kit-0.1.0/.github/workflows/validate.yml +43 -0
  4. ha_deploy_kit-0.1.0/.gitignore +224 -0
  5. ha_deploy_kit-0.1.0/CHANGELOG.md +82 -0
  6. ha_deploy_kit-0.1.0/LICENSE +21 -0
  7. ha_deploy_kit-0.1.0/PKG-INFO +199 -0
  8. ha_deploy_kit-0.1.0/README.md +151 -0
  9. ha_deploy_kit-0.1.0/docs/adopting.md +110 -0
  10. ha_deploy_kit-0.1.0/docs/configuration.md +73 -0
  11. ha_deploy_kit-0.1.0/docs/install.md +76 -0
  12. ha_deploy_kit-0.1.0/docs/releasing.md +100 -0
  13. ha_deploy_kit-0.1.0/examples/backend_only/.env.example +6 -0
  14. ha_deploy_kit-0.1.0/examples/backend_only/deploy.py +36 -0
  15. ha_deploy_kit-0.1.0/examples/backend_only/deploy.sh +8 -0
  16. ha_deploy_kit-0.1.0/examples/frontend_card/.env.example +6 -0
  17. ha_deploy_kit-0.1.0/examples/frontend_card/deploy.py +40 -0
  18. ha_deploy_kit-0.1.0/examples/frontend_card/deploy.sh +8 -0
  19. ha_deploy_kit-0.1.0/examples/frontend_card/ha_adapter.py +48 -0
  20. ha_deploy_kit-0.1.0/examples/synthetic_widget/deploy.py +38 -0
  21. ha_deploy_kit-0.1.0/examples/synthetic_widget/ha_adapter.py +44 -0
  22. ha_deploy_kit-0.1.0/pyproject.toml +106 -0
  23. ha_deploy_kit-0.1.0/src/ha_deploy_kit/__init__.py +9 -0
  24. ha_deploy_kit-0.1.0/src/ha_deploy_kit/cli.py +144 -0
  25. ha_deploy_kit-0.1.0/src/ha_deploy_kit/comparison.py +202 -0
  26. ha_deploy_kit-0.1.0/src/ha_deploy_kit/config.py +63 -0
  27. ha_deploy_kit-0.1.0/src/ha_deploy_kit/frontend.py +87 -0
  28. ha_deploy_kit-0.1.0/src/ha_deploy_kit/ha/__init__.py +20 -0
  29. ha_deploy_kit-0.1.0/src/ha_deploy_kit/ha/repairs_flow.py +48 -0
  30. ha_deploy_kit-0.1.0/src/ha_deploy_kit/ha/repairs_issue.py +46 -0
  31. ha_deploy_kit-0.1.0/src/ha_deploy_kit/ha/service.py +52 -0
  32. ha_deploy_kit-0.1.0/src/ha_deploy_kit/ha/serving.py +63 -0
  33. ha_deploy_kit-0.1.0/src/ha_deploy_kit/inventory.py +50 -0
  34. ha_deploy_kit-0.1.0/src/ha_deploy_kit/metadata/services.yaml.tmpl +6 -0
  35. ha_deploy_kit-0.1.0/src/ha_deploy_kit/metadata/strings.snippet.fixflow.json +15 -0
  36. ha_deploy_kit-0.1.0/src/ha_deploy_kit/metadata/strings.snippet.json +14 -0
  37. ha_deploy_kit-0.1.0/src/ha_deploy_kit/notify.py +150 -0
  38. ha_deploy_kit-0.1.0/src/ha_deploy_kit/settings.py +56 -0
  39. ha_deploy_kit-0.1.0/src/ha_deploy_kit/transport.py +117 -0
  40. ha_deploy_kit-0.1.0/tests/conftest.py +10 -0
  41. ha_deploy_kit-0.1.0/tests/ha/conftest.py +7 -0
  42. ha_deploy_kit-0.1.0/tests/ha/test_example_adapters.py +92 -0
  43. ha_deploy_kit-0.1.0/tests/ha/test_real_http_to_service.py +86 -0
  44. ha_deploy_kit-0.1.0/tests/ha/test_repairs_flow.py +27 -0
  45. ha_deploy_kit-0.1.0/tests/ha/test_repairs_issue.py +50 -0
  46. ha_deploy_kit-0.1.0/tests/ha/test_service.py +86 -0
  47. ha_deploy_kit-0.1.0/tests/ha/test_serving.py +75 -0
  48. ha_deploy_kit-0.1.0/tests/test_cli.py +333 -0
  49. ha_deploy_kit-0.1.0/tests/test_comparison.py +131 -0
  50. ha_deploy_kit-0.1.0/tests/test_config.py +47 -0
  51. ha_deploy_kit-0.1.0/tests/test_frontend.py +168 -0
  52. ha_deploy_kit-0.1.0/tests/test_installed_smoke.py +94 -0
  53. ha_deploy_kit-0.1.0/tests/test_inventory.py +48 -0
  54. ha_deploy_kit-0.1.0/tests/test_multi_consumer.py +279 -0
  55. ha_deploy_kit-0.1.0/tests/test_notify.py +290 -0
  56. ha_deploy_kit-0.1.0/tests/test_packaging.py +88 -0
  57. ha_deploy_kit-0.1.0/tests/test_transport.py +153 -0
@@ -0,0 +1,10 @@
1
+ # Separate coverage config for the HA adapter, run under the dev-ha env.
2
+ # Deterministic, env-independent gate scoped to the HA adapter only.
3
+ [run]
4
+ branch = true
5
+ source =
6
+ ha_deploy_kit.ha
7
+
8
+ [report]
9
+ fail_under = 90
10
+ show_missing = true
@@ -0,0 +1,56 @@
1
+ name: release
2
+
3
+ # Automated release. On every push to main (i.e. a merged PR),
4
+ # python-semantic-release reads the Conventional Commit messages since the last
5
+ # release and, if there is anything to release, bumps project.version, updates the
6
+ # changelog, commits that back to main ([skip ci]), tags v<version>, creates a
7
+ # GitHub release, and builds the artifacts. The publish job then uploads to PyPI
8
+ # via Trusted Publishing (OIDC) — no long-lived API token is stored anywhere.
9
+ # See docs/releasing.md for the one-time PyPI setup and the manual fallback.
10
+
11
+ on:
12
+ push:
13
+ branches:
14
+ - main
15
+
16
+ concurrency:
17
+ group: release
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ release:
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ contents: write # push the bump commit + tag to main, create the GH release
25
+ outputs:
26
+ released: ${{ steps.release.outputs.released }}
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ with:
30
+ fetch-depth: 0 # full history so commit analysis can find the last tag
31
+ token: ${{ secrets.GITHUB_TOKEN }}
32
+ - name: Python Semantic Release
33
+ id: release
34
+ uses: python-semantic-release/python-semantic-release@v9
35
+ with:
36
+ github_token: ${{ secrets.GITHUB_TOKEN }}
37
+ - name: Upload artifacts
38
+ if: steps.release.outputs.released == 'true'
39
+ uses: actions/upload-artifact@v4
40
+ with:
41
+ name: dist
42
+ path: dist/
43
+
44
+ publish:
45
+ needs: release
46
+ if: needs.release.outputs.released == 'true'
47
+ runs-on: ubuntu-latest
48
+ environment: pypi
49
+ permissions:
50
+ id-token: write # OIDC token for Trusted Publishing; replaces an API token
51
+ steps:
52
+ - uses: actions/download-artifact@v4
53
+ with:
54
+ name: dist
55
+ path: dist/
56
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,43 @@
1
+ # CI for ha-deploy-kit.
2
+ #
3
+ # This is a Python LIBRARY, not a Home Assistant integration, so there is no
4
+ # HACS / hassfest job. Two independent test jobs mirror the two dependency
5
+ # groups and Python environments:
6
+ # - tests-core: HA-free, runs on Python 3.10/3.11/3.12 with the dev-core group.
7
+ # - tests-ha: the HA adapter, runs on the HA dev Python with the dev-ha group.
8
+ # Every `uses:` is pinned to a 40-char commit SHA. A later per-consumer example
9
+ # may validate separately.
10
+ name: validate
11
+
12
+ on:
13
+ push:
14
+ branches: [master, main]
15
+ pull_request:
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ tests-core:
22
+ runs-on: ubuntu-latest
23
+ strategy:
24
+ fail-fast: false
25
+ matrix:
26
+ python-version: ["3.10", "3.11", "3.12"]
27
+ steps:
28
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
29
+ - uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v3.1.7
30
+ - name: Run core tests
31
+ run: uv run --python ${{ matrix.python-version }} --group dev-core pytest tests --ignore=tests/ha -m "not ha" --cov --cov-report=term-missing
32
+ - name: Lint
33
+ run: |
34
+ uv run --group dev-core ruff check
35
+ uv run --group dev-core ruff format --check
36
+
37
+ tests-ha:
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
41
+ - uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v3.1.7
42
+ - name: Run HA adapter tests
43
+ run: uv run --python 3.14 --group dev-ha --extra ha pytest tests/ha --cov --cov-config=.coveragerc-ha --cov-report=term-missing
@@ -0,0 +1,224 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ *.lcov
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ # Pipfile.lock
97
+
98
+ # uv
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries. ha-deploy-kit is a library, so the lock is ignored.
102
+ uv.lock
103
+
104
+ # poetry
105
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
106
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
107
+ # commonly ignored for libraries.
108
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
109
+ # poetry.lock
110
+ # poetry.toml
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
115
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
116
+ # pdm.lock
117
+ # pdm.toml
118
+ .pdm-python
119
+ .pdm-build/
120
+
121
+ # pixi
122
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
123
+ # pixi.lock
124
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
125
+ # in the .venv directory. It is recommended not to include this directory in version control.
126
+ .pixi/*
127
+ !.pixi/config.toml
128
+
129
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
130
+ __pypackages__/
131
+
132
+ # Celery stuff
133
+ celerybeat-schedule*
134
+ celerybeat.pid
135
+
136
+ # Redis
137
+ *.rdb
138
+ *.aof
139
+ *.pid
140
+
141
+ # RabbitMQ
142
+ mnesia/
143
+ rabbitmq/
144
+ rabbitmq-data/
145
+
146
+ # ActiveMQ
147
+ activemq-data/
148
+
149
+ # SageMath parsed files
150
+ *.sage.py
151
+
152
+ # Environments
153
+ .env
154
+ .envrc
155
+ .venv
156
+ env/
157
+ venv/
158
+ ENV/
159
+ env.bak/
160
+ venv.bak/
161
+
162
+ # Spyder project settings
163
+ .spyderproject
164
+ .spyproject
165
+
166
+ # Rope project settings
167
+ .ropeproject
168
+
169
+ # mkdocs/Zensical documentation
170
+ /site
171
+
172
+ # mypy
173
+ .mypy_cache/
174
+ .dmypy.json
175
+ dmypy.json
176
+
177
+ # Pyre type checker
178
+ .pyre/
179
+
180
+ # pytype static type analyzer
181
+ .pytype/
182
+
183
+ # Cython debug symbols
184
+ cython_debug/
185
+
186
+ # PyCharm
187
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
188
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
189
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
190
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
191
+ # .idea/
192
+
193
+ # Abstra
194
+ # Abstra is an AI-powered process automation framework.
195
+ # Ignore directories containing user credentials, local state, and settings.
196
+ # Learn more at https://abstra.io/docs
197
+ .abstra/
198
+
199
+ # Visual Studio Code
200
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that
201
+ # can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
202
+ # and can be added to the global gitignore or merged into this file. However, if you prefer, you
203
+ # could uncomment the following to ignore the entire vscode folder
204
+ # .vscode/
205
+ # Temporary file for partial code execution
206
+ tempCodeRunnerFile.py
207
+
208
+ # Ruff stuff:
209
+ .ruff_cache/
210
+
211
+ # PyPI configuration file
212
+ .pypirc
213
+
214
+ # Marimo
215
+ marimo/_static/
216
+ marimo/_lsp/
217
+ __marimo__/
218
+
219
+ # Streamlit
220
+ .streamlit/secrets.toml
221
+
222
+
223
+ .plans
224
+ .vscode
@@ -0,0 +1,82 @@
1
+ # CHANGELOG
2
+
3
+
4
+ ## v0.1.0 (2026-10-29)
5
+
6
+ ### Bug Fixes
7
+
8
+ - **notify**: Verify TLS against a real CA trust store (truststore/certifi)
9
+ ([`cc95ca9`](https://github.com/latrekc-meta/ha-deploy-kit/commit/cc95ca97546118c8620bb8fb1ba0f383270a7180))
10
+
11
+ The HTTPS notification used Python's default SSL context, which has no usable CA store on macOS /
12
+ python.org / fresh venvs and misreported valid certs as tls-error. Add _verifying_ssl_context()
13
+ (truststore -> certifi -> stdlib, all strictly verifying) and pass it to both the service POST and
14
+ the probe. Add certifi + truststore as runtime deps; tls-error now reflects genuine failures.
15
+
16
+ ### Continuous Integration
17
+
18
+ - Automate semantic versioning and PyPI publishing on merge to main
19
+ ([`38ac841`](https://github.com/latrekc-meta/ha-deploy-kit/commit/38ac841e33421bae630c165fc834cd639f217a15))
20
+
21
+ python-semantic-release derives the next version from Conventional Commit messages on each push to
22
+ main, bumps project.version, tags, changelogs, and builds; a publish job then uploads to PyPI via
23
+ Trusted Publishing (OIDC, no token). allow_zero_version + major_on_zero=false keep 0.x semantics,
24
+ so the first merge publishes 0.1.0 and tags v0.1.0 automatically. Docs make PyPI the primary
25
+ install path and document the one-time PyPI/environment setup.
26
+
27
+ ### Documentation
28
+
29
+ - Readme, install, configuration, and generic adoption guide
30
+ ([`9a5c9a0`](https://github.com/latrekc-meta/ha-deploy-kit/commit/9a5c9a04d75812025379202b9450c0f894139834))
31
+
32
+ ### Features
33
+
34
+ - Cli orchestration gated on backend changes
35
+ ([`e6baa76`](https://github.com/latrekc-meta/ha-deploy-kit/commit/e6baa76a3bf7a31ab828902cf6069ae876b8a790))
36
+
37
+ - Config loading without global env mutation
38
+ ([`22eeb50`](https://github.com/latrekc-meta/ha-deploy-kit/commit/22eeb505623ffa56bffd00c5fc222c928b937d7b))
39
+
40
+ - Ha adapter (service, repairs issue, opt-in fix flow, card serving)
41
+ ([`fb11c1c`](https://github.com/latrekc-meta/ha-deploy-kit/commit/fb11c1c7a256d76a0e4207d9076b03716c5bdf07))
42
+
43
+ - Ha-deploy-kit package skeleton and settings
44
+ ([`5df5334`](https://github.com/latrekc-meta/ha-deploy-kit/commit/5df533432d773eb2b04498077d9a29949e69bb66))
45
+
46
+ - Local inventory with component-relative keys
47
+ ([`0cd1f7a`](https://github.com/latrekc-meta/ha-deploy-kit/commit/0cd1f7a3739d89077ee021b64afdb1786759f094))
48
+
49
+ - Optional frontend profile (publish + run, no restart)
50
+ ([`07d5418`](https://github.com/latrekc-meta/ha-deploy-kit/commit/07d54181bd6886a7b3a1923160cbf75e795eaa00))
51
+
52
+ - Remote inventory wire format and comparison plan
53
+ ([`16f3461`](https://github.com/latrekc-meta/ha-deploy-kit/commit/16f3461283fa4a18bcd97e60102825e2ed8351b8))
54
+
55
+ - Sanitized HTTP notification client with status taxonomy
56
+ ([`22d7f21`](https://github.com/latrekc-meta/ha-deploy-kit/commit/22d7f218cf4a9b1de801e1fe967e319cda6cc9db))
57
+
58
+ - Ssh/tar transport with path validation
59
+ ([`211ccd0`](https://github.com/latrekc-meta/ha-deploy-kit/commit/211ccd0154690791191614680a736293d65411b1))
60
+
61
+ - **cli**: Make run frontend-aware so card consumers need no pipeline
62
+ ([`6eb8c3f`](https://github.com/latrekc-meta/ha-deploy-kit/commit/6eb8c3f154b5164c9b8ae6fee87e8fa628cc1f7d))
63
+
64
+ run now publishes the changed card (frontend_run) and applies a REMOTE_CONFIG env override via
65
+ dataclasses.replace, in addition to deploying the changed backend and notifying. A frontend-only
66
+ change publishes the card but never raises a restart. The three examples collapse to
67
+ build_settings(root)+run(), and frontend_run drops its now-contradictory backend-pending note.
68
+
69
+ - **ha**: Optional fixable flag for one-click restart Repairs card
70
+ ([`d2aaee2`](https://github.com/latrekc-meta/ha-deploy-kit/commit/d2aaee23ad01d87b4fe4ff564bbf917f0f7b3d05))
71
+
72
+ async_register_restart_service gains a keyword-only fixable (default False), forwarded as is_fixable
73
+ to the raised issue so a wired fix flow is reachable. Backend-only/notification-only behavior is
74
+ unchanged; the frontend_card example and docs now register fixable=True.
75
+
76
+ ### Testing
77
+
78
+ - Installed-wheel smoke test; fix py3.10 tomllib floor; ruff-format doc code blocks
79
+ ([`c7ef321`](https://github.com/latrekc-meta/ha-deploy-kit/commit/c7ef3214363e57e53a338b91fdb1dfa63b0cca26))
80
+
81
+ - Real HTTP-to-service proof (notify + HA adapter integrate)
82
+ ([`13b366f`](https://github.com/latrekc-meta/ha-deploy-kit/commit/13b366f6dab46e6333f49cad452ab1249bc5d902))
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 latrekc-meta
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,199 @@
1
+ Metadata-Version: 2.5
2
+ Name: ha-deploy-kit
3
+ Version: 0.1.0
4
+ Summary: HA-free SSH deploy / compare / notify / optional-frontend toolkit for Home Assistant custom components
5
+ Project-URL: Homepage, https://github.com/latrekc-meta/ha-deploy-kit
6
+ Project-URL: Repository, https://github.com/latrekc-meta/ha-deploy-kit
7
+ Project-URL: Issues, https://github.com/latrekc-meta/ha-deploy-kit/issues
8
+ Author: latrekc-meta
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 latrekc-meta
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: custom-component,deploy,home-assistant,homeassistant,lovelace,ssh
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Operating System :: OS Independent
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Topic :: Home Automation
41
+ Classifier: Topic :: Software Development :: Build Tools
42
+ Requires-Python: >=3.10
43
+ Requires-Dist: certifi
44
+ Requires-Dist: truststore>=0.9
45
+ Provides-Extra: ha
46
+ Requires-Dist: homeassistant; extra == 'ha'
47
+ Description-Content-Type: text/markdown
48
+
49
+ # ha-deploy-kit
50
+
51
+ A small, reusable library for deploying a Home Assistant custom integration's
52
+ code to a running HA host over SSH, and (optionally) wiring the HA-side restart
53
+ notification. The core is Home-Assistant-free and standard-library only, so a
54
+ deploy script can import it without pulling in Home Assistant; the HA adapter is
55
+ a separate, explicitly-imported subpackage.
56
+
57
+ One process can deploy several integrations without cross-talk: all per-consumer
58
+ state lives in a frozen `DeploySettings` passed by argument — no module globals
59
+ and no `os.environ` mutation.
60
+
61
+ ## What it does
62
+
63
+ - Builds a content-hash inventory of your `custom_components/<domain>` tree and
64
+ compares it to the remote tree, deploying only the changed files.
65
+ - Leaves remote-only files untouched (never deletes on the host).
66
+ - Streams files over SSH with an uncompressed, path-validated tar — no backups,
67
+ no lock files, no rollback, no auto-restart.
68
+ - Optionally publishes a separately-served Lovelace card.
69
+ - Optionally raises a Home Assistant *Repairs* issue (via an HA service) telling
70
+ the user a restart is needed. The engine never restarts HA itself; restart is
71
+ opt-in and happens only when a user confirms a Repairs fix flow.
72
+
73
+ ## Install
74
+
75
+ Published on PyPI. Pin an exact version.
76
+
77
+ ```sh
78
+ pip install "ha-deploy-kit==0.1.0"
79
+ ```
80
+
81
+ The HA adapter needs Home Assistant, which is an optional extra:
82
+
83
+ ```sh
84
+ pip install "ha-deploy-kit[ha]==0.1.0"
85
+ ```
86
+
87
+ - **Core** requires Python **3.10+** and only the standard library (plus a CA
88
+ trust store: `certifi`/`truststore`).
89
+ - **The `ha` adapter** requires the Python version Home Assistant itself
90
+ requires; install it in the HA runtime, not in your deploy tooling.
91
+ - **Home Assistant integrations** must pin a versioned PyPI requirement
92
+ (`"ha-deploy-kit==0.1.0"` in `manifest.json`), **never** a `git+…` VCS URL — HA
93
+ installs requirements in its own credential-less environment and treats URL
94
+ requirements as never-satisfied, retrying every startup.
95
+
96
+ See [docs/install.md](docs/install.md) for `pyproject.toml`, HA `manifest.json`,
97
+ and air-gapped host instructions, and [docs/releasing.md](docs/releasing.md) for
98
+ how releases are published.
99
+
100
+ ## Public API
101
+
102
+ | Symbol | Import | Purpose |
103
+ | --- | --- | --- |
104
+ | `DeploySettings` | `ha_deploy_kit` | Frozen per-consumer settings (domain, source root, remote config, optional frontend, timeouts). |
105
+ | `FrontendAsset` | `ha_deploy_kit` | Optional served-card mapping (`source_rel`, `published_remote`). |
106
+ | `DeployError` | `ha_deploy_kit` | Raised for recoverable, sanitized deployment errors. |
107
+ | `run(settings, argv, ssh=None, environ=None)` | `ha_deploy_kit.cli` | Orchestrate end to end: compare, report, publish the changed card, deploy the changed backend, notify. Honors a `REMOTE_CONFIG` env override. |
108
+ | `raise_restart_issue(base_url, token, settings)` | `ha_deploy_kit.notify` | POST to the HA restart service; returns a sanitized status string. |
109
+ | `frontend_run(settings, plan, ssh, *, check)` | `ha_deploy_kit.frontend` | Deploy the card into the tree and/or publish the served copy. |
110
+ | `ha.async_register_restart_service(hass, domain, ...)` | `ha_deploy_kit.ha` | Idempotently register the restart service (empty schema). Pass `fixable=True` for a one-click Repairs card (pair with the fix flow). |
111
+ | `ha.async_raise_restart_issue(hass, domain, ...)` | `ha_deploy_kit.ha` | Raise the deduplicated, non-persistent restart Repairs issue. |
112
+ | `ha.async_clear_restart_issue(hass, domain, ...)` | `ha_deploy_kit.ha` | Clear the restart issue. |
113
+ | `ha.async_create_restart_fix_flow(hass, issue_id, ...)` | `ha_deploy_kit.ha` | Optional opt-in confirm-and-restart flow (restart only on submit). |
114
+ | `ha.register_card_serving(hass, domain, lovelace_dir, card_url)` | `ha_deploy_kit.ha` | Serve the card and register it as a frontend module. |
115
+
116
+ ## CLI
117
+
118
+ The package installs a `ha-deploy` console script. It deploys by default; pass
119
+ `--check` (or its alias `--dry-run`) to preview without writing anything.
120
+
121
+ ```sh
122
+ ha-deploy # deploy changed backend files, then notify
123
+ ha-deploy --check # compare and report only; no writes, no notify
124
+ ha-deploy --root /path/to/your/repo
125
+ ```
126
+
127
+ The source root is resolved from `--root`, then `HA_DEPLOY_SOURCE_ROOT`, then
128
+ the current directory. The domain is read from `HA_DEPLOY_DOMAIN`, or inferred
129
+ when the root has exactly one `custom_components/<domain>` subdirectory.
130
+
131
+ ### One-liner for your own deploy script
132
+
133
+ `run` is fully frontend-aware, so a consumer's `deploy.py` is just settings + a
134
+ call — no pipeline to copy. It publishes a changed card and deploys a changed
135
+ backend; a frontend-only change publishes the card but never notifies a restart.
136
+
137
+ ```python
138
+ import sys
139
+ from pathlib import Path
140
+
141
+ from ha_deploy_kit import DeploySettings, FrontendAsset
142
+ from ha_deploy_kit.cli import run
143
+
144
+ settings = DeploySettings(
145
+ domain="your_domain",
146
+ source_root=Path(__file__).resolve().parent,
147
+ frontend=FrontendAsset( # omit entirely for backend-only
148
+ source_rel="lovelace/your-card.js",
149
+ published_remote="www/your_domain/your-card.js",
150
+ ),
151
+ )
152
+ raise SystemExit(run(settings, sys.argv[1:]))
153
+ ```
154
+
155
+ `TARGET`/`HA_BASE_URL`/`HA_TOKEN` and an optional `REMOTE_CONFIG` override come
156
+ from `.env` or the process environment; `run` applies `REMOTE_CONFIG` for you.
157
+
158
+ See [docs/configuration.md](docs/configuration.md) for settings and `.env`, and
159
+ [docs/adopting.md](docs/adopting.md) for a step-by-step adoption guide.
160
+
161
+ ## Design notes
162
+
163
+ This library deliberately differs from typical hand-rolled HA deploy scripts.
164
+ Each choice below is intentional:
165
+
166
+ - **No positional target and no `--restart` / `--no-restart` / `--card-only`
167
+ flags.** The target comes from the environment; the engine never restarts HA,
168
+ so there is no restart flag to pass.
169
+ - **The engine never executes a restart.** It can only raise a Repairs issue;
170
+ the actual restart is opt-in behind a user-confirmed fix flow.
171
+ - **A frontend-only change never notifies a backend restart.** Backend and
172
+ frontend are separate concerns.
173
+ - **Structured remote inventory.** The remote probe returns an explicit
174
+ four-field result (hashes, stale files, a missing-tree flag, published hash)
175
+ rather than guessing from text.
176
+ - **Environment precedence is explicit.** Process environment overrides `.env`;
177
+ the library never mutates `os.environ`.
178
+ - **Path validation.** Tar members are rejected if absolute, containing `..`,
179
+ NUL, or newlines, or escaping the component directory.
180
+ - **No backups, lock files, rollback, or auto-restart**, and tokens, raw bodies,
181
+ and secret-bearing errors are never printed. TLS and SSH verification are
182
+ never weakened.
183
+ - **Real CA trust store for the notification.** The HTTPS call to Home Assistant
184
+ verifies against the OS trust store via `truststore` (matching `curl`), falling
185
+ back to `certifi` then the stdlib default — all strictly verifying
186
+ (`CERT_REQUIRED` + hostname check). This avoids misreporting a valid cert as
187
+ `tls-error` on platforms whose default context has no usable CA store (macOS,
188
+ python.org, fresh venvs); `tls-error` is reserved for genuine failures.
189
+
190
+ ## Releases
191
+
192
+ Releases are automated: every merge to `main` runs semantic-release, which picks
193
+ the next version from the [Conventional Commit] messages (`fix:` → patch,
194
+ `feat:` → minor, `feat!:`/`BREAKING CHANGE:` → major; `docs:`/`chore:`/`test:`
195
+ publish nothing) and publishes to PyPI via Trusted Publishing. Write PR
196
+ titles/commits in that form. See [docs/releasing.md](docs/releasing.md) for the
197
+ one-time setup and the manual fallback.
198
+
199
+ [Conventional Commit]: https://www.conventionalcommits.org/