{"id":51015902,"url":"https://github.com/oliverziegert/unifi-port-profile-switcher","last_synced_at":"2026-06-21T10:30:30.627Z","repository":{"id":357839772,"uuid":"1238753394","full_name":"oliverziegert/unifi-port-profile-switcher","owner":"oliverziegert","description":"Switch UniFi switch ports between VLAN/port-profile presets — Go CLI and Home Assistant add-on","archived":false,"fork":false,"pushed_at":"2026-06-09T17:01:21.000Z","size":331,"stargazers_count":0,"open_issues_count":4,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-09T19:04:48.288Z","etag":null,"topics":["cli","go","golang","home-assistant","home-assistant-addon","homeassistant-addons","network-automation","networking","port-profile","ubiquiti","udm","udr","unifi","vlan"],"latest_commit_sha":null,"homepage":"https://github.com/oliverziegert/unifi-port-profile-switcher","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oliverziegert.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-14T12:22:55.000Z","updated_at":"2026-05-18T20:37:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/oliverziegert/unifi-port-profile-switcher","commit_stats":null,"previous_names":["oliverziegert/unifi-port-profile-switcher"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/oliverziegert/unifi-port-profile-switcher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverziegert%2Funifi-port-profile-switcher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverziegert%2Funifi-port-profile-switcher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverziegert%2Funifi-port-profile-switcher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverziegert%2Funifi-port-profile-switcher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oliverziegert","download_url":"https://codeload.github.com/oliverziegert/unifi-port-profile-switcher/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverziegert%2Funifi-port-profile-switcher/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34607126,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","go","golang","home-assistant","home-assistant-addon","homeassistant-addons","network-automation","networking","port-profile","ubiquiti","udm","udr","unifi","vlan"],"created_at":"2026-06-21T10:30:29.880Z","updated_at":"2026-06-21T10:30:30.619Z","avatar_url":"https://github.com/oliverziegert.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# UniFi Port Profile Switcher\n\nA small Go CLI that flips a UniFi switch port between named profile presets, by\ntalking to a self-hosted UniFi OS controller's internal REST API.\n\nBuilt to solve a specific problem: a docking station shared between a personal\nand a work laptop. Because the dock presents one MAC, the controller can't\nauto-assign different VLANs — but a one-shot CLI can.\n\n## Install\n\n```sh\ngo build -o unifi-port-profile-switcher .\nsudo install -m 0755 ./unifi-port-profile-switcher /usr/local/bin/\n```\n\n### Releases\n\nPrebuilt binaries for `linux/{amd64,arm64}` and `darwin/{amd64,arm64}` are\nattached to each [GitHub Release](https://github.com/oliverziegert/unifi-port-profile-switcher/releases),\nalong with a `SHA256SUMS` file:\n\n```sh\ncurl -L -o unifi-port-profile-switcher \\\n  \"https://github.com/oliverziegert/unifi-port-profile-switcher/releases/latest/download/unifi-port-profile-switcher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m)\"\nchmod +x unifi-port-profile-switcher\n```\n\nThe binaries are **unsigned**. On macOS, clear Gatekeeper quarantine before\nrunning the first time:\n\n```sh\nxattr -d com.apple.quarantine ./unifi-port-profile-switcher\n```\n\n## Configure\n\nCreate a dedicated local admin user on the UniFi Network app (e.g.\n`port-switcher`) — used only by this tool. Then write the config file:\n\n```sh\nsudo mkdir -p /etc/unifi-port-profile-switcher\nsudo install -m 0600 /dev/null /etc/unifi-port-profile-switcher/config.yaml\nsudo $EDITOR /etc/unifi-port-profile-switcher/config.yaml\n```\n\nExample:\n\n```yaml\ncontroller:\n  url: https://192.168.1.1\n  site: default\n  username: port-switcher\n  password: REPLACE_ME\n  insecure_tls: true        # self-signed cert on UDM/UDR\n\npresets:\n  work-laptop:\n    switch: \"Office USW-24\" # device name OR MAC (\"aa:bb:cc:dd:ee:ff\")\n    port: 5\n    profile: \"Work VLAN\"\n  personal-laptop:\n    switch: \"Office USW-24\"\n    port: 5\n    profile: \"Personal VLAN\"\n```\n\nThe tool refuses to load a config that is group- or world-readable. Keep it `0600`.\n\nThe tool will also look at `$XDG_CONFIG_HOME/unifi-port-profile-switcher/config.yaml`\n(or `~/.config/unifi-port-profile-switcher/config.yaml`) before falling back to\n`/etc/...`. Override with `--config /path/to/config.yaml`.\n\n## Usage\n\n```sh\nunifi-port-profile-switcher list                   # list presets\nunifi-port-profile-switcher status work-laptop     # current vs target\nunifi-port-profile-switcher --dry-run work-laptop  # show change, do not apply\nunifi-port-profile-switcher work-laptop            # flip the port\n```\n\nApply is idempotent: running it twice in a row is a no-op the second time. Other\nports' overrides on the same switch are preserved.\n\n### Flags\n\n- `--config PATH` — override config file path\n- `--dry-run` — compute the change without writing\n- `--json` — emit a single JSON object on stdout (machine-readable)\n- `-v`, `--verbose` — debug logging on stderr\n\n### Exit codes\n\n| code | meaning                                             |\n| ---: | --------------------------------------------------- |\n|    0 | success or no-op                                    |\n|    1 | generic / unexpected error                          |\n|    2 | preset not found in config                          |\n|    3 | controller authentication failure                   |\n|    4 | switch, port, or profile not found                  |\n|    5 | controller API error (non-2xx or `meta.rc != \"ok\"`) |\n\n## Why not API keys?\n\nUbiquiti's local API keys authorize the `/proxy/network/integration/v1/*` surface,\nwhich does **not** include port-override endpoints. The legacy\n`/proxy/network/api/s/\u003csite\u003e/rest/device/\u003cid\u003e` path (used by the web UI) still\nrequires session-cookie + `X-CSRF-Token` auth. This tool therefore uses a local\nadmin username/password.\n\n## Home Assistant add-on\n\nThe recommended way to use this tool on Home Assistant OS is via the **Home Assistant add-on**, which ships the same binary as an HTTP API service that HA can call from automations and dashboard buttons.\n\nFull documentation: [unifi-port-profile-switcher/DOCS.md](unifi-port-profile-switcher/DOCS.md)\n\n### Quick install\n\n1. In HA go to **Settings → Add-ons → Add-on Store → Repositories** and add:\n   ```\n   https://github.com/oliverziegert/unifi-port-profile-switcher\n   ```\n2. Install the **UniFi Port Profile Switcher** add-on from the store.\n3. Configure controller credentials, presets, and a bearer token in the **Configuration** tab.\n4. Start the add-on. The HTTP API is now reachable inside HA at port `8099`.\n\n### HTTP endpoints\n\n| Method | Path                                | Auth   | Purpose                                                       |\n|--------|-------------------------------------|--------|---------------------------------------------------------------|\n| GET    | `/healthz`                          | none   | Liveness probe.                                               |\n| GET    | `/presets`                          | bearer | List configured presets.                                      |\n| GET    | `/presets/{name}/status`            | bearer | Current vs target profile for the named preset.               |\n| POST   | `/presets/{name}/apply`             | bearer | Apply the named preset (idempotent). `?dry_run=1` to preview. |\n| GET    | `/ports/{switch}/{port}/active`     | bearer | Which configured preset is currently active on a port.        |\n\n### Phase 2 verification\n\nConfirm the add-on is talking to the controller:\n\n```sh\n# 1. Healthcheck (no auth)\ncurl http://\u003cha-ip\u003e:8099/healthz\n# → {\"ok\":true}\n\n# 2. List presets (bearer token required)\ncurl -H \"Authorization: Bearer \u003ctoken\u003e\" http://\u003cha-ip\u003e:8099/presets\n\n# 3. Dry-run apply — reads controller state, no write\ncurl -X POST -H \"Authorization: Bearer \u003ctoken\u003e\" \\\n  \"http://\u003cha-ip\u003e:8099/presets/work-laptop/apply?dry_run=1\"\n# → {\"changed\":false,\"dry_run\":true,...}\n\n# 4. Real apply\ncurl -X POST -H \"Authorization: Bearer \u003ctoken\u003e\" \\\n  http://\u003cha-ip\u003e:8099/presets/work-laptop/apply\n# → {\"changed\":true,...}\n\n# 5. Idempotent re-run (same apply again)\n# → {\"changed\":false,...}\n\n# 6. Active-preset lookup — read-only, one call per port instead of per preset\ncurl -H \"Authorization: Bearer \u003ctoken\u003e\" \\\n  \"http://\u003cha-ip\u003e:8099/ports/Office%20USW-24/5/active\"\n# → {\"switch\":\"Office USW-24\",\"port\":5,\"active_preset\":\"work-laptop\",...}\n```\n\n## Dependency updates\n\nThis repository uses the [Renovate GitHub App](https://github.com/apps/renovate) to keep dependencies up to date across Go modules, GitHub Actions, and the add-on Dockerfile base image.\n\n**How it works:**\n\n- Minor and patch updates are grouped into a single weekly PR per ecosystem (Go modules, GitHub Actions, Docker), opening Monday mornings (Europe/Berlin).\n- Vulnerability-driven updates open immediately as individual PRs, labelled `security`.\n- Major version bumps open as individual PRs immediately, outside any grouping, so they can be reviewed deliberately.\n- Auto-merge is intentionally **disabled** — every Renovate PR must be merged by a human reviewer.\n\n**Opting a dependency out:** add a `packageRules` entry to `renovate.json`:\n\n```json\n{\n  \"matchPackageNames\": [\"example/package\"],\n  \"enabled\": false\n}\n```\n\n**Note for contributors:** GitHub Actions `uses:` references are pinned to a 40-character commit SHA with a trailing version comment (e.g., `uses: actions/checkout@abc123 # v4.2.0`). Do not remove or \"clean up\" these comments — Renovate relies on them to track the current version. This includes `uses:` references inside the local composite action at `.github/actions/find-addons-filtered/action.yaml` — Renovate tracks those too.\n\n## Develop\n\n```sh\ngo test ./...\ngo vet ./...\ngo build ./...\n```\n\n### Lint\n\nCI runs `golangci-lint` against this module with a curated rule set covering\nthe bug categories that have shown up in this codebase's history. The eight\nenabled checks are:\n\n- `errcheck` — unchecked errors\n- `govet` — Go's built-in suspicious-construct check\n- `ineffassign` — assignments whose value is never read\n- `revive` — replacement for the deprecated `golint`\n- `staticcheck` — broad static-analysis pass\n- `unused` — unused identifiers\n- `gofmt` — canonical formatting\n- `goimports` — canonical formatting + local-import grouping\n\nRun the same set locally from `unifi-port-profile-switcher/`:\n\n```sh\ngolangci-lint run --config ../.golangci.yaml ./...\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foliverziegert%2Funifi-port-profile-switcher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foliverziegert%2Funifi-port-profile-switcher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foliverziegert%2Funifi-port-profile-switcher/lists"}