├── icon.png ├── .gitignore ├── rootfs └── etc │ ├── services.d │ └── usbip │ │ ├── finish │ │ └── run │ ├── cont-init.d │ ├── load_modules.sh │ └── create_devices.sh │ └── cont-finish.d │ └── detach_devices.sh ├── Dockerfile ├── repository.yaml ├── CHANGELOG.md ├── config.yaml ├── LICENSE ├── apparmor.txt └── README.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptedx/ha-usbip-client/HEAD/icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore common files 2 | *.pyc 3 | *.pyo 4 | *.pyd 5 | __pycache__/ 6 | .env 7 | .DS_Store 8 | *.log 9 | *.swp 10 | *.bak 11 | 12 | # Ignore Docker-related files 13 | *.tar 14 | *.img 15 | 16 | # Ignore VSCode settings 17 | .vscode/ 18 | .aider* 19 | -------------------------------------------------------------------------------- /rootfs/etc/services.d/usbip/finish: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck disable=SC1008 3 | 4 | if [[ "${1}" -ne 0 ]] && [[ "${1}" -ne 256 ]]; then 5 | bashio::log.warning "usbip crashed, halting add-on" 6 | /run/s6/basedir/bin/halt 7 | fi 8 | 9 | bashio::log.info "usbip stopped, restarting..." 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BUILD_FROM 2 | FROM ${BUILD_FROM} 3 | 4 | # Install requirements for add-on 5 | RUN apk add --no-cache \ 6 | kmod \ 7 | linux-tools-usbip \ 8 | hwids-usb \ 9 | device-mapper-libs 10 | 11 | # Copy root filesystem 12 | COPY rootfs / 13 | 14 | # Ensure scripts are executable 15 | RUN chmod +x /etc/cont-init.d/*.sh \ 16 | && chmod +x /etc/cont-finish.d/*.sh \ 17 | && chmod +x /etc/services.d/*/run \ 18 | && chmod +x /etc/services.d/*/finish 19 | -------------------------------------------------------------------------------- /rootfs/etc/cont-init.d/load_modules.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck disable=SC1008 3 | bashio::config.require 'log_level' 4 | bashio::log.level "$(bashio::config 'log_level')" 5 | 6 | # Logging before attempting to load the kernel module 7 | bashio::log.info "Attempting to load vhci-hcd kernel module..." 8 | if /sbin/modprobe vhci-hcd; then 9 | bashio::log.info "Successfully loaded vhci-hcd module." 10 | bashio::log.debug "Kernel modules currently loaded: $(lsmod | grep vhci)" 11 | else 12 | bashio::log.error "Failed to load vhci-hcd kernel module. Ensure it's available on the host." 13 | exit 1 14 | fi 15 | -------------------------------------------------------------------------------- /rootfs/etc/cont-finish.d/detach_devices.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck disable=SC1008 3 | 4 | bashio::log.info "Detaching USB/IP devices" 5 | 6 | # Get the list of attached ports 7 | ports=$(usbip port 2>/dev/null | grep -E 'Port [0-9]+' | awk '{print $2}' | tr -d ':') 8 | 9 | if bashio::var.is_empty "$ports"; then 10 | bashio::log.info "No USB/IP devices to detach." 11 | else 12 | for port in $ports; do 13 | if usbip detach -p "${port}"; then 14 | bashio::log.info "Detached device on port ${port}" 15 | else 16 | bashio::log.warning "Failed to detach device on port ${port}" 17 | fi 18 | done 19 | fi 20 | -------------------------------------------------------------------------------- /repository.yaml: -------------------------------------------------------------------------------- 1 | # repository.yaml 2 | 3 | name: "HA USB/IP Client" 4 | url: "https://github.com/cryptedx/ha-usbip-client" 5 | maintainer: "crypted " 6 | description: > 7 | A Home Assistant add-on to act as a USB/IP client, enabling remote USB devices 8 | from a USB/IP server to be accessed in Home Assistant. This add-on supports high availability 9 | configurations and device discovery. 10 | version: "0.1.3" 11 | arch: 12 | - aarch64 13 | - amd64 14 | - armhf 15 | - armv7 16 | - i386 17 | category: "Remote Access & Device Management" 18 | logo: "https://github.com/cryptedx/ha-usbip-client/blob/main/icon.png" 19 | documentation: "https://github.com/cryptedx/ha-usbip-client/blob/main/README.md" 20 | license: "MIT" 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [0.1.3] - 2024-12-21 6 | 7 | ### Changed 8 | 9 | - Added an automation example to the README.md. 10 | 11 | ## [0.1.2] - 2024-10-18 12 | 13 | ### Added 14 | 15 | - `log_level` option to configure the verbosity of the add-on logs. 16 | - Enhanced scripts to respect the `log_level` setting for better debugging. 17 | 18 | ## [0.1.1] - 2024-10-09 19 | 20 | ### Added 21 | 22 | - `repository.yaml` file for Home Assistant add-on repository metadata, enabling add-on discovery and compatibility with Home Assistant. 23 | 24 | ## [0.1.0] - 2024-10-07 25 | 26 | ### Added 27 | 28 | - Initial release. 29 | 30 | --- 31 | 32 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 33 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | name: "HA USB/IP Client" 2 | description: "A USB/IP client to connect to an existing USB/IP server and access remote USB devices." 3 | version: "0.1.3" 4 | slug: "ha_usbip_client" 5 | icon: "icon.png" 6 | arch: 7 | - aarch64 8 | - amd64 9 | - armhf 10 | - armv7 11 | - i386 12 | startup: services 13 | apparmor: false 14 | init: false 15 | full_access: true 16 | kernel_modules: 17 | - vhci-hcd 18 | host_network: true 19 | privileged: 20 | - NET_ADMIN 21 | - SYS_ADMIN 22 | - SYS_RAWIO 23 | - SYS_TIME 24 | - SYS_NICE 25 | options: 26 | log_level: info 27 | discovery_server_address: "192.168.1.44" 28 | devices: 29 | - server_address: "192.168.1.44" 30 | bus_id: "1-1.1.3" 31 | - server_address: "192.168.1.44" 32 | bus_id: "1-1.2" 33 | schema: 34 | log_level: list(trace|debug|info|notice|warning|error|fatal) 35 | discovery_server_address: str 36 | devices: 37 | - server_address: str 38 | bus_id: str 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2024 crypted 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. 22 | -------------------------------------------------------------------------------- /rootfs/etc/services.d/usbip/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck disable=SC1008 3 | bashio::config.require 'log_level' 4 | bashio::log.level "$(bashio::config 'log_level')" 5 | 6 | bashio::log.info "Starting USB/IP devices attachment process." 7 | 8 | # Run the attach device script 9 | bashio::log.info "Running the mount_devices script." 10 | if /usr/local/bin/mount_devices; then 11 | bashio::log.info "USB/IP devices attached successfully." 12 | attached_devices=0 13 | bashio::log.info "Reading the mount script for attached devices..." 14 | while read -r line; do 15 | if [[ "$line" == *"attach --remote="* ]]; then 16 | attached_devices=$((attached_devices + 1)) 17 | device_info=$(echo "$line" | sed -E 's/.*--remote=([0-9.]+) --busid=([0-9.-]+)/Server IP: \1, Bus ID: \2/') 18 | bashio::log.info "Attached device: ${device_info}" 19 | fi 20 | done < "/usr/local/bin/mount_devices" 21 | 22 | if [[ $attached_devices -eq 0 ]]; then 23 | bashio::log.warning "No devices were attached. Please check your configuration." 24 | else 25 | bashio::log.info "$attached_devices device(s) successfully attached." 26 | fi 27 | else 28 | bashio::log.error "Failed to attach USB/IP devices. Is the USB/IP server online and device(s) USB attached?" 29 | fi 30 | 31 | # Keep the service running 32 | bashio::log.info "Keeping service alive." 33 | exec sleep infinity 34 | -------------------------------------------------------------------------------- /apparmor.txt: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | profile ha_usbip_client flags=(attach_disconnected,mediate_deleted) { 4 | #include 5 | 6 | # Allow basic file operations 7 | file, 8 | signal (send) set=(kill,term,int,hup,cont), 9 | 10 | # S6-Overlay file access for initialization and runtime services 11 | /init ix, 12 | /bin/** ix, 13 | /usr/bin/** ix, 14 | /run/{s6,s6-rc*,service}/** ix, 15 | /package/** ix, 16 | /command/** ix, 17 | /etc/services.d/** rwix, 18 | /etc/cont-init.d/** rwix, 19 | /etc/cont-finish.d/** rwix, 20 | /run/{,**} rwk, 21 | /dev/tty rw, 22 | 23 | # Bashio library access for script management 24 | /usr/lib/bashio/** ix, 25 | /tmp/** rwk, 26 | 27 | # Add-on files and configuration 28 | /data/** rw, 29 | /config/** rw, 30 | 31 | # USB/IP and vhci-hcd module file access 32 | /sys/bus/usb/devices/** r, 33 | /sys/class/usb_device/** rwk, 34 | /sys/module/vhci_hcd/** r, 35 | /dev/vhci rw, 36 | /dev/bus/usb/** rw, 37 | 38 | # Networking capabilities needed for USB/IP communication 39 | network inet, 40 | network inet6, 41 | 42 | # Access required for logging and debugging 43 | /dev/kmsg rw, 44 | /var/log/** rw, 45 | 46 | # Start a new restricted profile for specific services as needed 47 | /usr/bin/my_usbip_service cx -> usbip-service, 48 | 49 | profile usbip-service flags=(attach_disconnected,mediate_deleted) { 50 | # Basic service capabilities 51 | network, 52 | signal (receive) peer=*_usbip-addon, 53 | 54 | # File and device access for USB/IP operations 55 | /dev/bus/usb/** rw, 56 | /data/** rw, 57 | /usr/bin/my_usbip_service r, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /rootfs/etc/cont-init.d/create_devices.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bashio 2 | # shellcheck disable=SC1008 3 | bashio::config.require 'log_level' 4 | bashio::log.level "$(bashio::config 'log_level')" 5 | 6 | declare server_address 7 | declare bus_id 8 | declare script_directory="/usr/local/bin" 9 | declare mount_script="/usr/local/bin/mount_devices" 10 | declare discovery_server_address 11 | 12 | discovery_server_address=$(bashio::config 'discovery_server_address') 13 | 14 | bashio::log.info "" 15 | bashio::log.info "-----------------------------------------------------------------------" 16 | bashio::log.info "-------------------- Starting USB/IP Client Add-on --------------------" 17 | bashio::log.info "-----------------------------------------------------------------------" 18 | bashio::log.info "" 19 | 20 | # Check if the script directory exists and log details 21 | bashio::log.debug "Checking if script directory ${script_directory} exists." 22 | if ! bashio::fs.directory_exists "${script_directory}"; then 23 | bashio::log.info "Creating script directory at ${script_directory}." 24 | mkdir -p "${script_directory}" || bashio::exit.nok "Could not create bin folder" 25 | else 26 | bashio::log.debug "Script directory ${script_directory} already exists." 27 | fi 28 | 29 | # Create or clean the mount script 30 | bashio::log.debug "Checking if mount script ${mount_script} exists." 31 | if bashio::fs.file_exists "${mount_script}"; then 32 | bashio::log.info "Mount script already exists. Removing old script." 33 | rm "${mount_script}" 34 | fi 35 | bashio::log.info "Creating new mount script at ${mount_script}." 36 | touch "${mount_script}" || bashio::exit.nok "Could not create mount script" 37 | chmod +x "${mount_script}" 38 | 39 | # Write initial content to the mount script 40 | echo '#!/command/with-contenv bashio' >"${mount_script}" 41 | echo 'mount -o remount -t sysfs sysfs /sys' >>"${mount_script}" 42 | bashio::log.debug "Mount script initialization complete." 43 | 44 | # Discover available devices 45 | bashio::log.info "Discovering devices from server ${discovery_server_address}." 46 | if available_devices=$(usbip list -r "${discovery_server_address}" 2>/dev/null); then 47 | if [ -z "$available_devices" ]; then 48 | bashio::log.warning "No devices found on server ${discovery_server_address}." 49 | else 50 | bashio::log.info "Available devices from ${discovery_server_address}:" 51 | echo "$available_devices" | while read -r line; do 52 | bashio::log.info "$line" 53 | done 54 | fi 55 | else 56 | bashio::log.error "Failed to retrieve device list from server ${discovery_server_address}." 57 | fi 58 | 59 | # Loop through configured devices 60 | bashio::log.info "Iterating over configured devices." 61 | for device in $(bashio::config 'devices|keys'); do 62 | server_address=$(bashio::config "devices[${device}].server_address") 63 | bus_id=$(bashio::config "devices[${device}].bus_id") 64 | 65 | bashio::log.info "Adding device from server ${server_address} on bus ${bus_id}" 66 | 67 | # Detach any existing attachments 68 | bashio::log.debug "Detaching device ${bus_id} from server ${server_address} if already attached." 69 | echo "/usr/sbin/usbip detach -r ${server_address} -b ${bus_id} >/dev/null 2>&1 || true" >>"${mount_script}" 70 | 71 | # Attach the device 72 | bashio::log.debug "Attaching device ${bus_id} from server ${server_address}." 73 | echo "/usr/sbin/usbip attach --remote=${server_address} --busid=${bus_id}" >>"${mount_script}" 74 | done 75 | 76 | bashio::log.info "Device configuration complete. Ready to attach devices." 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # USB/IP Client Home Assistant Add-on 2 | 3 | ![Supports aarch64 Architecture][aarch64-shield] 4 | ![Supports amd64 Architecture][amd64-shield] 5 | ![Supports armhf Architecture][armhf-shield] 6 | ![Supports armv7 Architecture][armv7-shield] 7 | ![Supports i386 Architecture][i386-shield] 8 | 9 | ![Project Maintenance][maintenance-shield] 10 | 11 | This is a Home Assistant add-on that acts as a USB/IP client. It connects to an existing USB/IP server to access remote USB devices, making them available to Home Assistant. 12 | 13 | ## Background story 14 | 15 | Huge thanks to [irakhlin's hassio-usbip-mounter](https://github.com/irakhlin/hassio-usbip-mounter) for the inspiration! While trying out his addon, I encountered some strange behavior with my HA addons, so I had to remove it, leaving me with an unresolved challenge — how to achieve high availability on my Proxmox cluster. So, I decided to create my own USB/IP addon, and here it is. 16 | 17 | ## Features 18 | 19 | - Connects to a remote USB/IP server. 20 | - Exposes remote USB devices for use in Home Assistant. 21 | - Configurable log levels for easier debugging. 22 | 23 | ## Installation 24 | 25 | 1. Add it to your Home Assistant add-on store as a custom repository. 26 | 27 | [![Open your Home Assistant instance and show the add add-on repository dialog with a specific repository URL pre-filled.](https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https://github.com/cryptedx/ha-usbip-client) 28 | 29 | 2. Install the **USB/IP Client** add-on. 30 | 3. Configure the add-on options to specify the USB/IP server IP address, USB device bus IDs, and desired log level. 31 | 4. Turn off protection mode. [Read more here about it](#security-considerations) 32 | 5. Start the add-on. 33 | 34 | ## Configuration 35 | 36 | The add-on requires the following configuration options: 37 | 38 | - **log_level**: (Optional) Sets the verbosity of the add-on logs. Default is `info`. Available options are `trace`, `debug`, `info`, `notice`, `warning`, `error`, `fatal`. 39 | - **discovery_server_address**: The IP address of the USB/IP server used for device discovery. 40 | - **devices**: A list of devices with the following options: 41 | - **server_address**: The IP address of the USB/IP server. 42 | - **bus_id**: The bus ID of the USB device on the USB/IP server. Example: `1-1.1.3` or `1-1.2`. 43 | 44 | Example configuration: 45 | 46 | ```yaml 47 | log_level: info 48 | discovery_server_address: "192.168.1.44" 49 | devices: 50 | - server_address: "192.168.1.44" 51 | bus_id: "1-1.1.3" 52 | - server_address: "192.168.1.44" 53 | bus_id: "1-1.2" 54 | ``` 55 | 56 | Replace `192.168.1.44` with your USB/IP server IP address and provide the correct bus IDs of the USB devices. 57 | 58 | ## Usage 59 | 60 | - Once the add-on is configured and started, it will connect to the specified USB/IP server and attach to the USB devices. 61 | - The devices will then be available for use in Home Assistant integrations. 62 | - Adjust the `log_level` in the configuration to control the verbosity of the logs for troubleshooting. 63 | 64 | ## Security Considerations 65 | 66 | This add-on requires elevated privileges to access and manage USB devices, which has potential security implications: 67 | 68 | - **Full Access**: The add-on is granted full access to the host system, which allows it to interact directly with USB devices and kernel modules. 69 | - **Kernel Modules**: The add-on loads the vhci-hcd kernel module to enable USB/IP functionality. Loading kernel modules can potentially introduce vulnerabilities if not properly managed. 70 | - **Host Network**: The add-on uses the host's network stack (host_network: true). This means that any vulnerabilities in the USB/IP protocol could potentially impact the host system. 71 | - **Privileged Operations**: The add-on requires several Linux capabilities (NET_ADMIN, SYS_ADMIN, SYS_RAWIO, etc.) to perform USB management operations. These permissions are powerful and, if exploited, could compromise the host system. 72 | 73 | It is recommended to: 74 | 75 | - Only use this add-on in a trusted network environment. 76 | - Regularly update the add-on to incorporate security patches. 77 | - Limit access to the Home Assistant instance to reduce exposure. 78 | 79 | ## Related Automation for Add-on Management 80 | 81 | To automate the management of other Home Assistant add-ons based on the status of this USB/IP Client add-on, you can use the following automation. 82 | 83 | *Note: The feedback of the addon status is very sluggish at the time I created this automation. This means that if you stop the USB/IP Client Home Assistant add-on, for example, it takes 1-5 minutes for the status of the sensor to be updated!* 84 | 85 | **Access Add-on Entities:** 86 | 87 | 1. Navigate to Settings > Devices & Services. 88 | 2. Locate and select Home Assistant Supervisor from the device list. 89 | 3. Click on Entities to view all entities associated with the Supervisor. 90 | 91 | **Enable the Running State Sensor:** 92 | 93 | 1. In the entities list, find the binary sensor corresponding to the add-on you wish to monitor. These sensors are typically named in the format binary_sensor.[addon_name]_running. 94 | 2. Click on the desired sensor to open its details. 95 | 3. Click on the Settings (cog) icon in the top-right corner. 96 | 4. Toggle the Enabled switch to activate the sensor. 97 | 5. Click Update to save your changes. 98 | 99 | ```yaml 100 | alias: USB/IP Client Home Assistant Add-on Management 101 | description: >- 102 | Starts or stops add-ons when the USB/IP client is on/off, with an optional 103 | startup delay. 104 | triggers: 105 | - entity_id: 106 | - binary_sensor.ha_usbip_client_running 107 | to: "on" 108 | id: usbip_start 109 | trigger: state 110 | - entity_id: 111 | - binary_sensor.ha_usbip_client_running 112 | to: "off" 113 | id: usbip_stop 114 | trigger: state 115 | actions: 116 | - choose: 117 | - conditions: 118 | - condition: template 119 | value_template: "{{ trigger.id == 'usbip_start' }}" 120 | sequence: 121 | - delay: 122 | seconds: "{{ start_delay }}" 123 | - repeat: 124 | for_each: "{{ managed_addons }}" 125 | sequence: 126 | - if: 127 | - condition: template 128 | value_template: "{{ is_state(repeat.item.addon_sensor, 'off') }}" 129 | then: 130 | - data: 131 | addon: "{{ repeat.item.addon_slug }}" 132 | action: hassio.addon_start 133 | - conditions: 134 | - condition: template 135 | value_template: "{{ trigger.id == 'usbip_stop' }}" 136 | sequence: 137 | - repeat: 138 | for_each: "{{ managed_addons }}" 139 | sequence: 140 | - if: 141 | - condition: template 142 | value_template: "{{ is_state(repeat.item.addon_sensor, 'on') }}" 143 | then: 144 | - data: 145 | addon: "{{ repeat.item.addon_slug }}" 146 | action: hassio.addon_stop 147 | mode: single 148 | variables: 149 | start_delay: 0 150 | managed_addons: 151 | - addon_name: Zigbee2MQTT 152 | addon_sensor: binary_sensor.zigbee2mqtt_running 153 | addon_slug: 45df7312_zigbee2mqtt 154 | - addon_name: Z-Wave JS 155 | addon_sensor: binary_sensor.zwave_js_running 156 | addon_slug: core_zwave_js 157 | 158 | ``` 159 | 160 | ## Accessing the USB/IP Container in Home Assistant via SSH 161 | 162 | 1. **SSH into your Home Assistant instance.** 163 | 164 | 2. **Locate the USB/IP container:** 165 | 166 | Run the following command to find the USB/IP container ID: 167 | 168 | ```bash 169 | docker ps | grep usbip 170 | ``` 171 | 172 | 3. **Access the container's bash shell** 173 | 174 | Once you have the ``, use it in this command to enter the container: 175 | 176 | ```bash 177 | docker exec -it /bin/bash 178 | ``` 179 | 180 | ## License 181 | 182 | This project is licensed under the MIT License. 183 | 184 | [aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg 185 | [amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg 186 | [armhf-shield]: https://img.shields.io/badge/armhf-yes-green.svg 187 | [armv7-shield]: https://img.shields.io/badge/armv7-yes-green.svg 188 | [i386-shield]: https://img.shields.io/badge/i386-yes-green.svg 189 | [maintenance-shield]: https://img.shields.io/maintenance/yes/2025.svg 190 | --------------------------------------------------------------------------------