├── .devcontainer.json ├── .github ├── dependabot.yaml └── workflows │ └── builder.yaml ├── LICENSE ├── README.md ├── addons ├── Dockerfile ├── devcontainer.json ├── rootfs │ └── usr │ │ └── bin │ │ ├── devcontainer_bootstrap │ │ └── supervisor_run ├── tasks.json └── version ├── common ├── install │ ├── cosign │ ├── docker │ ├── os-agent │ ├── shellcheck │ ├── versions.json │ └── yarn ├── rootfs │ ├── etc │ │ └── docker │ │ │ └── daemon.json │ ├── root │ │ └── .cas-trusted-signing-pub-key │ └── usr │ │ └── bin │ │ ├── common_install_packages │ │ ├── devcontainer_init │ │ ├── get_arch │ │ └── get_package_version └── rootfs_supervisor │ ├── etc │ └── supervisor_scripts │ │ └── common │ └── usr │ └── bin │ ├── ha │ └── supervisor_bootstrap └── supervisor ├── Dockerfile ├── rootfs └── usr │ └── bin │ ├── devcontainer_bootstrap │ ├── devcontainer_setup │ └── supervisor_run └── version /.devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "home-assistant/devcontainer", 3 | "image": "mcr.microsoft.com/devcontainers/base:bookworm" 4 | } -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "06:00" -------------------------------------------------------------------------------- /.github/workflows/builder.yaml: -------------------------------------------------------------------------------- 1 | name: Builder 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | name: Build devcontainer 15 | strategy: 16 | matrix: 17 | devcontainer: 18 | - "addons" 19 | - "supervisor" 20 | 21 | steps: 22 | - name: 📥 Checkout the repository 23 | uses: actions/checkout@v4.2.2 24 | 25 | - name: 🏗 Set up QEMU 26 | uses: docker/setup-qemu-action@v3.6.0 27 | 28 | - name: 🏗 Set up Docker Buildx 29 | uses: docker/setup-buildx-action@v3.10.0 30 | 31 | - name: 🔓 Registry login 32 | if: github.event_name == 'push' 33 | uses: docker/login-action@v3.4.0 34 | with: 35 | registry: ghcr.io 36 | username: ${{ github.repository_owner }} 37 | password: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: Get version of devcontainer 40 | id: get-version 41 | run: | 42 | echo "version=$(head -1 ./${{ matrix.devcontainer }}/version)" >> $GITHUB_OUTPUT 43 | echo "gitsha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT 44 | 45 | - name: 🛠️ Build devcontainer (${{ matrix.devcontainer }}) 46 | uses: docker/build-push-action@v6.18.0 47 | with: 48 | context: . 49 | file: ./${{ matrix.devcontainer }}/Dockerfile 50 | platforms: linux/amd64,linux/arm64 51 | tags: | 52 | ghcr.io/${{ github.repository_owner }}/devcontainer:${{ steps.get-version.outputs.version }}-${{ matrix.devcontainer }} 53 | ghcr.io/${{ github.repository_owner }}/devcontainer:${{ matrix.devcontainer }} 54 | ghcr.io/${{ github.repository_owner }}/devcontainer:${{ matrix.devcontainer }}-${{ steps.get-version.outputs.gitsha }} 55 | push: ${{ github.event_name == 'push' }} 56 | 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # devcontainer 2 | 3 | This project contains custom devcontainers for use in our repositories. 4 | 5 | 6 | ## Images 7 | 8 | Image | Description | Dockerfile 9 | -- | -- | -- 10 | `ghcr.io/home-assistant/devcontainer:addons` | For Add-on development | [./addons/Dockerfile](./addons/Dockerfile) 11 | `ghcr.io/home-assistant/devcontainer:supervisor` | For Supervisor development | [./supervisor/Dockerfile](./supervisor/Dockerfile) 12 | 13 | Versioned images are available with the custom devcontainer version prepended (e.g. `1-supervisor`). This loosly resembles what 14 | upstream devcontainers are providing as well. The version is meant to be incremented when non-backwards compatible changes are 15 | made. That allows existing devcontainer configuration to work while updating the devcontainers (e.g. when the Supervisor devcontainer 16 | is updated to a new Python version). 17 | 18 | ## Example files 19 | 20 | Example files to use with Visual Studio Code 21 | 22 | ### addon 23 | 24 | Example files for the `addons` devcontainer 25 | 26 | - [Example configuration (for `.devcontainer/devcontainer.json`)](./addons/devcontainer.json) 27 | - [Example tasks file (for `.vscode/tasks.json`)](./addons/tasks.json) 28 | 29 | 30 | 31 | ## Notes 32 | 33 | ### `addons` and `supervisor` 34 | 35 | - Use the command `supervisor_run` to start Home Assistant inside the devcontainer, or run the task "Start Home Assistant" if you copied the tasks file. 36 | - Use `ha` to use the custom Home Assistant CLI (Needs the supervisor to be running). 37 | 38 | -------------------------------------------------------------------------------- /addons/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/base:1-debian 2 | 3 | ENV \ 4 | DEBIAN_FRONTEND=noninteractive \ 5 | DEVCONTAINER=1 6 | 7 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 8 | 9 | # Install tools 10 | RUN \ 11 | apt-get update \ 12 | && apt-get install -y --no-install-recommends \ 13 | dbus \ 14 | network-manager \ 15 | libpulse0 \ 16 | xz-utils 17 | 18 | COPY ./common/rootfs / 19 | COPY ./common/rootfs_supervisor / 20 | COPY ./common/install /tmp/common/install 21 | 22 | # Install common 23 | RUN \ 24 | bash devcontainer_init \ 25 | && common_install_packages \ 26 | docker \ 27 | shellcheck \ 28 | cosign \ 29 | os-agent \ 30 | && usermod -aG docker vscode 31 | 32 | COPY ./addons/rootfs / 33 | -------------------------------------------------------------------------------- /addons/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Example devcontainer for add-on repositories", 3 | "image": "ghcr.io/home-assistant/devcontainer:2-addons", 4 | "appPort": ["7123:8123", "7357:4357"], 5 | "postStartCommand": "bash devcontainer_bootstrap", 6 | "runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"], 7 | "containerEnv": { 8 | "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" 9 | }, 10 | "customizations": { 11 | "vscode": { 12 | "extensions": ["timonwong.shellcheck", "esbenp.prettier-vscode"], 13 | "settings": { 14 | "terminal.integrated.profiles.linux": { 15 | "zsh": { 16 | "path": "/usr/bin/zsh" 17 | } 18 | }, 19 | "terminal.integrated.defaultProfile.linux": "zsh", 20 | "editor.formatOnPaste": false, 21 | "editor.formatOnSave": true, 22 | "editor.formatOnType": true, 23 | "files.trimTrailingWhitespace": true 24 | } 25 | } 26 | }, 27 | "mounts": [ 28 | "type=volume,target=/var/lib/docker", 29 | "type=volume,target=/mnt/supervisor" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /addons/rootfs/usr/bin/devcontainer_bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | bash /usr/bin/supervisor_bootstrap 6 | 7 | exit 0 -------------------------------------------------------------------------------- /addons/rootfs/usr/bin/supervisor_run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source /etc/supervisor_scripts/common 6 | 7 | echo "Run Supervisor" 8 | 9 | start_systemd_journald 10 | start_docker 11 | trap "stop_docker" ERR 12 | 13 | function run_supervisor() { 14 | validate_devcontainer "addons" 15 | 16 | docker run --rm --privileged \ 17 | --name hassio_supervisor \ 18 | --privileged \ 19 | --security-opt seccomp=unconfined \ 20 | --security-opt apparmor=unconfined \ 21 | -v /run/docker.sock:/run/docker.sock:rw \ 22 | -v /run/dbus:/run/dbus:ro \ 23 | -v /run/udev:/run/udev:ro \ 24 | -v /mnt/supervisor:/data:rw \ 25 | -v "$WORKSPACE_DIRECTORY":/data/addons/local:rw \ 26 | -v /etc/machine-id:/etc/machine-id:ro \ 27 | -e SUPERVISOR_SHARE="/mnt/supervisor" \ 28 | -e SUPERVISOR_NAME=hassio_supervisor \ 29 | -e SUPERVISOR_DEV=1 \ 30 | -e SUPERVISOR_MACHINE="qemu${QEMU_ARCH}" \ 31 | -e SUPERVISOR_SYSTEMD_JOURNAL_GATEWAYD_URL="http://172.30.32.1:19531/" \ 32 | "${SUPERVISOR_IMAGE}:${SUPERVISOR_VERSION}" 33 | } 34 | 35 | 36 | if [ "$( docker container inspect -f '{{.State.Status}}' hassio_supervisor )" == "running" ]; then 37 | echo "Restarting Supervisor" 38 | docker rm -f hassio_supervisor 39 | init_dbus 40 | init_udev 41 | init_os_agent 42 | cleanup_lastboot 43 | run_supervisor 44 | stop_docker 45 | 46 | else 47 | echo "Starting Supervisor" 48 | docker system prune -f 49 | cleanup_lastboot 50 | cleanup_docker 51 | init_dbus 52 | init_udev 53 | init_os_agent 54 | run_supervisor 55 | stop_docker 56 | fi 57 | -------------------------------------------------------------------------------- /addons/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Start Home Assistant", 6 | "type": "shell", 7 | "command": "supervisor_run", 8 | "group": { 9 | "kind": "test", 10 | "isDefault": true 11 | }, 12 | "presentation": { 13 | "reveal": "always", 14 | "panel": "new" 15 | }, 16 | "problemMatcher": [] 17 | } 18 | ] 19 | } 20 | 21 | -------------------------------------------------------------------------------- /addons/version: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /common/install/cosign: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ARCH=$(get_arch docker) 6 | 7 | COSIGN_VERSION=$(get_package_version cosign) 8 | 9 | curl -fLs \ 10 | "https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-${ARCH}" \ 11 | --output ./cosign 12 | 13 | chmod +x ./cosign 14 | mv -f ./cosign /usr/local/bin/cosign 15 | rm -f ./cosign 16 | -------------------------------------------------------------------------------- /common/install/docker: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | apt-get update 6 | apt-get install -y --no-install-recommends \ 7 | ca-certificates \ 8 | curl \ 9 | gnupg \ 10 | systemd-journal-remote 11 | 12 | install -m 0755 -d /etc/apt/keyrings 13 | curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg 14 | chmod a+r /etc/apt/keyrings/docker.gpg 15 | 16 | echo \ 17 | "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ 18 | "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ 19 | tee /etc/apt/sources.list.d/docker.list > /dev/null 20 | 21 | apt-get update 22 | apt-get install -y --no-install-recommends \ 23 | docker-ce \ 24 | docker-ce-cli \ 25 | containerd.io 26 | 27 | rm -rf /var/lib/apt/lists/* 28 | -------------------------------------------------------------------------------- /common/install/os-agent: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ARCH=$(get_arch) 6 | OS_AGENT_VERSION=$(get_package_version os-agent) 7 | 8 | apt update 9 | apt install -y udisks2 10 | 11 | curl -Lso ./os-agent.deb \ 12 | "https://github.com/home-assistant/os-agent/releases/download/${OS_AGENT_VERSION}/os-agent_${OS_AGENT_VERSION}_linux_${ARCH}.deb" 13 | 14 | dpkg -i ./os-agent.deb 15 | rm ./os-agent.deb 16 | -------------------------------------------------------------------------------- /common/install/shellcheck: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ARCH=$(get_arch) 6 | 7 | curl -fLs \ 8 | "https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.linux.${ARCH}.tar.xz" \ 9 | | tar -xJ 10 | 11 | mv -f "./shellcheck-stable/shellcheck" "/usr/bin/shellcheck" 12 | rm -rf "./shellcheck-stable" 13 | -------------------------------------------------------------------------------- /common/install/versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "cosign": "2.4.0", 3 | "os-agent": "1.6.0", 4 | "nvm": "0.40.1" 5 | } 6 | -------------------------------------------------------------------------------- /common/install/yarn: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | NVM_VERSION=$(get_package_version nvm) 6 | 7 | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - 8 | 9 | apt-get update 10 | apt-get install -y --no-install-recommends \ 11 | curl \ 12 | git \ 13 | apt-utils \ 14 | apt-transport-https 15 | 16 | echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list 17 | 18 | apt-get update 19 | apt-get install -y --no-install-recommends \ 20 | nodejs \ 21 | yarn 22 | 23 | curl -o - "https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh" | bash 24 | rm -rf /var/lib/apt/lists/* 25 | -------------------------------------------------------------------------------- /common/rootfs/etc/docker/daemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "log-driver": "journald", 3 | "ip6tables": true, 4 | "experimental": true, 5 | "log-opts": { 6 | "tag": "{{.Name}}" 7 | } 8 | } -------------------------------------------------------------------------------- /common/rootfs/root/.cas-trusted-signing-pub-key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE03LvYuz79GTJx4uKp3w6NrSe5JZI 3 | iBtgzzYi0YQYtZO/r+xFpgDJEa0gLHkXtl94fpqrFiN89In83lzaszbZtA== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /common/rootfs/usr/bin/common_install_packages: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | packages="${@}" 6 | 7 | for package in ${packages}; do 8 | echo "Installing ${package}"; 9 | bash "/tmp/common/install/${package}"; 10 | done 11 | 12 | rm -rf /tmp/common -------------------------------------------------------------------------------- /common/rootfs/usr/bin/devcontainer_init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | for script in \ 6 | common_install_packages \ 7 | get_package_version \ 8 | ; do 9 | if [ -f "/usr/bin/${script}" ]; then 10 | chmod +x "/usr/bin/${script}" 11 | fi 12 | done 13 | -------------------------------------------------------------------------------- /common/rootfs/usr/bin/get_arch: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | mode=${1:-unix} 6 | case $(arch) in 7 | x86_64|amd64) 8 | case $mode in 9 | unix) 10 | echo "x86_64";; 11 | docker|ha) 12 | echo "amd64";; 13 | qemu) 14 | echo "x86-64";; 15 | esac 16 | ;; 17 | aarch64|arm64) 18 | case $mode in 19 | unix|ha) 20 | echo "aarch64";; 21 | docker) 22 | echo "arm64";; 23 | qemu) 24 | echo "arm-64";; 25 | esac 26 | ;; 27 | esac 28 | -------------------------------------------------------------------------------- /common/rootfs/usr/bin/get_package_version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | package="$1" 6 | jq -r --arg package "$package" '.[$package]' /tmp/common/install/versions.json 7 | -------------------------------------------------------------------------------- /common/rootfs_supervisor/etc/supervisor_scripts/common: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | VERSION_INFO=$(curl -s https://version.home-assistant.io/dev.json) 6 | HA_ARCH=$(get_arch ha) 7 | QEMU_ARCH=$(get_arch qemu) 8 | DOCKER_TIMEOUT=30 9 | SUPERVISOR_SHARE="/mnt/supervisor" 10 | 11 | export SUPERVISOR_VERSION="$(echo ${VERSION_INFO} | jq -e -r '.supervisor')" 12 | export SUPERVISOR_IMAGE="$(sed "s/{arch}/${HA_ARCH}/g" <<< "$(echo ${VERSION_INFO} | jq -e -r '.images.supervisor')")" 13 | 14 | 15 | function start_systemd_journald() { 16 | if ! [ -e /var/log/journal ]; then 17 | echo "Creating systemd-journald tmpfiles." 18 | sudo systemd-tmpfiles --create --prefix /var/log/journal 19 | fi 20 | 21 | if ! pgrep -f systemd-journald; then 22 | echo "Starting systemd-journald." 23 | sudo /usr/lib/systemd/systemd-journald & 24 | fi 25 | 26 | if ! pgrep -f systemd-journal-gatewayd; then 27 | echo "Starting systemd-journal-gatewayd." 28 | sudo /usr/lib/systemd/systemd-journal-gatewayd --system 2> /dev/null & 29 | fi 30 | } 31 | 32 | 33 | function start_docker() { 34 | local start_time 35 | local current_time 36 | local elapsed_time 37 | 38 | if grep -q 'microsoft-standard\|standard-WSL' /proc/version; then 39 | # The docker daemon does not start when running WSL2 without adjusting iptables 40 | sudo update-alternatives --set iptables /usr/sbin/iptables-legacy || echo "Fails adjust iptables" 41 | sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || echo "Fails adjust ip6tables" 42 | fi 43 | 44 | echo "Starting docker." 45 | if stat -f -c %T /var/lib/docker | grep -q overlayfs; then 46 | echo "Using \"vfs\" storage driver. Bind mount /var/lib/docker to use the faster overlay2 driver." 47 | storage_driver="vfs" 48 | else 49 | storage_driver="overlay2" 50 | fi 51 | sudo dockerd --storage-driver="${storage_driver}" > /dev/null 2>&1 & 52 | 53 | echo "Waiting for Docker to initialize..." 54 | socket_path="/var/run/docker.sock" 55 | 56 | start_time=$(date +%s) 57 | while [[ ! -S "$socket_path" || ! -r "$socket_path" ]]; do 58 | current_time=$(date +%s) 59 | elapsed_time=$((current_time - start_time)) 60 | 61 | if [[ $elapsed_time -ge $DOCKER_TIMEOUT ]]; then 62 | echo "Timeout: Docker did not start within $DOCKER_TIMEOUT seconds." 63 | exit 1 64 | fi 65 | 66 | sleep 1 67 | done 68 | 69 | # It seems that dockerd messes with the terminal somehow, carriage returns (\r) 70 | # seem not to function properly. Resetting the terminal fixes this. 71 | stty sane 72 | echo "Docker was initialized" 73 | } 74 | 75 | function stop_docker() { 76 | local start_time 77 | local current_time 78 | local elapsed_time 79 | 80 | # Check if docker pid is there 81 | if [ ! -f /var/run/docker.pid ]; then 82 | echo "No pid found for docker" 83 | return 0 84 | fi 85 | 86 | echo "Stopping Docker daemon..." 87 | docker_pid=$(cat /var/run/docker.pid) 88 | echo "Sending SIGTERM to docker daemon $docker_pid" 89 | if sudo kill -0 "$docker_pid" 2> /dev/null; then 90 | start_time="$(date +%s)" 91 | 92 | # Now wait for it to exit 93 | sudo kill "$docker_pid" 94 | while sudo kill -0 "$docker_pid" 2> /dev/null; do 95 | current_time=$(date +%s) 96 | elapsed_time=$((current_time - start_time)) 97 | if [[ $elapsed_time -ge $DOCKER_TIMEOUT ]]; then 98 | echo "Timeout while waiting for Docker daemon to exit" 99 | exit 1 100 | fi 101 | sleep 1 102 | done 103 | else 104 | echo "Unable to find Docker daemon process" 105 | fi 106 | } 107 | 108 | function cleanup_lastboot() { 109 | if [[ -f /mnt/supervisor/config.json ]]; then 110 | echo "Cleaning up last boot" 111 | sudo jq -rM 'del(.last_boot)' /mnt/supervisor/config.json > /tmp/config.json 112 | sudo mv /tmp/config.json /mnt/supervisor/config.json 113 | fi 114 | } 115 | 116 | function cleanup_docker() { 117 | echo "Stopping and removing containers..." 118 | containers=$(docker ps -a -q) 119 | if [ -n "$containers" ]; then 120 | docker stop $containers 121 | docker rm $containers 122 | fi 123 | } 124 | 125 | function init_dbus() { 126 | if pgrep dbus-daemon; then 127 | echo "Dbus is running" 128 | return 0 129 | fi 130 | 131 | echo "Startup dbus" 132 | sudo mkdir -p /var/lib/dbus 133 | sudo cp -f /etc/machine-id /var/lib/dbus/machine-id 134 | 135 | # cleanups 136 | sudo mkdir -p /run/dbus 137 | sudo rm -f /run/dbus/pid 138 | 139 | # run 140 | sudo dbus-daemon --system --print-address 141 | } 142 | 143 | 144 | function init_udev() { 145 | if pgrep systemd-udevd; then 146 | echo "udev is running" 147 | return 0 148 | fi 149 | 150 | echo "Startup udev" 151 | 152 | # cleanups 153 | sudo mkdir -p /run/udev 154 | 155 | # run 156 | sudo /lib/systemd/systemd-udevd --daemon 157 | sleep 3 158 | sudo udevadm trigger && sudo udevadm settle 159 | } 160 | 161 | 162 | function init_os_agent() { 163 | if pgrep os-agent; then 164 | echo "os-agent is running" 165 | return 0 166 | fi 167 | 168 | sudo os-agent & 169 | } 170 | 171 | 172 | validate_devcontainer() { 173 | local devcontainer_type="$1" 174 | 175 | if ! grep -q "$SUPERVISOR_SHARE" /proc/mounts; then 176 | echo "" 177 | echo "WARNING: $SUPERVISOR_SHARE is not a mount! This might cause issues for the" 178 | echo "Home Assistant Core media and share folders mount propagation option. Make" 179 | echo "sure to update your .devcontainer." 180 | echo "" 181 | if [ "$devcontainer_type" == "addons" ]; then 182 | echo "You can find the template for addons here:" 183 | echo "https://github.com/home-assistant/devcontainer/blob/main/addons/devcontainer.json" 184 | echo "" 185 | fi 186 | # For some tests this might be good enough, so create a directory to make things 187 | # progress. 188 | sudo mkdir -p "$SUPERVISOR_SHARE" 189 | fi 190 | } -------------------------------------------------------------------------------- /common/rootfs_supervisor/usr/bin/ha: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # shellcheck disable=SC2048,SC2086 3 | 4 | docker exec hassio_cli ha $* 5 | -------------------------------------------------------------------------------- /common/rootfs_supervisor/usr/bin/supervisor_bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | sudo rm /etc/machine-id 6 | sudo dbus-uuidgen --ensure=/etc/machine-id 7 | 8 | if grep -q 'microsoft-standard\|standard-WSL' /proc/version; then 9 | # The docker daemon does not start when running WSL2 without adjusting iptables 10 | sudo update-alternatives --set iptables /usr/sbin/iptables-legacy || echo "Fails adjust iptables" 11 | sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || echo "Fails adjust ip6tables" 12 | fi 13 | -------------------------------------------------------------------------------- /supervisor/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/python:1-3.13 2 | 3 | ENV \ 4 | DEBIAN_FRONTEND=noninteractive \ 5 | DEVCONTAINER=1 \ 6 | NVM_DIR="/root/.nvm" 7 | 8 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 9 | 10 | # Install tools 11 | RUN \ 12 | apt-get update \ 13 | && apt-get install -y --no-install-recommends \ 14 | dbus-daemon \ 15 | network-manager \ 16 | libpulse0 \ 17 | xz-utils 18 | 19 | COPY ./common/rootfs / 20 | COPY ./common/rootfs_supervisor / 21 | COPY ./common/install /tmp/common/install 22 | 23 | # Install common 24 | RUN \ 25 | pip3 install --no-cache-dir uv \ 26 | && bash devcontainer_init \ 27 | && common_install_packages \ 28 | docker \ 29 | shellcheck \ 30 | cosign \ 31 | os-agent \ 32 | yarn \ 33 | && usermod -aG docker vscode 34 | 35 | COPY ./supervisor/rootfs / 36 | 37 | # Set up venv for UV 38 | USER vscode 39 | ENV VIRTUAL_ENV="/home/vscode/.local/ha-venv" 40 | RUN uv venv $VIRTUAL_ENV 41 | 42 | # Setting PATH here isn't enough, VSCode rewites it after initial scripts finish 43 | # Must also be set in remoteEnv in devcontainer.json 44 | ENV PATH="$VIRTUAL_ENV/bin:$PATH" 45 | -------------------------------------------------------------------------------- /supervisor/rootfs/usr/bin/devcontainer_bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | uv pip install -U setuptools uv 6 | uv pip install --compile -r requirements.txt -r requirements_tests.txt 7 | uv pip install tox 8 | -------------------------------------------------------------------------------- /supervisor/rootfs/usr/bin/devcontainer_setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | bash /usr/bin/supervisor_bootstrap 6 | 7 | if [ ! -n "$VIRTUAL_ENV" ]; then 8 | if [ -x "$(command -v uv)" ]; then 9 | uv venv venv 10 | else 11 | python3 -m venv venv 12 | fi 13 | source venv/bin/activate 14 | fi 15 | 16 | if ! [ -x "$(command -v uv)" ]; then 17 | python3 -m pip install uv 18 | fi 19 | 20 | bash /usr/bin/devcontainer_bootstrap 21 | 22 | git config --global --add safe.directory "${WORKSPACE_DIRECTORY}" 23 | pre-commit install 24 | -------------------------------------------------------------------------------- /supervisor/rootfs/usr/bin/supervisor_run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source /etc/supervisor_scripts/common 6 | 7 | WD="${WORKSPACE_DIRECTORY:=/workspaces/supervisor}" 8 | 9 | echo "Run Supervisor" 10 | 11 | start_systemd_journald 12 | start_docker 13 | trap "stop_docker" ERR 14 | 15 | function build_supervisor() { 16 | docker pull "ghcr.io/home-assistant/${HA_ARCH}-builder:dev" 17 | cosign verify \ 18 | --certificate-oidc-issuer https://token.actions.githubusercontent.com \ 19 | --certificate-identity-regexp https://github.com/home-assistant/builder/.* \ 20 | "ghcr.io/home-assistant/${HA_ARCH}-builder:dev" 21 | 22 | docker run --rm \ 23 | --privileged \ 24 | -v /run/docker.sock:/run/docker.sock \ 25 | -v "$(pwd):/data" \ 26 | "ghcr.io/home-assistant/${HA_ARCH}-builder:dev" \ 27 | --generic latest \ 28 | --target /data \ 29 | --test \ 30 | "--${HA_ARCH}" \ 31 | --no-cache 32 | } 33 | 34 | function run_supervisor() { 35 | validate_devcontainer "supervisor" 36 | 37 | echo "Start Supervisor" 38 | docker run --rm --privileged \ 39 | --name hassio_supervisor \ 40 | --privileged \ 41 | --security-opt seccomp=unconfined \ 42 | --security-opt apparmor=unconfined \ 43 | -v /run/docker.sock:/run/docker.sock:rw \ 44 | -v /run/dbus:/run/dbus:ro \ 45 | -v /run/udev:/run/udev:ro \ 46 | -v "/mnt/supervisor":/data:rw \ 47 | -v /etc/machine-id:/etc/machine-id:ro \ 48 | -v "${WD}:/usr/src/supervisor" \ 49 | -e SUPERVISOR_SHARE="/mnt/supervisor" \ 50 | -e SUPERVISOR_NAME=hassio_supervisor \ 51 | -e SUPERVISOR_DEV=1 \ 52 | -e SUPERVISOR_MACHINE="qemu${QEMU_ARCH}" \ 53 | -e SUPERVISOR_SYSTEMD_JOURNAL_GATEWAYD_URL="http://172.30.32.1:19531/" \ 54 | "ghcr.io/home-assistant/${HA_ARCH}-hassio-supervisor:latest" 55 | } 56 | 57 | 58 | if [ "$( docker container inspect -f '{{.State.Status}}' hassio_supervisor )" == "running" ]; then 59 | echo "Restarting Supervisor" 60 | docker rm -f hassio_supervisor 61 | init_dbus 62 | init_udev 63 | init_os_agent 64 | cleanup_lastboot 65 | run_supervisor 66 | stop_docker 67 | 68 | else 69 | echo "Starting Supervisor" 70 | docker system prune -f 71 | build_supervisor 72 | cleanup_lastboot 73 | cleanup_docker 74 | init_dbus 75 | init_udev 76 | init_os_agent 77 | run_supervisor 78 | stop_docker 79 | fi 80 | -------------------------------------------------------------------------------- /supervisor/version: -------------------------------------------------------------------------------- 1 | 2 2 | --------------------------------------------------------------------------------