redo
This commit is contained in:
parent
3c30321175
commit
44ba435716
|
|
@ -1,30 +0,0 @@
|
|||
/up.conf
|
||||
/unifi-poller
|
||||
/unifi-poller*.gz
|
||||
/unifi-poller*.zip
|
||||
/unifi-poller*.1
|
||||
/unifi-poller*.deb
|
||||
/unifi-poller*.rpm
|
||||
/unifi-poller.*.arm
|
||||
/unifi-poller.exe
|
||||
/unifi-poller.*.macos
|
||||
/unifi-poller.*.linux
|
||||
/unifi-poller.rb
|
||||
*.sha256
|
||||
/vendor
|
||||
.DS_Store
|
||||
*~
|
||||
/package_build_*
|
||||
/release
|
||||
MANUAL
|
||||
MANUAL.html
|
||||
README
|
||||
README.html
|
||||
/unifi-poller_manual.html
|
||||
/homebrew_release_repo
|
||||
/.metadata.make
|
||||
bitly_token
|
||||
github_deploy_key
|
||||
gpg.signing.key
|
||||
.secret-files.tar
|
||||
*.so
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
# Each line must have an export clause.
|
||||
# This file is parsed and sourced by the Makefile, Docker and Homebrew builds.
|
||||
# Powered by Application Builder: https://github.com/golift/application-builder
|
||||
|
||||
# Must match the repo name.
|
||||
BINARY="unifi-poller"
|
||||
# github username
|
||||
GHUSER="davidnewhall"
|
||||
# Github repo containing homebrew formula repo.
|
||||
HBREPO="golift/homebrew-mugs"
|
||||
MAINT="David Newhall II <david at sleepers dot pro>"
|
||||
VENDOR="Go Lift <code at golift dot io>"
|
||||
DESC="Polls a UniFi controller, exports metrics to InfluxDB and Prometheus"
|
||||
GOLANGCI_LINT_ARGS="--enable-all -D gochecknoglobals -D funlen -e G402 -D gochecknoinits"
|
||||
# Example must exist at examples/$CONFIG_FILE.example
|
||||
CONFIG_FILE="up.conf"
|
||||
LICENSE="MIT"
|
||||
# FORMULA is either 'service' or 'tool'. Services run as a daemon, tools do not.
|
||||
# This affects the homebrew formula (launchd) and linux packages (systemd).
|
||||
FORMULA="service"
|
||||
|
||||
export BINARY GHUSER HBREPO MAINT VENDOR DESC GOLANGCI_LINT_ARGS CONFIG_FILE LICENSE FORMULA
|
||||
|
||||
# The rest is mostly automatic.
|
||||
# Fix the repo if it doesn't match the binary name.
|
||||
# Provide a better URL if one exists.
|
||||
|
||||
# Used for source links and wiki links.
|
||||
SOURCE_URL="https://github.com/${GHUSER}/${BINARY}"
|
||||
# Used for documentation links.
|
||||
URL="${SOURCE_URL}"
|
||||
|
||||
# Dynamic. Recommend not changing.
|
||||
VVERSION=$(git describe --abbrev=0 --tags $(git rev-list --tags --max-count=1))
|
||||
VERSION="$(echo $VVERSION | tr -d v | grep -E '^\S+$' || echo development)"
|
||||
# This produces a 0 in some envirnoments (like Homebrew), but it's only used for packages.
|
||||
ITERATION=$(git rev-list --count --all || echo 0)
|
||||
DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
COMMIT="$(git rev-parse --short HEAD || echo 0)"
|
||||
|
||||
# This is a custom download path for homebrew formula.
|
||||
SOURCE_PATH=https://golift.io/${BINARY}/archive/v${VERSION}.tar.gz
|
||||
|
||||
export SOURCE_URL URL VVERSION VERSION ITERATION DATE COMMIT SOURCE_PATH
|
||||
Binary file not shown.
|
|
@ -1,84 +0,0 @@
|
|||
# Powered by Application Builder: https://github.com/golift/application-builder
|
||||
language: go
|
||||
git:
|
||||
depth: false
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- ruby-dev
|
||||
- rpm
|
||||
- build-essential
|
||||
- git
|
||||
- libgnome-keyring-dev
|
||||
- fakeroot
|
||||
- zip
|
||||
- debsigs
|
||||
- gnupg
|
||||
- expect
|
||||
go:
|
||||
- 1.13.x
|
||||
services:
|
||||
- docker
|
||||
install:
|
||||
- mkdir -p $GOPATH/bin
|
||||
# Download the `dep` binary to bin folder in $GOPATH
|
||||
- curl -sLo $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v0.5.3/dep-linux-amd64
|
||||
- chmod +x $GOPATH/bin/dep
|
||||
# download super-linter: golangci-lint
|
||||
- curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin latest
|
||||
- rvm install 2.0.0
|
||||
- rvm 2.0.0 do gem install --no-document fpm
|
||||
before_script:
|
||||
- gpg --import gpg.public.key
|
||||
# Create your own deploy key, tar it, and encrypt the file to make this work. Optionally add a bitly_token file to the archive.
|
||||
- openssl aes-256-cbc -K $encrypted_9f3147001275_key -iv $encrypted_9f3147001275_iv -in .secret-files.tar.enc -out .secret-files.tar -d
|
||||
- tar -xf .secret-files.tar
|
||||
- gpg --import gpg.signing.key
|
||||
- rm -f gpg.signing.key .secret-files.tar
|
||||
- source .metadata.sh
|
||||
- make vendor
|
||||
script:
|
||||
# Test Go and Docker.
|
||||
- make test
|
||||
- make docker
|
||||
# Test built docker image.
|
||||
- docker run $BINARY -v 2>&1 | grep -Eq "^$BINARY v$VERSION"
|
||||
# Build everything
|
||||
- rvm 2.0.0 do make release
|
||||
after_success:
|
||||
# Display Release Folder
|
||||
- ls -l release/
|
||||
# Setup the ssh client so we can clone and push to the homebrew formula repo.
|
||||
# You must put github_deploy_file into .secret_files.tar.enc
|
||||
# This is an ssh key added to your homebrew forumla repo.
|
||||
- |
|
||||
mkdir -p $HOME/.ssh
|
||||
declare -r SSH_FILE="$(mktemp -u $HOME/.ssh/XXXXX)"
|
||||
echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> $HOME/.ssh/config
|
||||
[ ! -f github_deploy_key ] || (mv github_deploy_key $SSH_FILE \
|
||||
&& chmod 600 "$SSH_FILE" \
|
||||
&& printf "%s\n" \
|
||||
"Host github.com" \
|
||||
" IdentityFile $SSH_FILE" \
|
||||
" StrictHostKeyChecking no" \
|
||||
" LogLevel ERROR" >> $HOME/.ssh/config)
|
||||
deploy:
|
||||
- provider: releases
|
||||
api_key:
|
||||
secure: GsvW0m+EnRELQMk8DjH63VXinqbwse4FJ4vNUslOE6CZ8PBXPrH0ZgaI7ic/uxRtm7CYj0sir4CZq62W5l6uhoXCCQfjOnmJspqnQcrFZ1xRdWktsNXaRwM6hlzaUThsJ/1PD9Psc66uKXBYTg0IlUz0yjZAZk7tCUE4libuj41z40ZKxUcbfcNvH4Njc9IpNB4QSA3ss+a9/6ZwBz4tHVamsGIrzaE0Zf99ItNBYvaOwhM2rC/NWIsFmwt8w4rIA2NIrkZgMDV+Z2Niqh4JRLAWCQNx/RjC5U52lG2yhqivUC3TromZ+q4O4alUltsyIzF2nVanLWgJmbeFo8uXT5A+gd3ovSkFLU9medXd9i4kap7kN/o5m9p5QZvrdEYHEmIU4ml5rjT2EQQVy5CtSmpiRAbhpEJIvA1wDtRq8rdz8IVfJXkHNjg2XdouNmMMWqa3OkEPw21+uxsqv4LscW/6ZjsavzL5SSdnBRU9n79EfGJE/tJLKiNumah/vLuJ5buNhgqmCdtX/Tg+DhQS1BOyYg4l4L8s9IIKZgFRwrOPsZnA/KsrWg4ZsjJ87cqKCaT/qs2EJx5odZcZWJYLBngeO8Tc6cQtLgJdieY2oEKo51Agq4rgikZDt21m6TY9/R5lPN0piwdpy3ZGKfv1ijXx74raMT03qskputzMCvc=
|
||||
overwrite: true
|
||||
skip_cleanup: true
|
||||
file_glob: true
|
||||
file: release/*
|
||||
on:
|
||||
tags: true
|
||||
- provider: script
|
||||
script: scripts/formula-deploy.sh
|
||||
on:
|
||||
tags: true
|
||||
- provider: script
|
||||
script: scripts/package-deploy.sh
|
||||
skip_cleanup: true
|
||||
on:
|
||||
all_branches: true
|
||||
condition: $TRAVIS_BRANCH =~ ^(master|v[0-9.]+)$
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
_This doc is far from complete._
|
||||
|
||||
# Build Pipeline
|
||||
|
||||
Lets talk about how the software gets built for our users before we talk about
|
||||
making changes to it.
|
||||
|
||||
## TravisCI
|
||||
|
||||
This repo is tested, built and deployed by [Travis-CI](https://travis-ci.org/davidnewhall/unifi-poller).
|
||||
|
||||
The [.travis.yml](.travis.yml) file in this repo coordinates the entire process.
|
||||
As long as this document is kept up to date, this is what the travis file does:
|
||||
|
||||
- Creates a go-capable build environment on a Linux host, some debian variant.
|
||||
- Install ruby-devel to get rubygems.
|
||||
- Installs other build tools including rpm and fpm from rubygems.
|
||||
- Starts docker, builds the docker container and runs it.
|
||||
- Tests that the Docker container ran and produced expected output.
|
||||
- Makes a release. `make release`: This does a lot of things, controlled by the [Makefile](Makefile).
|
||||
- Runs go tests and go linters.
|
||||
- Compiles the application binaries for Windows, Linux and macOS.
|
||||
- Compiles a man page that goes into the packages.
|
||||
- Creates rpm and deb packages using fpm.
|
||||
- Puts the packages, gzipped binaries and files containing the SHA256s of each asset into a release folder.
|
||||
|
||||
After the release is built and Docker image tested:
|
||||
- Deploys the release assets to the tagged release on GitHub using an encrypted GitHub Token (api key).
|
||||
- Runs [another script](scripts/formula-deploy.sh) to create and upload a Homebrew formula to [golift/homebrew-mugs](https://github.com/golift/homebrew-mugs).
|
||||
- Uses an encrypted SSH key to upload the updated formula to the repo.
|
||||
- Travis does nothing else with Docker; it just makes sure the thing compiles and runs.
|
||||
|
||||
### Homebrew
|
||||
|
||||
it's a mac thing.
|
||||
|
||||
[Homebrew](https://brew.sh) is all I use at home. Please don't break the homebrew
|
||||
formula stuff; it took a lot of pain to get it just right. I am very interested
|
||||
in how it works for you.
|
||||
|
||||
### Docker
|
||||
|
||||
Docker is built automatically by Docker Cloud using the Dockerfile in the path
|
||||
[init/docker/Dockerfile](init/docker/Dockerfile). Some of the configuration is
|
||||
done in the Cloud service under my personal account `golift`, but the majority
|
||||
happens in the build files in the [init/docker/hooks/](init/docker/hooks/) directory.
|
||||
|
||||
If you have need to change the Dockerfile, please clearly explain what problem your
|
||||
changes are solving, and how it has been tested and validated. As far as I'm
|
||||
concerned this file should never need to change again, but I'm not a Docker expert;
|
||||
you're welcome to prove me wrong.
|
||||
|
||||
# Contributing
|
||||
|
||||
Make a pull request and tell me what you're fixing. Pretty simple. If I need to
|
||||
I'll add more "rules." For now I'm happy to have help. Thank you!
|
||||
|
||||
## Wiki
|
||||
|
||||
**If you see typos, errors, omissions, etc, please fix them.**
|
||||
|
||||
At this point, the wiki is pretty solid. Please keep your edits brief and without
|
||||
too much opinion. If you want to provide a way to do something, please also provide
|
||||
any alternatives you're aware of. If you're not sure, just open an issue and we can
|
||||
hash it out. I'm reasonable.
|
||||
|
||||
## UniFi Library
|
||||
|
||||
If you're trying to fix something in the UniFi data collection (ie. you got an
|
||||
unmarshal error, or you want to add something I didn't include) then you
|
||||
should look at the [UniFi library](https://github.com/golift/unifi). All the
|
||||
data collection and export code lives there. Contributions and Issues are welcome
|
||||
on that code base as well.
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
MIT LICENSE.
|
||||
Copyright (c) 2016 Garrett Bjerkhoel
|
||||
Copyright (c) 2018-2019 David Newhall II
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -1,311 +0,0 @@
|
|||
# This Makefile is written as generic as possible.
|
||||
# Setting the variables in .metadata.sh and creating the paths in the repo makes this work.
|
||||
# See more: https://github.com/golift/application-builder
|
||||
|
||||
# Suck in our application information.
|
||||
IGNORED:=$(shell bash -c "source .metadata.sh ; env | sed 's/=/:=/;s/^/export /' > .metadata.make")
|
||||
|
||||
# md2roff turns markdown into man files and html files.
|
||||
MD2ROFF_BIN=github.com/github/hub/md2roff-bin
|
||||
|
||||
|
||||
# Travis CI passes the version in. Local builds get it from the current git tag.
|
||||
ifeq ($(VERSION),)
|
||||
include .metadata.make
|
||||
else
|
||||
# Preserve the passed-in version & iteration (homebrew).
|
||||
_VERSION:=$(VERSION)
|
||||
_ITERATION:=$(ITERATION)
|
||||
include .metadata.make
|
||||
VERSION:=$(_VERSION)
|
||||
ITERATION:=$(_ITERATION)
|
||||
endif
|
||||
|
||||
# rpm is wierd and changes - to _ in versions.
|
||||
RPMVERSION:=$(shell echo $(VERSION) | tr -- - _)
|
||||
|
||||
PACKAGE_SCRIPTS=
|
||||
ifeq ($(FORMULA),service)
|
||||
PACKAGE_SCRIPTS=--after-install scripts/after-install.sh --before-remove scripts/before-remove.sh
|
||||
endif
|
||||
|
||||
define PACKAGE_ARGS
|
||||
$(PACKAGE_SCRIPTS) \
|
||||
--name $(BINARY) \
|
||||
--deb-no-default-config-files \
|
||||
--rpm-os linux \
|
||||
--iteration $(ITERATION) \
|
||||
--license $(LICENSE) \
|
||||
--url $(URL) \
|
||||
--maintainer "$(MAINT)" \
|
||||
--vendor "$(VENDOR)" \
|
||||
--description "$(DESC)" \
|
||||
--config-files "/etc/$(BINARY)/$(CONFIG_FILE)"
|
||||
endef
|
||||
|
||||
PLUGINS:=$(patsubst plugins/%/main.go,%,$(wildcard plugins/*/main.go))
|
||||
|
||||
VERSION_LDFLAGS:= \
|
||||
-X github.com/prometheus/common/version.Branch=$(TRAVIS_BRANCH) \
|
||||
-X github.com/prometheus/common/version.BuildDate=$(DATE) \
|
||||
-X github.com/prometheus/common/version.Revision=$(COMMIT) \
|
||||
-X github.com/prometheus/common/version.Version=$(VERSION)-$(ITERATION)
|
||||
|
||||
# Makefile targets follow.
|
||||
|
||||
all: build
|
||||
|
||||
# Prepare a release. Called in Travis CI.
|
||||
release: clean macos windows linux_packages
|
||||
# Prepareing a release!
|
||||
mkdir -p $@
|
||||
mv $(BINARY).*.macos $(BINARY).*.linux $@/
|
||||
gzip -9r $@/
|
||||
for i in $(BINARY)*.exe; do zip -9qm $@/$$i.zip $$i;done
|
||||
mv *.rpm *.deb $@/
|
||||
# Generating File Hashes
|
||||
openssl dgst -r -sha256 $@/* | sed 's#release/##' | tee $@/checksums.sha256.txt
|
||||
|
||||
|
||||
# Delete all build assets.
|
||||
clean:
|
||||
# Cleaning up.
|
||||
rm -f $(BINARY) $(BINARY).*.{macos,linux,exe}{,.gz,.zip} $(BINARY).1{,.gz} $(BINARY).rb
|
||||
rm -f $(BINARY){_,-}*.{deb,rpm} v*.tar.gz.sha256 examples/MANUAL .metadata.make
|
||||
rm -f cmd/$(BINARY)/README{,.html} README{,.html} ./$(BINARY)_manual.html
|
||||
rm -rf package_build_* release
|
||||
|
||||
# Build a man page from a markdown file using md2roff.
|
||||
# This also turns the repo readme into an html file.
|
||||
# md2roff is needed to build the man file and html pages from the READMEs.
|
||||
man: $(BINARY).1.gz
|
||||
$(BINARY).1.gz: md2roff
|
||||
# Building man page. Build dependency first: md2roff
|
||||
go run $(MD2ROFF_BIN) --manual $(BINARY) --version $(VERSION) --date "$(DATE)" examples/MANUAL.md
|
||||
gzip -9nc examples/MANUAL > $@
|
||||
mv examples/MANUAL.html $(BINARY)_manual.html
|
||||
|
||||
md2roff:
|
||||
go get $(MD2ROFF_BIN)
|
||||
|
||||
# TODO: provide a template that adds the date to the built html file.
|
||||
readme: README.html
|
||||
README.html: md2roff
|
||||
# This turns README.md into README.html
|
||||
go run $(MD2ROFF_BIN) --manual $(BINARY) --version $(VERSION) --date "$(DATE)" README.md
|
||||
|
||||
# Binaries
|
||||
|
||||
build: $(BINARY)
|
||||
$(BINARY): main.go pkg/*/*.go
|
||||
go build -o $(BINARY) -ldflags "-w -s $(VERSION_LDFLAGS)"
|
||||
|
||||
linux: $(BINARY).amd64.linux
|
||||
$(BINARY).amd64.linux: main.go pkg/*/*.go
|
||||
# Building linux 64-bit x86 binary.
|
||||
GOOS=linux GOARCH=amd64 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)"
|
||||
|
||||
linux386: $(BINARY).i386.linux
|
||||
$(BINARY).i386.linux: main.go pkg/*/*.go
|
||||
# Building linux 32-bit x86 binary.
|
||||
GOOS=linux GOARCH=386 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)"
|
||||
|
||||
arm: arm64 armhf
|
||||
|
||||
arm64: $(BINARY).arm64.linux
|
||||
$(BINARY).arm64.linux: main.go pkg/*/*.go
|
||||
# Building linux 64-bit ARM binary.
|
||||
GOOS=linux GOARCH=arm64 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)"
|
||||
|
||||
armhf: $(BINARY).armhf.linux
|
||||
$(BINARY).armhf.linux: main.go pkg/*/*.go
|
||||
# Building linux 32-bit ARM binary.
|
||||
GOOS=linux GOARCH=arm GOARM=6 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)"
|
||||
|
||||
macos: $(BINARY).amd64.macos
|
||||
$(BINARY).amd64.macos: main.go pkg/*/*.go
|
||||
# Building darwin 64-bit x86 binary.
|
||||
GOOS=darwin GOARCH=amd64 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)"
|
||||
|
||||
exe: $(BINARY).amd64.exe
|
||||
windows: $(BINARY).amd64.exe
|
||||
$(BINARY).amd64.exe: main.go pkg/*/*.go
|
||||
# Building windows 64-bit x86 binary.
|
||||
GOOS=windows GOARCH=amd64 go build -o $@ -ldflags "-w -s $(VERSION_LDFLAGS)"
|
||||
|
||||
# Packages
|
||||
|
||||
linux_packages: rpm deb rpm386 deb386 debarm rpmarm debarmhf rpmarmhf
|
||||
|
||||
rpm: $(BINARY)-$(RPMVERSION)-$(ITERATION).x86_64.rpm
|
||||
$(BINARY)-$(RPMVERSION)-$(ITERATION).x86_64.rpm: package_build_linux check_fpm
|
||||
@echo "Building 'rpm' package for $(BINARY) version '$(RPMVERSION)-$(ITERATION)'."
|
||||
fpm -s dir -t rpm $(PACKAGE_ARGS) -a x86_64 -v $(RPMVERSION) -C $<
|
||||
[ "$(SIGNING_KEY)" == "" ] || expect -c "spawn rpmsign --key-id=$(SIGNING_KEY) --resign $(BINARY)-$(RPMVERSION)-$(ITERATION).x86_64.rpm; expect -exact \"Enter pass phrase: \"; send \"$(PRIVATE_KEY)\r\"; expect eof"
|
||||
|
||||
deb: $(BINARY)_$(VERSION)-$(ITERATION)_amd64.deb
|
||||
$(BINARY)_$(VERSION)-$(ITERATION)_amd64.deb: package_build_linux check_fpm
|
||||
@echo "Building 'deb' package for $(BINARY) version '$(VERSION)-$(ITERATION)'."
|
||||
fpm -s dir -t deb $(PACKAGE_ARGS) -a amd64 -v $(VERSION) -C $<
|
||||
[ "$(SIGNING_KEY)" == "" ] || expect -c "spawn debsigs --default-key="$(SIGNING_KEY)" --sign=origin $(BINARY)_$(VERSION)-$(ITERATION)_amd64.deb; expect -exact \"Enter passphrase: \"; send \"$(PRIVATE_KEY)\r\"; expect eof"
|
||||
|
||||
rpm386: $(BINARY)-$(RPMVERSION)-$(ITERATION).i386.rpm
|
||||
$(BINARY)-$(RPMVERSION)-$(ITERATION).i386.rpm: package_build_linux_386 check_fpm
|
||||
@echo "Building 32-bit 'rpm' package for $(BINARY) version '$(RPMVERSION)-$(ITERATION)'."
|
||||
fpm -s dir -t rpm $(PACKAGE_ARGS) -a i386 -v $(RPMVERSION) -C $<
|
||||
[ "$(SIGNING_KEY)" == "" ] || expect -c "spawn rpmsign --key-id=$(SIGNING_KEY) --resign $(BINARY)-$(RPMVERSION)-$(ITERATION).i386.rpm; expect -exact \"Enter pass phrase: \"; send \"$(PRIVATE_KEY)\r\"; expect eof"
|
||||
|
||||
deb386: $(BINARY)_$(VERSION)-$(ITERATION)_i386.deb
|
||||
$(BINARY)_$(VERSION)-$(ITERATION)_i386.deb: package_build_linux_386 check_fpm
|
||||
@echo "Building 32-bit 'deb' package for $(BINARY) version '$(VERSION)-$(ITERATION)'."
|
||||
fpm -s dir -t deb $(PACKAGE_ARGS) -a i386 -v $(VERSION) -C $<
|
||||
[ "$(SIGNING_KEY)" == "" ] || expect -c "spawn debsigs --default-key="$(SIGNING_KEY)" --sign=origin $(BINARY)_$(VERSION)-$(ITERATION)_i386.deb; expect -exact \"Enter passphrase: \"; send \"$(PRIVATE_KEY)\r\"; expect eof"
|
||||
|
||||
rpmarm: $(BINARY)-$(RPMVERSION)-$(ITERATION).arm64.rpm
|
||||
$(BINARY)-$(RPMVERSION)-$(ITERATION).arm64.rpm: package_build_linux_arm64 check_fpm
|
||||
@echo "Building 64-bit ARM8 'rpm' package for $(BINARY) version '$(RPMVERSION)-$(ITERATION)'."
|
||||
fpm -s dir -t rpm $(PACKAGE_ARGS) -a arm64 -v $(RPMVERSION) -C $<
|
||||
[ "$(SIGNING_KEY)" == "" ] || expect -c "spawn rpmsign --key-id=$(SIGNING_KEY) --resign $(BINARY)-$(RPMVERSION)-$(ITERATION).arm64.rpm; expect -exact \"Enter pass phrase: \"; send \"$(PRIVATE_KEY)\r\"; expect eof"
|
||||
|
||||
debarm: $(BINARY)_$(VERSION)-$(ITERATION)_arm64.deb
|
||||
$(BINARY)_$(VERSION)-$(ITERATION)_arm64.deb: package_build_linux_arm64 check_fpm
|
||||
@echo "Building 64-bit ARM8 'deb' package for $(BINARY) version '$(VERSION)-$(ITERATION)'."
|
||||
fpm -s dir -t deb $(PACKAGE_ARGS) -a arm64 -v $(VERSION) -C $<
|
||||
[ "$(SIGNING_KEY)" == "" ] || expect -c "spawn debsigs --default-key="$(SIGNING_KEY)" --sign=origin $(BINARY)_$(VERSION)-$(ITERATION)_arm64.deb; expect -exact \"Enter passphrase: \"; send \"$(PRIVATE_KEY)\r\"; expect eof"
|
||||
|
||||
rpmarmhf: $(BINARY)-$(RPMVERSION)-$(ITERATION).armhf.rpm
|
||||
$(BINARY)-$(RPMVERSION)-$(ITERATION).armhf.rpm: package_build_linux_armhf check_fpm
|
||||
@echo "Building 32-bit ARM6/7 HF 'rpm' package for $(BINARY) version '$(RPMVERSION)-$(ITERATION)'."
|
||||
fpm -s dir -t rpm $(PACKAGE_ARGS) -a armhf -v $(RPMVERSION) -C $<
|
||||
[ "$(SIGNING_KEY)" == "" ] || expect -c "spawn rpmsign --key-id=$(SIGNING_KEY) --resign $(BINARY)-$(RPMVERSION)-$(ITERATION).armhf.rpm; expect -exact \"Enter pass phrase: \"; send \"$(PRIVATE_KEY)\r\"; expect eof"
|
||||
|
||||
debarmhf: $(BINARY)_$(VERSION)-$(ITERATION)_armhf.deb
|
||||
$(BINARY)_$(VERSION)-$(ITERATION)_armhf.deb: package_build_linux_armhf check_fpm
|
||||
@echo "Building 32-bit ARM6/7 HF 'deb' package for $(BINARY) version '$(VERSION)-$(ITERATION)'."
|
||||
fpm -s dir -t deb $(PACKAGE_ARGS) -a armhf -v $(VERSION) -C $<
|
||||
[ "$(SIGNING_KEY)" == "" ] || expect -c "spawn debsigs --default-key="$(SIGNING_KEY)" --sign=origin $(BINARY)_$(VERSION)-$(ITERATION)_armhf.deb; expect -exact \"Enter passphrase: \"; send \"$(PRIVATE_KEY)\r\"; expect eof"
|
||||
|
||||
# Build an environment that can be packaged for linux.
|
||||
package_build_linux: readme man plugins_linux_amd64 linux
|
||||
# Building package environment for linux.
|
||||
mkdir -p $@/usr/bin $@/etc/$(BINARY) $@/usr/share/man/man1 $@/usr/share/doc/$(BINARY) $@/usr/lib/$(BINARY)
|
||||
# Copying the binary, config file, unit file, and man page into the env.
|
||||
cp $(BINARY).amd64.linux $@/usr/bin/$(BINARY)
|
||||
cp *.1.gz $@/usr/share/man/man1
|
||||
rm -f $@/usr/lib/$(BINARY)/*.so
|
||||
cp *amd64.so $@/usr/lib/$(BINARY)/
|
||||
cp examples/$(CONFIG_FILE).example $@/etc/$(BINARY)/
|
||||
cp examples/$(CONFIG_FILE).example $@/etc/$(BINARY)/$(CONFIG_FILE)
|
||||
cp LICENSE *.html examples/*?.?* $@/usr/share/doc/$(BINARY)/
|
||||
[ "$(FORMULA)" != "service" ] || mkdir -p $@/lib/systemd/system
|
||||
[ "$(FORMULA)" != "service" ] || \
|
||||
sed -e "s/{{BINARY}}/$(BINARY)/g" -e "s/{{DESC}}/$(DESC)/g" \
|
||||
init/systemd/template.unit.service > $@/lib/systemd/system/$(BINARY).service
|
||||
|
||||
package_build_linux_386: package_build_linux linux386
|
||||
mkdir -p $@
|
||||
cp -r $</* $@/
|
||||
cp $(BINARY).i386.linux $@/usr/bin/$(BINARY)
|
||||
|
||||
package_build_linux_arm64: package_build_linux arm64
|
||||
mkdir -p $@
|
||||
cp -r $</* $@/
|
||||
cp $(BINARY).arm64.linux $@/usr/bin/$(BINARY)
|
||||
|
||||
package_build_linux_armhf: package_build_linux armhf
|
||||
mkdir -p $@
|
||||
cp -r $</* $@/
|
||||
cp $(BINARY).armhf.linux $@/usr/bin/$(BINARY)
|
||||
|
||||
check_fpm:
|
||||
@fpm --version > /dev/null || (echo "FPM missing. Install FPM: https://fpm.readthedocs.io/en/latest/installing.html" && false)
|
||||
|
||||
docker:
|
||||
docker build -f init/docker/Dockerfile \
|
||||
--build-arg "BUILD_DATE=$(DATE)" \
|
||||
--build-arg "COMMIT=$(COMMIT)" \
|
||||
--build-arg "VERSION=$(VERSION)-$(ITERATION)" \
|
||||
--build-arg "LICENSE=$(LICENSE)" \
|
||||
--build-arg "DESC=$(DESC)" \
|
||||
--build-arg "URL=$(URL)" \
|
||||
--build-arg "VENDOR=$(VENDOR)" \
|
||||
--build-arg "AUTHOR=$(MAINT)" \
|
||||
--build-arg "BINARY=$(BINARY)" \
|
||||
--build-arg "SOURCE_URL=$(SOURCE_URL)" \
|
||||
--build-arg "CONFIG_FILE=$(CONFIG_FILE)" \
|
||||
--tag $(BINARY) .
|
||||
|
||||
# This builds a Homebrew formula file that can be used to install this app from source.
|
||||
# The source used comes from the released version on GitHub. This will not work with local source.
|
||||
# This target is used by Travis CI to update the released Forumla when a new tag is created.
|
||||
formula: $(BINARY).rb
|
||||
v$(VERSION).tar.gz.sha256:
|
||||
# Calculate the SHA from the Github source file.
|
||||
curl -sL $(URL)/archive/v$(VERSION).tar.gz | openssl dgst -r -sha256 | tee $@
|
||||
$(BINARY).rb: v$(VERSION).tar.gz.sha256 init/homebrew/$(FORMULA).rb.tmpl
|
||||
# Creating formula from template using sed.
|
||||
sed -e "s/{{Version}}/$(VERSION)/g" \
|
||||
-e "s/{{Iter}}/$(ITERATION)/g" \
|
||||
-e "s/{{SHA256}}/$(shell head -c64 $<)/g" \
|
||||
-e "s/{{Desc}}/$(DESC)/g" \
|
||||
-e "s%{{URL}}%$(URL)%g" \
|
||||
-e "s%{{SOURCE_PATH}}%$(SOURCE_PATH)%g" \
|
||||
-e "s%{{SOURCE_URL}}%$(SOURCE_URL)%g" \
|
||||
-e "s%{{CONFIG_FILE}}%$(CONFIG_FILE)%g" \
|
||||
-e "s%{{Class}}%$(shell echo $(BINARY) | perl -pe 's/(?:\b|-)(\p{Ll})/\u$$1/g')%g" \
|
||||
init/homebrew/$(FORMULA).rb.tmpl | tee $(BINARY).rb
|
||||
# That perl line turns hello-world into HelloWorld, etc.
|
||||
|
||||
plugins: $(patsubst %,%.so,$(PLUGINS))
|
||||
$(patsubst %,%.so,$(PLUGINS)):
|
||||
go build -o $@ -ldflags "$(VERSION_LDFLAGS)" -buildmode=plugin ./plugins/$(patsubst %.so,%,$@)
|
||||
|
||||
linux_plugins: plugins_linux_amd64 plugins_linux_i386 plugins_linux_arm64 plugins_linux_armhf
|
||||
plugins_linux_amd64: $(patsubst %,%.linux_amd64.so,$(PLUGINS))
|
||||
$(patsubst %,%.linux_amd64.so,$(PLUGINS)):
|
||||
GOOS=linux GOARCH=amd64 go build -o $@ -ldflags "$(VERSION_LDFLAGS)" -buildmode=plugin ./plugins/$(patsubst %.linux_amd64.so,%,$@)
|
||||
|
||||
plugins_darwin: $(patsubst %,%.darwin.so,$(PLUGINS))
|
||||
$(patsubst %,%.darwin.so,$(PLUGINS)):
|
||||
GOOS=darwin go build -o $@ -ldflags "$(VERSION_LDFLAGS)" -buildmode=plugin ./plugins/$(patsubst %.darwin.so,%,$@)
|
||||
|
||||
# Extras
|
||||
|
||||
# Run code tests and lint.
|
||||
test: lint
|
||||
# Testing.
|
||||
go test -race -covermode=atomic ./...
|
||||
lint:
|
||||
# Checking lint.
|
||||
golangci-lint run $(GOLANGCI_LINT_ARGS)
|
||||
|
||||
# This is safe; recommended even.
|
||||
dep: vendor
|
||||
vendor: go.mod go.sum
|
||||
go mod vendor
|
||||
|
||||
# Don't run this unless you're ready to debug untested vendored dependencies.
|
||||
deps: update vendor
|
||||
update:
|
||||
go get -u -d
|
||||
|
||||
# Homebrew stuff. macOS only.
|
||||
|
||||
# Used for Homebrew only. Other distros can create packages.
|
||||
install: man readme $(BINARY) plugins_darwin
|
||||
@echo - Done Building! -
|
||||
@echo - Local installation with the Makefile is only supported on macOS.
|
||||
@echo If you wish to install the application manually on Linux, check out the wiki: https://$(SOURCE_URL)/wiki/Installation
|
||||
@echo - Otherwise, build and install a package: make rpm -or- make deb
|
||||
@echo See the Package Install wiki for more info: https://$(SOURCE_URL)/wiki/Package-Install
|
||||
@[ "$(shell uname)" = "Darwin" ] || (echo "Unable to continue, not a Mac." && false)
|
||||
@[ "$(PREFIX)" != "" ] || (echo "Unable to continue, PREFIX not set. Use: make install PREFIX=/usr/local ETC=/usr/local/etc" && false)
|
||||
@[ "$(ETC)" != "" ] || (echo "Unable to continue, ETC not set. Use: make install PREFIX=/usr/local ETC=/usr/local/etc" && false)
|
||||
# Copying the binary, config file, unit file, and man page into the env.
|
||||
/usr/bin/install -m 0755 -d $(PREFIX)/bin $(PREFIX)/share/man/man1 $(ETC)/$(BINARY) $(PREFIX)/share/doc/$(BINARY) $(PREFIX)/lib/$(BINARY)
|
||||
/usr/bin/install -m 0755 -cp $(BINARY) $(PREFIX)/bin/$(BINARY)
|
||||
/usr/bin/install -m 0755 -cp *darwin.so $(PREFIX)/lib/$(BINARY)/
|
||||
/usr/bin/install -m 0644 -cp $(BINARY).1.gz $(PREFIX)/share/man/man1
|
||||
/usr/bin/install -m 0644 -cp examples/$(CONFIG_FILE).example $(ETC)/$(BINARY)/
|
||||
[ -f $(ETC)/$(BINARY)/$(CONFIG_FILE) ] || /usr/bin/install -m 0644 -cp examples/$(CONFIG_FILE).example $(ETC)/$(BINARY)/$(CONFIG_FILE)
|
||||
/usr/bin/install -m 0644 -cp LICENSE *.html examples/* $(PREFIX)/share/doc/$(BINARY)/
|
||||
|
|
@ -1,133 +1,4 @@
|
|||
<img width="320px" src="https://raw.githubusercontent.com/wiki/davidnewhall/unifi-poller/images/unifi-poller-logo.png">
|
||||
# prometheus
|
||||
|
||||
[](https://discord.gg/KnyKYt2)
|
||||
[](https://twitter.com/TwitchCaptain)
|
||||
[](http://grafana.com/dashboards?search=unifi-poller)
|
||||
[](https://hub.docker.com/r/golift/unifi-poller)
|
||||
[](https://www.somsubhra.com/github-release-stats/?username=davidnewhall&repository=unifi-poller)
|
||||
|
||||
[](https://github.com/golift/unifi)
|
||||
[](https://github.com/golift/application-builder)
|
||||
[](https://github.com/davidnewhall/unifi-poller)
|
||||
[](https://travis-ci.org/davidnewhall/unifi-poller)
|
||||
|
||||
Collect your UniFi controller data and report it to an InfluxDB instance,
|
||||
or export it for Prometheus collection. Prometheus support is
|
||||
[new](https://github.com/davidnewhall/unifi-poller/issues/88), and much
|
||||
of the documentation still needs to be updated; 12/2/2019.
|
||||
[Ten Grafana Dashboards](http://grafana.com/dashboards?search=unifi-poller)
|
||||
included; with screenshots. Five for InfluxDB and five for Prometheus.
|
||||
|
||||
## Installation
|
||||
[See the Wiki!](https://github.com/davidnewhall/unifi-poller/wiki/Installation)
|
||||
We have a special place for [Docker Users](https://github.com/davidnewhall/unifi-poller/wiki/Docker).
|
||||
I'm willing to help if you have troubles.
|
||||
Open an [Issue](https://github.com/davidnewhall/unifi-poller/issues) and
|
||||
we'll figure out how to get things working for you. You can also get help in
|
||||
the #unifi-poller channel on the [Ubiquiti Discord server](https://discord.gg/KnyKYt2).
|
||||
I've also [provided a forum post](https://community.ui.com/questions/Unifi-Poller-Store-Unifi-Controller-Metrics-in-InfluxDB-without-SNMP/58a0ea34-d2b3-41cd-93bb-d95d3896d1a1) you may use to get additional help.
|
||||
|
||||
## Description
|
||||
[Ubiquiti](https://www.ui.com) makes networking devices like switches, gateways
|
||||
(routers) and wireless access points. They have a line of equipment named
|
||||
[UniFi](https://www.ui.com/products/#unifi) that uses a
|
||||
[controller](https://www.ui.com/download/unifi/) to keep stats and simplify network
|
||||
device configuration. This controller can be installed on Windows, macOS and Linux.
|
||||
Ubiquiti also provides a dedicated hardware device called a
|
||||
[CloudKey](https://www.ui.com/unifi/unifi-cloud-key/) that runs the controller software. More recently they've developed the Dream Machine; it's still in
|
||||
beta / early access, but UniFi Poller can collect its data!
|
||||
|
||||
UniFi Poller is a small Golang application that runs on Windows, macOS, Linux or
|
||||
Docker. In Influx-mode it polls a UniFi controller every 30 seconds for
|
||||
measurements and exports the data to an Influx database. In Prometheus mode the
|
||||
poller opens a web port and accepts Prometheus polling. It converts the UniFi
|
||||
Controller API data into Prometheus exports on the fly.
|
||||
|
||||
This application requires your controller to be running all the time. If you run
|
||||
a UniFi controller, there's no excuse not to install
|
||||
[Influx](https://github.com/davidnewhall/unifi-poller/wiki/InfluxDB) or
|
||||
[Prometheus](https://prometheus.io),
|
||||
[Grafana](https://github.com/davidnewhall/unifi-poller/wiki/Grafana) and this app.
|
||||
You'll have a plethora of data at your fingertips and the ability to craft custom
|
||||
graphs to slice the data any way you choose. Good luck!
|
||||
|
||||
## Backstory
|
||||
I found a simple piece of code on GitHub that sorta did what I needed;
|
||||
we all know that story. I wanted more data, so I added more data collection.
|
||||
I believe I've completely rewritten every piece of original code, except the
|
||||
copyright/license file and that's fine with me. I probably wouldn't have made
|
||||
it this far if [Garrett](https://github.com/dewski/unifi) hadn't written the
|
||||
original code I started with. Many props my man.
|
||||
|
||||
The original code pulled only the client data. This app now pulls data
|
||||
for clients, access points, security gateways, dream machines and switches. I
|
||||
used to own two UAP-AC-PROs, one USG-3 and one US-24-250W, but have since upgraded
|
||||
a few devices. Many other users have also provided feedback to improve this app,
|
||||
and we have reports of it working on nearly every switch, AP and gateway.
|
||||
|
||||
## What's this data good for?
|
||||
I've been trying to get my UAP data into Grafana. Sure, google search that.
|
||||
You'll find [this](https://community.ubnt.com/t5/UniFi-Wireless/Grafana-dashboard-for-UniFi-APs-now-available/td-p/1833532). What if you don't want to deal with SNMP?
|
||||
Well, here you go. I've replicated 400% of what you see on those SNMP-powered
|
||||
dashboards with this Go app running on the same mac as my UniFi controller.
|
||||
All without enabling SNMP nor trying to understand those OIDs. Mad props
|
||||
to [waterside](https://community.ubnt.com/t5/user/viewprofilepage/user-id/303058)
|
||||
for making this dashboard; it gave me a fantastic start to making my own dashboards.
|
||||
|
||||
## Operation
|
||||
You can control this app with puppet, chef, saltstack, homebrew or a simple bash
|
||||
script if you needed to. Packages are available for macOS, Linux and Docker.
|
||||
It comes with a systemd service unit that allows you automatically start it up on most Linux hosts.
|
||||
It works just fine on [Windows](https://github.com/davidnewhall/unifi-poller/wiki/Windows) too.
|
||||
Most people prefer Docker, and this app is right at home in that environment.
|
||||
|
||||
## Development
|
||||
The UniFi data extraction is provided as an [external library](https://godoc.org/golift.io/unifi),
|
||||
and you can import that code directly without futzing with this application. That
|
||||
means, if you wanted to do something like make telegraf collect your data instead
|
||||
of UniFi Poller you can achieve that with a little bit of Go code. You could write
|
||||
a small app that acts as a telegraf input plugin using the [unifi](https://github.com/golift/unifi)
|
||||
library to grab the data from your controller. As a bonus, all of the code in UniFi Poller is
|
||||
[in libraries](https://godoc.org/github.com/davidnewhall/unifi-poller/pkg)
|
||||
and can be used in other projects.
|
||||
|
||||
## What's it look like?
|
||||
|
||||
There are five total dashboards available. Below you'll find screenshots of a few.
|
||||
|
||||
##### Client Dashboard (InfluxDB)
|
||||

|
||||
|
||||
##### USG Dashboard (InfluxDB)
|
||||

|
||||
|
||||
##### UAP Dashboard (InfluxDB)
|
||||

|
||||
|
||||
##### USW / Switch Dashboard (InfluxDB)
|
||||
You can drill down into specific sites, switches, and ports. Compare ports in different
|
||||
sites side-by-side. So easy! This screenshot barely does it justice.
|
||||

|
||||
|
||||
## Integrations
|
||||
|
||||
The following fine folks are providing their services, completely free! These service
|
||||
integrations are used for things like storage, building, compiling, distribution and
|
||||
documentation support. This project succeeds because of them. Thank you!
|
||||
|
||||
<p style="text-align: center;">
|
||||
<a title="Jfrog Bintray" alt="Jfrog Bintray" href="https://bintray.com"><img src="https://docs.golift.io/integrations/bintray.png"/></a>
|
||||
<a title="GitHub" alt="GitHub" href="https://GitHub.com"><img src="https://docs.golift.io/integrations/octocat.png"/></a>
|
||||
<a title="Docker Cloud" alt="Docker" href="https://cloud.docker.com"><img src="https://docs.golift.io/integrations/docker.png"/></a>
|
||||
<a title="Travis-CI" alt="Travis-CI" href="https://Travis-CI.com"><img src="https://docs.golift.io/integrations/travis-ci.png"/></a>
|
||||
<a title="Homebrew" alt="Homebrew" href="https://brew.sh"><img src="https://docs.golift.io/integrations/homebrew.png"/></a>
|
||||
<a title="Go Lift" alt="Go Lift" href="https://golift.io"><img src="https://docs.golift.io/integrations/golift.png"/></a>
|
||||
<a title="Grafana" alt="Grafana" href="https://grafana.com"><img src="https://docs.golift.io/integrations/grafana.png"/></a>
|
||||
</p>
|
||||
|
||||
## Copyright & License
|
||||
<img style="float: right;" align="right" width="200px" src="https://raw.githubusercontent.com/wiki/davidnewhall/unifi-poller/images/unifi-poller-logo.png">
|
||||
|
||||
- Copyright © 2016 Garrett Bjerkhoel.
|
||||
- Copyright © 2018-2019 David Newhall II.
|
||||
- See [LICENSE](LICENSE) for license information.
|
||||
This package provides the interface to turn UniFi measurements into prometheus
|
||||
exported metrics. Requires the poller package for actual UniFi data collection.
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/davidnewhall/unifi-poller/pkg/poller"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/prometheus/common/version"
|
||||
"github.com/unifi-poller/poller"
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
unifi-poller(1) -- Utility to poll UniFi Controller Metrics and store them in InfluxDB
|
||||
===
|
||||
|
||||
SYNOPSIS
|
||||
---
|
||||
`unifi-poller -c /etc/unifi-poller.conf`
|
||||
|
||||
This daemon polls a UniFi controller at a short interval and stores the collected
|
||||
measurements in an Influx Database. The measurements and metrics collected belong
|
||||
to every available site, device and client found on the controller. Including
|
||||
UniFi Security Gateways, Access Points, Switches and possibly more.
|
||||
|
||||
Dashboards for Grafana are available.
|
||||
Find them at [Grafana.com](https://grafana.com/dashboards?search=unifi-poller).
|
||||
|
||||
DESCRIPTION
|
||||
---
|
||||
UniFi Poller is a small Golang application that runs on Windows, macOS, Linux or
|
||||
Docker. It polls a UniFi controller every 30 seconds for measurements and stores
|
||||
the data in an Influx database. See the example configuration file for more
|
||||
examples and default configurations.
|
||||
|
||||
* See the example configuration file for more examples and default configurations.
|
||||
|
||||
OPTIONS
|
||||
---
|
||||
`unifi-poller [-c <config-file>] [-j <filter>] [-h] [-v]`
|
||||
|
||||
-c, --config <config-file>
|
||||
Provide a configuration file (instead of the default).
|
||||
|
||||
-v, --version
|
||||
Display version and exit.
|
||||
|
||||
-j, --dumpjson <filter>
|
||||
This is a debug option; use this when you are missing data in your graphs,
|
||||
and/or you want to inspect the raw data coming from the controller. The
|
||||
filter accepts three options: devices, clients, other. This will print a
|
||||
lot of information. Recommend piping it into a file and/or into jq for
|
||||
better visualization. This requires a valid config file that contains
|
||||
working authentication details for a UniFi Controller. This only dumps
|
||||
data for sites listed in the config file. The application exits after
|
||||
printing the JSON payload; it does not daemonize or report to InfluxDB
|
||||
with this option. The `other` option is special. This allows you request
|
||||
any api path. It must be enclosed in quotes with the word other. Example:
|
||||
unifi-poller -j "other /stat/admins"
|
||||
|
||||
-h, --help
|
||||
Display usage and exit.
|
||||
|
||||
CONFIGURATION
|
||||
---
|
||||
* Config File Default Location:
|
||||
* Linux: `/etc/unifi-poller/up.conf`
|
||||
* macOS: `/usr/local/etc/unifi-poller/up.conf`
|
||||
* Windows: `C:\ProgramData\unifi-poller\up.conf`
|
||||
* Config File Default Format: `TOML`
|
||||
* Possible formats: `XML`, `JSON`, `TOML`, `YAML`
|
||||
|
||||
The config file can be written in four different syntax formats. The application
|
||||
decides which one to use based on the file's name. If it contains `.xml` it will
|
||||
be parsed as XML. The same goes for `.json` and `.yaml`. If the filename contains
|
||||
none of these strings, then it is parsed as the default format, TOML. This option
|
||||
is provided so the application can be easily adapted to any environment.
|
||||
|
||||
`Config File Parameters`
|
||||
|
||||
Configuration file (up.conf) parameters are documented in the wiki.
|
||||
|
||||
* [https://github.com/davidnewhall/unifi-poller/wiki/Configuration](https://github.com/davidnewhall/unifi-poller/wiki/Configuration)
|
||||
|
||||
`Shell Environment Parameters`
|
||||
|
||||
This application can be fully configured using shell environment variables.
|
||||
Find documentation for this feature on the Docker Wiki page.
|
||||
|
||||
* [https://github.com/davidnewhall/unifi-poller/wiki/Docker](https://github.com/davidnewhall/unifi-poller/wiki/Docker)
|
||||
|
||||
GO DURATION
|
||||
---
|
||||
This application uses the Go Time Durations for a polling interval.
|
||||
The format is an integer followed by a time unit. You may append
|
||||
multiple time units to add them together. A few valid time units are:
|
||||
|
||||
ms (millisecond)
|
||||
s (second)
|
||||
m (minute)
|
||||
|
||||
Example Use: `35s`, `1m`, `1m30s`
|
||||
|
||||
AUTHOR
|
||||
---
|
||||
* Garrett Bjerkhoel (original code) ~ 2016
|
||||
* David Newhall II (rewritten) ~ 4/20/2018
|
||||
* David Newhall II (still going) ~ 6/7/2019
|
||||
|
||||
LOCATION
|
||||
---
|
||||
* UniFi Poller: [https://github.com/davidnewhall/unifi-poller](https://github.com/davidnewhall/unifi-poller)
|
||||
* UniFi Library: [https://github.com/golift/unifi](https://github.com/golift/unifi)
|
||||
* Grafana Dashboards: [https://grafana.com/dashboards?search=unifi-poller](https://grafana.com/dashboards?search=unifi-poller)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# Examples
|
||||
|
||||
This folder contains example configuration files in four
|
||||
supported formats. You can use any format you want for
|
||||
the config file, just give it the appropriate suffix for
|
||||
the format. An XML file should end with `.xml`, a JSON
|
||||
file with `.json`, and YAML with `.yaml`. The default
|
||||
format is always TOML and may have any _other_ suffix.
|
||||
|
||||
#### Dashboards
|
||||
This folder used to contain Grafana Dashboards.
|
||||
**They are now located at [Grafana.com](https://grafana.com/dashboards?search=unifi-poller).**
|
||||
Also see [Grafana Dashboards](https://github.com/davidnewhall/unifi-poller/wiki/Grafana-Dashboards) Wiki.
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
# UniFi Poller primary configuration file. TOML FORMAT #
|
||||
########################################################
|
||||
|
||||
[poller]
|
||||
# Turns on line numbers, microsecond logging, and a per-device log.
|
||||
# The default is false, but I personally leave this on at home (four devices).
|
||||
# This may be noisy if you have a lot of devices. It adds one line per device.
|
||||
debug = false
|
||||
|
||||
# Turns off per-interval logs. Only startup and error logs will be emitted.
|
||||
# Recommend enabling debug with this setting for better error logging.
|
||||
quiet = false
|
||||
|
||||
# Load dynamic plugins. Advanced use; only sample mysql plugin provided by default.
|
||||
plugins = []
|
||||
|
||||
#### OUTPUTS
|
||||
|
||||
# If you don't use an output, you can disable it.
|
||||
|
||||
[prometheus]
|
||||
disable = false
|
||||
# This controls on which ip and port /metrics is exported when mode is "prometheus".
|
||||
# This has no effect in other modes. Must contain a colon and port.
|
||||
http_listen = "0.0.0.0:9130"
|
||||
report_errors = false
|
||||
|
||||
[influxdb]
|
||||
disable = false
|
||||
# InfluxDB does not require auth by default, so the user/password are probably unimportant.
|
||||
url = "http://127.0.0.1:8086"
|
||||
user = "unifipoller"
|
||||
pass = "unifipoller"
|
||||
# Be sure to create this database.
|
||||
db = "unifi"
|
||||
# If your InfluxDB uses a valid SSL cert, set this to true.
|
||||
verify_ssl = false
|
||||
# The UniFi Controller only updates traffic stats about every 30 seconds.
|
||||
# Setting this to something lower may lead to "zeros" in your data.
|
||||
# If you're getting zeros now, set this to "1m"
|
||||
interval = "30s"
|
||||
|
||||
#### INPUTS
|
||||
|
||||
[unifi]
|
||||
# Setting this to true and providing default credentials allows you to skip
|
||||
# configuring controllers in this config file. Instead you configure them in
|
||||
# your prometheus.yml config. Prometheus then sends the controller URL to
|
||||
# unifi-poller when it performs the scrape. This is useful if you have many,
|
||||
# or changing controllers. Most people can leave this off. See wiki for more.
|
||||
dynamic = false
|
||||
|
||||
# The following section contains the default credentials/configuration for any
|
||||
# dynamic controller (see above section), or the primary controller if you do not
|
||||
# provide one and dynamic is disabled. In other words, you can just add your
|
||||
# controller here and delete the following section. Either works.
|
||||
[unifi.defaults]
|
||||
role = "https://127.0.0.1:8443"
|
||||
url = "https://127.0.0.1:8443"
|
||||
user = "unifipoller"
|
||||
pass = "unifipoller"
|
||||
sites = ["all"]
|
||||
save_ids = false
|
||||
save_dpi = false
|
||||
save_sites = true
|
||||
verify_ssl = false
|
||||
|
||||
# You may repeat the following section to poll additional controllers.
|
||||
[[unifi.controller]]
|
||||
# Friendly name used in dashboards. Uses URL if left empty; which is fine.
|
||||
# Avoid changing this later because it will live forever in your database.
|
||||
# Multiple controllers may share a role. This allows grouping during scrapes.
|
||||
role = ""
|
||||
|
||||
url = "https://127.0.0.1:8443"
|
||||
# Make a read-only user in the UniFi Admin Settings.
|
||||
user = "unifipoller"
|
||||
pass = "4BB9345C-2341-48D7-99F5-E01B583FF77F"
|
||||
|
||||
# If the controller has more than one site, specify which sites to poll here.
|
||||
# Set this to ["default"] to poll only the first site on the controller.
|
||||
# A setting of ["all"] will poll all sites; this works if you only have 1 site too.
|
||||
sites = ["all"]
|
||||
|
||||
# Enable collection of Intrusion Detection System Data (InfluxDB only).
|
||||
# Only useful if IDS or IPS are enabled on one of the sites.
|
||||
save_ids = false
|
||||
|
||||
# Enable collection of Deep Packet Inspection data. This data breaks down traffic
|
||||
# types for each client and site, it powers a dedicated DPI dashboard.
|
||||
# Enabling this adds roughly 150 data points per client. That's 6000 metrics for
|
||||
# 40 clients. This adds a little bit of poller run time per interval and causes
|
||||
# more API requests to your controller(s). Don't let these "cons" sway you:
|
||||
# it's cool data. Please provide feedback on your experience with this feature.
|
||||
save_dpi = false
|
||||
|
||||
# Enable collection of site data. This data powers the Network Sites dashboard.
|
||||
# It's not valuable to everyone and setting this to false will save resources.
|
||||
save_sites = true
|
||||
|
||||
# If your UniFi controller has a valid SSL certificate (like lets encrypt),
|
||||
# you can enable this option to validate it. Otherwise, any SSL certificate is
|
||||
# valid. If you don't know if you have a valid SSL cert, then you don't have one.
|
||||
verify_ssl = false
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
{
|
||||
"poller": {
|
||||
"debug": false,
|
||||
"quiet": false,
|
||||
"plugins": []
|
||||
},
|
||||
|
||||
"prometheus": {
|
||||
"disable": false,
|
||||
"http_listen": "0.0.0.0:9130",
|
||||
"report_errors": false
|
||||
},
|
||||
|
||||
"influxdb": {
|
||||
"disable": false,
|
||||
"url": "http://127.0.0.1:8086",
|
||||
"user": "unifipoller",
|
||||
"pass": "unifipoller",
|
||||
"db": "unifi",
|
||||
"verify_ssl": false,
|
||||
"interval": "30s"
|
||||
},
|
||||
|
||||
"unifi": {
|
||||
"dynamic": false,
|
||||
"defaults": {
|
||||
"role": "https://127.0.0.1:8443",
|
||||
"user": "unifipoller",
|
||||
"pass": "unifipoller",
|
||||
"url": "https://127.0.0.1:8443",
|
||||
"sites": ["all"],
|
||||
"save_ids": false,
|
||||
"save_dpi": false,
|
||||
"save_sites": true,
|
||||
"verify_ssl": false
|
||||
},
|
||||
"controllers": [
|
||||
{
|
||||
"role": "",
|
||||
"user": "unifipoller",
|
||||
"pass": "unifipoller",
|
||||
"url": "https://127.0.0.1:8443",
|
||||
"sites": ["all"],
|
||||
"save_dpi": false,
|
||||
"save_ids": false,
|
||||
"save_sites": true,
|
||||
"verify_ssl": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
#######################################################
|
||||
# UniFi Poller primary configuration file. XML FORMAT #
|
||||
# provided values are defaults. See up.conf.example! #
|
||||
#######################################################
|
||||
|
||||
<plugin> and <site> are lists of strings and may be repeated.
|
||||
-->
|
||||
<poller debug="false" quiet="false">
|
||||
<!-- plugin></plugin -->
|
||||
|
||||
<prometheus disable="false">
|
||||
<http_listen>0.0.0.0:9130</http_listen>
|
||||
<report_errors>false</report_errors>
|
||||
</prometheus>
|
||||
|
||||
<influxdb disable="false">
|
||||
<interval>30s</interval>
|
||||
<url>http://127.0.0.1:8086</url>
|
||||
<user>unifipoller</user>
|
||||
<pass>unifipoller</pass>
|
||||
<db>unifi</db>
|
||||
<verify_ssl>false</verify_ssl>
|
||||
</influxdb>
|
||||
|
||||
<unifi dynamic="false">
|
||||
<default role="https://127.0.0.1:8443">
|
||||
<site>all</site>
|
||||
<user>unifipoller</user>
|
||||
<pass>unifipoller</pass>
|
||||
<url>https://127.0.0.1:8443</url>
|
||||
<verify_ssl>false</verify_ssl>
|
||||
<save_ids>false</save_ids>
|
||||
<save_dpi>false</save_dpi>
|
||||
<save_sites>true</save_sites>
|
||||
</default>
|
||||
|
||||
<!-- Repeat this stanza to poll additional controllers. -->
|
||||
<controller role="">
|
||||
<site>all</site>
|
||||
<user>unifipoller</user>
|
||||
<pass>unifipoller</pass>
|
||||
<url>https://127.0.0.1:8443</url>
|
||||
<verify_ssl>false</verify_ssl>
|
||||
<save_ids>false</save_ids>
|
||||
<save_dpi>false</save_dpi>
|
||||
<save_sites>true</save_sites>
|
||||
</controller>
|
||||
|
||||
</unifi>
|
||||
</poller>
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
########################################################
|
||||
# UniFi Poller primary configuration file. YAML FORMAT #
|
||||
# provided values are defaults. See up.conf.example! #
|
||||
########################################################
|
||||
---
|
||||
|
||||
poller:
|
||||
debug: false
|
||||
quiet: false
|
||||
plugins: []
|
||||
|
||||
prometheus:
|
||||
disable: false
|
||||
http_listen: "0.0.0.0:9130"
|
||||
report_errors: false
|
||||
|
||||
influxdb:
|
||||
disable: false
|
||||
interval: "30s"
|
||||
url: "http://127.0.0.1:8086"
|
||||
user: "unifipoller"
|
||||
pass: "unifipoller"
|
||||
db: "unifi"
|
||||
verify_ssl: false
|
||||
|
||||
unifi:
|
||||
dynamic: false
|
||||
defaults:
|
||||
role: "https://127.0.0.1:8443"
|
||||
user: "unifipoller"
|
||||
pass: "unifipoller"
|
||||
url: "https://127.0.0.1:8443"
|
||||
sites:
|
||||
- all
|
||||
verify_ssl: false
|
||||
save_ids: false
|
||||
save_dpi: false
|
||||
save_sites: true
|
||||
|
||||
|
||||
controllers:
|
||||
# Repeat the following stanza to poll more controllers.
|
||||
- role: ""
|
||||
user: "unifipoller"
|
||||
pass: "unifipoller"
|
||||
url: "https://127.0.0.1:8443"
|
||||
sites:
|
||||
- all
|
||||
verify_ssl: false
|
||||
save_ids: false
|
||||
save_dpi: false
|
||||
save_sites: true
|
||||
|
|
@ -1,13 +1,10 @@
|
|||
module github.com/davidnewhall/unifi-poller
|
||||
module github.com/unifi-poller/promunifi
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d
|
||||
github.com/prometheus/client_golang v1.3.0
|
||||
github.com/prometheus/common v0.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golift.io/cnfg v0.0.5
|
||||
github.com/unifi-poller/poller v0.0.1
|
||||
golift.io/unifi v0.0.400
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
|
|||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
|
|
@ -65,6 +63,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/unifi-poller/poller v0.0.1 h1:/SIsahlUEVJ+v9+C94spjV58+MIqR5DucVZqOstj2vM=
|
||||
github.com/unifi-poller/poller v0.0.1/go.mod h1:sZfDL7wcVwenlkrm/92bsSuoKKUnjj0bwcSUCT+aA2s=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBF3ozJsBEADKOz87H0/nBgoiY/CXC2PKKFCvxxUEmuub+Xjs2IjvMmFjAXG/
|
||||
d4JP8ZUfuIL2snYZbaQ8IwsbHoElGEwTXeZeYwJKZpmOua1vd9xASf1NFzGnNlCk
|
||||
kdgi5CSiNQNphHRUYFVJWD+X+GjMfv2aEpt0FXSx2a95YS2Rqq4fSEfjT6xOgVXQ
|
||||
JUlusAZ4b22or9gLIYzFc0VCtSQthpgdlMIAitN7t2q+67v3TFyt0U3LO1jNnWGS
|
||||
FBM83gqCFT5ZITgH8jmVq9mn0odv/R2OTT5QEHBikP+WWjbKHqrFisFOQYza8qro
|
||||
Gn86SUAqGU0EQvMNk62YPnMD+AWEuDaZx53sJaSgzuEGG0lZYYrSdz0Dk+HIHrPd
|
||||
IsVn6s/BEHRFuZTLg0h90aSJB4TCK/HKux6hKcPKYySZcRDOxPJjQqUO37iPU2ak
|
||||
bDkOiuUrW0HcuV5/Sw6n5k8rDKub3l1wkg2Wfsgr8PHl0y5GtfA8kFBpmAQnBXwA
|
||||
mrfTz6CLf2WzYHfzxVvqOCy8Vo7yB9LpFLq27Z8eeY2wsRdQmUqRGLK7QvZEepQF
|
||||
QW3JUfseSW8dqpMPOOf0zN7P1UE/fp3wA7BDvTdu+IpMKV2SZvwkvhtCmoiI2dWo
|
||||
QvmgaKbxWL1NgLqc7xJWntxvTwKv4CLbu5DqHAn6NMOmO0lHuw08QNYl3wARAQAB
|
||||
tBhHbyBMaWZ0IDxjb2RlQGdvbGlmdC5pbz6JAk4EEwEIADgWIQS5PdZu+Y5U4urg
|
||||
JboBZq00q8WlfAUCXejMmwIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAB
|
||||
Zq00q8WlfN/CD/9Rb3WzQK5aFBmIYVxfOxsBeyMKf1zTRTnwM1Y7RqsT0m4NnlcT
|
||||
jAiE93H6+pibW5T9ujsL3wCGGf69sXo34lv112DJ5hDgR1TaYO5AQWpUKGqq5XNQ
|
||||
t+R8O50Xro34CTqrxrfCj5YD+zrZaDvr6F69JJSzUtO1MCx5j1Ujn5YF7IammSno
|
||||
nbufHKpv4jGeKSCtutPpOPrdR9JXdVG7Uo3XIiDn1z2Rv5MtC0uwiPCbAWhu4XbB
|
||||
su858TBcin5jgWJYjiab7IX7MNcZUgK6aCR/1qUG/rhXqjCz3Vm7XJ+hb5afAASR
|
||||
AJq8vqscmGgz0K4Ct9dI1OG0BhGs8mBUcRBVqLKAtc061SkM8oeive9JpCcVSyij
|
||||
6X+YVBESoFWxEO3ACNQ/mWGBIOOTT27Dabob5IOuBLSLJZdVB5tT9Py91JEd08Xi
|
||||
/O12+zpBcq6XUS/cUOiffDVmfByA3F8YmpgScvgdLxHc39fdaz4YtR7FbgSMJDux
|
||||
BXdT+GaSFXbYzQV0jUxkeesJr9/ZJPMVm+Q3mD91mTZ6yJ/mJbrsBhTTyx+gyd7O
|
||||
RusqAYSiTTjRdG6ZzPit8BGoX7s8TIq/dIxb5xnkXgVaaMORHjrpC2Ll9d4olsKs
|
||||
zyaXcSYZ+HohPI3JNU/Mr6bRnHDAOk7849ranOoWX+eHG+JyET4ko6wlObkCDQRd
|
||||
6MybARAA1QJ1onzGlXh1HHgMa3wy7WxK7jJ4anPnT+Nt2t4LvTFUq46LL2hgzmvK
|
||||
zJ5tFDrMUBCyybk1s/+hJow+bRBYIwQDkKuuBXq1LLSk2gheMDNaQJxr55EGeMVL
|
||||
drXuHQg6mFm2b6JgkEzu2srnIo9qaJMsj3i5O3ZfPgGVUda33r/66Izb3P9kN6xN
|
||||
wWvLtt+dcPYVxbX8X8d33p9KRw8yYYn0dEmj5rpXrm00oiSEuYj9Y/aPKHwbhrkj
|
||||
1yRdK9SawQBaTb8umaccpAK4tuhuzx5LOKzlO6D0ZydbCAkRbKshlO7bYVAkSkSI
|
||||
ldDIMQY0mG4P4A0s/qBjTtFleeg1roJkWDqchhuq6D+M1x4ZM3W4k1kyQPX6b9c6
|
||||
7v6n+2WPWtqOIahvRLb7zXkonH6TOv3Oopzoj16luSauXwXQhfcJ/8B+rpuEdsdJ
|
||||
mCsr9UyUHNC6/Dt+Sr82Tkqg74VkCkv00zXb85EYTuXx7AJeiCrNjEG4D8UQUGC7
|
||||
vyYwAPFAgvhNM/zA8yitflj45bpGcgrXoJ20NmLQLgJKJYuVODmJzn2ylcXQlhNf
|
||||
P1DwDfzUIeIX04Jg/qbnDseGrmp/jXq0oqQ8LujH+v8KZbBMminlmLIKJmO2TWiM
|
||||
WfKiNFCD5kQWlqtxZxlxuisRTqp9CrVxGeayxQ1uzX9NhMQjA+EAEQEAAYkCNgQY
|
||||
AQgAIBYhBLk91m75jlTi6uAlugFmrTSrxaV8BQJd6MybAhsMAAoJEAFmrTSrxaV8
|
||||
TswP/34pBQmyvyM5zxl7lRpHdptU+Zp9HskjeFGTgJZihRpRu/CzdFTSq2MXpaBW
|
||||
RLlkVEiOh8txX5bnA3DAFfTyKJ26Cc7WOIPXuGioX7rV5tqWHIQ3FO0QeGpwONli
|
||||
VGY9cGWMRfe5KfIxcUJY5ckI4c9leAnHjcuM0f/0W4xWg4pofK4zD6jvneUB8IA1
|
||||
KPHIuzO0EKCFaoedKkW5S3waVc8SaeYTk9R0Dl2tNbK9Q7pIPBt0bH7dwnTt7nCr
|
||||
tJgS7dpKjRo6xpSfN1j2P0E7bf5oT94wKM8ZTMSWqJtyNgYfDlAs5RUMkrAijdXb
|
||||
TkADHwWuF5jku0P0tPkGcbOus0UtGR9nxb0gTPzOWQzkvyPczY7JNTT5JP1Md3VW
|
||||
YYPN2xI/kzaxecMXj3Afbly082H7uaHU3JSFDeb99AHOC5poEZqvV12gHYmWDflM
|
||||
LsaCSKlmfcShzNm3R0Vnm283zaBK2q4KqvmNsA65+oM/KoN4jqlltH5zGPHnHs0t
|
||||
ye81ROOUR/6IJvbtXQBoThwFLabXX5Nwu1FE1e0fiPuuHCdwAN/86n9Gnsdn46MM
|
||||
ZvxBVxdDkr24txKTuKyJytIieQ3gyvVnQZvfS4fI0vd7IsV44YQ8Q8A9pmwpbW56
|
||||
R1GKXX4MXbnuPJn5bfbsTOxGlMgoT+9Mie4YhW43wc/MkWMW
|
||||
=Ej9Z
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
#
|
||||
# This is part of Application Builder.
|
||||
# https://github.com/golift/application-builder
|
||||
#
|
||||
|
||||
ARG ARCH=amd64
|
||||
ARG OS=linux
|
||||
ARG BUILD_DATE=0
|
||||
ARG COMMIT=0
|
||||
ARG VERSION=unknown
|
||||
ARG BINARY=application-builder
|
||||
|
||||
FROM golang:stretch as builder
|
||||
ARG ARCH
|
||||
ARG OS
|
||||
ARG BINARY
|
||||
|
||||
RUN mkdir -p $GOPATH/pkg/mod $GOPATH/bin $GOPATH/src /${BINARY}
|
||||
COPY . /${BINARY}
|
||||
WORKDIR /${BINARY}
|
||||
|
||||
RUN go mod vendor \
|
||||
&& CGO_ENABLED=0 make ${BINARY}.${ARCH}.${OS}
|
||||
|
||||
FROM scratch
|
||||
ARG ARCH
|
||||
ARG OS
|
||||
ARG BUILD_DATE
|
||||
ARG COMMIT
|
||||
ARG VERSION
|
||||
ARG LICENSE=MIT
|
||||
ARG BINARY
|
||||
ARG SOURCE_URL=http://github.com/golift/application-builder
|
||||
ARG URL=http://github.com/golift/application-builder
|
||||
ARG DESC=application-builder
|
||||
ARG VENDOR=golift
|
||||
ARG AUTHOR=golift
|
||||
ARG CONFIG_FILE=config.conf
|
||||
|
||||
# Build-time metadata as defined at https://github.com/opencontainers/image-spec/blob/master/annotations.md
|
||||
LABEL org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
org.opencontainers.image.title="${BINARY}" \
|
||||
org.opencontainers.image.documentation="${SOURCE_URL}/wiki/Docker" \
|
||||
org.opencontainers.image.description="${DESC}" \
|
||||
org.opencontainers.image.url="${URL}" \
|
||||
org.opencontainers.image.revision="${COMMIT}" \
|
||||
org.opencontainers.image.source="${SOURCE_URL}" \
|
||||
org.opencontainers.image.vendor="${VENDOR}" \
|
||||
org.opencontainers.image.authors="${AUTHOR}" \
|
||||
org.opencontainers.image.architecture="${OS} ${ARCH}" \
|
||||
org.opencontainers.image.licenses="${LICENSE}" \
|
||||
org.opencontainers.image.version="${VERSION}"
|
||||
|
||||
COPY --from=builder /${BINARY}/${BINARY}.${ARCH}.${OS} /image
|
||||
COPY --from=builder /${BINARY}/examples/${CONFIG_FILE}.example /etc/${BINARY}/${CONFIG_FILE}
|
||||
COPY --from=builder /etc/ssl /etc/ssl
|
||||
|
||||
VOLUME [ "/etc/${BINARY}" ]
|
||||
ENTRYPOINT [ "/image" ]
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
## Docker Cloud Builds
|
||||
|
||||
This folder contains the files that build our Docker image. The image
|
||||
is built by Docker Hub "automatically" using the [Dockerfile](Dockerfile)
|
||||
and [hooks/](hooks/) in this folder.
|
||||
|
||||
## Docker Compose
|
||||
|
||||
The other files in this folder can be used locally to spin up
|
||||
a full set of applications (minus the UniFi controller) to get
|
||||
UniFi Poller up and running. Including InfluxDB, Grafana, and
|
||||
Chronograph. This last app is useful to inspect the data stored
|
||||
in InfluxDB by UniFi Poller.
|
||||
|
||||
##### HOWTO
|
||||
**Learn more about how and when to use these *Docker Compose* files in the
|
||||
[Docker Wiki](https://github.com/davidnewhall/unifi-poller/wiki/Docker).**
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
INFLUXDB_USERNAME=admin
|
||||
INFLUXDB_PASSWORD=admin
|
||||
INFLUXDB_DATABASE=unifi
|
||||
|
||||
GRAFANA_USERNAME=admin
|
||||
GRAFANA_PASSWORD=admin
|
||||
|
||||
UP_BRANCH=stable
|
||||
UP_UNIFI_USER=influx
|
||||
UP_UNIFI_PASS=
|
||||
UP_UNIFI_URL=https://127.0.0.1:8443
|
||||
UP_DEBUG=false
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
version: '2'
|
||||
services:
|
||||
influxdb:
|
||||
restart: always
|
||||
image: influxdb:latest
|
||||
ports:
|
||||
- '8086:8086'
|
||||
volumes:
|
||||
- influxdb-storage:/var/lib/influxdb
|
||||
environment:
|
||||
- INFLUXDB_DB=${INFLUXDB_DATABASE}
|
||||
- INFLUXDB_ADMIN_USER=${INFLUXDB_USERNAME}
|
||||
- INFLUXDB_ADMIN_PASSWORD=${INFLUXDB_PASSWORD}
|
||||
chronograf:
|
||||
image: chronograf:latest
|
||||
restart: always
|
||||
ports:
|
||||
- '127.0.0.1:8888:8888'
|
||||
volumes:
|
||||
- chronograf-storage:/var/lib/chronograf
|
||||
depends_on:
|
||||
- influxdb
|
||||
environment:
|
||||
- INFLUXDB_URL=http://influxdb:8086
|
||||
- INFLUXDB_USERNAME=${INFLUXDB_USERNAME}
|
||||
- INFLUXDB_PASSWORD=${INFLUXDB_PASSWORD}
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
restart: always
|
||||
ports:
|
||||
- '3000:3000'
|
||||
volumes:
|
||||
- grafana-storage:/var/lib/grafana
|
||||
depends_on:
|
||||
- influxdb
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_USER=${GRAFANA_USERNAME}
|
||||
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
|
||||
- GF_INSTALL_PLUGINS=grafana-clock-panel,natel-discrete-panel,grafana-piechart-panel
|
||||
unifi-poller:
|
||||
restart: always
|
||||
image: golift/unifi-poller:${UP_BRANCH}
|
||||
environment:
|
||||
- UP_INFLUX_DB=${INFLUXDB_DATABASE}
|
||||
- UP_INFLUX_USER=${INFLUXDB_USERNAME}
|
||||
- UP_INFLUX_PASS=${INFLUXDB_PASSWORD}
|
||||
- UP_INFLUX_URL=http://influxdb:8086
|
||||
- UP_UNIFI_USER=${UP_UNIFI_USER}
|
||||
- UP_UNIFI_PASS=${UP_UNIFI_PASS}
|
||||
- UP_UNIFI_URL=${UP_UNIFI_URL}
|
||||
- UP_DEBUG=${UP_DEBUG}
|
||||
volumes:
|
||||
influxdb-storage:
|
||||
chronograf-storage:
|
||||
grafana-storage:
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# Application Builder
|
||||
|
||||
[https://github.com/golift/application-builder](https://github.com/golift/application-builder)
|
||||
|
||||
## Docker Build Hooks
|
||||
|
||||
The files in this folder are used by Docker Cloud to automate image builds.
|
||||
Do not edit these files.
|
||||
|
||||
If you want to build, maintain and push multi-architecture Docker images, you may
|
||||
follow the example provided here. All of the hooks are generic, and will work with
|
||||
any build. Two environment variables must be passed in from Docker Cloud config.
|
||||
|
||||
1. `BUILDS` must be set to the builds you're trying to perform. This repo is currently set to:
|
||||
- `linux:armhf:arm: linux:arm64:arm64:armv8 linux:amd64:amd64: linux:i386:386:`
|
||||
- The format is `os:name:arch:variant`.
|
||||
- `os` and `name` are passed into the Dockerfile.
|
||||
- `os`, `arch` and `variant` are passed into `docker manifest annotate`.
|
||||
- This does not yet work with an OS other than `linux`.
|
||||
1. Set `DOCKER_CLI_EXPERIMENTAL` to `enabled`. Not optional.
|
||||
|
||||
Keep the build simple; see screenshot. This only supports one build tag, but it creates many more.
|
||||
|
||||

|
||||
|
||||
The fancy source tag is `/^v((\d+\.\d+)(?:\.\d+)?)$/` and it allows you to capture
|
||||
the minor version without patch-level in `{\2}`. I no longer use `{\2}` in my build.
|
||||
[See how it works here](https://regex101.com/r/fzt6ki/1).
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
#!/bin/bash
|
||||
# The Docker Cloud config must pass in the BUILDS env variable.
|
||||
# See README.md (in this dir) and the screenshot for more info.
|
||||
# This is part of Application Builder.
|
||||
# https://github.com/golift/application-builder
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
# This always run local to the Dockerfile folder, so the path is ../..
|
||||
pushd ../..
|
||||
|
||||
source .metadata.sh
|
||||
|
||||
# Build each configured image from Docker Cloud.
|
||||
for build in $BUILDS; do
|
||||
# os:name:arch:variant
|
||||
os=$(echo $build | cut -d: -f1)
|
||||
name=$(echo $build | cut -d: -f2)
|
||||
echo "Building Image ${IMAGE_NAME}_${os}_${name}"
|
||||
docker build \
|
||||
--build-arg "ARCH=${name}" \
|
||||
--build-arg "BUILD_DATE=${DATE}" \
|
||||
--build-arg "COMMIT=${COMMIT}" \
|
||||
--build-arg "VERSION=${VERSION}-${ITERATION}" \
|
||||
--build-arg "LICENSE=${LICENSE}" \
|
||||
--build-arg "DESC=${DESC}" \
|
||||
--build-arg "URL=${URL}" \
|
||||
--build-arg "VENDOR=${VENDOR}" \
|
||||
--build-arg "AUTHOR=${MAINT}" \
|
||||
--build-arg "BINARY=${BINARY}" \
|
||||
--build-arg "SOURCE_URL=${SOURCE_URL}" \
|
||||
--build-arg "CONFIG_FILE=${CONFIG_FILE}" \
|
||||
--tag "${IMAGE_NAME}_${os}_${name}" \
|
||||
--file ${DOCKERFILE_PATH} .
|
||||
done
|
||||
|
||||
popd
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#!/bin/bash
|
||||
# This is part of Application Builder.
|
||||
# https://github.com/golift/application-builder
|
||||
|
||||
# https://www.smockle.com/blog/2019/04/22/migrating-from-travis-ci-to-docker-hub-automated-builds/
|
||||
|
||||
# This upgrades the docker client on the Docker Cloud server to a version
|
||||
# that contains the `docker manifest` command. To use `docker manifest`
|
||||
# set `DOCKER_CLI_EXPERIMENTAL=enabled` in your build environment.
|
||||
# See README.md (in this dir) and the screenshot for more info.
|
||||
|
||||
apt-get -y update
|
||||
apt-get -y --only-upgrade install docker-ee
|
||||
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
#!/bin/bash
|
||||
# This post build hook creates multi-architecture docker manifests.
|
||||
# It's all a bit complicated for some reason.
|
||||
# This is part of Application Builder.
|
||||
# https://github.com/golift/application-builder
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
pushd ../..
|
||||
source .metadata.sh
|
||||
popd
|
||||
|
||||
if [ "$BUILDS" != "" ]; then
|
||||
TAGS=$DOCKER_TAG
|
||||
fi
|
||||
|
||||
# Push the extra custom images that were created.
|
||||
for build in $BUILDS; do
|
||||
os=$(echo $build | cut -d: -f1)
|
||||
name=$(echo $build | cut -d: -f2)
|
||||
echo "Pushing Image ${IMAGE_NAME}_${os}_${name}"
|
||||
docker push ${IMAGE_NAME}_${os}_${name}
|
||||
IMAGES="${IMAGES} ${IMAGE_NAME}_${os}_${name}"
|
||||
done
|
||||
echo "Annotating Images: ${IMAGES}"
|
||||
|
||||
# Build all the Docker tags if the source branch is a release and not a branch.
|
||||
[ "v$VERSION" != "$SOURCE_BRANCH" ] || TAGS="latest $VERSION $SHORTVER stable"
|
||||
echo "Version: $VERSION, Source: $SOURCE_BRANCH, Building tags: ${TAGS}"
|
||||
|
||||
# Create multi-architecture manifests for each tag with all the built images.
|
||||
for tag in $TAGS; do
|
||||
docker manifest create --amend ${DOCKER_REPO}:${tag} $IMAGES
|
||||
for build in $BUILDS; do
|
||||
# os:name:arch:variant, ie linux:amd64:amd64: (no variant is ok)
|
||||
os=$(echo $build | cut -d: -f1)
|
||||
name=$(echo $build | cut -d: -f2)
|
||||
arch=$(echo $build | cut -d: -f3)
|
||||
vari=$(echo $build | cut -d: -f4)
|
||||
# Annotating updates the manifest to describe each images' capabilities.
|
||||
docker manifest annotate ${DOCKER_REPO}:${tag} ${IMAGE_NAME}_${os}_${name} --os ${os} --arch ${arch} --variant "${vari}"
|
||||
done
|
||||
echo "Pushing Manifest ${DOCKER_REPO}:${tag}"
|
||||
docker manifest push ${DOCKER_REPO}:${tag}
|
||||
done
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
# Homebrew Formula Template. Built by Makefile: `make fomula`
|
||||
# This is part of Application Builder.
|
||||
# https://github.com/golift/application-builder
|
||||
class {{Class}} < Formula
|
||||
desc "{{Desc}}"
|
||||
homepage "{{URL}}"
|
||||
url "{{SOURCE_PATH}}"
|
||||
sha256 "{{SHA256}}"
|
||||
head "{{SOURCE_URL}}"
|
||||
|
||||
depends_on "go" => :build
|
||||
depends_on "dep"
|
||||
|
||||
def install
|
||||
bin_path = buildpath/"#{name}"
|
||||
# Copy all files from their current location to buildpath/#{name}
|
||||
bin_path.install Dir["*",".??*"]
|
||||
cd bin_path do
|
||||
system "make" "vendor"
|
||||
system "make", "install", "VERSION=#{version}", "ITERATION={{Iter}}", "PREFIX=#{prefix}", "ETC=#{etc}"
|
||||
# If this fails, the user gets a nice big warning about write permissions on their
|
||||
# #{var}/log folder. The alternative could be letting the app silently fail
|
||||
# to start when it cannot write logs. This is better. Fix perms; reinstall.
|
||||
touch("#{var}/log/#{name}.log")
|
||||
end
|
||||
end
|
||||
|
||||
def caveats
|
||||
<<-EOS
|
||||
Edit the config file at #{etc}/#{name}/{{CONFIG_FILE}} then start #{name} with
|
||||
brew services start #{name} ~ log file: #{var}/log/#{name}.log
|
||||
The manual explains the config file options: man #{name}
|
||||
EOS
|
||||
end
|
||||
|
||||
plist_options :startup => false
|
||||
|
||||
def plist
|
||||
<<-EOS
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>#{plist_name}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>#{bin}/#{name}</string>
|
||||
<string>--config</string>
|
||||
<string>#{etc}/#{name}/{{CONFIG_FILE}}</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>#{var}/log/#{name}.log</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>#{var}/log/#{name}.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOS
|
||||
end
|
||||
|
||||
test do
|
||||
assert_match "#{name} v#{version}", shell_output("#{bin}/#{name} -v 2>&1", 2)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
This file isn't used by the build or for any packages. The homebrew launchd is
|
||||
in the [homebrew](../homebrew) folder. This file is for reference only.
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.github.davidnewhall.unifi-poller</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/unifi-poller</string>
|
||||
<string>-c</string>
|
||||
<string>/usr/local/etc/unifi-poller/up.conf</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/usr/local/var/log/unifi-poller.log</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/usr/local/var/log/unifi-poller.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# Systemd service unit for {{BINARY}}.
|
||||
# This is part of Application Builder.
|
||||
# https://github.com/golift/application-builder
|
||||
|
||||
[Unit]
|
||||
Description={{BINARY}} - {{DESC}}
|
||||
After=network.target
|
||||
Requires=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/{{BINARY}} $DAEMON_OPTS
|
||||
EnvironmentFile=-/etc/default/{{BINARY}}
|
||||
EnvironmentFile=-/etc/sysconfig/{{BINARY}}
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier={{BINARY}}
|
||||
Type=simple
|
||||
User=nobody
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/davidnewhall/unifi-poller/pkg/poller"
|
||||
// Load input plugins!
|
||||
_ "github.com/davidnewhall/unifi-poller/pkg/inputunifi"
|
||||
// Load output plugins!
|
||||
_ "github.com/davidnewhall/unifi-poller/pkg/influxunifi"
|
||||
_ "github.com/davidnewhall/unifi-poller/pkg/promunifi"
|
||||
)
|
||||
|
||||
// Keep it simple.
|
||||
func main() {
|
||||
if err := poller.New().Start(); err != nil {
|
||||
log.Fatalln("[ERROR]", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# influx
|
||||
|
||||
This package provides the methods to turn UniFi measurements into influx
|
||||
data-points with appropriate tags and fields.
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
// batchClient generates Unifi Client datapoints for InfluxDB.
|
||||
// These points can be passed directly to influx.
|
||||
func (u *InfluxUnifi) batchClient(r report, s *unifi.Client) {
|
||||
tags := map[string]string{
|
||||
"mac": s.Mac,
|
||||
"site_name": s.SiteName,
|
||||
"source": s.SourceName,
|
||||
"ap_name": s.ApName,
|
||||
"gw_name": s.GwName,
|
||||
"sw_name": s.SwName,
|
||||
"oui": s.Oui,
|
||||
"radio_name": s.RadioName,
|
||||
"radio": s.Radio,
|
||||
"radio_proto": s.RadioProto,
|
||||
"name": s.Name,
|
||||
"fixed_ip": s.FixedIP,
|
||||
"sw_port": s.SwPort.Txt,
|
||||
"os_class": s.OsClass.Txt,
|
||||
"os_name": s.OsName.Txt,
|
||||
"dev_cat": s.DevCat.Txt,
|
||||
"dev_id": s.DevID.Txt,
|
||||
"dev_vendor": s.DevVendor.Txt,
|
||||
"dev_family": s.DevFamily.Txt,
|
||||
"is_wired": s.IsWired.Txt,
|
||||
"is_guest": s.IsGuest.Txt,
|
||||
"use_fixedip": s.UseFixedIP.Txt,
|
||||
"channel": s.Channel.Txt,
|
||||
"vlan": s.Vlan.Txt,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"anomalies": s.Anomalies,
|
||||
"ip": s.IP,
|
||||
"essid": s.Essid,
|
||||
"bssid": s.Bssid,
|
||||
"channel": s.Channel.Val,
|
||||
"hostname": s.Name,
|
||||
"radio_desc": s.RadioDescription,
|
||||
"satisfaction": s.Satisfaction.Val,
|
||||
"bytes_r": s.BytesR,
|
||||
"ccq": s.Ccq,
|
||||
"noise": s.Noise,
|
||||
"note": s.Note,
|
||||
"roam_count": s.RoamCount,
|
||||
"rssi": s.Rssi,
|
||||
"rx_bytes": s.RxBytes,
|
||||
"rx_bytes_r": s.RxBytesR,
|
||||
"rx_packets": s.RxPackets,
|
||||
"rx_rate": s.RxRate,
|
||||
"signal": s.Signal,
|
||||
"tx_bytes": s.TxBytes,
|
||||
"tx_bytes_r": s.TxBytesR,
|
||||
"tx_packets": s.TxPackets,
|
||||
"tx_retries": s.TxRetries,
|
||||
"tx_power": s.TxPower,
|
||||
"tx_rate": s.TxRate,
|
||||
"uptime": s.Uptime,
|
||||
"wifi_tx_attempts": s.WifiTxAttempts,
|
||||
"wired-rx_bytes": s.WiredRxBytes,
|
||||
"wired-rx_bytes-r": s.WiredRxBytesR,
|
||||
"wired-rx_packets": s.WiredRxPackets,
|
||||
"wired-tx_bytes": s.WiredTxBytes,
|
||||
"wired-tx_bytes-r": s.WiredTxBytesR,
|
||||
"wired-tx_packets": s.WiredTxPackets,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "clients", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) batchClientDPI(r report, s *unifi.DPITable) {
|
||||
for _, dpi := range s.ByApp {
|
||||
r.send(&metric{
|
||||
Table: "clientdpi",
|
||||
Tags: map[string]string{
|
||||
"category": unifi.DPICats.Get(dpi.Cat),
|
||||
"application": unifi.DPIApps.GetApp(dpi.Cat, dpi.App),
|
||||
"name": s.Name,
|
||||
"mac": s.MAC,
|
||||
"site_name": s.SiteName,
|
||||
"source": s.SourceName,
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"tx_packets": dpi.TxPackets,
|
||||
"rx_packets": dpi.RxPackets,
|
||||
"tx_bytes": dpi.TxBytes,
|
||||
"rx_bytes": dpi.RxBytes,
|
||||
}},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
// batchIDS generates intrusion detection datapoints for InfluxDB.
|
||||
// These points can be passed directly to influx.
|
||||
func (u *InfluxUnifi) batchIDS(r report, i *unifi.IDS) {
|
||||
tags := map[string]string{
|
||||
"site_name": i.SiteName,
|
||||
"source": i.SourceName,
|
||||
"in_iface": i.InIface,
|
||||
"event_type": i.EventType,
|
||||
"proto": i.Proto,
|
||||
"app_proto": i.AppProto,
|
||||
"usgip": i.Usgip,
|
||||
"country_code": i.SrcipGeo.CountryCode,
|
||||
"country_name": i.SrcipGeo.CountryName,
|
||||
"region": i.SrcipGeo.Region,
|
||||
"city": i.SrcipGeo.City,
|
||||
"postal_code": i.SrcipGeo.PostalCode,
|
||||
"srcipASN": i.SrcipASN,
|
||||
"usgipASN": i.UsgipASN,
|
||||
"alert_category": i.InnerAlertCategory,
|
||||
"subsystem": i.Subsystem,
|
||||
"catname": i.Catname,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"event_type": i.EventType,
|
||||
"proto": i.Proto,
|
||||
"app_proto": i.AppProto,
|
||||
"usgip": i.Usgip,
|
||||
"country_name": i.SrcipGeo.CountryName,
|
||||
"city": i.SrcipGeo.City,
|
||||
"postal_code": i.SrcipGeo.PostalCode,
|
||||
"srcipASN": i.SrcipASN,
|
||||
"usgipASN": i.UsgipASN,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "intrusion_detect", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
|
@ -1,295 +0,0 @@
|
|||
// Package influxunifi provides the methods to turn UniFi measurements into influx
|
||||
// data-points with appropriate tags and fields.
|
||||
package influxunifi
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/davidnewhall/unifi-poller/pkg/poller"
|
||||
influx "github.com/influxdata/influxdb1-client/v2"
|
||||
"golift.io/cnfg"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultInterval = 30 * time.Second
|
||||
minimumInterval = 10 * time.Second
|
||||
defaultInfluxDB = "unifi"
|
||||
defaultInfluxUser = "unifipoller"
|
||||
defaultInfluxURL = "http://127.0.0.1:8086"
|
||||
)
|
||||
|
||||
// Config defines the data needed to store metrics in InfluxDB
|
||||
type Config struct {
|
||||
Interval cnfg.Duration `json:"interval,omitempty" toml:"interval,omitempty" xml:"interval" yaml:"interval"`
|
||||
Disable bool `json:"disable" toml:"disable" xml:"disable,attr" yaml:"disable"`
|
||||
VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"`
|
||||
URL string `json:"url,omitempty" toml:"url,omitempty" xml:"url" yaml:"url"`
|
||||
User string `json:"user,omitempty" toml:"user,omitempty" xml:"user" yaml:"user"`
|
||||
Pass string `json:"pass,omitempty" toml:"pass,omitempty" xml:"pass" yaml:"pass"`
|
||||
DB string `json:"db,omitempty" toml:"db,omitempty" xml:"db" yaml:"db"`
|
||||
}
|
||||
|
||||
// InfluxDB allows the data to be nested in the config file.
|
||||
type InfluxDB struct {
|
||||
Config Config `json:"influxdb" toml:"influxdb" xml:"influxdb" yaml:"influxdb"`
|
||||
}
|
||||
|
||||
// InfluxUnifi is returned by New() after you provide a Config.
|
||||
type InfluxUnifi struct {
|
||||
Collector poller.Collect
|
||||
influx influx.Client
|
||||
LastCheck time.Time
|
||||
*InfluxDB
|
||||
}
|
||||
|
||||
type metric struct {
|
||||
Table string
|
||||
Tags map[string]string
|
||||
Fields map[string]interface{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
u := &InfluxUnifi{InfluxDB: &InfluxDB{}, LastCheck: time.Now()}
|
||||
|
||||
poller.NewOutput(&poller.Output{
|
||||
Name: "influxdb",
|
||||
Config: u.InfluxDB,
|
||||
Method: u.Run,
|
||||
})
|
||||
}
|
||||
|
||||
// PollController runs forever, polling UniFi and pushing to InfluxDB
|
||||
// This is started by Run() or RunBoth() after everything checks out.
|
||||
func (u *InfluxUnifi) PollController() {
|
||||
interval := u.Config.Interval.Round(time.Second)
|
||||
ticker := time.NewTicker(interval)
|
||||
log.Printf("[INFO] Everything checks out! Poller started, InfluxDB interval: %v", interval)
|
||||
|
||||
for u.LastCheck = range ticker.C {
|
||||
metrics, ok, err := u.Collector.Metrics()
|
||||
if err != nil {
|
||||
u.Collector.LogErrorf("%v", err)
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
report, err := u.ReportMetrics(metrics)
|
||||
if err != nil {
|
||||
// XXX: reset and re-auth? not sure..
|
||||
u.Collector.LogErrorf("%v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
u.LogInfluxReport(report)
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs a ticker to poll the unifi server and update influxdb.
|
||||
func (u *InfluxUnifi) Run(c poller.Collect) error {
|
||||
var err error
|
||||
|
||||
if u.Config.Disable {
|
||||
return nil
|
||||
}
|
||||
|
||||
u.Collector = c
|
||||
u.setConfigDefaults()
|
||||
|
||||
u.influx, err = influx.NewHTTPClient(influx.HTTPConfig{
|
||||
Addr: u.Config.URL,
|
||||
Username: u.Config.User,
|
||||
Password: u.Config.Pass,
|
||||
TLSConfig: &tls.Config{InsecureSkipVerify: !u.Config.VerifySSL},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.PollController()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) setConfigDefaults() {
|
||||
if u.Config.URL == "" {
|
||||
u.Config.URL = defaultInfluxURL
|
||||
}
|
||||
|
||||
if u.Config.User == "" {
|
||||
u.Config.User = defaultInfluxUser
|
||||
}
|
||||
|
||||
if u.Config.Pass == "" {
|
||||
u.Config.Pass = defaultInfluxUser
|
||||
}
|
||||
|
||||
if u.Config.DB == "" {
|
||||
u.Config.DB = defaultInfluxDB
|
||||
}
|
||||
|
||||
if u.Config.Interval.Duration == 0 {
|
||||
u.Config.Interval = cnfg.Duration{Duration: defaultInterval}
|
||||
} else if u.Config.Interval.Duration < minimumInterval {
|
||||
u.Config.Interval = cnfg.Duration{Duration: minimumInterval}
|
||||
}
|
||||
|
||||
u.Config.Interval = cnfg.Duration{Duration: u.Config.Interval.Duration.Round(time.Second)}
|
||||
}
|
||||
|
||||
// ReportMetrics batches all device and client data into influxdb data points.
|
||||
// Call this after you've collected all the data you care about.
|
||||
// Returns an error if influxdb calls fail, otherwise returns a report.
|
||||
func (u *InfluxUnifi) ReportMetrics(m *poller.Metrics) (*Report, error) {
|
||||
r := &Report{Metrics: m, ch: make(chan *metric), Start: time.Now()}
|
||||
defer close(r.ch)
|
||||
|
||||
var err error
|
||||
|
||||
// Make a new Influx Points Batcher.
|
||||
r.bp, err = influx.NewBatchPoints(influx.BatchPointsConfig{Database: u.Config.DB})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("influx.NewBatchPoints: %v", err)
|
||||
}
|
||||
|
||||
go u.collect(r, r.ch)
|
||||
// Batch all the points.
|
||||
u.loopPoints(r)
|
||||
r.wg.Wait() // wait for all points to finish batching!
|
||||
|
||||
// Send all the points.
|
||||
if err = u.influx.Write(r.bp); err != nil {
|
||||
return nil, fmt.Errorf("influxdb.Write(points): %v", err)
|
||||
}
|
||||
|
||||
r.Elapsed = time.Since(r.Start)
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// collect runs in a go routine and batches all the points.
|
||||
func (u *InfluxUnifi) collect(r report, ch chan *metric) {
|
||||
for m := range ch {
|
||||
pt, err := influx.NewPoint(m.Table, m.Tags, m.Fields, r.metrics().TS)
|
||||
if err != nil {
|
||||
r.error(err)
|
||||
} else {
|
||||
r.batch(m, pt)
|
||||
}
|
||||
|
||||
r.done()
|
||||
}
|
||||
}
|
||||
|
||||
// loopPoints kicks off 3 or 7 go routines to process metrics and send them
|
||||
// to the collect routine through the metric channel.
|
||||
func (u *InfluxUnifi) loopPoints(r report) {
|
||||
m := r.metrics()
|
||||
|
||||
r.add()
|
||||
r.add()
|
||||
r.add()
|
||||
r.add()
|
||||
r.add()
|
||||
|
||||
go func() {
|
||||
defer r.done()
|
||||
|
||||
for _, s := range m.SitesDPI {
|
||||
u.batchSiteDPI(r, s)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer r.done()
|
||||
|
||||
for _, s := range m.Sites {
|
||||
u.batchSite(r, s)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer r.done()
|
||||
|
||||
for _, s := range m.ClientsDPI {
|
||||
u.batchClientDPI(r, s)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer r.done()
|
||||
|
||||
for _, s := range m.Clients {
|
||||
u.batchClient(r, s)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer r.done()
|
||||
|
||||
for _, s := range m.IDSList {
|
||||
u.batchIDS(r, s)
|
||||
}
|
||||
}()
|
||||
|
||||
u.loopDevicePoints(r)
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) loopDevicePoints(r report) {
|
||||
m := r.metrics()
|
||||
if m.Devices == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.add()
|
||||
r.add()
|
||||
r.add()
|
||||
r.add()
|
||||
|
||||
go func() {
|
||||
defer r.done()
|
||||
|
||||
for _, s := range m.UAPs {
|
||||
u.batchUAP(r, s)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer r.done()
|
||||
|
||||
for _, s := range m.USGs {
|
||||
u.batchUSG(r, s)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer r.done()
|
||||
|
||||
for _, s := range m.USWs {
|
||||
u.batchUSW(r, s)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer r.done()
|
||||
|
||||
for _, s := range m.UDMs {
|
||||
u.batchUDM(r, s)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// LogInfluxReport writes a log message after exporting to influxdb.
|
||||
func (u *InfluxUnifi) LogInfluxReport(r *Report) {
|
||||
idsMsg := fmt.Sprintf("IDS Events: %d, ", len(r.Metrics.IDSList))
|
||||
u.Collector.Logf("UniFi Metrics Recorded. Sites: %d, Clients: %d, "+
|
||||
"UAP: %d, USG/UDM: %d, USW: %d, %sPoints: %d, Fields: %d, Errs: %d, Elapsed: %v",
|
||||
len(r.Metrics.Sites), len(r.Metrics.Clients), len(r.Metrics.UAPs),
|
||||
len(r.Metrics.UDMs)+len(r.Metrics.USGs), len(r.Metrics.USWs), idsMsg, r.Total,
|
||||
r.Fields, len(r.Errors), r.Elapsed.Round(time.Millisecond))
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/davidnewhall/unifi-poller/pkg/poller"
|
||||
influx "github.com/influxdata/influxdb1-client/v2"
|
||||
)
|
||||
|
||||
// Report is returned to the calling procedure after everything is processed.
|
||||
type Report struct {
|
||||
Metrics *poller.Metrics
|
||||
Errors []error
|
||||
Total int
|
||||
Fields int
|
||||
Start time.Time
|
||||
Elapsed time.Duration
|
||||
ch chan *metric
|
||||
wg sync.WaitGroup
|
||||
bp influx.BatchPoints
|
||||
}
|
||||
|
||||
// report is an internal interface that can be mocked and overrridden for tests.
|
||||
type report interface {
|
||||
add()
|
||||
done()
|
||||
send(m *metric)
|
||||
error(err error)
|
||||
batch(m *metric, pt *influx.Point)
|
||||
metrics() *poller.Metrics
|
||||
}
|
||||
|
||||
func (r *Report) metrics() *poller.Metrics {
|
||||
return r.Metrics
|
||||
}
|
||||
|
||||
// satisfy gomnd
|
||||
const one = 1
|
||||
|
||||
func (r *Report) add() {
|
||||
r.wg.Add(one)
|
||||
}
|
||||
|
||||
func (r *Report) done() {
|
||||
r.wg.Add(-one)
|
||||
}
|
||||
|
||||
func (r *Report) send(m *metric) {
|
||||
r.wg.Add(one)
|
||||
r.ch <- m
|
||||
}
|
||||
|
||||
/* The following methods are not thread safe. */
|
||||
|
||||
func (r *Report) error(err error) {
|
||||
r.Errors = append(r.Errors, err)
|
||||
}
|
||||
|
||||
func (r *Report) batch(m *metric, p *influx.Point) {
|
||||
r.Total++
|
||||
r.Fields += len(m.Fields)
|
||||
r.bp.AddPoint(p)
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
// batchSite generates Unifi Sites' datapoints for InfluxDB.
|
||||
// These points can be passed directly to influx.
|
||||
func (u *InfluxUnifi) batchSite(r report, s *unifi.Site) {
|
||||
for _, h := range s.Health {
|
||||
tags := map[string]string{
|
||||
"name": s.Name,
|
||||
"site_name": s.SiteName,
|
||||
"source": s.SourceName,
|
||||
"desc": s.Desc,
|
||||
"status": h.Status,
|
||||
"subsystem": h.Subsystem,
|
||||
"wan_ip": h.WanIP,
|
||||
"gw_name": h.GwName,
|
||||
"lan_ip": h.LanIP,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"num_user": h.NumUser.Val,
|
||||
"num_guest": h.NumGuest.Val,
|
||||
"num_iot": h.NumIot.Val,
|
||||
"tx_bytes-r": h.TxBytesR.Val,
|
||||
"rx_bytes-r": h.RxBytesR.Val,
|
||||
"num_ap": h.NumAp.Val,
|
||||
"num_adopted": h.NumAdopted.Val,
|
||||
"num_disabled": h.NumDisabled.Val,
|
||||
"num_disconnected": h.NumDisconnected.Val,
|
||||
"num_pending": h.NumPending.Val,
|
||||
"num_gw": h.NumGw.Val,
|
||||
"wan_ip": h.WanIP,
|
||||
"num_sta": h.NumSta.Val,
|
||||
"gw_cpu": h.GwSystemStats.CPU.Val,
|
||||
"gw_mem": h.GwSystemStats.Mem.Val,
|
||||
"gw_uptime": h.GwSystemStats.Uptime.Val,
|
||||
"latency": h.Latency.Val,
|
||||
"uptime": h.Uptime.Val,
|
||||
"drops": h.Drops.Val,
|
||||
"xput_up": h.XputUp.Val,
|
||||
"xput_down": h.XputDown.Val,
|
||||
"speedtest_ping": h.SpeedtestPing.Val,
|
||||
"speedtest_lastrun": h.SpeedtestLastrun.Val,
|
||||
"num_sw": h.NumSw.Val,
|
||||
"remote_user_num_active": h.RemoteUserNumActive.Val,
|
||||
"remote_user_num_inactive": h.RemoteUserNumInactive.Val,
|
||||
"remote_user_rx_bytes": h.RemoteUserRxBytes.Val,
|
||||
"remote_user_tx_bytes": h.RemoteUserTxBytes.Val,
|
||||
"remote_user_rx_packets": h.RemoteUserRxPackets.Val,
|
||||
"remote_user_tx_packets": h.RemoteUserTxPackets.Val,
|
||||
"num_new_alarms": s.NumNewAlarms.Val,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "subsystems", Tags: tags, Fields: fields})
|
||||
}
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) batchSiteDPI(r report, s *unifi.DPITable) {
|
||||
for _, dpi := range s.ByApp {
|
||||
r.send(&metric{
|
||||
Table: "sitedpi",
|
||||
Tags: map[string]string{
|
||||
"category": unifi.DPICats.Get(dpi.Cat),
|
||||
"application": unifi.DPIApps.GetApp(dpi.Cat, dpi.App),
|
||||
"site_name": s.SiteName,
|
||||
"source": s.SourceName,
|
||||
},
|
||||
Fields: map[string]interface{}{
|
||||
"tx_packets": dpi.TxPackets,
|
||||
"rx_packets": dpi.RxPackets,
|
||||
"tx_bytes": dpi.TxBytes,
|
||||
"rx_bytes": dpi.RxBytes,
|
||||
}},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
// batchUAP generates Wireless-Access-Point datapoints for InfluxDB.
|
||||
// These points can be passed directly to influx.
|
||||
func (u *InfluxUnifi) batchUAP(r report, s *unifi.UAP) {
|
||||
if !s.Adopted.Val || s.Locating.Val {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"mac": s.Mac,
|
||||
"site_name": s.SiteName,
|
||||
"source": s.SourceName,
|
||||
"name": s.Name,
|
||||
"version": s.Version,
|
||||
"model": s.Model,
|
||||
"serial": s.Serial,
|
||||
"type": s.Type,
|
||||
}
|
||||
fields := Combine(u.processUAPstats(s.Stat.Ap), u.batchSysStats(s.SysStats, s.SystemStats))
|
||||
fields["ip"] = s.IP
|
||||
fields["bytes"] = s.Bytes.Val
|
||||
fields["last_seen"] = s.LastSeen.Val
|
||||
fields["rx_bytes"] = s.RxBytes.Val
|
||||
fields["tx_bytes"] = s.TxBytes.Val
|
||||
fields["uptime"] = s.Uptime.Val
|
||||
fields["state"] = s.State
|
||||
fields["user-num_sta"] = int(s.UserNumSta.Val)
|
||||
fields["guest-num_sta"] = int(s.GuestNumSta.Val)
|
||||
fields["num_sta"] = s.NumSta.Val
|
||||
|
||||
r.send(&metric{Table: "uap", Tags: tags, Fields: fields})
|
||||
u.processRadTable(r, tags, s.RadioTable, s.RadioTableStats)
|
||||
u.processVAPTable(r, tags, s.VapTable)
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) processUAPstats(ap *unifi.Ap) map[string]interface{} {
|
||||
if ap == nil {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
// Accumulative Statistics.
|
||||
return map[string]interface{}{
|
||||
"stat_user-rx_packets": ap.UserRxPackets.Val,
|
||||
"stat_guest-rx_packets": ap.GuestRxPackets.Val,
|
||||
"stat_rx_packets": ap.RxPackets.Val,
|
||||
"stat_user-rx_bytes": ap.UserRxBytes.Val,
|
||||
"stat_guest-rx_bytes": ap.GuestRxBytes.Val,
|
||||
"stat_rx_bytes": ap.RxBytes.Val,
|
||||
"stat_user-rx_errors": ap.UserRxErrors.Val,
|
||||
"stat_guest-rx_errors": ap.GuestRxErrors.Val,
|
||||
"stat_rx_errors": ap.RxErrors.Val,
|
||||
"stat_user-rx_dropped": ap.UserRxDropped.Val,
|
||||
"stat_guest-rx_dropped": ap.GuestRxDropped.Val,
|
||||
"stat_rx_dropped": ap.RxDropped.Val,
|
||||
"stat_user-rx_crypts": ap.UserRxCrypts.Val,
|
||||
"stat_guest-rx_crypts": ap.GuestRxCrypts.Val,
|
||||
"stat_rx_crypts": ap.RxCrypts.Val,
|
||||
"stat_user-rx_frags": ap.UserRxFrags.Val,
|
||||
"stat_guest-rx_frags": ap.GuestRxFrags.Val,
|
||||
"stat_rx_frags": ap.RxFrags.Val,
|
||||
"stat_user-tx_packets": ap.UserTxPackets.Val,
|
||||
"stat_guest-tx_packets": ap.GuestTxPackets.Val,
|
||||
"stat_tx_packets": ap.TxPackets.Val,
|
||||
"stat_user-tx_bytes": ap.UserTxBytes.Val,
|
||||
"stat_guest-tx_bytes": ap.GuestTxBytes.Val,
|
||||
"stat_tx_bytes": ap.TxBytes.Val,
|
||||
"stat_user-tx_errors": ap.UserTxErrors.Val,
|
||||
"stat_guest-tx_errors": ap.GuestTxErrors.Val,
|
||||
"stat_tx_errors": ap.TxErrors.Val,
|
||||
"stat_user-tx_dropped": ap.UserTxDropped.Val,
|
||||
"stat_guest-tx_dropped": ap.GuestTxDropped.Val,
|
||||
"stat_tx_dropped": ap.TxDropped.Val,
|
||||
"stat_user-tx_retries": ap.UserTxRetries.Val,
|
||||
"stat_guest-tx_retries": ap.GuestTxRetries.Val,
|
||||
}
|
||||
}
|
||||
|
||||
// processVAPTable creates points for Wifi Radios. This works with several types of UAP-capable devices.
|
||||
func (u *InfluxUnifi) processVAPTable(r report, t map[string]string, vt unifi.VapTable) {
|
||||
for _, s := range vt {
|
||||
tags := map[string]string{
|
||||
"device_name": t["name"],
|
||||
"site_name": t["site_name"],
|
||||
"source": t["source"],
|
||||
"ap_mac": s.ApMac,
|
||||
"bssid": s.Bssid,
|
||||
"id": s.ID,
|
||||
"name": s.Name,
|
||||
"radio_name": s.RadioName,
|
||||
"radio": s.Radio,
|
||||
"essid": s.Essid,
|
||||
"site_id": s.SiteID,
|
||||
"usage": s.Usage,
|
||||
"state": s.State,
|
||||
"is_guest": s.IsGuest.Txt,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"ccq": s.Ccq,
|
||||
"mac_filter_rejections": s.MacFilterRejections,
|
||||
"num_satisfaction_sta": s.NumSatisfactionSta.Val,
|
||||
"avg_client_signal": s.AvgClientSignal.Val,
|
||||
"satisfaction": s.Satisfaction.Val,
|
||||
"satisfaction_now": s.SatisfactionNow.Val,
|
||||
"num_sta": s.NumSta,
|
||||
"channel": s.Channel.Val,
|
||||
"rx_bytes": s.RxBytes.Val,
|
||||
"rx_crypts": s.RxCrypts.Val,
|
||||
"rx_dropped": s.RxDropped.Val,
|
||||
"rx_errors": s.RxErrors.Val,
|
||||
"rx_frags": s.RxFrags.Val,
|
||||
"rx_nwids": s.RxNwids.Val,
|
||||
"rx_packets": s.RxPackets.Val,
|
||||
"tx_bytes": s.TxBytes.Val,
|
||||
"tx_dropped": s.TxDropped.Val,
|
||||
"tx_errors": s.TxErrors.Val,
|
||||
"tx_packets": s.TxPackets.Val,
|
||||
"tx_power": s.TxPower.Val,
|
||||
"tx_retries": s.TxRetries.Val,
|
||||
"tx_combined_retries": s.TxCombinedRetries.Val,
|
||||
"tx_data_mpdu_bytes": s.TxDataMpduBytes.Val,
|
||||
"tx_rts_retries": s.TxRtsRetries.Val,
|
||||
"tx_success": s.TxSuccess.Val,
|
||||
"tx_total": s.TxTotal.Val,
|
||||
"tx_tcp_goodbytes": s.TxTCPStats.Goodbytes.Val,
|
||||
"tx_tcp_lat_avg": s.TxTCPStats.LatAvg.Val,
|
||||
"tx_tcp_lat_max": s.TxTCPStats.LatMax.Val,
|
||||
"tx_tcp_lat_min": s.TxTCPStats.LatMin.Val,
|
||||
"rx_tcp_goodbytes": s.RxTCPStats.Goodbytes.Val,
|
||||
"rx_tcp_lat_avg": s.RxTCPStats.LatAvg.Val,
|
||||
"rx_tcp_lat_max": s.RxTCPStats.LatMax.Val,
|
||||
"rx_tcp_lat_min": s.RxTCPStats.LatMin.Val,
|
||||
"wifi_tx_latency_mov_avg": s.WifiTxLatencyMov.Avg.Val,
|
||||
"wifi_tx_latency_mov_max": s.WifiTxLatencyMov.Max.Val,
|
||||
"wifi_tx_latency_mov_min": s.WifiTxLatencyMov.Min.Val,
|
||||
"wifi_tx_latency_mov_total": s.WifiTxLatencyMov.Total.Val,
|
||||
"wifi_tx_latency_mov_cuont": s.WifiTxLatencyMov.TotalCount.Val,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "uap_vaps", Tags: tags, Fields: fields})
|
||||
}
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) processRadTable(r report, t map[string]string, rt unifi.RadioTable, rts unifi.RadioTableStats) {
|
||||
for _, p := range rt {
|
||||
tags := map[string]string{
|
||||
"device_name": t["name"],
|
||||
"site_name": t["site_name"],
|
||||
"source": t["source"],
|
||||
"channel": p.Channel.Txt,
|
||||
"radio": p.Radio,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"current_antenna_gain": p.CurrentAntennaGain.Val,
|
||||
"ht": p.Ht.Txt,
|
||||
"max_txpower": p.MaxTxpower.Val,
|
||||
"min_txpower": p.MinTxpower.Val,
|
||||
"nss": p.Nss.Val,
|
||||
"radio_caps": p.RadioCaps.Val,
|
||||
}
|
||||
|
||||
for _, t := range rts {
|
||||
if t.Name == p.Name {
|
||||
fields["ast_be_xmit"] = t.AstBeXmit.Val
|
||||
fields["channel"] = t.Channel.Val
|
||||
fields["cu_self_rx"] = t.CuSelfRx.Val
|
||||
fields["cu_self_tx"] = t.CuSelfTx.Val
|
||||
fields["cu_total"] = t.CuTotal.Val
|
||||
fields["extchannel"] = t.Extchannel.Val
|
||||
fields["gain"] = t.Gain.Val
|
||||
fields["guest-num_sta"] = t.GuestNumSta.Val
|
||||
fields["num_sta"] = t.NumSta.Val
|
||||
fields["radio"] = t.Radio
|
||||
fields["tx_packets"] = t.TxPackets.Val
|
||||
fields["tx_power"] = t.TxPower.Val
|
||||
fields["tx_retries"] = t.TxRetries.Val
|
||||
fields["user-num_sta"] = t.UserNumSta.Val
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "uap_radios", Tags: tags, Fields: fields})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
// Combine concatenates N maps. This will delete things if not used with caution.
|
||||
func Combine(in ...map[string]interface{}) map[string]interface{} {
|
||||
out := make(map[string]interface{})
|
||||
|
||||
for i := range in {
|
||||
for k := range in[i] {
|
||||
out[k] = in[i][k]
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// batchSysStats is used by all device types.
|
||||
func (u *InfluxUnifi) batchSysStats(s unifi.SysStats, ss unifi.SystemStats) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"loadavg_1": s.Loadavg1.Val,
|
||||
"loadavg_5": s.Loadavg5.Val,
|
||||
"loadavg_15": s.Loadavg15.Val,
|
||||
"mem_used": s.MemUsed.Val,
|
||||
"mem_buffer": s.MemBuffer.Val,
|
||||
"mem_total": s.MemTotal.Val,
|
||||
"cpu": ss.CPU.Val,
|
||||
"mem": ss.Mem.Val,
|
||||
"system_uptime": ss.Uptime.Val,
|
||||
}
|
||||
}
|
||||
|
||||
// batchUDM generates Unifi Gateway datapoints for InfluxDB.
|
||||
// These points can be passed directly to influx.
|
||||
func (u *InfluxUnifi) batchUDM(r report, s *unifi.UDM) {
|
||||
if !s.Adopted.Val || s.Locating.Val {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"source": s.SourceName,
|
||||
"mac": s.Mac,
|
||||
"site_name": s.SiteName,
|
||||
"name": s.Name,
|
||||
"version": s.Version,
|
||||
"model": s.Model,
|
||||
"serial": s.Serial,
|
||||
"type": s.Type,
|
||||
}
|
||||
fields := Combine(
|
||||
u.batchUSGstat(s.SpeedtestStatus, s.Stat.Gw, s.Uplink),
|
||||
u.batchSysStats(s.SysStats, s.SystemStats),
|
||||
map[string]interface{}{
|
||||
"source": s.SourceName,
|
||||
"ip": s.IP,
|
||||
"bytes": s.Bytes.Val,
|
||||
"last_seen": s.LastSeen.Val,
|
||||
"license_state": s.LicenseState,
|
||||
"guest-num_sta": s.GuestNumSta.Val,
|
||||
"rx_bytes": s.RxBytes.Val,
|
||||
"tx_bytes": s.TxBytes.Val,
|
||||
"uptime": s.Uptime.Val,
|
||||
"state": s.State.Val,
|
||||
"user-num_sta": s.UserNumSta.Val,
|
||||
"version": s.Version,
|
||||
"num_desktop": s.NumDesktop.Val,
|
||||
"num_handheld": s.NumHandheld.Val,
|
||||
"num_mobile": s.NumMobile.Val,
|
||||
},
|
||||
)
|
||||
|
||||
r.send(&metric{Table: "usg", Tags: tags, Fields: fields})
|
||||
u.batchNetTable(r, tags, s.NetworkTable)
|
||||
u.batchUSGwans(r, tags, s.Wan1, s.Wan2)
|
||||
|
||||
tags = map[string]string{
|
||||
"mac": s.Mac,
|
||||
"site_name": s.SiteName,
|
||||
"source": s.SourceName,
|
||||
"name": s.Name,
|
||||
"version": s.Version,
|
||||
"model": s.Model,
|
||||
"serial": s.Serial,
|
||||
"type": s.Type,
|
||||
}
|
||||
fields = Combine(
|
||||
u.batchUSWstat(s.Stat.Sw),
|
||||
map[string]interface{}{
|
||||
"guest-num_sta": s.GuestNumSta.Val,
|
||||
"ip": s.IP,
|
||||
"bytes": s.Bytes.Val,
|
||||
"last_seen": s.LastSeen.Val,
|
||||
"rx_bytes": s.RxBytes.Val,
|
||||
"tx_bytes": s.TxBytes.Val,
|
||||
"uptime": s.Uptime.Val,
|
||||
"state": s.State.Val,
|
||||
})
|
||||
|
||||
r.send(&metric{Table: "usw", Tags: tags, Fields: fields})
|
||||
u.batchPortTable(r, tags, s.PortTable)
|
||||
|
||||
if s.Stat.Ap == nil {
|
||||
return // we're done now. the following code process UDM (non-pro) UAP data.
|
||||
}
|
||||
|
||||
tags = map[string]string{
|
||||
"mac": s.Mac,
|
||||
"site_name": s.SiteName,
|
||||
"source": s.SourceName,
|
||||
"name": s.Name,
|
||||
"version": s.Version,
|
||||
"model": s.Model,
|
||||
"serial": s.Serial,
|
||||
"type": s.Type,
|
||||
}
|
||||
fields = u.processUAPstats(s.Stat.Ap)
|
||||
fields["ip"] = s.IP
|
||||
fields["bytes"] = s.Bytes.Val
|
||||
fields["last_seen"] = s.LastSeen.Val
|
||||
fields["rx_bytes"] = s.RxBytes.Val
|
||||
fields["tx_bytes"] = s.TxBytes.Val
|
||||
fields["uptime"] = s.Uptime.Val
|
||||
fields["state"] = s.State
|
||||
fields["user-num_sta"] = int(s.UserNumSta.Val)
|
||||
fields["guest-num_sta"] = int(s.GuestNumSta.Val)
|
||||
fields["num_sta"] = s.NumSta.Val
|
||||
|
||||
r.send(&metric{Table: "uap", Tags: tags, Fields: fields})
|
||||
u.processRadTable(r, tags, *s.RadioTable, *s.RadioTableStats)
|
||||
u.processVAPTable(r, tags, *s.VapTable)
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
// batchUSG generates Unifi Gateway datapoints for InfluxDB.
|
||||
// These points can be passed directly to influx.
|
||||
func (u *InfluxUnifi) batchUSG(r report, s *unifi.USG) {
|
||||
if !s.Adopted.Val || s.Locating.Val {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"mac": s.Mac,
|
||||
"site_name": s.SiteName,
|
||||
"source": s.SourceName,
|
||||
"name": s.Name,
|
||||
"version": s.Version,
|
||||
"model": s.Model,
|
||||
"serial": s.Serial,
|
||||
"type": s.Type,
|
||||
}
|
||||
fields := Combine(
|
||||
u.batchSysStats(s.SysStats, s.SystemStats),
|
||||
u.batchUSGstat(s.SpeedtestStatus, s.Stat.Gw, s.Uplink),
|
||||
map[string]interface{}{
|
||||
"ip": s.IP,
|
||||
"bytes": s.Bytes.Val,
|
||||
"last_seen": s.LastSeen.Val,
|
||||
"license_state": s.LicenseState,
|
||||
"guest-num_sta": s.GuestNumSta.Val,
|
||||
"rx_bytes": s.RxBytes.Val,
|
||||
"tx_bytes": s.TxBytes.Val,
|
||||
"uptime": s.Uptime.Val,
|
||||
"state": s.State.Val,
|
||||
"user-num_sta": s.UserNumSta.Val,
|
||||
"version": s.Version,
|
||||
"num_desktop": s.NumDesktop.Val,
|
||||
"num_handheld": s.NumHandheld.Val,
|
||||
"num_mobile": s.NumMobile.Val,
|
||||
},
|
||||
)
|
||||
|
||||
r.send(&metric{Table: "usg", Tags: tags, Fields: fields})
|
||||
u.batchNetTable(r, tags, s.NetworkTable)
|
||||
u.batchUSGwans(r, tags, s.Wan1, s.Wan2)
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) batchUSGstat(ss unifi.SpeedtestStatus, gw *unifi.Gw, ul unifi.Uplink) map[string]interface{} {
|
||||
if gw == nil {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"uplink_latency": ul.Latency.Val,
|
||||
"uplink_speed": ul.Speed.Val,
|
||||
"speedtest-status_latency": ss.Latency.Val,
|
||||
"speedtest-status_runtime": ss.Runtime.Val,
|
||||
"speedtest-status_ping": ss.StatusPing.Val,
|
||||
"speedtest-status_xput_download": ss.XputDownload.Val,
|
||||
"speedtest-status_xput_upload": ss.XputUpload.Val,
|
||||
"lan-rx_bytes": gw.LanRxBytes.Val,
|
||||
"lan-rx_packets": gw.LanRxPackets.Val,
|
||||
"lan-tx_bytes": gw.LanTxBytes.Val,
|
||||
"lan-tx_packets": gw.LanTxPackets.Val,
|
||||
"lan-rx_dropped": gw.LanRxDropped.Val,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) batchUSGwans(r report, tags map[string]string, wans ...unifi.Wan) {
|
||||
for _, wan := range wans {
|
||||
if !wan.Up.Val {
|
||||
continue
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"device_name": tags["name"],
|
||||
"site_name": tags["site_name"],
|
||||
"source": tags["source"],
|
||||
"ip": wan.IP,
|
||||
"purpose": wan.Name,
|
||||
"mac": wan.Mac,
|
||||
"ifname": wan.Ifname,
|
||||
"type": wan.Type,
|
||||
"up": wan.Up.Txt,
|
||||
"enabled": wan.Enable.Txt,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"bytes-r": wan.BytesR.Val,
|
||||
"full_duplex": wan.FullDuplex.Val,
|
||||
"gateway": wan.Gateway,
|
||||
"max_speed": wan.MaxSpeed.Val,
|
||||
"rx_bytes": wan.RxBytes.Val,
|
||||
"rx_bytes-r": wan.RxBytesR.Val,
|
||||
"rx_dropped": wan.RxDropped.Val,
|
||||
"rx_errors": wan.RxErrors.Val,
|
||||
"rx_broadcast": wan.RxBroadcast.Val,
|
||||
"rx_multicast": wan.RxMulticast.Val,
|
||||
"rx_packets": wan.RxPackets.Val,
|
||||
"speed": wan.Speed.Val,
|
||||
"tx_bytes": wan.TxBytes.Val,
|
||||
"tx_bytes-r": wan.TxBytesR.Val,
|
||||
"tx_dropped": wan.TxDropped.Val,
|
||||
"tx_errors": wan.TxErrors.Val,
|
||||
"tx_packets": wan.TxPackets.Val,
|
||||
"tx_broadcast": wan.TxBroadcast.Val,
|
||||
"tx_multicast": wan.TxMulticast.Val,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "usg_wan_ports", Tags: tags, Fields: fields})
|
||||
}
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) batchNetTable(r report, tags map[string]string, nt unifi.NetworkTable) {
|
||||
for _, p := range nt {
|
||||
tags := map[string]string{
|
||||
"device_name": tags["name"],
|
||||
"site_name": tags["site_name"],
|
||||
"source": tags["source"],
|
||||
"up": p.Up.Txt,
|
||||
"enabled": p.Enabled.Txt,
|
||||
"ip": p.IP,
|
||||
"mac": p.Mac,
|
||||
"name": p.Name,
|
||||
"domain_name": p.DomainName,
|
||||
"purpose": p.Purpose,
|
||||
"is_guest": p.IsGuest.Txt,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"num_sta": p.NumSta.Val,
|
||||
"rx_bytes": p.RxBytes.Val,
|
||||
"rx_packets": p.RxPackets.Val,
|
||||
"tx_bytes": p.TxBytes.Val,
|
||||
"tx_packets": p.TxPackets.Val,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "usg_networks", Tags: tags, Fields: fields})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
// batchUSW generates Unifi Switch datapoints for InfluxDB.
|
||||
// These points can be passed directly to influx.
|
||||
func (u *InfluxUnifi) batchUSW(r report, s *unifi.USW) {
|
||||
if !s.Adopted.Val || s.Locating.Val {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"mac": s.Mac,
|
||||
"site_name": s.SiteName,
|
||||
"source": s.SourceName,
|
||||
"name": s.Name,
|
||||
"version": s.Version,
|
||||
"model": s.Model,
|
||||
"serial": s.Serial,
|
||||
"type": s.Type,
|
||||
}
|
||||
fields := Combine(
|
||||
u.batchUSWstat(s.Stat.Sw),
|
||||
u.batchSysStats(s.SysStats, s.SystemStats),
|
||||
map[string]interface{}{
|
||||
"guest-num_sta": s.GuestNumSta.Val,
|
||||
"ip": s.IP,
|
||||
"bytes": s.Bytes.Val,
|
||||
"fan_level": s.FanLevel.Val,
|
||||
"general_temperature": s.GeneralTemperature.Val,
|
||||
"last_seen": s.LastSeen.Val,
|
||||
"rx_bytes": s.RxBytes.Val,
|
||||
"tx_bytes": s.TxBytes.Val,
|
||||
"uptime": s.Uptime.Val,
|
||||
"state": s.State.Val,
|
||||
"user-num_sta": s.UserNumSta.Val,
|
||||
})
|
||||
|
||||
r.send(&metric{Table: "usw", Tags: tags, Fields: fields})
|
||||
u.batchPortTable(r, tags, s.PortTable)
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) batchUSWstat(sw *unifi.Sw) map[string]interface{} {
|
||||
if sw == nil {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"stat_bytes": sw.Bytes.Val,
|
||||
"stat_rx_bytes": sw.RxBytes.Val,
|
||||
"stat_rx_crypts": sw.RxCrypts.Val,
|
||||
"stat_rx_dropped": sw.RxDropped.Val,
|
||||
"stat_rx_errors": sw.RxErrors.Val,
|
||||
"stat_rx_frags": sw.RxFrags.Val,
|
||||
"stat_rx_packets": sw.TxPackets.Val,
|
||||
"stat_tx_bytes": sw.TxBytes.Val,
|
||||
"stat_tx_dropped": sw.TxDropped.Val,
|
||||
"stat_tx_errors": sw.TxErrors.Val,
|
||||
"stat_tx_packets": sw.TxPackets.Val,
|
||||
"stat_tx_retries": sw.TxRetries.Val,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) batchPortTable(r report, t map[string]string, pt []unifi.Port) {
|
||||
for _, p := range pt {
|
||||
if !p.Up.Val || !p.Enable.Val {
|
||||
continue // only record UP ports.
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": t["site_name"],
|
||||
"device_name": t["name"],
|
||||
"source": t["source"],
|
||||
"name": p.Name,
|
||||
"poe_mode": p.PoeMode,
|
||||
"port_poe": p.PortPoe.Txt,
|
||||
"port_idx": p.PortIdx.Txt,
|
||||
"port_id": t["name"] + " Port " + p.PortIdx.Txt,
|
||||
"poe_enable": p.PoeEnable.Txt,
|
||||
"flowctrl_rx": p.FlowctrlRx.Txt,
|
||||
"flowctrl_tx": p.FlowctrlTx.Txt,
|
||||
"media": p.Media,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"dbytes_r": p.BytesR.Val,
|
||||
"rx_broadcast": p.RxBroadcast.Val,
|
||||
"rx_bytes": p.RxBytes.Val,
|
||||
"rx_bytes-r": p.RxBytesR.Val,
|
||||
"rx_dropped": p.RxDropped.Val,
|
||||
"rx_errors": p.RxErrors.Val,
|
||||
"rx_multicast": p.RxMulticast.Val,
|
||||
"rx_packets": p.RxPackets.Val,
|
||||
"speed": p.Speed.Val,
|
||||
"stp_pathcost": p.StpPathcost.Val,
|
||||
"tx_broadcast": p.TxBroadcast.Val,
|
||||
"tx_bytes": p.TxBytes.Val,
|
||||
"tx_bytes-r": p.TxBytesR.Val,
|
||||
"tx_dropped": p.TxDropped.Val,
|
||||
"tx_errors": p.TxErrors.Val,
|
||||
"tx_multicast": p.TxMulticast.Val,
|
||||
"tx_packets": p.TxPackets.Val,
|
||||
}
|
||||
|
||||
if p.PoeEnable.Val && p.PortPoe.Val {
|
||||
fields["poe_current"] = p.PoeCurrent.Val
|
||||
fields["poe_power"] = p.PoePower.Val
|
||||
fields["poe_voltage"] = p.PoeVoltage.Val
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "usw_ports", Tags: tags, Fields: fields})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
package inputunifi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/davidnewhall/unifi-poller/pkg/poller"
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
func (u *InputUnifi) isNill(c *Controller) bool {
|
||||
u.RLock()
|
||||
defer u.RUnlock()
|
||||
|
||||
return c.Unifi == nil
|
||||
}
|
||||
|
||||
// newDynamicCntrlr creates and saves a controller (with auth cookie) for repeated use.
|
||||
// This is called when an unconfigured controller is requested.
|
||||
func (u *InputUnifi) newDynamicCntrlr(url string) (bool, *Controller) {
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
c := u.dynamic[url]
|
||||
if c != nil {
|
||||
// it already exists.
|
||||
return false, c
|
||||
}
|
||||
|
||||
ccopy := u.Default // copy defaults into new controller
|
||||
c = &ccopy
|
||||
u.dynamic[url] = c
|
||||
c.Role = url
|
||||
c.URL = url
|
||||
|
||||
return true, c
|
||||
}
|
||||
|
||||
func (u *InputUnifi) dynamicController(url string) (*poller.Metrics, error) {
|
||||
if !strings.HasPrefix(url, "http") {
|
||||
return nil, fmt.Errorf("scrape filter match failed, and filter is not http URL")
|
||||
}
|
||||
|
||||
new, c := u.newDynamicCntrlr(url)
|
||||
|
||||
if new {
|
||||
u.Logf("Authenticating to Dynamic UniFi Controller: %s", url)
|
||||
|
||||
if err := u.getUnifi(c); err != nil {
|
||||
return nil, fmt.Errorf("authenticating to %s: %v", url, err)
|
||||
}
|
||||
}
|
||||
|
||||
return u.collectController(c)
|
||||
}
|
||||
|
||||
func (u *InputUnifi) collectController(c *Controller) (*poller.Metrics, error) {
|
||||
if u.isNill(c) {
|
||||
u.Logf("Re-authenticating to UniFi Controller: %s", c.URL)
|
||||
|
||||
if err := u.getUnifi(c); err != nil {
|
||||
return nil, fmt.Errorf("re-authenticating to %s: %v", c.Role, err)
|
||||
}
|
||||
}
|
||||
|
||||
return u.pollController(c)
|
||||
}
|
||||
|
||||
func (u *InputUnifi) pollController(c *Controller) (*poller.Metrics, error) {
|
||||
var err error
|
||||
|
||||
u.RLock()
|
||||
defer u.RUnlock()
|
||||
|
||||
m := &poller.Metrics{TS: time.Now()} // At this point, it's the Current Check.
|
||||
|
||||
// Get the sites we care about.
|
||||
if m.Sites, err = u.getFilteredSites(c); err != nil {
|
||||
return m, fmt.Errorf("unifi.GetSites(%v): %v", c.URL, err)
|
||||
}
|
||||
|
||||
if c.SaveDPI {
|
||||
if m.SitesDPI, err = c.Unifi.GetSiteDPI(m.Sites); err != nil {
|
||||
return m, fmt.Errorf("unifi.GetSiteDPI(%v): %v", c.URL, err)
|
||||
}
|
||||
|
||||
if m.ClientsDPI, err = c.Unifi.GetClientsDPI(m.Sites); err != nil {
|
||||
return m, fmt.Errorf("unifi.GetClientsDPI(%v): %v", c.URL, err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.SaveIDS {
|
||||
m.IDSList, err = c.Unifi.GetIDS(m.Sites, time.Now().Add(2*time.Minute), time.Now())
|
||||
if err != nil {
|
||||
return m, fmt.Errorf("unifi.GetIDS(%v): %v", c.URL, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get all the points.
|
||||
if m.Clients, err = c.Unifi.GetClients(m.Sites); err != nil {
|
||||
return m, fmt.Errorf("unifi.GetClients(%v): %v", c.URL, err)
|
||||
}
|
||||
|
||||
if m.Devices, err = c.Unifi.GetDevices(m.Sites); err != nil {
|
||||
return m, fmt.Errorf("unifi.GetDevices(%v): %v", c.URL, err)
|
||||
}
|
||||
|
||||
return u.augmentMetrics(c, m), nil
|
||||
}
|
||||
|
||||
// augmentMetrics is our middleware layer between collecting metrics and writing them.
|
||||
// This is where we can manipuate the returned data or make arbitrary decisions.
|
||||
// This function currently adds parent device names to client metrics.
|
||||
func (u *InputUnifi) augmentMetrics(c *Controller, metrics *poller.Metrics) *poller.Metrics {
|
||||
if metrics == nil || metrics.Devices == nil || metrics.Clients == nil {
|
||||
return metrics
|
||||
}
|
||||
|
||||
devices := make(map[string]string)
|
||||
bssdIDs := make(map[string]string)
|
||||
|
||||
for _, r := range metrics.UAPs {
|
||||
devices[r.Mac] = r.Name
|
||||
|
||||
for _, v := range r.VapTable {
|
||||
bssdIDs[v.Bssid] = fmt.Sprintf("%s %s %s:", r.Name, v.Radio, v.RadioName)
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range metrics.USGs {
|
||||
devices[r.Mac] = r.Name
|
||||
}
|
||||
|
||||
for _, r := range metrics.USWs {
|
||||
devices[r.Mac] = r.Name
|
||||
}
|
||||
|
||||
for _, r := range metrics.UDMs {
|
||||
devices[r.Mac] = r.Name
|
||||
}
|
||||
|
||||
// These come blank, so set them here.
|
||||
for i, c := range metrics.Clients {
|
||||
if devices[c.Mac] = c.Name; c.Name == "" {
|
||||
devices[c.Mac] = c.Hostname
|
||||
}
|
||||
|
||||
metrics.Clients[i].SwName = devices[c.SwMac]
|
||||
metrics.Clients[i].ApName = devices[c.ApMac]
|
||||
metrics.Clients[i].GwName = devices[c.GwMac]
|
||||
metrics.Clients[i].RadioDescription = bssdIDs[metrics.Clients[i].Bssid] + metrics.Clients[i].RadioProto
|
||||
}
|
||||
|
||||
for i := range metrics.ClientsDPI {
|
||||
// Name on Client DPI data also comes blank, find it based on MAC address.
|
||||
metrics.ClientsDPI[i].Name = devices[metrics.ClientsDPI[i].MAC]
|
||||
if metrics.ClientsDPI[i].Name == "" {
|
||||
metrics.ClientsDPI[i].Name = metrics.ClientsDPI[i].MAC
|
||||
}
|
||||
}
|
||||
|
||||
if !*c.SaveSites {
|
||||
metrics.Sites = nil
|
||||
}
|
||||
|
||||
return metrics
|
||||
}
|
||||
|
||||
// getFilteredSites returns a list of sites to fetch data for.
|
||||
// Omits requested but unconfigured sites. Grabs the full list from the
|
||||
// controller and returns the sites provided in the config file.
|
||||
func (u *InputUnifi) getFilteredSites(c *Controller) (unifi.Sites, error) {
|
||||
u.RLock()
|
||||
defer u.RUnlock()
|
||||
|
||||
sites, err := c.Unifi.GetSites()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(c.Sites) < 1 || StringInSlice("all", c.Sites) {
|
||||
return sites, nil
|
||||
}
|
||||
|
||||
i := 0
|
||||
|
||||
for _, s := range sites {
|
||||
// Only include valid sites in the request filter.
|
||||
if StringInSlice(s.Name, c.Sites) {
|
||||
sites[i] = s
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return sites[:i], nil
|
||||
}
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
// Package inputunifi implements the poller.Input interface and bridges the gap between
|
||||
// metrics from the unifi library, and the augments required to pump them into unifi-poller.
|
||||
package inputunifi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/davidnewhall/unifi-poller/pkg/poller"
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultURL = "https://127.0.0.1:8443"
|
||||
defaultUser = "unifipoller"
|
||||
defaultPass = "unifipoller"
|
||||
defaultSite = "all"
|
||||
)
|
||||
|
||||
// InputUnifi contains the running data.
|
||||
type InputUnifi struct {
|
||||
*Config `json:"unifi" toml:"unifi" xml:"unifi" yaml:"unifi"`
|
||||
dynamic map[string]*Controller
|
||||
sync.Mutex // to lock the map above.
|
||||
poller.Logger
|
||||
}
|
||||
|
||||
// Controller represents the configuration for a UniFi Controller.
|
||||
// Each polled controller may have its own configuration.
|
||||
type Controller struct {
|
||||
VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"`
|
||||
SaveIDS bool `json:"save_ids" toml:"save_ids" xml:"save_ids" yaml:"save_ids"`
|
||||
SaveDPI bool `json:"save_dpi" toml:"save_dpi" xml:"save_dpi" yaml:"save_dpi"`
|
||||
SaveSites *bool `json:"save_sites" toml:"save_sites" xml:"save_sites" yaml:"save_sites"`
|
||||
Role string `json:"role" toml:"role" xml:"role,attr" yaml:"role"`
|
||||
User string `json:"user" toml:"user" xml:"user" yaml:"user"`
|
||||
Pass string `json:"pass" toml:"pass" xml:"pass" yaml:"pass"`
|
||||
URL string `json:"url" toml:"url" xml:"url" yaml:"url"`
|
||||
Sites []string `json:"sites,omitempty" toml:"sites,omitempty" xml:"site" yaml:"sites"`
|
||||
Unifi *unifi.Unifi `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// Config contains our configuration data
|
||||
type Config struct {
|
||||
sync.RWMutex // locks the Unifi struct member when re-authing to unifi.
|
||||
Default Controller `json:"defaults" toml:"defaults" xml:"default" yaml:"defaults"`
|
||||
Disable bool `json:"disable" toml:"disable" xml:"disable,attr" yaml:"disable"`
|
||||
Dynamic bool `json:"dynamic" toml:"dynamic" xml:"dynamic,attr" yaml:"dynamic"`
|
||||
Controllers []*Controller `json:"controllers" toml:"controller" xml:"controller" yaml:"controllers"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
u := &InputUnifi{}
|
||||
|
||||
poller.NewInput(&poller.InputPlugin{
|
||||
Name: "unifi",
|
||||
Input: u, // this library implements poller.Input interface for Metrics().
|
||||
Config: u, // Defines our config data interface.
|
||||
})
|
||||
}
|
||||
|
||||
// getUnifi (re-)authenticates to a unifi controller.
|
||||
func (u *InputUnifi) getUnifi(c *Controller) error {
|
||||
var err error
|
||||
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
if c.Unifi != nil {
|
||||
c.Unifi.CloseIdleConnections()
|
||||
}
|
||||
|
||||
// Create an authenticated session to the Unifi Controller.
|
||||
c.Unifi, err = unifi.NewUnifi(&unifi.Config{
|
||||
User: c.User,
|
||||
Pass: c.Pass,
|
||||
URL: c.URL,
|
||||
VerifySSL: c.VerifySSL,
|
||||
ErrorLog: u.LogErrorf, // Log all errors.
|
||||
DebugLog: u.LogDebugf, // Log debug messages.
|
||||
})
|
||||
if err != nil {
|
||||
c.Unifi = nil
|
||||
return fmt.Errorf("unifi controller: %v", err)
|
||||
}
|
||||
|
||||
u.LogDebugf("Authenticated with controller successfully, %s", c.URL)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkSites makes sure the list of provided sites exists on the controller.
|
||||
// This only runs once during initialization.
|
||||
func (u *InputUnifi) checkSites(c *Controller) error {
|
||||
u.RLock()
|
||||
defer u.RUnlock()
|
||||
|
||||
if len(c.Sites) < 1 || c.Sites[0] == "" {
|
||||
c.Sites = []string{"all"}
|
||||
}
|
||||
|
||||
u.LogDebugf("Checking Controller Sites List")
|
||||
|
||||
sites, err := c.Unifi.GetSites()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := []string{}
|
||||
for _, site := range sites {
|
||||
msg = append(msg, site.Name+" ("+site.Desc+")")
|
||||
}
|
||||
|
||||
u.Logf("Found %d site(s) on controller %s: %v", len(msg), c.Role, strings.Join(msg, ", "))
|
||||
|
||||
if StringInSlice("all", c.Sites) {
|
||||
c.Sites = []string{"all"}
|
||||
return nil
|
||||
}
|
||||
|
||||
keep := []string{}
|
||||
|
||||
FIRST:
|
||||
for _, s := range c.Sites {
|
||||
for _, site := range sites {
|
||||
if s == site.Name {
|
||||
keep = append(keep, s)
|
||||
continue FIRST
|
||||
}
|
||||
}
|
||||
u.LogErrorf("Configured site not found on controller %s: %v", c.Role, s)
|
||||
}
|
||||
|
||||
if c.Sites = keep; len(keep) < 1 {
|
||||
c.Sites = []string{"all"}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *InputUnifi) dumpSitesJSON(c *Controller, path, name string, sites unifi.Sites) ([]byte, error) {
|
||||
allJSON := []byte{}
|
||||
|
||||
for _, s := range sites {
|
||||
apiPath := fmt.Sprintf(path, s.Name)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping %s: '%s' JSON for site: %s (%s):\n", name, apiPath, s.Desc, s.Name)
|
||||
|
||||
body, err := c.Unifi.GetJSON(apiPath)
|
||||
if err != nil {
|
||||
return allJSON, err
|
||||
}
|
||||
|
||||
allJSON = append(allJSON, body...)
|
||||
}
|
||||
|
||||
return allJSON, nil
|
||||
}
|
||||
|
||||
func (u *InputUnifi) setDefaults(c *Controller) {
|
||||
if c.SaveSites == nil {
|
||||
t := true
|
||||
c.SaveSites = &t
|
||||
}
|
||||
|
||||
if c.URL == "" {
|
||||
c.URL = defaultURL
|
||||
}
|
||||
|
||||
if c.Role == "" {
|
||||
c.Role = c.URL
|
||||
}
|
||||
|
||||
if c.Pass == "" {
|
||||
c.Pass = defaultPass
|
||||
}
|
||||
|
||||
if c.User == "" {
|
||||
c.User = defaultUser
|
||||
}
|
||||
|
||||
if len(c.Sites) < 1 {
|
||||
c.Sites = []string{defaultSite}
|
||||
}
|
||||
}
|
||||
|
||||
// StringInSlice returns true if a string is in a slice.
|
||||
func StringInSlice(str string, slice []string) bool {
|
||||
for _, s := range slice {
|
||||
if strings.EqualFold(s, str) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
package inputunifi
|
||||
|
||||
/* This file contains the three poller.Input interface methods. */
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/davidnewhall/unifi-poller/pkg/poller"
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
// Initialize gets called one time when starting up.
|
||||
// Satisfies poller.Input interface.
|
||||
func (u *InputUnifi) Initialize(l poller.Logger) error {
|
||||
if u.Disable {
|
||||
l.Logf("UniFi input plugin disabled!")
|
||||
return nil
|
||||
}
|
||||
|
||||
if u.setDefaults(&u.Default); len(u.Controllers) < 1 && !u.Dynamic {
|
||||
new := u.Default // copy defaults.
|
||||
u.Controllers = []*Controller{&new}
|
||||
}
|
||||
|
||||
if len(u.Controllers) < 1 {
|
||||
l.Logf("No controllers configured. Polling dynamic controllers only!")
|
||||
}
|
||||
|
||||
u.dynamic = make(map[string]*Controller)
|
||||
u.Logger = l
|
||||
|
||||
for _, c := range u.Controllers {
|
||||
u.setDefaults(c)
|
||||
|
||||
switch err := u.getUnifi(c); err {
|
||||
case nil:
|
||||
if err := u.checkSites(c); err != nil {
|
||||
u.LogErrorf("checking sites on %s: %v", c.Role, err)
|
||||
}
|
||||
|
||||
u.Logf("Configured UniFi Controller at %s v%s as user %s. Sites: %v",
|
||||
c.URL, c.Unifi.ServerVersion, c.User, c.Sites)
|
||||
default:
|
||||
u.LogErrorf("Controller Auth or Connection failed, but continuing to retry! %s: %v", c.Role, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Metrics grabs all the measurements from a UniFi controller and returns them.
|
||||
func (u *InputUnifi) Metrics() (*poller.Metrics, bool, error) {
|
||||
return u.MetricsFrom(nil)
|
||||
}
|
||||
|
||||
// MetricsFrom grabs all the measurements from a UniFi controller and returns them.
|
||||
func (u *InputUnifi) MetricsFrom(filter *poller.Filter) (*poller.Metrics, bool, error) {
|
||||
if u.Disable {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
errs := []string{}
|
||||
metrics := &poller.Metrics{}
|
||||
ok := false
|
||||
|
||||
if filter != nil && filter.Path != "" {
|
||||
if !u.Dynamic {
|
||||
return metrics, false, fmt.Errorf("filter path requested but dynamic lookups disabled")
|
||||
}
|
||||
|
||||
// Attempt a dynamic metrics fetch from an unconfigured controller.
|
||||
m, err := u.dynamicController(filter.Path)
|
||||
|
||||
return m, err == nil && m != nil, err
|
||||
}
|
||||
|
||||
// Check if the request is for an existing, configured controller.
|
||||
for _, c := range u.Controllers {
|
||||
if filter != nil && !strings.EqualFold(c.Role, filter.Role) {
|
||||
continue
|
||||
}
|
||||
|
||||
m, err := u.collectController(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ok = true
|
||||
metrics = poller.AppendMetrics(metrics, m)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return metrics, ok, fmt.Errorf(strings.Join(errs, ", "))
|
||||
}
|
||||
|
||||
return metrics, ok, nil
|
||||
}
|
||||
|
||||
// RawMetrics returns API output from the first configured unifi controller.
|
||||
func (u *InputUnifi) RawMetrics(filter *poller.Filter) ([]byte, error) {
|
||||
if l := len(u.Controllers); filter.Unit >= l {
|
||||
return nil, fmt.Errorf("control number %d not found, %d controller(s) configured (0 index)", filter.Unit, l)
|
||||
}
|
||||
|
||||
c := u.Controllers[filter.Unit]
|
||||
if u.isNill(c) {
|
||||
u.Logf("Re-authenticating to UniFi Controller: %s", c.URL)
|
||||
|
||||
if err := u.getUnifi(c); err != nil {
|
||||
return nil, fmt.Errorf("re-authenticating to %s: %v", c.Role, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := u.checkSites(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sites, err := u.getFilteredSites(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch filter.Kind {
|
||||
case "d", "device", "devices":
|
||||
return u.dumpSitesJSON(c, unifi.APIDevicePath, "Devices", sites)
|
||||
case "client", "clients", "c":
|
||||
return u.dumpSitesJSON(c, unifi.APIClientPath, "Clients", sites)
|
||||
case "other", "o":
|
||||
_, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping Path '%s':\n", filter.Path)
|
||||
return c.Unifi.GetJSON(filter.Path)
|
||||
default:
|
||||
return []byte{}, fmt.Errorf("must provide filter: devices, clients, other")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
// +build darwin
|
||||
|
||||
package poller
|
||||
|
||||
// DefaultConfFile is where to find config if --config is not prvided.
|
||||
const DefaultConfFile = "/usr/local/etc/unifi-poller/up.conf"
|
||||
|
||||
// DefaultObjPath is the path to look for shared object libraries (plugins).
|
||||
const DefaultObjPath = "/usr/local/lib/unifi-poller"
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
// +build !windows,!darwin
|
||||
|
||||
package poller
|
||||
|
||||
// DefaultConfFile is where to find config if --config is not prvided.
|
||||
const DefaultConfFile = "/etc/unifi-poller/up.conf"
|
||||
|
||||
// DefaultObjPath is the path to look for shared object libraries (plugins).
|
||||
const DefaultObjPath = "/usr/lib/unifi-poller"
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package poller
|
||||
|
||||
// DefaultConfFile is where to find config if --config is not prvided.
|
||||
const DefaultConfFile = `C:\ProgramData\unifi-poller\up.conf`
|
||||
|
||||
// DefaultObjPath is useless in this context. Bummer.
|
||||
const DefaultObjPath = "PLUGINS_DO_NOT_WORK_ON_WINDOWS_SOWWWWWY"
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
package poller
|
||||
|
||||
/*
|
||||
I consider this file the pinacle example of how to allow a Go application to be configured from a file.
|
||||
You can put your configuration into any file format: XML, YAML, JSON, TOML, and you can override any
|
||||
struct member using an environment variable. The Duration type is also supported. All of the Config{}
|
||||
and Duration{} types and methods are reusable in other projects. Just adjust the data in the struct to
|
||||
meet your app's needs. See the New() procedure and Start() method in start.go for example usage.
|
||||
*/
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"plugin"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"golift.io/cnfg"
|
||||
"golift.io/cnfg/cnfgfile"
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
const (
|
||||
// AppName is the name of the application.
|
||||
AppName = "unifi-poller"
|
||||
// ENVConfigPrefix is the prefix appended to an env variable tag name.
|
||||
ENVConfigPrefix = "UP"
|
||||
)
|
||||
|
||||
// UnifiPoller contains the application startup data, and auth info for UniFi & Influx.
|
||||
type UnifiPoller struct {
|
||||
Flags *Flags
|
||||
*Config
|
||||
}
|
||||
|
||||
// Flags represents the CLI args available and their settings.
|
||||
type Flags struct {
|
||||
ConfigFile string
|
||||
DumpJSON string
|
||||
ShowVer bool
|
||||
*pflag.FlagSet
|
||||
}
|
||||
|
||||
// Metrics is a type shared by the exporting and reporting packages.
|
||||
type Metrics struct {
|
||||
TS time.Time
|
||||
unifi.Sites
|
||||
unifi.IDSList
|
||||
unifi.Clients
|
||||
*unifi.Devices
|
||||
SitesDPI []*unifi.DPITable
|
||||
ClientsDPI []*unifi.DPITable
|
||||
}
|
||||
|
||||
// Config represents the core library input data.
|
||||
type Config struct {
|
||||
*Poller `json:"poller" toml:"poller" xml:"poller" yaml:"poller"`
|
||||
}
|
||||
|
||||
// Poller is the global config values.
|
||||
type Poller struct {
|
||||
Plugins []string `json:"plugins" toml:"plugins" xml:"plugin" yaml:"plugins"`
|
||||
Debug bool `json:"debug" toml:"debug" xml:"debug,attr" yaml:"debug"`
|
||||
Quiet bool `json:"quiet,omitempty" toml:"quiet,omitempty" xml:"quiet,attr" yaml:"quiet"`
|
||||
}
|
||||
|
||||
// LoadPlugins reads-in dynamic shared libraries.
|
||||
// Not used very often, if at all.
|
||||
func (u *UnifiPoller) LoadPlugins() error {
|
||||
for _, p := range u.Plugins {
|
||||
name := strings.TrimSuffix(p, ".so") + ".so"
|
||||
|
||||
if name == ".so" {
|
||||
continue // Just ignore it. uhg.
|
||||
}
|
||||
|
||||
if _, err := os.Stat(name); os.IsNotExist(err) {
|
||||
name = path.Join(DefaultObjPath, name)
|
||||
}
|
||||
|
||||
u.Logf("Loading Dynamic Plugin: %s", name)
|
||||
|
||||
if _, err := plugin.Open(name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseConfigs parses the poller config and the config for each registered output plugin.
|
||||
func (u *UnifiPoller) ParseConfigs() error {
|
||||
// Parse core config.
|
||||
if err := u.parseInterface(u.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load dynamic plugins.
|
||||
if err := u.LoadPlugins(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := u.parseInputs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return u.parseOutputs()
|
||||
}
|
||||
|
||||
// parseInterface parses the config file and environment variables into the provided interface.
|
||||
func (u *UnifiPoller) parseInterface(i interface{}) error {
|
||||
// Parse config file into provided interface.
|
||||
if err := cnfgfile.Unmarshal(i, u.Flags.ConfigFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse environment variables into provided interface.
|
||||
_, err := cnfg.UnmarshalENV(i, ENVConfigPrefix)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse input plugin configs.
|
||||
func (u *UnifiPoller) parseInputs() error {
|
||||
inputSync.Lock()
|
||||
defer inputSync.Unlock()
|
||||
|
||||
for _, i := range inputs {
|
||||
if err := u.parseInterface(i.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse output plugin configs.
|
||||
func (u *UnifiPoller) parseOutputs() error {
|
||||
outputSync.Lock()
|
||||
defer outputSync.Unlock()
|
||||
|
||||
for _, o := range outputs {
|
||||
if err := u.parseInterface(o.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DumpJSONPayload prints raw json from the UniFi Controller. This is currently
|
||||
// tied into the -j CLI arg, and is probably not very useful outside that context.
|
||||
func (u *UnifiPoller) DumpJSONPayload() (err error) {
|
||||
u.Config.Quiet = true
|
||||
split := strings.SplitN(u.Flags.DumpJSON, " ", 2)
|
||||
filter := &Filter{Kind: split[0]}
|
||||
|
||||
if split2 := strings.Split(filter.Kind, ":"); len(split2) > 1 {
|
||||
filter.Kind = split2[0]
|
||||
filter.Unit, _ = strconv.Atoi(split2[1])
|
||||
}
|
||||
|
||||
if len(split) > 1 {
|
||||
filter.Path = split[1]
|
||||
}
|
||||
|
||||
m, err := inputs[0].RawMetrics(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(m))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golift.io/unifi"
|
||||
)
|
||||
|
||||
var (
|
||||
inputs []*InputPlugin
|
||||
inputSync sync.Mutex
|
||||
)
|
||||
|
||||
// Input plugins must implement this interface.
|
||||
type Input interface {
|
||||
Initialize(Logger) error // Called once on startup to initialize the plugin.
|
||||
Metrics() (*Metrics, bool, error) // Called every time new metrics are requested.
|
||||
MetricsFrom(*Filter) (*Metrics, bool, error) // Called every time new metrics are requested.
|
||||
RawMetrics(*Filter) ([]byte, error)
|
||||
}
|
||||
|
||||
// InputPlugin describes an input plugin's consumable interface.
|
||||
type InputPlugin struct {
|
||||
Name string
|
||||
Config interface{} // Each config is passed into an unmarshaller later.
|
||||
Input
|
||||
}
|
||||
|
||||
// Filter is used for metrics filters. Many fields for lots of expansion.
|
||||
type Filter struct {
|
||||
Type string
|
||||
Term string
|
||||
Name string
|
||||
Tags string
|
||||
Role string
|
||||
Kind string
|
||||
Path string
|
||||
Area int
|
||||
Item int
|
||||
Unit int
|
||||
Sign int64
|
||||
Mass int64
|
||||
Rate float64
|
||||
Cost float64
|
||||
Free bool
|
||||
True bool
|
||||
Done bool
|
||||
Stop bool
|
||||
}
|
||||
|
||||
// NewInput creates a metric input. This should be called by input plugins
|
||||
// init() functions.
|
||||
func NewInput(i *InputPlugin) {
|
||||
inputSync.Lock()
|
||||
defer inputSync.Unlock()
|
||||
|
||||
if i == nil || i.Input == nil {
|
||||
panic("nil output or method passed to poller.NewOutput")
|
||||
}
|
||||
|
||||
inputs = append(inputs, i)
|
||||
}
|
||||
|
||||
// InitializeInputs runs the passed-in initializer method for each input plugin.
|
||||
func (u *UnifiPoller) InitializeInputs() error {
|
||||
inputSync.Lock()
|
||||
defer inputSync.Unlock()
|
||||
|
||||
for _, input := range inputs {
|
||||
// This must return, or the app locks up here.
|
||||
if err := input.Initialize(u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Metrics aggregates all the measurements from all configured inputs and returns them.
|
||||
func (u *UnifiPoller) Metrics() (*Metrics, bool, error) {
|
||||
errs := []string{}
|
||||
metrics := &Metrics{}
|
||||
ok := false
|
||||
|
||||
for _, input := range inputs {
|
||||
m, _, err := input.Metrics()
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ok = true
|
||||
metrics = AppendMetrics(metrics, m)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if len(errs) > 0 {
|
||||
err = fmt.Errorf(strings.Join(errs, ", "))
|
||||
}
|
||||
|
||||
return metrics, ok, err
|
||||
}
|
||||
|
||||
// MetricsFrom aggregates all the measurements from filtered inputs and returns them.
|
||||
func (u *UnifiPoller) MetricsFrom(filter *Filter) (*Metrics, bool, error) {
|
||||
errs := []string{}
|
||||
metrics := &Metrics{}
|
||||
ok := false
|
||||
|
||||
for _, input := range inputs {
|
||||
if !strings.EqualFold(input.Name, filter.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
m, _, err := input.MetricsFrom(filter)
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ok = true
|
||||
metrics = AppendMetrics(metrics, m)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if len(errs) > 0 {
|
||||
err = fmt.Errorf(strings.Join(errs, ", "))
|
||||
}
|
||||
|
||||
return metrics, ok, err
|
||||
}
|
||||
|
||||
// AppendMetrics combined the metrics from two sources.
|
||||
func AppendMetrics(existing *Metrics, m *Metrics) *Metrics {
|
||||
existing.SitesDPI = append(existing.SitesDPI, m.SitesDPI...)
|
||||
existing.Sites = append(existing.Sites, m.Sites...)
|
||||
existing.ClientsDPI = append(existing.ClientsDPI, m.ClientsDPI...)
|
||||
existing.Clients = append(existing.Clients, m.Clients...)
|
||||
existing.IDSList = append(existing.IDSList, m.IDSList...)
|
||||
|
||||
if m.Devices == nil {
|
||||
return existing
|
||||
}
|
||||
|
||||
if existing.Devices == nil {
|
||||
existing.Devices = &unifi.Devices{}
|
||||
}
|
||||
|
||||
existing.UAPs = append(existing.UAPs, m.UAPs...)
|
||||
existing.USGs = append(existing.USGs, m.USGs...)
|
||||
existing.USWs = append(existing.USWs, m.USWs...)
|
||||
existing.UDMs = append(existing.UDMs, m.UDMs...)
|
||||
|
||||
return existing
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
const callDepth = 2
|
||||
|
||||
// Logger is passed into input packages so they may write logs.
|
||||
type Logger interface {
|
||||
Logf(m string, v ...interface{})
|
||||
LogErrorf(m string, v ...interface{})
|
||||
LogDebugf(m string, v ...interface{})
|
||||
}
|
||||
|
||||
// Logf prints a log entry if quiet is false.
|
||||
func (u *UnifiPoller) Logf(m string, v ...interface{}) {
|
||||
if !u.Quiet {
|
||||
_ = log.Output(callDepth, fmt.Sprintf("[INFO] "+m, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// LogDebugf prints a debug log entry if debug is true and quite is false
|
||||
func (u *UnifiPoller) LogDebugf(m string, v ...interface{}) {
|
||||
if u.Debug && !u.Quiet {
|
||||
_ = log.Output(callDepth, fmt.Sprintf("[DEBUG] "+m, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// LogErrorf prints an error log entry.
|
||||
func (u *UnifiPoller) LogErrorf(m string, v ...interface{}) {
|
||||
_ = log.Output(callDepth, fmt.Sprintf("[ERROR] "+m, v...))
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
outputs []*Output
|
||||
outputSync sync.Mutex
|
||||
)
|
||||
|
||||
// Collect is passed into output packages so they may collect metrics to output.
|
||||
// Output packages must implement this interface.
|
||||
type Collect interface {
|
||||
Metrics() (*Metrics, bool, error)
|
||||
MetricsFrom(*Filter) (*Metrics, bool, error)
|
||||
Logger
|
||||
}
|
||||
|
||||
// Output defines the output data for a metric exporter like influx or prometheus.
|
||||
// Output packages should call NewOutput with this struct in init().
|
||||
type Output struct {
|
||||
Name string
|
||||
Config interface{} // Each config is passed into an unmarshaller later.
|
||||
Method func(Collect) error // Called on startup for each configured output.
|
||||
}
|
||||
|
||||
// NewOutput should be called by each output package's init function.
|
||||
func NewOutput(o *Output) {
|
||||
outputSync.Lock()
|
||||
defer outputSync.Unlock()
|
||||
|
||||
if o == nil || o.Method == nil {
|
||||
panic("nil output or method passed to poller.NewOutput")
|
||||
}
|
||||
|
||||
outputs = append(outputs, o)
|
||||
}
|
||||
|
||||
// InitializeOutputs runs all the configured output plugins.
|
||||
// If none exist, or they all exit an error is returned.
|
||||
func (u *UnifiPoller) InitializeOutputs() error {
|
||||
v := make(chan error)
|
||||
defer close(v)
|
||||
|
||||
var count int
|
||||
|
||||
for _, o := range outputs {
|
||||
count++
|
||||
|
||||
go func(o *Output) {
|
||||
v <- o.Method(u)
|
||||
}(o)
|
||||
}
|
||||
|
||||
if count < 1 {
|
||||
return fmt.Errorf("no output plugins imported")
|
||||
}
|
||||
|
||||
for err := range v {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count--; count < 1 {
|
||||
return fmt.Errorf("all output plugins have stopped, or none enabled")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
// Package poller provides the CLI interface to setup unifi-poller.
|
||||
package poller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/prometheus/common/version"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// New returns a new poller struct.
|
||||
func New() *UnifiPoller {
|
||||
return &UnifiPoller{Config: &Config{Poller: &Poller{}}, Flags: &Flags{}}
|
||||
}
|
||||
|
||||
// Start begins the application from a CLI.
|
||||
// Parses cli flags, parses config file, parses env vars, sets up logging, then:
|
||||
// - dumps a json payload OR - executes Run().
|
||||
func (u *UnifiPoller) Start() error {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.SetFlags(log.LstdFlags)
|
||||
u.Flags.Parse(os.Args[1:])
|
||||
|
||||
if u.Flags.ShowVer {
|
||||
fmt.Printf("%s v%s\n", AppName, version.Version)
|
||||
return nil // don't run anything else w/ version request.
|
||||
}
|
||||
|
||||
if u.Flags.DumpJSON == "" { // do not print this when dumping JSON.
|
||||
u.Logf("Loading Configuration File: %s", u.Flags.ConfigFile)
|
||||
}
|
||||
|
||||
// Parse config file and ENV variables.
|
||||
if err := u.ParseConfigs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return u.Run()
|
||||
}
|
||||
|
||||
// Parse turns CLI arguments into data structures. Called by Start() on startup.
|
||||
func (f *Flags) Parse(args []string) {
|
||||
f.FlagSet = pflag.NewFlagSet(AppName, pflag.ExitOnError)
|
||||
f.Usage = func() {
|
||||
fmt.Printf("Usage: %s [--config=/path/to/up.conf] [--version]", AppName)
|
||||
f.PrintDefaults()
|
||||
}
|
||||
|
||||
f.StringVarP(&f.DumpJSON, "dumpjson", "j", "",
|
||||
"This debug option prints a json payload and exits. See man page for more info.")
|
||||
f.StringVarP(&f.ConfigFile, "config", "c", DefaultConfFile, "Poller config file path.")
|
||||
f.BoolVarP(&f.ShowVer, "version", "v", false, "Print the version and exit.")
|
||||
_ = f.FlagSet.Parse(args) // pflag.ExitOnError means this will never return error.
|
||||
}
|
||||
|
||||
// Run picks a mode and executes the associated functions. This will do one of three things:
|
||||
// 1. Start the collector routine that polls unifi and reports to influx on an interval. (default)
|
||||
// 2. Run the collector one time and report the metrics to influxdb. (lambda)
|
||||
// 3. Start a web server and wait for Prometheus to poll the application for metrics.
|
||||
func (u *UnifiPoller) Run() error {
|
||||
if u.Flags.DumpJSON != "" {
|
||||
if err := u.InitializeInputs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return u.DumpJSONPayload()
|
||||
}
|
||||
|
||||
if u.Debug {
|
||||
log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.Ldate)
|
||||
u.LogDebugf("Debug Logging Enabled")
|
||||
}
|
||||
|
||||
log.Printf("[INFO] UniFi Poller v%v Starting Up! PID: %d", version.Version, os.Getpid())
|
||||
|
||||
if err := u.InitializeInputs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return u.InitializeOutputs()
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# prometheus
|
||||
|
||||
This package provides the interface to turn UniFi measurements into prometheus
|
||||
exported metrics. Requires the poller package for actual UniFi data collection.
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# MYSQL Output Plugin Example
|
||||
|
||||
The code here, and the dynamic plugin provided shows an example of how you can
|
||||
write your own output for unifi-poller. This plugin records some very basic
|
||||
data about clients on a unifi network into a mysql database.
|
||||
|
||||
You could write outputs that do... anything. An example: They could compare current
|
||||
connected clients to a previous list (in a db, or stored in memory), and send a
|
||||
notification if it changes. The possibilities are endless.
|
||||
|
||||
You must compile your plugin using the unifi-poller source for the version you're
|
||||
using. In other words, to build a plugin for version 2.0.1, do this:
|
||||
```
|
||||
mkdir -p $GOPATH/src/github.com/davidnewhall
|
||||
cd $GOPATH/src/github.com/davidnewhall
|
||||
|
||||
git clone git@github.com:davidnewhall/unifi-poller.git
|
||||
cd unifi-poller
|
||||
|
||||
git checkout v2.0.1
|
||||
make vendor
|
||||
|
||||
cp -r <your plugin> plugins/
|
||||
GOOS=linux make plugins
|
||||
```
|
||||
The plugin you copy in *must* have a `main.go` file for `make plugins` to build it.
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/davidnewhall/unifi-poller/pkg/poller"
|
||||
"golift.io/cnfg"
|
||||
)
|
||||
|
||||
// mysqlConfig represents the data that is unmarshalled from the up.conf config file for this plugins.
|
||||
type mysqlConfig struct {
|
||||
Interval cnfg.Duration `json:"interval" toml:"interval" xml:"interval" yaml:"interval"`
|
||||
Host string `json:"host" toml:"host" xml:"host" yaml:"host"`
|
||||
User string `json:"user" toml:"user" xml:"user" yaml:"user"`
|
||||
Pass string `json:"pass" toml:"pass" xml:"pass" yaml:"pass"`
|
||||
DB string `json:"db" toml:"db" xml:"db" yaml:"db"`
|
||||
Table string `json:"table" toml:"table" xml:"table" yaml:"table"`
|
||||
// Maps do not work with ENV VARIABLES yet, but may in the future.
|
||||
Fields []string `json:"fields" toml:"fields" xml:"field" yaml:"fields"`
|
||||
}
|
||||
|
||||
// Pointers are ignored during ENV variable unmarshal, avoid pointers to your config.
|
||||
// Only capital (exported) members are unmarshaled when passed into poller.NewOutput().
|
||||
type plugin struct {
|
||||
Config mysqlConfig `json:"mysql" toml:"mysql" xml:"mysql" yaml:"mysql"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
u := &plugin{Config: mysqlConfig{}}
|
||||
|
||||
poller.NewOutput(&poller.Output{
|
||||
Name: "mysql",
|
||||
Config: u, // pass in the struct *above* your config (so it can see the struct tags).
|
||||
Method: u.Run,
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("this is a unifi-poller plugin; not an application")
|
||||
}
|
||||
|
||||
func (a *plugin) Run(c poller.Collect) error {
|
||||
c.Logf("mysql plugin is not finished")
|
||||
return nil
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/davidnewhall/unifi-poller/pkg/poller"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/unifi-poller/poller"
|
||||
)
|
||||
|
||||
// This file contains the report interface.
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This file is used by deb and rpm packages.
|
||||
# FPM adds this as the after-install script.
|
||||
|
||||
if [ -x "/bin/systemctl" ]; then
|
||||
# Reload and restart - this starts the application as user nobody.
|
||||
/bin/systemctl daemon-reload
|
||||
/bin/systemctl enable unifi-poller
|
||||
/bin/systemctl restart unifi-poller
|
||||
fi
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This file is used by rpm and deb packages. FPM use.
|
||||
|
||||
if [ "$1" = "upgrade" ] || [ "$1" = "1" ] ; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -x "/bin/systemctl" ]; then
|
||||
/bin/systemctl stop unifi-poller
|
||||
/bin/systemctl disable unifi-poller
|
||||
fi
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
#!/bin/bash -x
|
||||
|
||||
# Deploys a new homebrew formula file to a github homebrew formula repo: $HBREPO
|
||||
# Requires SSH credentials in ssh-agent to work.
|
||||
# Run by Travis-CI when a new release is created on GitHub.
|
||||
# Do not edit this file. It's part of application-builder.
|
||||
# https://github.com/golift/application-builder
|
||||
|
||||
source .metadata.sh
|
||||
|
||||
make ${BINARY}.rb
|
||||
|
||||
git config --global user.email "${BINARY}@auto.releaser"
|
||||
git config --global user.name "${BINARY}-auto-releaser"
|
||||
|
||||
rm -rf homebrew_release_repo
|
||||
git clone git@github.com:${HBREPO}.git homebrew_release_repo
|
||||
|
||||
# If a bitly token file exists, we'll use that to shorten the link (and allow download counting).
|
||||
if [ -f "bitly_token" ]; then
|
||||
API=https://api-ssl.bitly.com/v4/bitlinks
|
||||
# Request payload. In single quotes with double quotes escaped. :see_no_evil:
|
||||
JSON='{\"domain\": \"bit.ly\",\"title\": \"${BINARY}.v${VERSION}-${ITERATION}.tgz\", \
|
||||
\"tags\": [\"${BINARY}\"], \"long_url\": \"${SOURCE_PATH}\"}'
|
||||
# Request with headers and data. Using bash -c to hide token from bash -x in travis logs.
|
||||
OUT=$(bash -c "curl -s -X POST -H 'Content-type: application/json' ${API} -H \"\$(<bitly_token)\" -d \"${JSON}\"")
|
||||
# Extract link from reply.
|
||||
LINK="$(echo ${OUT} | jq -r .link | sed 's/http:/https:/')?v=v${VERSION}"
|
||||
# Replace link in formula.
|
||||
sed "s#^ url.*\$# url \"${LINK}\"#" ${BINARY}.rb > ${BINARY}.rb.new
|
||||
if [ "$?" = "0" ] && [ "$LINK" != "null?v=v${VERSION}" ] && [ "$LINK" != "?v=v${VERSION}" ]; then
|
||||
mv ${BINARY}.rb.new ${BINARY}.rb
|
||||
fi
|
||||
fi
|
||||
|
||||
cp ${BINARY}.rb homebrew_release_repo/Formula
|
||||
pushd homebrew_release_repo
|
||||
git add Formula/${BINARY}.rb
|
||||
git commit -m "Update ${BINARY} on Release: v${VERSION}-${ITERATION}"
|
||||
git push
|
||||
popd
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This is a quick and drity script to install the latest Linux package.
|
||||
#
|
||||
# Use it like this: (sudo is optional)
|
||||
# ===
|
||||
# curl https://raw.githubusercontent.com/davidnewhall/unifi-poller/master/scripts/install.sh | sudo bash
|
||||
# ===
|
||||
# If you're on redhat, this installs the latest rpm. If you're on Debian, it installs the latest deb package.
|
||||
#
|
||||
# This is part of application-builder.
|
||||
# https://github.com/golift/application-builder
|
||||
|
||||
REPO=davidnewhall/unifi-poller
|
||||
LATEST=https://api.github.com/repos/${REPO}/releases/latest
|
||||
ARCH=$(uname -m)
|
||||
|
||||
# $ARCH is passed into egrep to find the right file.
|
||||
if [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ]; then
|
||||
ARCH="x86_64|amd64"
|
||||
elif [[ $ARCH == *386* ]] || [[ $ARCH == *686* ]]; then
|
||||
ARCH="i386"
|
||||
elif [[ $ARCH == *arm64* ]] || [[ $ARCH == *armv8* ]]; then
|
||||
ARCH="arm64"
|
||||
elif [[ $ARCH == *armv6* ]] || [[ $ARCH == *armv7* ]]; then
|
||||
ARCH="armhf"
|
||||
else
|
||||
echo "Unknown Architecture. Submit a pull request to fix this, please."
|
||||
echo ==> $ARCH
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$1" == "deb" ] || [ "$1" == "rpm" ]; then
|
||||
FILE=$1
|
||||
else
|
||||
# If you have both, rpm wins.
|
||||
rpm --version > /dev/null 2>&1
|
||||
if [ "$?" = "0" ]; then
|
||||
FILE=rpm
|
||||
else
|
||||
dpkg --version > /dev/null 2>&1
|
||||
if [ "$?" = "0" ]; then
|
||||
FILE=deb
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$FILE" = "" ]; then
|
||||
echo "No dpkg or rpm package managers found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# curl or wget?
|
||||
curl --version > /dev/null 2>&1
|
||||
if [ "$?" = "0" ]; then
|
||||
CMD="curl -L"
|
||||
else
|
||||
wget --version > /dev/null 2>&1
|
||||
if [ "$?" = "0" ]; then
|
||||
CMD="wget -O-"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$CMD" = "" ]; then
|
||||
echo "Need curl or wget - could not find either!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Grab latest release file from github.
|
||||
URL=$($CMD ${LATEST} | egrep "browser_download_url.*(${ARCH})\.${FILE}\"" | cut -d\" -f 4)
|
||||
|
||||
if [ "$?" != "0" ] || [ "$URL" = "" ]; then
|
||||
echo "Error locating latest release at ${LATEST}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INSTALLER="rpm -Uvh"
|
||||
if [ "$FILE" = "deb" ]; then
|
||||
INSTALLER="dpkg --force-confdef --force-confold --install"
|
||||
fi
|
||||
|
||||
FILE=$(basename ${URL})
|
||||
echo "Downloading: ${URL} to /tmp/${FILE}"
|
||||
$CMD ${URL} > /tmp/${FILE}
|
||||
|
||||
# Install it.
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
echo "==================================="
|
||||
echo "Downloaded. Installing the package!"
|
||||
echo "Running: ${INSTALLER} /tmp/${FILE}"
|
||||
$INSTALLER /tmp/${FILE}
|
||||
else
|
||||
echo "================================"
|
||||
echo "Downloaded. Install the package:"
|
||||
echo "sudo $INSTALLER /tmp/${FILE}"
|
||||
fi
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Deploy our built packages to jfrog bintray.
|
||||
|
||||
COMPONENT=unstable
|
||||
if [ "$TRAVIS_BRANCH" == "$TRAVIS_TAG" ] && [ "$TRAVIS_BRANCH" != "" ]; then
|
||||
COMPONENT=main
|
||||
fi
|
||||
echo "deploying packages from branch: $TRAVIS_BRANCH, tag: $TRAVIS_TAG to repo: $COMPONENT"
|
||||
|
||||
source .metadata.sh
|
||||
|
||||
for os in el centos; do
|
||||
for arch in arm64 armhf x86_64 i386; do
|
||||
file="unifi-poller-${VERSION}-${ITERATION}.${arch}.rpm"
|
||||
opts="publish=1;override=1"
|
||||
url="https://api.bintray.com/content/golift/${os}/unifi-poller/${VERSION}-${ITERATION}/${COMPONENT}/${arch}/${file}"
|
||||
echo curl -T "release/${file}" "${url};${opts}"
|
||||
curl -T "release/${file}" -u "${JFROG_USER_API_KEY}" "${url};${opts}"
|
||||
echo
|
||||
done
|
||||
done
|
||||
|
||||
for os in ubuntu debian; do
|
||||
for arch in arm64 armhf amd64 i386; do
|
||||
file="unifi-poller_${VERSION}-${ITERATION}_${arch}.deb"
|
||||
opts="deb_distribution=xenial,bionic,focal,jesse,stretch,buster,bullseye;deb_component=${COMPONENT};deb_architecture=${arch};publish=1;override=1"
|
||||
url="https://api.bintray.com/content/golift/${os}/unifi-poller/${VERSION}-${ITERATION}/${file}"
|
||||
echo curl -T "release/${file}" "${url};${opts}"
|
||||
curl -T "release/${file}" -u "${JFROG_USER_API_KEY}" "${url};${opts}"
|
||||
echo
|
||||
done
|
||||
done
|
||||
Loading…
Reference in New Issue