Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Dmitry Mamontov committed Jan 16, 2022
0 parents commit b8c043c
Showing 16 changed files with 1,168 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Validate

on:
push:
pull_request:

jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v2"
- name: HACS validation
uses: "hacs/action@main"
with:
category: "integration"
ignore: brands topics
131 changes: 131 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

.DS_Store
107 changes: 107 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Systemd Manager for Home Assistant
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs)
[![donate paypal](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/dslonyara)
[![donate tinkoff](https://img.shields.io/badge/Donate-Tinkoff-yellow.svg)](https://www.tinkoff.ru/sl/3FteV5DtBOV)

The component allows you to manage systemd services via [D-Bus](https://www.freedesktop.org/wiki/Software/dbus/)

## Table of Contents
- [Prerequisites](#prerequisites)
- [Install](#install)
- [Config](#config)
- [Advanced config](#advanced-config)
- [Services](#services)
- [Performance table](#performance-table)

## Prerequisites
#### Ubuntu
1. This component uses `D-Bus` to control `systemd`. You need to have D-Bus itself and its python bindings in order to use this component:
```shell
sudo apt install dbus libdbus-glib-1-dev libdbus-1-dev python-dbus
```

2. You also need to set up a `polkit` rule so that your user can control systemd via D-Bus. To do this, run the command and paste the content:
```shell
sudo nano /etc/polkit-1/localauthority/50-local.d/systemd-manager.pkla
```
```ini
[Allow user systemd-manager to execute systemctl commands]
Identity=unix-user:<your user from which hass is executed.>
Action=org.freedesktop.systemd1.manage-units
ResultAny=yes
```
#### Other
Not yet tested

## Install
Installed through the custom repository [HACS](https://hacs.xyz/) - `dmamontov/hass-systemd-manager`

Or by copying the `systemd_manager` folder from [the latest release](https://github.com/dmamontov/hass-systemd-manager/releases/latest) to the custom_components folder (create if necessary) of the configs directory.

## Config
#### Via GUI (Only)

`Settings` > `Integrations` > `Plus` > `Systemd Manager`

All you have to do is select the systemd services you want to manage.

#### Warnings
1. Only one configuration is allowed;
2. Do not select all services, this increases the load on the processor, especially D-Bus;

## Advanced config
To get the status of the services, the component requests the status of the services every 10 seconds. This value can be changed in the component's settings.

## Services
All services support only entity_id.

[Mode detail](https://www.freedesktop.org/wiki/Software/systemd/dbus/)

**start**
```yaml
service: systemd_manager.start
data:
mode: REPLACE # One of REPLACE, FAIL, ISOLATE, IGNORE_DEPENDENCIES, IGNORE_REQUIREMENTS
target:
entity_id: switch.systemd_...
```
**stop**
```yaml
service: systemd_manager.stop
data:
mode: REPLACE # One of REPLACE, FAIL, IGNORE_DEPENDENCIES, IGNORE_REQUIREMENTS
target:
entity_id: switch.systemd_...
```
**restart**
```yaml
service: systemd_manager.restart
data:
mode: REPLACE # One of REPLACE, FAIL, IGNORE_DEPENDENCIES, IGNORE_REQUIREMENTS
target:
entity_id: switch.systemd_...
```
**enable**
```yaml
service: systemd_manager.enable
target:
entity_id: switch.systemd_...
```
**disable**
```yaml
service: systemd_manager.disable
target:
entity_id: switch.systemd_...
```
## Performance table
![](table.png)
1. Install [Flex Table](https://github.com/custom-cards/flex-table-card) from HACS
2. Add new Lovelace tab with **Panel Mode**
3. Add new Lovelace card:
- [example](https://gist.github.com/dmamontov/e8c52c129fb19fca633d0d2d779676e3)
110 changes: 110 additions & 0 deletions custom_components/systemd_manager/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import logging

from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry

from .core.const import (
DOMAIN,
CONF_MODE,
SERVICE_START,
SERVICE_STOP,
SERVICE_RESTART,
SERVICE_ENABLE,
SERVICE_DISABLE,
ATTR_UNIT_NAME
)
from .core.worker import Worker
from .core.manager import Mode

_LOGGER = logging.getLogger(__name__)

async def async_setup(hass: HomeAssistant, config: dict) -> bool:
await async_init_services(hass)

if DOMAIN not in config:
return True

if DOMAIN in hass.data:
return False

hass.data.setdefault(DOMAIN, {})

hass.async_create_task(hass.config_entries.flow.async_init(
DOMAIN, context = {'source': SOURCE_IMPORT}, data = config
))

return True

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
if config_entry.data:
hass.config_entries.async_update_entry(config_entry, data = {} , options = config_entry.data)

worker = Worker(hass, config_entry)

hass.data.setdefault(DOMAIN, worker)

if not await worker.async_setup():
return False

return True

async def async_init_services(hass: HomeAssistant) -> None:
async def service_start(service_call: ServiceCall) -> None:
await async_call_action(hass, SERVICE_START, dict(service_call.data))

async def service_stop(service_call: ServiceCall) -> None:
await async_call_action(hass, SERVICE_STOP, dict(service_call.data))

async def service_restart(service_call: ServiceCall) -> None:
await async_call_action(hass, SERVICE_RESTART, dict(service_call.data))

async def service_enable(service_call: ServiceCall) -> None:
await async_call_action(hass, SERVICE_ENABLE, dict(service_call.data))

async def service_disable(service_call: ServiceCall) -> None:
await async_call_action(hass, SERVICE_DISABLE, dict(service_call.data))

hass.services.async_register(DOMAIN, SERVICE_START, service_start)
hass.services.async_register(DOMAIN, SERVICE_STOP, service_stop)
hass.services.async_register(DOMAIN, SERVICE_RESTART, service_restart)
hass.services.async_register(DOMAIN, SERVICE_ENABLE, service_enable)
hass.services.async_register(DOMAIN, SERVICE_DISABLE, service_disable)

async def async_call_action(hass: HomeAssistant, action: str, data: dict) -> None:
entities = data.pop('entity_id', None)

if not entities:
return

mode = data.pop(CONF_MODE, None)
if mode:
mode = Mode[mode]

manager = hass.data[DOMAIN].manager

for entity_id in entities:
state = hass.states.get(entity_id)
if not state:
continue

unit_name = state.attributes[ATTR_UNIT_NAME]

if action == SERVICE_START:
manager.start(unit_name, mode)
return

if action == SERVICE_STOP:
manager.stop(unit_name, mode)
return

if action == SERVICE_RESTART:
manager.restart(unit_name, mode)
return

if action == SERVICE_ENABLE:
manager.enable(unit_name)
return

if action == SERVICE_DISABLE:
manager.disable(unit_name)
return
Loading

0 comments on commit b8c043c

Please sign in to comment.