Compare commits
11 commits
22bfe751de
...
0e68e5f5c6
Author | SHA1 | Date | |
---|---|---|---|
Felix Niederwanger | 0e68e5f5c6 | ||
2eecfffdf8 | |||
Felix Niederwanger | 122ff16589 | ||
Felix Niederwanger | d534856fb6 | ||
c96f61e181 | |||
Felix Niederwanger | f89d8a9c4b | ||
d67f3c1cc3 | |||
Felix Niederwanger | 932fa7e376 | ||
Felix Niederwanger | 1b1419961b | ||
9a9107f6b3 | |||
Felix Niederwanger | c42acccc28 |
|
@ -1,4 +1,4 @@
|
|||
FROM golang:alpine AS builder
|
||||
FROM docker.io/golang:alpine AS builder
|
||||
WORKDIR /app
|
||||
ADD . /app
|
||||
RUN apk update && apk add build-base
|
2
Makefile
2
Makefile
|
@ -11,6 +11,8 @@ ot-browser-static: cmd/ot-browser/*.go
|
|||
export CGO_ENABLED=0
|
||||
CGO_ENABLED=0 go build -ldflags="-w -s" -o ot-browser $^
|
||||
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
container: Dockerfile cmd/ot-browser/*.go
|
||||
docker build . -t feldspaten.org/ot-browser
|
||||
|
|
15
build_container.sh
Executable file
15
build_container.sh
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
export MANIFEST="ot-browser"
|
||||
export IMAGE="codeberg.org/grisu48/ot-browser"
|
||||
|
||||
|
||||
buildah manifest create "$MANIFEST"
|
||||
|
||||
buildah build --arch amd64 --tag "$IMAGE" --manifest "$MANIFEST" . &
|
||||
buildah build --arch arm64 --tag "$IMAGE" --manifest "$MANIFEST" . &
|
||||
# TODO: Add RISC-V
|
||||
#buildah build --arch riscv64 --tag "$IMAGE" --manifest "$MANIFEST" . &
|
||||
wait
|
||||
|
||||
buildah manifest push --all "$MANIFEST" "docker://$IMAGE"
|
|
@ -12,6 +12,8 @@ type Location struct {
|
|||
Alt float32 `json:"alt"`
|
||||
Acc float32 `json:"acc"`
|
||||
Velocity float32 `json:"vel"`
|
||||
// Bearing is currently not pushed via Owntracks
|
||||
Bearing float32
|
||||
}
|
||||
|
||||
// Distance computes the distance (in meters) between two points
|
||||
|
|
|
@ -19,10 +19,12 @@ type Config struct {
|
|||
mqttRemote string
|
||||
mqttClientId string
|
||||
mqttTopic string
|
||||
db string
|
||||
}
|
||||
|
||||
var config Config
|
||||
var mqtt MQTTReceiver
|
||||
var db Storage
|
||||
var devices map[string]Location
|
||||
|
||||
// SetDefaults sets the configuration defaults
|
||||
|
@ -32,16 +34,26 @@ func (c *Config) SetDefaults() {
|
|||
c.mqttRemote = "127.0.0.1"
|
||||
c.mqttTopic = "owntracks/#"
|
||||
c.mqttClientId = "ot-browser"
|
||||
c.db = ""
|
||||
}
|
||||
|
||||
func mqttRecv(id string, loc Location) {
|
||||
if !loc.IsPlausible() { // Ignore stupid locations
|
||||
return
|
||||
}
|
||||
if old, ok := devices[id]; ok { // if we already have a location
|
||||
|
||||
// Write all datapoints before doing additional checks on known devices
|
||||
if config.db != "" {
|
||||
if err := db.InsertLocation(id, loc); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error writing location to database: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Additional checks on known devices
|
||||
if old, ok := devices[id]; ok {
|
||||
// Ignore, if the new location is less precise, and the old one lies within it's accuracy
|
||||
// This might happen, if there is a provider switch on the phone (e.g. GPX -> coarse location)
|
||||
if loc.Acc > old.Acc {
|
||||
// Ignore, if the new location is less precise, and the old one lies within it's accuracy
|
||||
// This might happen, if there is a provider switch on the phone (e.g. GPX -> coarse location)
|
||||
distance := old.Distance(loc)
|
||||
if distance <= loc.Acc {
|
||||
return
|
||||
|
@ -66,6 +78,7 @@ func parseProgramArguments() error {
|
|||
fmt.Println(" -b,--bind ADDR Bind webserver to ADDR")
|
||||
fmt.Println(" --mqtt ADDR Set MQTT remote address")
|
||||
fmt.Println(" --clientid CLIENTID Set MQTT client id")
|
||||
fmt.Println(" --db FILENAME Set storage database")
|
||||
os.Exit(0)
|
||||
} else if arg == "-w" || arg == "--www" {
|
||||
i++
|
||||
|
@ -84,6 +97,9 @@ func parseProgramArguments() error {
|
|||
} else if arg == "--clientid" {
|
||||
i++
|
||||
config.mqttClientId = args[i]
|
||||
} else if arg == "--db" {
|
||||
i++
|
||||
config.db = args[i]
|
||||
} else {
|
||||
return fmt.Errorf("invalid argument: %s", arg)
|
||||
}
|
||||
|
@ -92,19 +108,30 @@ func parseProgramArguments() error {
|
|||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("ot-browser")
|
||||
var err error
|
||||
devices = make(map[string]Location, 0)
|
||||
config.SetDefaults()
|
||||
parseProgramArguments()
|
||||
|
||||
// Setup database
|
||||
if config.db != "" {
|
||||
db, err = CreateDatabase(config.db)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "database error: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Connect mqtt
|
||||
if config.mqttRemote == "" {
|
||||
fmt.Fprintf(os.Stderr, "No mqtt remote set\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
mqtt.Received = mqttRecv
|
||||
if err := mqtt.Connect(config.mqttRemote, config.mqttTopic, "", "", config.mqttClientId); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "mqtt error: %s\n", err)
|
||||
if err = mqtt.Connect(config.mqttRemote, config.mqttTopic, "", "", config.mqttClientId); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
fmt.Fprintf(os.Stderr, "error: mqtt connection failed\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
@ -113,9 +140,11 @@ func main() {
|
|||
http.Handle("/", http.StripPrefix("/", fs))
|
||||
http.HandleFunc("/devices", handlerDevices)
|
||||
http.HandleFunc("/devices.json", handlerDevices)
|
||||
http.HandleFunc("/health.json", handlerHealth)
|
||||
http.HandleFunc("/health", handlerHealth)
|
||||
http.HandleFunc("/locations", handlerLocations)
|
||||
http.HandleFunc("/devices/", handlerDeviceQuery)
|
||||
fmt.Println("Serving: http://" + config.bindAddr)
|
||||
fmt.Println("ot-browser serving: http://" + config.bindAddr)
|
||||
log.Fatal(http.ListenAndServe(config.bindAddr, nil))
|
||||
}
|
||||
|
||||
|
@ -133,6 +162,7 @@ func (c *Config) ReadFile(filename string) error {
|
|||
if val := mqtt.Key("clientid").String(); val != "" {
|
||||
c.mqttClientId = val
|
||||
}
|
||||
|
||||
www := cfg.Section("www")
|
||||
if val := www.Key("remote").String(); val != "" {
|
||||
c.wwwDir = val
|
||||
|
@ -140,9 +170,20 @@ func (c *Config) ReadFile(filename string) error {
|
|||
if val := www.Key("bind").String(); val != "" {
|
||||
c.bindAddr = val
|
||||
}
|
||||
|
||||
storage := cfg.Section("storage")
|
||||
if val := storage.Key("database").String(); val != "" {
|
||||
c.db = val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handlerHealth - health endpoint - writes a health status message to the client
|
||||
func handlerHealth(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"status":"ok"}`))
|
||||
}
|
||||
|
||||
func handlerDevices(w http.ResponseWriter, r *http.Request) {
|
||||
devs := make([]string, 0)
|
||||
for dev := range devices {
|
||||
|
|
|
@ -32,7 +32,6 @@ func (mqtt *MQTTReceiver) Connect(remote string, topic string, username string,
|
|||
}
|
||||
|
||||
// Add default port to remote if not existing
|
||||
// TODO: IPv6 handling is not yet implemented
|
||||
if !strings.Contains(remote, ":") {
|
||||
remote += ":1883"
|
||||
}
|
||||
|
@ -55,9 +54,8 @@ func (mqtt *MQTTReceiver) Connect(remote string, topic string, username string,
|
|||
opts.SetAutoReconnect(true)
|
||||
c := MQTT.NewClient(opts)
|
||||
mqtt.mqtt = c
|
||||
// TODO: Add listener also if initial connection fails (and attempt reconnects)
|
||||
if token := c.Connect(); token.Wait() && token.Error() != nil {
|
||||
log.Println(fmt.Sprintf("Error connecting to MQTT %s - %s", remote, token.Error()))
|
||||
return token.Error()
|
||||
} else {
|
||||
if token := c.Subscribe(topic, 0, mqtt.mqttReceive); token.Wait() && token.Error() != nil {
|
||||
log.Println(fmt.Sprintf("Error subscribing listener %s - %s", remote, token.Error()))
|
||||
|
@ -97,9 +95,8 @@ func (mqtt *MQTTReceiver) mqttReceive(client MQTT.Client, msg MQTT.Message) {
|
|||
if mqtt.Received != nil {
|
||||
mqtt.Received(identifier, loc)
|
||||
}
|
||||
} else {
|
||||
// Invalid topic. Ignore for now.
|
||||
}
|
||||
// Ignore invalid topic.
|
||||
}
|
||||
|
||||
func (mqtt *MQTTReceiver) mqttConnectionLost(client MQTT.Client, err error) {
|
||||
|
@ -108,7 +105,7 @@ func (mqtt *MQTTReceiver) mqttConnectionLost(client MQTT.Client, err error) {
|
|||
remotes := options.Servers()
|
||||
remote := ""
|
||||
if len(remotes) > 0 {
|
||||
// TODO:: What to do if there are more?
|
||||
// TODO: What to do if there are more?
|
||||
remote = remotes[0].String()
|
||||
}
|
||||
for {
|
||||
|
|
83
cmd/ot-browser/storage.go
Normal file
83
cmd/ot-browser/storage.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (stor *Storage) init() error {
|
||||
// initialization
|
||||
sql_table := `
|
||||
CREATE TABLE IF NOT EXISTS geopoints (
|
||||
device VARCHAR(256), timestamp INTEGER, lon REAL, lat REAL, alt REAL, acc REAL, velocity REAL, bearing REAL, PRIMARY KEY(device, timestamp)
|
||||
);
|
||||
`
|
||||
tx, err := stor.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(sql_table); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (stor *Storage) IsReady() bool {
|
||||
return stor.db != nil
|
||||
}
|
||||
|
||||
func CreateDatabase(filename string) (Storage, error) {
|
||||
var stor Storage
|
||||
var err error
|
||||
stor.db, err = sql.Open("sqlite3", filename)
|
||||
if err != nil {
|
||||
return stor, err
|
||||
}
|
||||
|
||||
return stor, stor.init()
|
||||
}
|
||||
|
||||
func (stor *Storage) InsertLocation(device string, loc Location) error {
|
||||
tx, err := stor.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sql := "INSERT INTO geopoints (device, timestamp, lon, lat, alt, acc, velocity, bearing) VALUES (?,?,?,?,?,?,?,?)"
|
||||
stmt, err := tx.Prepare(sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = stmt.Exec(device, loc.Timestamp, loc.Lon, loc.Lat, loc.Alt, loc.Acc, loc.Velocity, loc.Bearing)
|
||||
if err != nil {
|
||||
stmt.Close()
|
||||
return err
|
||||
}
|
||||
if err := stmt.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
func (stor *Storage) GetLastLocation(device string) (Location, error) {
|
||||
var loc Location
|
||||
sql := "SELECT timestamp, lon, lat, alt, acc, velocity, bearing FROM geopoints WHERE device LIKE ? ORDER BY timestamp DESC LIMIT 1"
|
||||
stmt, err := stor.db.Prepare(sql)
|
||||
if err != nil {
|
||||
return loc, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
rows, err := stmt.Query(device)
|
||||
if err != nil {
|
||||
return loc, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
err := rows.Scan(&loc.Timestamp, &loc.Lon, &loc.Lat, &loc.Alt, &loc.Acc, &loc.Velocity, &loc.Bearing)
|
||||
return loc, err
|
||||
}
|
||||
return loc, nil
|
||||
}
|
63
cmd/ot-browser/storage_test.go
Normal file
63
cmd/ot-browser/storage_test.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DATABASE = ":memory:"
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if _, err := os.Stat(DATABASE); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "error: test database exists already\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
// Run tests
|
||||
ret := m.Run()
|
||||
if DATABASE != ":memory:" {
|
||||
os.Remove(DATABASE) // Ignore errors
|
||||
}
|
||||
os.Exit(ret)
|
||||
}
|
||||
|
||||
func TestLocation(t *testing.T) {
|
||||
var ref Location
|
||||
|
||||
stor, err := CreateDatabase(DATABASE)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
device1 := "test1"
|
||||
t0 := time.Now().Unix()
|
||||
for i := 0; i < 360; i++ {
|
||||
ref.Timestamp = t0 + int64(i) // Never let it be zerom otherwise the check below will fail!
|
||||
ref.Lon = float32(i - 180)
|
||||
ref.Lat = float32((-90 + i) % 180)
|
||||
ref.Alt = float32(-100 + i*2)
|
||||
ref.Acc = float32(i % 2 * 10)
|
||||
ref.Velocity = float32(i / 1000.0)
|
||||
ref.Bearing = 0
|
||||
|
||||
if err := stor.InsertLocation(device1, ref); err != nil {
|
||||
t.Errorf("InsertLocation failed: %s", err)
|
||||
return
|
||||
}
|
||||
loc, err := stor.GetLastLocation(device1)
|
||||
if err != nil {
|
||||
t.Errorf("GetLastLocation failed: %s", err)
|
||||
return
|
||||
}
|
||||
if loc.Timestamp == 0 {
|
||||
t.Error("GetLastLocation returns an empty result", err)
|
||||
return
|
||||
}
|
||||
if loc != ref {
|
||||
t.Errorf("Fetched location doesn't match reference")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
5
go.mod
5
go.mod
|
@ -3,7 +3,8 @@ module feldspaten.org/ot-browser/v2
|
|||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/eclipse/paho.mqtt.golang v1.3.4
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.3
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
)
|
||||
|
|
41
go.sum
41
go.sum
|
@ -1,22 +1,63 @@
|
|||
github.com/eclipse/paho.mqtt.golang v1.3.4 h1:/sS2PA+PgomTO1bfJSDJncox+U7X5Boa3AfhEywYdgI=
|
||||
github.com/eclipse/paho.mqtt.golang v1.3.4/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
|
|
@ -7,3 +7,6 @@ clientid = ot-browser
|
|||
[www]
|
||||
dir = ./www
|
||||
bind = 127.0.0.1:8090
|
||||
|
||||
[storage]
|
||||
database = otb.db
|
Loading…
Reference in a new issue