Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2f4005a7ba | ||
|
8d4df68b95 | ||
b2e810f0dd | |||
a4b42715a4 | |||
fe35e0ff21 | |||
c6bacca63d | |||
0d9bf1a3f7 | |||
826df446d7 | |||
5e64dfa04a | |||
aa37e8e291 | |||
6ad260078e | |||
2008c9f999 | |||
7e1ba5dd4d | |||
8c14e3bc41 | |||
e0dacb1e4b | |||
|
647a90c0f2 | ||
|
92469f11b2 | ||
a75aeeb8f9 | |||
|
d4f2624635 | ||
|
2259d3ed6b | ||
|
8fde62955d | ||
|
e03bf19427 | ||
|
24ca04b2aa | ||
|
22487faf06 | ||
|
3f70299c36 | ||
|
fddac32cc7 | ||
|
74cb7ba215 | ||
|
a6b928dc0e |
1
.env
1
.env
@@ -1,3 +1,4 @@
|
||||
KDOORPI_DOOR=workshop
|
||||
KDOORPI_API_ALLOWED=http://127.0.0.1:3333/allowed
|
||||
KDOORPI_API_LONGPOLL=http://127.0.0.1:3333/longpoll
|
||||
KDOORPI_API_SWIPE=http://127.0.0.1:3333/cardswipe
|
||||
|
28
.woodpecker/build.yaml
Normal file
28
.woodpecker/build.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
matrix:
|
||||
ARCH:
|
||||
- amd64
|
||||
- arm64
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: woodpeckerci/plugin-kaniko
|
||||
backend_options:
|
||||
kubernetes:
|
||||
nodeSelector:
|
||||
kubernetes.io/arch: ${ARCH}
|
||||
tolerations:
|
||||
- key: arch
|
||||
operator: Equal
|
||||
value: ${ARCH}
|
||||
effect: NoSchedule
|
||||
settings:
|
||||
repo: ${CI_REPO}
|
||||
registry: harbor.k-space.ee
|
||||
tags: latest-${ARCH}
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
when:
|
||||
- branch: master
|
30
.woodpecker/manifest.yaml
Normal file
30
.woodpecker/manifest.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
skip_clone: true
|
||||
|
||||
steps:
|
||||
- name: manifest
|
||||
image: mirror.gcr.io/mplatform/manifest-tool:alpine-v2.1.6
|
||||
secrets:
|
||||
- docker_username
|
||||
- docker_password
|
||||
commands:
|
||||
- |
|
||||
cat << EOF > spec.yaml
|
||||
image: "harbor.k-space.ee/${CI_REPO}:latest"
|
||||
manifests:
|
||||
- image: "harbor.k-space.ee/${CI_REPO}:latest-amd64"
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: "harbor.k-space.ee/${CI_REPO}:latest-arm64"
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
EOF
|
||||
- /manifest-tool --username $docker_username --password $docker_password push from-spec spec.yaml > stdout
|
||||
- cat stdout
|
||||
when:
|
||||
- branch: master
|
||||
|
||||
depends_on:
|
||||
- build
|
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
FROM mirror.gcr.io/library/golang:1.21-alpine as build
|
||||
RUN apk add ca-certificates
|
||||
|
||||
WORKDIR /godoor
|
||||
|
||||
# pre-copy/cache go.mod for pre-downloading dependencies and only redownloading them in subsequent builds if they change
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download && go mod verify
|
||||
|
||||
|
||||
COPY . .
|
||||
|
||||
ENV GOBUILDLDFLAGS="-linkmode 'external' -extldflags '-static'"
|
||||
RUN go build -tags netgo .
|
||||
|
||||
FROM scratch
|
||||
WORKDIR /
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=build /godoor/godoor /godoor
|
||||
|
||||
ENTRYPOINT ["/godoor"]
|
16
Makefile
16
Makefile
@@ -1,4 +1,16 @@
|
||||
dev:
|
||||
GOOS=linux GOARCH=arm64 go build .
|
||||
.PHONY: all build build_arm64 dev
|
||||
|
||||
all: build
|
||||
|
||||
GOBUILDLDFLAGS="-X main.Version=`git describe --always --dirty`"
|
||||
#GOBUILDLDFLAGS="-X main.Version=`git describe --dirty` -linkmode 'external' -extldflags '-static'"
|
||||
|
||||
build:
|
||||
go build -tags netgo -ldflags=${GOBUILDLDFLAGS} .
|
||||
|
||||
build_arm64:
|
||||
GOOS=linux GOARCH=arm64 go build -tags netgo -ldflags=${GOBUILDLDFLAGS} .
|
||||
|
||||
dev: build_arm64
|
||||
scp godoor workshopdoor:/tmp/
|
||||
ssh workshopdoor 'mv -f /tmp/godoor ~/ && sudo systemctl restart godoor'
|
||||
|
65
README.md
65
README.md
@@ -1,14 +1,65 @@
|
||||
# GoDoor
|
||||
|
||||
[kdoorpi](https://git.k-space.ee/arti/kdoorpi) but in Go
|
||||
Go rewrite of [kdoorpi](https://git.k-space.ee/arti/kdoorpi). Uses
|
||||
[libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about/)
|
||||
native Go library to access the general purpose pins in cross platform way.
|
||||
|
||||
Uses Go native [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about/)
|
||||
library to access the gpio pins in cross platform way.
|
||||
# Developing
|
||||
|
||||
# Cross compile
|
||||
Either build directly on Raspberry Pi or build locally and copy the binary
|
||||
|
||||
GOOS=linux GOARCH=arm64 go build
|
||||
```
|
||||
GOOS=linux GOARCH=arm64 go build
|
||||
scp godoor rpi4b:
|
||||
```
|
||||
|
||||
# Test deploy
|
||||
# Deployment
|
||||
|
||||
scp godoor rpi4b:
|
||||
The CI system should automatically build docker image and push it to [Harbor](https://harbor.k-space.ee)
|
||||
|
||||
Refer to
|
||||
[Ansible playbook](https://git.k-space.ee/k-space/kube/src/branch/master/ansible-doors.yml)
|
||||
on how to deploy on all door controllers
|
||||
|
||||
# Usage
|
||||
|
||||
For end users door can be opened by:
|
||||
|
||||
* [Web interface](https://inventory.k-space.ee/m/doorboy)
|
||||
* Push button
|
||||
* Enrolling keyfob and swiping keyfob at door cardreader
|
||||
* Issuing `/open-...-door` command in #members channel
|
||||
|
||||
For emergency cases:
|
||||
|
||||
* Log into door controller via SSH and issue `killall -sUSR1 godoor`
|
||||
|
||||
# Door system architecture
|
||||
|
||||
* Web UI / Slack commands - https://git.k-space.ee/k-space/inventory-app
|
||||
* Card List provider - https://git.k-space.ee/k-space/doorboy-proxy
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
┌──────────────────┐ │ Card Reader │ ...
|
||||
│ Slack /open-door │ └───────┬──────┘
|
||||
└─────────────────┬┘ │
|
||||
│ ┌──────────────┐
|
||||
┌──────────────┐ │ ┌────│ RPI/w godoor │ ...
|
||||
│ Web Open door│ │ │ └──────────────┘
|
||||
└─────────┬────┘ │ │Swipe ▲
|
||||
│ │ │Event │ Open event
|
||||
▼ ▼ ▼ │ Card list
|
||||
┌───────────────┐ ┌─────────┴─────┐
|
||||
│ inventory-app │ │ doorboy-proxy │
|
||||
│ doorboy.py │ └───────────────┘
|
||||
└───────────────┘ ▲
|
||||
│ │
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌───────────┐
|
||||
└──────────►│ MongoDB │
|
||||
└───────────┘
|
||||
```
|
||||
|
||||
https://asciiflow.com/ diagram
|
||||
|
22
go.mod
22
go.mod
@@ -1,10 +1,22 @@
|
||||
module godoor
|
||||
|
||||
go 1.18
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/warthog618/gpiod v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
github.com/warthog618/gpiod v0.8.2
|
||||
golang.org/x/crypto v0.12.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.1 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
)
|
||||
|
53
go.sum
53
go.sum
@@ -1,10 +1,47 @@
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/warthog618/gpiod v0.8.0 h1:qxH9XVvWHpTxzWFSndBcujFyNH5zVRzHM63tcmm85o4=
|
||||
github.com/warthog618/gpiod v0.8.0/go.mod h1:a7Csa+IJtDBZ39++zC/6Srjo01qWejt/5velrDWuNkY=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
||||
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
|
||||
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/warthog618/go-gpiosim v0.1.0 h1:2rTMTcKUVZxpUuvRKsagnKAbKpd3Bwffp87xywEDVGI=
|
||||
github.com/warthog618/go-gpiosim v0.1.0/go.mod h1:Ngx/LYI5toxHr4E+Vm6vTgCnt0of0tktsSuMUEJ2wCI=
|
||||
github.com/warthog618/gpiod v0.8.2 h1:2HgQ9pNowPp7W77sXhX5ut5Tqq1WoS3t7bXYDxtYvxc=
|
||||
github.com/warthog618/gpiod v0.8.2/go.mod h1:O7BNpHjCn/4YS5yFVmoFZAlY1LuYuQ8vhPf0iy/qdi4=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
264
godoor.go
264
godoor.go
@@ -11,20 +11,27 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"godoor/hash"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"godoor/hash"
|
||||
)
|
||||
|
||||
const wiegand_a = 17
|
||||
const wiegand_b = 18
|
||||
const wiegand_bit_timeout = time.Millisecond * 8
|
||||
const solenoid = 21
|
||||
const (
|
||||
wiegand_a_default = 17
|
||||
wiegand_b_default = 18
|
||||
wiegand_bit_timeout = time.Millisecond * 8
|
||||
solenoid_default = 21
|
||||
)
|
||||
|
||||
type card struct {
|
||||
UidHash string `json:"uid_hash"`
|
||||
@@ -42,7 +49,7 @@ type ValidUids map[string]bool // bool has no meaning
|
||||
type Config struct {
|
||||
door string
|
||||
uidSalt string
|
||||
doorOpenTime string
|
||||
doorOpenTime time.Duration
|
||||
mock bool
|
||||
api struct {
|
||||
allowed string
|
||||
@@ -50,6 +57,12 @@ type Config struct {
|
||||
swipe string
|
||||
key string
|
||||
}
|
||||
pins struct {
|
||||
wiegandA int
|
||||
wiegandB int
|
||||
solenoid int
|
||||
}
|
||||
prometheusMetricsBind string
|
||||
}
|
||||
|
||||
type KeepDoorOpen struct {
|
||||
@@ -62,33 +75,66 @@ type OpenedTimestamp struct {
|
||||
Closed *time.Time
|
||||
}
|
||||
|
||||
var config Config
|
||||
var globalLock sync.Mutex
|
||||
var validUids ValidUids
|
||||
var wiegand Wiegand
|
||||
var keepDoorOpen KeepDoorOpen
|
||||
var Commit = func() string {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
for _, setting := range info.Settings {
|
||||
if setting.Key == "vcs.revision" {
|
||||
return setting.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var lastSyncedTimestamp *time.Time
|
||||
var openDoorTimestamps []OpenedTimestamp
|
||||
return ""
|
||||
}()
|
||||
|
||||
var Version string
|
||||
|
||||
var (
|
||||
config Config
|
||||
globalLock sync.Mutex
|
||||
validUids ValidUids
|
||||
wiegand Wiegand
|
||||
keepDoorOpen KeepDoorOpen
|
||||
)
|
||||
|
||||
var (
|
||||
godoorBuildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "godoor_build_info",
|
||||
Help: "Build Information",
|
||||
}, []string{"version", "revision"})
|
||||
lastSyncTimestamp = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "godoor_last_allow_list_sync_timestamp_seconds",
|
||||
Help: "Last time list of card hashes was pulled from the server",
|
||||
})
|
||||
apiFailuresCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "godoor_api_request_failures_total",
|
||||
Help: "HTTP API request failures count",
|
||||
}, []string{"api", "endpoint"})
|
||||
nrCardsInAllowList = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "godoor_allowed_card_hashes_total",
|
||||
Help: "Number of card hashes in memory that can open the door",
|
||||
})
|
||||
doorOpenedCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "godoor_door_opens_total",
|
||||
Help: "Number of times door was opened",
|
||||
}, []string{"source"})
|
||||
cardSwipesCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "godoor_swipes_total",
|
||||
Help: "Number of times a card has been swiped",
|
||||
}, []string{"status"})
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
log.Printf("GoDoor ver: %s (%s)", Version, Commit)
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
godotenv.Load()
|
||||
loadConfig()
|
||||
|
||||
config.door = os.Getenv("KDOORPI_DOOR")
|
||||
config.api.allowed = os.Getenv("KDOORPI_API_ALLOWED")
|
||||
config.api.longpoll = os.Getenv("KDOORPI_API_LONGPOLL")
|
||||
config.api.swipe = os.Getenv("KDOORPI_API_SWIPE")
|
||||
config.api.key = os.Getenv("KDOORPI_API_KEY")
|
||||
config.uidSalt = os.Getenv("KDOORPI_UID_SALT")
|
||||
config.doorOpenTime = os.Getenv("KDOORPI_OPEN_TIME")
|
||||
_, config.mock = os.LookupEnv("KDOORPI_MOCK_HW")
|
||||
godoorBuildInfo.WithLabelValues(Version, Commit).Set(1)
|
||||
|
||||
go func() {
|
||||
setup()
|
||||
setup(ctx)
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
@@ -97,7 +143,52 @@ func main() {
|
||||
// cleanup
|
||||
}
|
||||
|
||||
func setup() {
|
||||
func loadConfig() {
|
||||
var err error
|
||||
|
||||
log.Println("Loading .env config")
|
||||
err = godotenv.Load()
|
||||
if err != nil {
|
||||
log.Println("Failed to load .env config, using internal defaults")
|
||||
}
|
||||
|
||||
config.door = os.Getenv("KDOORPI_DOOR")
|
||||
config.api.allowed = os.Getenv("KDOORPI_API_ALLOWED")
|
||||
config.api.longpoll = os.Getenv("KDOORPI_API_LONGPOLL")
|
||||
config.api.swipe = os.Getenv("KDOORPI_API_SWIPE")
|
||||
config.api.key = os.Getenv("KDOORPI_API_KEY")
|
||||
config.uidSalt = os.Getenv("KDOORPI_UID_SALT")
|
||||
|
||||
config.doorOpenTime = 8 * time.Second
|
||||
if doorOpenTimeStr, ok := os.LookupEnv("KDOORPI_OPEN_TIME"); ok {
|
||||
if openTime, err := time.ParseDuration(doorOpenTimeStr); err != nil {
|
||||
log.Printf("parsing KDOORPI_OPEN_TIME: %v, keeping default %v", err, config.doorOpenTime)
|
||||
} else {
|
||||
config.doorOpenTime = openTime
|
||||
}
|
||||
}
|
||||
|
||||
_, config.mock = os.LookupEnv("KDOORPI_MOCK_HW")
|
||||
config.prometheusMetricsBind = os.Getenv("KDOORPI_PROMETHEUS_METRICS_BIND")
|
||||
if config.prometheusMetricsBind == "" {
|
||||
config.prometheusMetricsBind = ":3334"
|
||||
}
|
||||
|
||||
config.pins.wiegandA, err = strconv.Atoi(os.Getenv("KDOORPI_PIN_WIEGAND_A"))
|
||||
if err != nil {
|
||||
config.pins.wiegandA = wiegand_a_default
|
||||
}
|
||||
config.pins.wiegandB, err = strconv.Atoi(os.Getenv("KDOORPI_PIN_WIEGAND_B"))
|
||||
if err != nil {
|
||||
config.pins.wiegandB = wiegand_b_default
|
||||
}
|
||||
config.pins.solenoid, err = strconv.Atoi(os.Getenv("KDOORPI_PIN_SOLENOID"))
|
||||
if err != nil {
|
||||
config.pins.solenoid = solenoid_default
|
||||
}
|
||||
}
|
||||
|
||||
func setup(ctx context.Context) {
|
||||
log.Println("Started Setup")
|
||||
|
||||
if config.mock {
|
||||
@@ -107,68 +198,78 @@ func setup() {
|
||||
}
|
||||
wiegand = &WiegandMock{}
|
||||
} else {
|
||||
wiegand = WiegandSetup(wiegand_a, wiegand_b, wiegand_bit_timeout, solenoid)
|
||||
wiegand = WiegandSetup(config.pins.wiegandA, config.pins.wiegandB, wiegand_bit_timeout, config.pins.solenoid)
|
||||
}
|
||||
log.Println("HW Setup done")
|
||||
|
||||
go runHttpServer()
|
||||
|
||||
http.DefaultClient.Timeout = 120 * time.Second
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := waitEvents()
|
||||
if err != nil {
|
||||
log.Printf("LongPoll for events failed: %v", err)
|
||||
log.Println("Will try to LongPoll again in 120 seconds")
|
||||
time.Sleep(120 * time.Second)
|
||||
go reloadTokens()
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Println("Initialized longpoll event loop")
|
||||
|
||||
for {
|
||||
log.Println("Start initial token population")
|
||||
err := reloadTokens()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
apiFailuresCount.WithLabelValues("allowed", config.api.allowed).Inc()
|
||||
log.Printf("Initial token population failed. err: %v", err)
|
||||
log.Println("Retrying in 10 seconds...")
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
|
||||
go runHttpServer()
|
||||
|
||||
log.Println("Initial token population success")
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := waitEvents()
|
||||
if err != nil {
|
||||
apiFailuresCount.WithLabelValues("longpoll", config.api.longpoll).Inc()
|
||||
log.Printf("LongPoll for events failed: %v", err)
|
||||
log.Println("Will try to LongPoll again soon")
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
go func() {
|
||||
err := reloadTokens()
|
||||
if err != nil {
|
||||
log.Printf("ReloadTokens failed: %q", err)
|
||||
apiFailuresCount.WithLabelValues("allowed", config.api.allowed).Inc()
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
go listenSig1(ctx, wiegand)
|
||||
|
||||
log.Println("Initialized longpoll event loop")
|
||||
|
||||
go cardRunner(wiegand)
|
||||
|
||||
log.Println("Setup completed")
|
||||
}
|
||||
|
||||
func listenSig1(ctx context.Context, wiegand Wiegand) {
|
||||
usrSig := make(chan os.Signal)
|
||||
signal.Notify(usrSig, syscall.SIGUSR1)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-usrSig:
|
||||
err := OpenAndCloseDoor(wiegand)
|
||||
log.Printf("Emergecy opening door as prompted by SIGUSR1")
|
||||
if err != nil {
|
||||
log.Printf("opening door: %v", err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runHttpServer() {
|
||||
http.HandleFunc("/lastsync", func(w http.ResponseWriter, r *http.Request) {
|
||||
e := json.NewEncoder(w)
|
||||
e.Encode(map[string]any{
|
||||
"last_synced": lastSyncedTimestamp,
|
||||
})
|
||||
})
|
||||
http.HandleFunc("/opened", func(w http.ResponseWriter, r *http.Request) {
|
||||
e := json.NewEncoder(w)
|
||||
e.Encode(map[string]any{
|
||||
"open_timestamps": openDoorTimestamps,
|
||||
})
|
||||
})
|
||||
http.HandleFunc("/isopen", func(w http.ResponseWriter, r *http.Request) {
|
||||
e := json.NewEncoder(w)
|
||||
open, _ := wiegand.IsDoorOpen()
|
||||
e.Encode(map[string]any{
|
||||
"open": open,
|
||||
})
|
||||
})
|
||||
http.ListenAndServe(":3334", nil)
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
log.Printf("Running prometheus metrics on http://%s/metrics", config.prometheusMetricsBind)
|
||||
log.Fatal(http.ListenAndServe(config.prometheusMetricsBind, nil))
|
||||
}
|
||||
|
||||
func OpenAndCloseDoor(w Wiegand) error {
|
||||
@@ -184,7 +285,7 @@ func OpenAndCloseDoor(w Wiegand) error {
|
||||
|
||||
fmt.Println("Door is now open")
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
time.Sleep(config.doorOpenTime)
|
||||
|
||||
err = CloseDoor(w)
|
||||
if err != nil {
|
||||
@@ -201,7 +302,6 @@ func OpenDoor(w Wiegand) error {
|
||||
return nil
|
||||
}
|
||||
w.OpenDoor()
|
||||
openDoorTimestamps = append(openDoorTimestamps, OpenedTimestamp{Opened: time.Now(), Closed: nil})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -211,8 +311,6 @@ func CloseDoor(w Wiegand) error {
|
||||
return nil
|
||||
}
|
||||
w.CloseDoor()
|
||||
t := time.Now()
|
||||
openDoorTimestamps[len(openDoorTimestamps)-1].Closed = &t
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -223,7 +321,7 @@ func cardRunner(w Wiegand) {
|
||||
continue
|
||||
}
|
||||
|
||||
printCardId(card)
|
||||
// printCardId(card)
|
||||
hashedHex := hash.HashCardUid(card)
|
||||
log.Println(hashedHex)
|
||||
|
||||
@@ -234,17 +332,21 @@ func cardRunner(w Wiegand) {
|
||||
go func() {
|
||||
err := sendSwipeEvent(hashedHex, ok)
|
||||
if err != nil {
|
||||
log.Println("Failed to send swipe event: %v", err)
|
||||
apiFailuresCount.WithLabelValues("swipe", config.api.swipe).Inc()
|
||||
log.Printf("Failed to send swipe event: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if ok {
|
||||
log.Println("Opening door")
|
||||
err := OpenAndCloseDoor(w)
|
||||
cardSwipesCount.WithLabelValues("accepted").Inc()
|
||||
doorOpenedCount.WithLabelValues("card").Inc()
|
||||
if err != nil {
|
||||
log.Println("There was an error opening and closing the Door")
|
||||
}
|
||||
} else {
|
||||
cardSwipesCount.WithLabelValues("denied").Inc()
|
||||
log.Println("Unknown card")
|
||||
}
|
||||
|
||||
@@ -272,7 +374,6 @@ func ParseNextMessage(r *bufio.Reader) (string, error) {
|
||||
|
||||
r.UnreadByte()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func waitEvents() error {
|
||||
@@ -291,6 +392,9 @@ func waitEvents() error {
|
||||
for {
|
||||
msg, err := ParseNextMessage(reader)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -302,6 +406,7 @@ func waitEvents() error {
|
||||
log.Printf("got server data: %q\n", data)
|
||||
if strings.TrimSpace(data) == config.door {
|
||||
err := OpenAndCloseDoor(wiegand)
|
||||
doorOpenedCount.WithLabelValues("api").Inc()
|
||||
if err != nil {
|
||||
log.Println("There was an error opening and closing the Door")
|
||||
}
|
||||
@@ -309,9 +414,6 @@ func waitEvents() error {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.Printf("%v\n", resp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func reloadTokens() error {
|
||||
@@ -345,24 +447,23 @@ func reloadTokens() error {
|
||||
validUids = make(ValidUids)
|
||||
var totalCardCount int = 0
|
||||
for i, val := range cl.AllowedUids {
|
||||
//log.Printf("%d: %+v\n", i, val.Token.UidHash)
|
||||
// log.Printf("%d: %+v\n", i, val.Token.UidHash)
|
||||
validUids[val.Token.UidHash] = true
|
||||
totalCardCount = i
|
||||
}
|
||||
log.Printf("Got %d cards from server", totalCardCount)
|
||||
nrCardsInAllowList.Set(float64(totalCardCount))
|
||||
|
||||
if cl.KeepOpenUntil != nil {
|
||||
updateKeepOpenDoor(*cl.KeepOpenUntil)
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
lastSyncedTimestamp = &t
|
||||
lastSyncTimestamp.SetToCurrentTime()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateKeepOpenDoor(newKeepOpenTime time.Time) {
|
||||
|
||||
// is there one active?
|
||||
if keepDoorOpen.timer != nil {
|
||||
keepDoorOpen.timer.Stop()
|
||||
@@ -389,11 +490,11 @@ func handleKeepDoorOpenCloseCleanup() {
|
||||
}
|
||||
|
||||
func sendSwipeEvent(cardUidHash string, success bool) error {
|
||||
swipeEvent := map[string]string{
|
||||
swipeEvent := map[string]any{
|
||||
"uid_hash": cardUidHash,
|
||||
"door": config.door,
|
||||
"timestamp": time.Now().Format(time.DateTime),
|
||||
"success": fmt.Sprint(success),
|
||||
"timestamp": time.Now().Format(time.RFC3339),
|
||||
"success": success,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(swipeEvent)
|
||||
@@ -406,6 +507,7 @@ func sendSwipeEvent(cardUidHash string, success bool) error {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("KEY", config.api.key)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
|
7
godoor_server/README.md
Normal file
7
godoor_server/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Local Development Mock server
|
||||
|
||||
Just for simple local testing
|
||||
|
||||
# NOT TO BE USED IN PROD!
|
||||
|
||||
Move it into a seperate repo if before making it more useful
|
@@ -1,4 +1,6 @@
|
||||
// really stupid test door server
|
||||
// Only for local testing
|
||||
// I WIL HUNT YOU DOWN IT YOU TRY TO USE IT IN PROD!
|
||||
|
||||
package main
|
||||
|
||||
@@ -217,7 +219,7 @@ func (doobserver *DoorBoyServer) postKeepDoorOpen(w http.ResponseWriter, r *http
|
||||
rs, _ := io.ReadAll(r.Body)
|
||||
parsedTime, err := time.Parse(time.RFC3339, strings.TrimSpace(string(rs)))
|
||||
if err != nil {
|
||||
fmt.Println("Error with parsing time: %v", err)
|
||||
fmt.Printf("Error with parsing time: %v", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
keepOpenUntil = &parsedTime
|
||||
@@ -229,6 +231,9 @@ func main() {
|
||||
|
||||
var dumpKeys []cardToken
|
||||
savedKeys, err := os.ReadFile("keys.json")
|
||||
if err != nil {
|
||||
log.Fatalf("reading saved keys from keys.json: %v", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(savedKeys, &dumpKeys)
|
||||
if err == nil {
|
||||
@@ -268,7 +273,7 @@ func main() {
|
||||
keyLock.Lock()
|
||||
data, err := json.Marshal(keys)
|
||||
if err == nil {
|
||||
os.WriteFile("keys.json", data, 0600)
|
||||
os.WriteFile("keys.json", data, 0o600)
|
||||
log.Println("Saved keys successfully!")
|
||||
}
|
||||
keyLock.Unlock()
|
||||
|
@@ -103,6 +103,7 @@ func (w *WiegandHW) wiegandBEvent(evt gpiod.LineEvent) {
|
||||
|
||||
func WiegandSetup(a int, b int, bitTimeout time.Duration, solenoid int) *WiegandHW {
|
||||
|
||||
log.Printf("Wiegand GPIO-s: A:%d B:%d Solenoid:%d", a, b, solenoid)
|
||||
var wiegand WiegandHW
|
||||
wiegand.bitTimeout = bitTimeout
|
||||
|
||||
@@ -112,14 +113,14 @@ func WiegandSetup(a int, b int, bitTimeout time.Duration, solenoid int) *Wiegand
|
||||
}
|
||||
|
||||
wa, err := gpiod.RequestLine("gpiochip0", a, gpiod.AsInput,
|
||||
gpiod.WithFallingEdge, gpiod.WithEventHandler(wiegand.wiegandAEvent))
|
||||
gpiod.WithFallingEdge, gpiod.LineBiasPullUp, gpiod.WithEventHandler(wiegand.wiegandAEvent))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
wiegand.aLine = wa
|
||||
|
||||
wb, err := gpiod.RequestLine("gpiochip0", b, gpiod.AsInput,
|
||||
gpiod.WithFallingEdge, gpiod.WithEventHandler(wiegand.wiegandBEvent))
|
||||
gpiod.WithFallingEdge, gpiod.LineBiasPullUp, gpiod.WithEventHandler(wiegand.wiegandBEvent))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user