mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-09-16 00:19:31 +00:00
Compare commits
15 Commits
2023.06.1
...
remove-pas
Author | SHA1 | Date | |
---|---|---|---|
![]() |
de7ef86f52 | ||
![]() |
6f614c91d7 | ||
![]() |
8b4e8e9804 | ||
![]() |
5d1ef34f17 | ||
![]() |
9504eff889 | ||
![]() |
d5828a6815 | ||
![]() |
488f246f75 | ||
![]() |
000d4ec78a | ||
![]() |
6c0415163b | ||
![]() |
0205cbb78b | ||
![]() |
72db559adc | ||
![]() |
a57c145870 | ||
![]() |
759fd1077a | ||
![]() |
fb90e6d07e | ||
![]() |
86d17acd83 |
18
.github/workflows/builder.yml
vendored
18
.github/workflows/builder.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
requirements: ${{ steps.requirements.outputs.changed }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -121,14 +121,14 @@ jobs:
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: docker/login-action@v2.1.0
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: docker/login-action@v2.1.0
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
run: echo "BUILD_ARGS=--test" >> $GITHUB_ENV
|
||||
|
||||
- name: Build supervisor
|
||||
uses: home-assistant/builder@2023.03.0
|
||||
uses: home-assistant/builder@2023.06.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -156,7 +156,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Initialize git
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
@@ -220,11 +220,11 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Build the Supervisor
|
||||
if: needs.init.outputs.publish != 'true'
|
||||
uses: home-assistant/builder@2023.03.0
|
||||
uses: home-assistant/builder@2023.06.0
|
||||
with:
|
||||
args: |
|
||||
--test \
|
||||
|
22
.github/workflows/ci.yaml
vendored
22
.github/workflows/ci.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
name: Prepare Python dependencies
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python
|
||||
id: python
|
||||
uses: actions/setup-python@v4.6.1
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Register hadolint problem matcher
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
@@ -187,7 +187,7 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
@@ -228,7 +228,7 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
@@ -272,7 +272,7 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
@@ -304,7 +304,7 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
@@ -345,7 +345,7 @@ jobs:
|
||||
name: Run tests Python ${{ needs.prepare.outputs.python-version }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
@@ -403,7 +403,7 @@ jobs:
|
||||
needs: ["pytest", "prepare"]
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.6.1
|
||||
id: python
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4.0.0
|
||||
- uses: dessant/lock-threads@v4.0.1
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: "30"
|
||||
|
2
.github/workflows/release-drafter.yml
vendored
2
.github/workflows/release-drafter.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Release Drafter
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
2
.github/workflows/sentry.yaml
vendored
2
.github/workflows/sentry.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.3
|
||||
- name: Sentry Release
|
||||
uses: getsentry/action-release@v1.4.1
|
||||
env:
|
||||
|
@@ -20,7 +20,7 @@ pulsectl==23.5.2
|
||||
pyudev==0.24.1
|
||||
ruamel.yaml==0.17.21
|
||||
securetar==2023.3.0
|
||||
sentry-sdk==1.25.0
|
||||
sentry-sdk==1.25.1
|
||||
voluptuous==0.13.1
|
||||
dbus-fast==1.86.0
|
||||
typing_extensions==4.6.3
|
||||
|
@@ -2,15 +2,15 @@ black==23.3.0
|
||||
coverage==7.2.7
|
||||
flake8-docstrings==1.7.0
|
||||
flake8==6.0.0
|
||||
pre-commit==3.3.2
|
||||
pre-commit==3.3.3
|
||||
pydocstyle==6.3.0
|
||||
pylint==2.17.4
|
||||
pytest-aiohttp==1.0.4
|
||||
pytest-asyncio==0.18.3
|
||||
pytest-cov==4.1.0
|
||||
pytest-timeout==2.1.0
|
||||
pytest==7.3.1
|
||||
pyupgrade==3.4.0
|
||||
pytest==7.3.2
|
||||
pyupgrade==3.6.0
|
||||
time-machine==2.9.0
|
||||
typing_extensions==4.6.3
|
||||
urllib3==1.26.15
|
||||
urllib3==2.0.3
|
||||
|
@@ -100,6 +100,7 @@ class APIBackups(CoreSysAttributes):
|
||||
ATTR_DATE: backup.date,
|
||||
ATTR_TYPE: backup.sys_type,
|
||||
ATTR_SIZE: backup.size,
|
||||
ATTR_LOCATON: backup.location,
|
||||
ATTR_PROTECTED: backup.protected,
|
||||
ATTR_COMPRESSED: backup.compressed,
|
||||
ATTR_CONTENT: {
|
||||
@@ -172,6 +173,7 @@ class APIBackups(CoreSysAttributes):
|
||||
ATTR_PROTECTED: backup.protected,
|
||||
ATTR_SUPERVISOR_VERSION: backup.supervisor_version,
|
||||
ATTR_HOMEASSISTANT: backup.homeassistant_version,
|
||||
ATTR_LOCATON: backup.location,
|
||||
ATTR_ADDONS: data_addons,
|
||||
ATTR_REPOSITORIES: backup.repositories,
|
||||
ATTR_FOLDERS: backup.folders,
|
||||
|
@@ -2,6 +2,7 @@
|
||||
from base64 import b64decode, b64encode
|
||||
from collections.abc import Awaitable
|
||||
from datetime import timedelta
|
||||
from functools import cached_property
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
@@ -150,6 +151,14 @@ class Backup(CoreSysAttributes):
|
||||
"""Set the Docker config data."""
|
||||
self._data[ATTR_DOCKER] = value
|
||||
|
||||
@cached_property
|
||||
def location(self) -> str | None:
|
||||
"""Return the location of the backup."""
|
||||
for backup_mount in self.sys_mounts.backup_mounts:
|
||||
if self.tarfile.is_relative_to(backup_mount.local_where):
|
||||
return backup_mount.name
|
||||
return None
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""Return backup size."""
|
||||
|
@@ -387,7 +387,7 @@ class DockerAddon(DockerInterface):
|
||||
source=self.sys_config.path_extern_share.as_posix(),
|
||||
target="/share",
|
||||
read_only=addon_mapping[MAP_SHARE],
|
||||
propagation=PropagationMode.SLAVE.value,
|
||||
propagation=PropagationMode.RSLAVE.value,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -398,7 +398,7 @@ class DockerAddon(DockerInterface):
|
||||
source=self.sys_config.path_extern_media.as_posix(),
|
||||
target="/media",
|
||||
read_only=addon_mapping[MAP_MEDIA],
|
||||
propagation=PropagationMode.SLAVE.value,
|
||||
propagation=PropagationMode.RSLAVE.value,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -457,7 +457,7 @@ class DockerAddon(DockerInterface):
|
||||
mounts += [
|
||||
Mount(
|
||||
type=MountType.BIND.value,
|
||||
source=self.sys_homeassistant.path_extern_pulse.as_posix(),
|
||||
source=self.addon.path_extern_pulse.as_posix(),
|
||||
target="/etc/pulse/client.conf",
|
||||
read_only=True,
|
||||
),
|
||||
|
@@ -97,14 +97,14 @@ class DockerHomeAssistant(DockerInterface):
|
||||
source=self.sys_config.path_extern_share.as_posix(),
|
||||
target="/share",
|
||||
read_only=False,
|
||||
propagation=PropagationMode.SLAVE.value,
|
||||
propagation=PropagationMode.RSLAVE.value,
|
||||
),
|
||||
Mount(
|
||||
type=MountType.BIND.value,
|
||||
source=self.sys_config.path_extern_media.as_posix(),
|
||||
target="/media",
|
||||
read_only=False,
|
||||
propagation=PropagationMode.SLAVE.value,
|
||||
propagation=PropagationMode.RSLAVE.value,
|
||||
),
|
||||
# Configuration audio
|
||||
Mount(
|
||||
|
@@ -358,7 +358,8 @@ class CIFSMount(NetworkMount):
|
||||
def options(self) -> list[str]:
|
||||
"""Options to use to mount."""
|
||||
return (
|
||||
super().options + [f"username={self.username}", f"password={self.password}"]
|
||||
super().options
|
||||
+ [f"username={self.username}", f"password='{self.password}'"]
|
||||
if self.username
|
||||
else []
|
||||
)
|
||||
|
@@ -21,13 +21,12 @@ from .const import (
|
||||
|
||||
RE_MOUNT_NAME = re.compile(r"^\w+$")
|
||||
RE_PATH_PART = re.compile(r"^[^\\\/]+")
|
||||
RE_MOUNT_OPTION = re.compile(r"^[^,=]+$")
|
||||
RE_MOUNT_OPTION = re.compile(r"^[^']+$")
|
||||
|
||||
VALIDATE_NAME = vol.Match(RE_MOUNT_NAME)
|
||||
VALIDATE_SERVER = vol.Match(RE_PATH_PART)
|
||||
VALIDATE_SHARE = vol.Match(RE_PATH_PART)
|
||||
VALIDATE_USERNAME = vol.Match(RE_MOUNT_OPTION)
|
||||
VALIDATE_PASSWORD = vol.Match(RE_MOUNT_OPTION)
|
||||
|
||||
|
||||
_SCHEMA_BASE_MOUNT_CONFIG = vol.Schema(
|
||||
{
|
||||
@@ -49,8 +48,8 @@ SCHEMA_MOUNT_CIFS = _SCHEMA_MOUNT_NETWORK.extend(
|
||||
{
|
||||
vol.Required(ATTR_TYPE): MountType.CIFS.value,
|
||||
vol.Required(ATTR_SHARE): VALIDATE_SHARE,
|
||||
vol.Inclusive(ATTR_USERNAME, "basic_auth"): VALIDATE_USERNAME,
|
||||
vol.Inclusive(ATTR_PASSWORD, "basic_auth"): VALIDATE_PASSWORD,
|
||||
vol.Inclusive(ATTR_USERNAME, "basic_auth"): vol.Match(RE_MOUNT_OPTION),
|
||||
vol.Inclusive(ATTR_PASSWORD, "basic_auth"): vol.Match(RE_MOUNT_OPTION),
|
||||
}
|
||||
)
|
||||
|
||||
|
@@ -12,7 +12,7 @@ from .base import CheckBase
|
||||
def _check_container(container: DockerInterface) -> bool:
|
||||
"""Return true if container has a config issue."""
|
||||
return any(
|
||||
mount.get("Propagation") != PropagationMode.SLAVE.value
|
||||
mount.get("Propagation") != PropagationMode.RSLAVE.value
|
||||
for mount in container.meta_mounts
|
||||
if mount.get("Destination") in ["/media", "/share"]
|
||||
)
|
||||
|
@@ -5,7 +5,7 @@ from ...coresys import CoreSys
|
||||
from ..const import UnsupportedReason
|
||||
from .base import EvaluateBase
|
||||
|
||||
SUPPORTED_OS = ["Debian GNU/Linux 11 (bullseye)"]
|
||||
SUPPORTED_OS = ["Debian GNU/Linux 11 (bullseye)", "Debian GNU/Linux 12 (bookworm)"]
|
||||
|
||||
|
||||
def setup(coresys: CoreSys) -> EvaluateBase:
|
||||
|
@@ -95,6 +95,11 @@ async def test_backup_to_location(
|
||||
|
||||
assert (tmp_supervisor_data / backup_dir / f"{slug}.tar").exists()
|
||||
|
||||
resp = await api_client.get(f"/backups/{slug}/info")
|
||||
result = await resp.json()
|
||||
assert result["result"] == "ok"
|
||||
assert result["data"]["location"] == location
|
||||
|
||||
|
||||
async def test_backup_to_default(
|
||||
api_client: TestClient,
|
||||
|
@@ -121,7 +121,7 @@ def test_addon_map_folder_defaults(
|
||||
source=coresys.config.path_extern_media.as_posix(),
|
||||
target="/media",
|
||||
read_only=True,
|
||||
propagation="slave",
|
||||
propagation="rslave",
|
||||
)
|
||||
in docker_addon.mounts
|
||||
)
|
||||
@@ -133,7 +133,7 @@ def test_addon_map_folder_defaults(
|
||||
source=coresys.config.path_extern_share.as_posix(),
|
||||
target="/share",
|
||||
read_only=True,
|
||||
propagation="slave",
|
||||
propagation="rslave",
|
||||
)
|
||||
in docker_addon.mounts
|
||||
)
|
||||
|
@@ -39,7 +39,7 @@ async def test_cifs_mount(
|
||||
"server": "test.local",
|
||||
"share": "camera",
|
||||
"username": "admin",
|
||||
"password": "password",
|
||||
"password": "p@assword!,=",
|
||||
}
|
||||
mount: CIFSMount = Mount.from_dict(coresys, mount_data)
|
||||
|
||||
@@ -54,7 +54,7 @@ async def test_cifs_mount(
|
||||
assert mount.what == "//test.local/camera"
|
||||
assert mount.where == Path("/mnt/data/supervisor/mounts/test")
|
||||
assert mount.local_where == tmp_supervisor_data / "mounts" / "test"
|
||||
assert mount.options == ["username=admin", "password=password"]
|
||||
assert mount.options == ["username=admin", "password='p@assword!,='"]
|
||||
|
||||
assert not mount.local_where.exists()
|
||||
assert mount.to_dict(skip_secrets=False) == mount_data
|
||||
@@ -73,7 +73,7 @@ async def test_cifs_mount(
|
||||
"mnt-data-supervisor-mounts-test.mount",
|
||||
"fail",
|
||||
[
|
||||
["Options", Variant("s", "username=admin,password=password")],
|
||||
["Options", Variant("s", "username=admin,password='p@assword!,='")],
|
||||
["Type", Variant("s", "cifs")],
|
||||
["Description", Variant("s", "Supervisor cifs mount: test")],
|
||||
["What", Variant("s", "//test.local/camera")],
|
||||
|
@@ -1,5 +1,7 @@
|
||||
"""Tests for mount manager validation."""
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from voluptuous import Invalid
|
||||
|
||||
@@ -15,6 +17,8 @@ async def test_valid_mounts():
|
||||
"type": "cifs",
|
||||
"server": "test.local",
|
||||
"share": "test",
|
||||
"username": "admin",
|
||||
"password": "p@assword!,=",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -77,12 +81,39 @@ async def test_invalid_cifs():
|
||||
SCHEMA_MOUNT_CONFIG(base)
|
||||
|
||||
# Path is for NFS
|
||||
with pytest.raises(Invalid):
|
||||
SCHEMA_MOUNT_CONFIG({"path": "backups"})
|
||||
with pytest.raises(
|
||||
Invalid, match=re.escape("required key not provided @ data['share']")
|
||||
):
|
||||
SCHEMA_MOUNT_CONFIG({**base, "path": "backups"})
|
||||
|
||||
# Username and password must be together
|
||||
with pytest.raises(Invalid):
|
||||
SCHEMA_MOUNT_CONFIG({"username": "admin"})
|
||||
with pytest.raises(
|
||||
Invalid,
|
||||
match=re.escape(
|
||||
"some but not all values in the same group of inclusion 'basic_auth' @ data[<basic_auth>]"
|
||||
),
|
||||
):
|
||||
SCHEMA_MOUNT_CONFIG({**base, "share": "test", "username": "admin"})
|
||||
|
||||
# Username and password must be together
|
||||
with pytest.raises(
|
||||
Invalid,
|
||||
match=re.escape(
|
||||
"some but not all values in the same group of inclusion 'basic_auth' @ data[<basic_auth>]"
|
||||
),
|
||||
):
|
||||
SCHEMA_MOUNT_CONFIG({**base, "share": "test", "password": "my=!pass"})
|
||||
|
||||
# Invalid password
|
||||
with pytest.raises(
|
||||
Invalid,
|
||||
match=re.escape(
|
||||
"does not match regular expression ^[^']+$ for dictionary value @ data['password']"
|
||||
),
|
||||
):
|
||||
SCHEMA_MOUNT_CONFIG(
|
||||
{**base, "share": "test", "username": "admin", "password": "my=!pa'ss,"}
|
||||
)
|
||||
|
||||
|
||||
async def test_invalid_nfs():
|
||||
|
Reference in New Issue
Block a user