Merge remote-tracking branch 'origin/main' into alertmanager-interface

This commit is contained in:
cubicroot 2022-04-18 12:53:09 +02:00
commit d1c62e24ed
26 changed files with 301 additions and 107 deletions

View file

@ -5,17 +5,10 @@ on:
tags: tags:
- 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs: jobs:
test_build_publish: test:
name: Test, build, and publish name: Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -36,6 +29,22 @@ jobs:
source $(poetry env info --path)/bin/activate source $(poetry env info --path)/bin/activate
make test make test
publish_docker_image:
name: Publish Docker image
needs: test
runs-on: ubuntu-latest
permissions:
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@v1 uses: docker/login-action@v1
with: with:
@ -49,12 +58,41 @@ jobs:
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: | tags: |
type=raw,value=latest type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: .
push: true push: true
build-args: PB_BUILD_VERSION=${{ github.ref_name }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
publish_github_release:
name: Publish GitHub Release
needs: test
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Export GOBIN
uses: actions/setup-go@v2
with:
go-version: 1.16
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
distribution: goreleaser
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,4 +1,4 @@
name: Main name: Test
on: on:
push: push:
@ -32,3 +32,6 @@ jobs:
run: | run: |
source $(poetry env info --path)/bin/activate source $(poetry env info --path)/bin/activate
make test make test
- name: Build
run: make build

19
.goreleaser.yml Normal file
View file

@ -0,0 +1,19 @@
builds:
- id: pushbits
main: ./cmd/pushbits
goos:
- linux
goarch:
- amd64
- arm64
ldflags:
- -s -w -X main.version=v{{.Version}}
checksum:
algorithm: sha256
archives:
- id: pushbits
builds:
- pushbits
format: tar.gz

16
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/pushbits",
"cwd": "${workspaceFolder}"
}
]
}

View file

@ -1,5 +1,7 @@
FROM docker.io/library/golang:alpine as builder FROM docker.io/library/golang:alpine as builder
ARG PB_BUILD_VERSION
WORKDIR /build WORKDIR /build
COPY . . COPY . .
@ -8,7 +10,7 @@ RUN set -ex \
&& apk add --no-cache build-base \ && apk add --no-cache build-base \
&& go mod download \ && go mod download \
&& go mod verify \ && go mod verify \
&& make build \ && PB_BUILD_VERSION="$PB_BUILD_VERSION" make build \
&& chmod +x /build/out/pushbits && chmod +x /build/out/pushbits
FROM docker.io/library/alpine FROM docker.io/library/alpine

View file

@ -5,12 +5,17 @@ DOCS_DIR := ./docs
OUT_DIR := ./out OUT_DIR := ./out
TESTS_DIR := ./tests TESTS_DIR := ./tests
PB_BUILD_VERSION ?= $(shell git describe --tags)
ifeq ($(PB_BUILD_VERSION),)
_ := $(error Cannot determine build version)
endif
SEMGREP_MODFILE := $(TESTS_DIR)/semgrep-rules/go.mod SEMGREP_MODFILE := $(TESTS_DIR)/semgrep-rules/go.mod
.PHONY: build .PHONY: build
build: build:
mkdir -p $(OUT_DIR) mkdir -p $(OUT_DIR)
go build -ldflags="-w -s" -o $(OUT_DIR)/pushbits ./cmd/pushbits go build -ldflags="-w -s -X main.version=$(PB_BUILD_VERSION)" -o $(OUT_DIR)/pushbits ./cmd/pushbits
.PHONY: clean .PHONY: clean
clean: clean:
@ -37,7 +42,7 @@ setup:
go install github.com/fzipp/gocyclo/cmd/gocyclo@latest go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
go install github.com/securego/gosec/v2/cmd/gosec@latest go install github.com/securego/gosec/v2/cmd/gosec@latest
go install github.com/swaggo/swag/cmd/swag@latest go install github.com/swaggo/swag/cmd/swag@latest
go install honnef.co/go/tools/cmd/staticcheck@latest go install honnef.co/go/tools/cmd/staticcheck@v0.2.2
poetry install poetry install
.PHONY: swag .PHONY: swag

View file

@ -72,7 +72,7 @@ This project totally would've used Signal if it would offer a proper API.
Sadly, neither [Signal](https://signal.org/) nor [WhatsApp](https://www.whatsapp.com/) come with an API (at the time of writing) through which PushBits could interact. Sadly, neither [Signal](https://signal.org/) nor [WhatsApp](https://www.whatsapp.com/) come with an API (at the time of writing) through which PushBits could interact.
In [Telegram](https://telegram.org/) there is an API to run bots, but these are limited in that they cannot create chats by themselves. In [Telegram](https://telegram.org/) there is an API to run bots, but these are limited in that they cannot create chats by themselves.
If you insist on going with Telegram, have a look at [webhook2telegram](https://github.com/muety/webhook2telegram). If you insist on going with Telegram, have a look at [telepush](https://github.com/muety/telepush).
The idea of a federated, synchronized but yet end-to-end encrypted protocol is awesome, but its clients simply aren't really there yet. The idea of a federated, synchronized but yet end-to-end encrypted protocol is awesome, but its clients simply aren't really there yet.
Still, if you haven't tried it yet, we'd encourage you to check it out. Still, if you haven't tried it yet, we'd encourage you to check it out.

View file

@ -1,7 +1,6 @@
package main package main
import ( import (
"log"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
@ -10,10 +9,13 @@ import (
"github.com/pushbits/server/internal/configuration" "github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/database" "github.com/pushbits/server/internal/database"
"github.com/pushbits/server/internal/dispatcher" "github.com/pushbits/server/internal/dispatcher"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/router" "github.com/pushbits/server/internal/router"
"github.com/pushbits/server/internal/runner" "github.com/pushbits/server/internal/runner"
) )
var version string
func setupCleanup(db *database.Database, dp *dispatcher.Dispatcher) { func setupCleanup(db *database.Database, dp *dispatcher.Dispatcher) {
c := make(chan os.Signal) c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
@ -30,41 +32,46 @@ func setupCleanup(db *database.Database, dp *dispatcher.Dispatcher) {
// @version 0.7.2 // @version 0.7.2
// @description Documentation for the PushBits server API. // @description Documentation for the PushBits server API.
// @contact.name PushBits // @contact.name The PushBits Developers
// @contact.url https://www.pushbits.io // @contact.url https://www.pushbits.io
// @license.name ISC // @license.name ISC
// @license.url https://github.com/pushbits/server/blob/master/LICENSE // @license.url https://github.com/pushbits/server/blob/master/LICENSE
// @host your-domain.net
// @BasePath / // @BasePath /
// @query.collection.format multi // @query.collection.format multi
// @schemes http https
// @securityDefinitions.basic BasicAuth // @securityDefinitions.basic BasicAuth
func main() { func main() {
log.Println("Starting PushBits.") if len(version) == 0 {
log.L.Panic("Version not set")
} else {
log.L.Printf("Starting PushBits %s", version)
}
c := configuration.Get() c := configuration.Get()
if c.Debug { if c.Debug {
log.Printf("%+v", c) log.SetDebug()
log.L.Printf("%+v", c)
} }
cm := credentials.CreateManager(c.Security.CheckHIBP, c.Crypto) cm := credentials.CreateManager(c.Security.CheckHIBP, c.Crypto)
db, err := database.Create(cm, c.Database.Dialect, c.Database.Connection) db, err := database.Create(cm, c.Database.Dialect, c.Database.Connection)
if err != nil { if err != nil {
log.Fatal(err) log.L.Fatal(err)
} }
defer db.Close() defer db.Close()
if err := db.Populate(c.Admin.Name, c.Admin.Password, c.Admin.MatrixID); err != nil { if err := db.Populate(c.Admin.Name, c.Admin.Password, c.Admin.MatrixID); err != nil {
log.Fatal(err) log.L.Fatal(err)
} }
dp, err := dispatcher.Create(c.Matrix.Homeserver, c.Matrix.Username, c.Matrix.Password, c.Formatting) dp, err := dispatcher.Create(c.Matrix.Homeserver, c.Matrix.Username, c.Matrix.Password, c.Formatting)
if err != nil { if err != nil {
log.Fatal(err) log.L.Fatal(err)
} }
defer dp.Close() defer dp.Close()
@ -72,13 +79,13 @@ func main() {
err = db.RepairChannels(dp) err = db.RepairChannels(dp)
if err != nil { if err != nil {
log.Fatal(err) log.L.Fatal(err)
} }
engine := router.Create(c.Debug, cm, db, dp, &c.Alertmanager) engine := router.Create(c.Debug, cm, db, dp, &c.Alertmanager)
err = runner.Run(engine, c.HTTP.ListenAddress, c.HTTP.Port) err = runner.Run(engine, c.HTTP.ListenAddress, c.HTTP.Port)
if err != nil { if err != nil {
log.Fatal(err) log.L.Fatal(err)
} }
} }

1
go.mod
View file

@ -14,6 +14,7 @@ require (
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/ugorji/go v1.2.4 // indirect github.com/ugorji/go v1.2.4 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect

3
go.sum
View file

@ -95,7 +95,10 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

View file

@ -2,10 +2,10 @@ package api
import ( import (
"errors" "errors"
"log"
"net/http" "net/http"
"github.com/pushbits/server/internal/authentication" "github.com/pushbits/server/internal/authentication"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model" "github.com/pushbits/server/internal/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -27,7 +27,7 @@ func (h *ApplicationHandler) generateToken(compat bool) string {
} }
func (h *ApplicationHandler) registerApplication(ctx *gin.Context, a *model.Application, u *model.User) error { func (h *ApplicationHandler) registerApplication(ctx *gin.Context, a *model.Application, u *model.User) error {
log.Printf("Registering application %s.", a.Name) log.L.Printf("Registering application %s.", a.Name)
channelID, err := h.DP.RegisterApplication(a.ID, a.Name, a.Token, u.MatrixID) channelID, err := h.DP.RegisterApplication(a.ID, a.Name, a.Token, u.MatrixID)
if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success {
@ -45,7 +45,7 @@ func (h *ApplicationHandler) registerApplication(ctx *gin.Context, a *model.Appl
} }
func (h *ApplicationHandler) createApplication(ctx *gin.Context, u *model.User, name string, compat bool) (*model.Application, error) { func (h *ApplicationHandler) createApplication(ctx *gin.Context, u *model.User, name string, compat bool) (*model.Application, error) {
log.Printf("Creating application %s.", name) log.L.Printf("Creating application %s.", name)
application := model.Application{} application := model.Application{}
application.Name = name application.Name = name
@ -60,7 +60,7 @@ func (h *ApplicationHandler) createApplication(ctx *gin.Context, u *model.User,
if err := h.registerApplication(ctx, &application, u); err != nil { if err := h.registerApplication(ctx, &application, u); err != nil {
err := h.DB.DeleteApplication(&application) err := h.DB.DeleteApplication(&application)
if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success {
log.Printf("Cannot delete application with ID %d.", application.ID) log.L.Printf("Cannot delete application with ID %d.", application.ID)
} }
return nil, err return nil, err
@ -70,7 +70,7 @@ func (h *ApplicationHandler) createApplication(ctx *gin.Context, u *model.User,
} }
func (h *ApplicationHandler) deleteApplication(ctx *gin.Context, a *model.Application, u *model.User) error { func (h *ApplicationHandler) deleteApplication(ctx *gin.Context, a *model.Application, u *model.User) error {
log.Printf("Deleting application %s (ID %d).", a.Name, a.ID) log.L.Printf("Deleting application %s (ID %d).", a.Name, a.ID)
err := h.DP.DeregisterApplication(a, u) err := h.DP.DeregisterApplication(a, u)
if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success {
@ -86,15 +86,15 @@ func (h *ApplicationHandler) deleteApplication(ctx *gin.Context, a *model.Applic
} }
func (h *ApplicationHandler) updateApplication(ctx *gin.Context, a *model.Application, updateApplication *model.UpdateApplication) error { func (h *ApplicationHandler) updateApplication(ctx *gin.Context, a *model.Application, updateApplication *model.UpdateApplication) error {
log.Printf("Updating application %s (ID %d).", a.Name, a.ID) log.L.Printf("Updating application %s (ID %d).", a.Name, a.ID)
if updateApplication.Name != nil { if updateApplication.Name != nil {
log.Printf("Updating application name to '%s'.", *updateApplication.Name) log.L.Printf("Updating application name to '%s'.", *updateApplication.Name)
a.Name = *updateApplication.Name a.Name = *updateApplication.Name
} }
if updateApplication.RefreshToken != nil && (*updateApplication.RefreshToken) { if updateApplication.RefreshToken != nil && (*updateApplication.RefreshToken) {
log.Print("Updating application token.") log.L.Print("Updating application token.")
compat := updateApplication.StrictCompatibility != nil && (*updateApplication.StrictCompatibility) compat := updateApplication.StrictCompatibility != nil && (*updateApplication.StrictCompatibility)
a.Token = h.generateToken(compat) a.Token = h.generateToken(compat)
} }
@ -129,7 +129,7 @@ func (h *ApplicationHandler) CreateApplication(ctx *gin.Context) {
var createApplication model.CreateApplication var createApplication model.CreateApplication
if err := ctx.Bind(&createApplication); err != nil { if err := ctx.Bind(&createApplication); err != nil {
log.Println(err) log.L.Println(err)
return return
} }

View file

@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"testing" "testing"
@ -12,6 +11,7 @@ import (
"github.com/pushbits/server/internal/authentication/credentials" "github.com/pushbits/server/internal/authentication/credentials"
"github.com/pushbits/server/internal/configuration" "github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/database" "github.com/pushbits/server/internal/database"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model" "github.com/pushbits/server/internal/model"
"github.com/pushbits/server/tests" "github.com/pushbits/server/tests"
"github.com/pushbits/server/tests/mockups" "github.com/pushbits/server/tests/mockups"
@ -50,7 +50,7 @@ func TestMain(m *testing.M) {
db, err := mockups.GetEmptyDatabase(config.Crypto) db, err := mockups.GetEmptyDatabase(config.Crypto)
if err != nil { if err != nil {
cleanUp() cleanUp()
log.Println("Can not set up database: ", err) log.L.Println("Can not set up database: ", err)
os.Exit(1) os.Exit(1)
} }
TestDatabase = db TestDatabase = db
@ -58,7 +58,7 @@ func TestMain(m *testing.M) {
appHandler, err := getApplicationHandler(config) appHandler, err := getApplicationHandler(config)
if err != nil { if err != nil {
cleanUp() cleanUp()
log.Println("Can not set up application handler: ", err) log.L.Println("Can not set up application handler: ", err)
os.Exit(1) os.Exit(1)
} }
@ -80,7 +80,7 @@ func TestMain(m *testing.M) {
// Run // Run
m.Run() m.Run()
log.Println("Clean up after Test") log.L.Println("Clean up after Test")
cleanUp() cleanUp()
} }

View file

@ -1,12 +1,12 @@
package api package api
import ( import (
"log"
"net/http" "net/http"
"net/url" "net/url"
"time" "time"
"github.com/pushbits/server/internal/authentication" "github.com/pushbits/server/internal/authentication"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model" "github.com/pushbits/server/internal/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -45,7 +45,7 @@ type NotificationHandler struct {
// @Router /message [post] // @Router /message [post]
func (h *NotificationHandler) CreateNotification(ctx *gin.Context) { func (h *NotificationHandler) CreateNotification(ctx *gin.Context) {
application := authentication.GetApplication(ctx) application := authentication.GetApplication(ctx)
log.Printf("Sending notification for application %s.", application.Name) log.L.Printf("Sending notification for application %s.", application.Name)
var notification model.Notification var notification model.Notification
if err := ctx.Bind(&notification); err != nil { if err := ctx.Bind(&notification); err != nil {
@ -79,7 +79,7 @@ func (h *NotificationHandler) CreateNotification(ctx *gin.Context) {
// @Router /message/{message_id} [DELETE] // @Router /message/{message_id} [DELETE]
func (h *NotificationHandler) DeleteNotification(ctx *gin.Context) { func (h *NotificationHandler) DeleteNotification(ctx *gin.Context) {
application := authentication.GetApplication(ctx) application := authentication.GetApplication(ctx)
log.Printf("Deleting notification for application %s.", application.Name) log.L.Printf("Deleting notification for application %s.", application.Name)
id, err := getMessageID(ctx) id, err := getMessageID(ctx)
if success := successOrAbort(ctx, http.StatusUnprocessableEntity, err); !success { if success := successOrAbort(ctx, http.StatusUnprocessableEntity, err); !success {

View file

@ -2,10 +2,10 @@ package api
import ( import (
"errors" "errors"
"log"
"net/http" "net/http"
"github.com/pushbits/server/internal/authentication" "github.com/pushbits/server/internal/authentication"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model" "github.com/pushbits/server/internal/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -90,7 +90,7 @@ func (h *UserHandler) updateUser(ctx *gin.Context, u *model.User, updateUser mod
} }
} }
log.Printf("Updating user %s.", u.Name) log.L.Printf("Updating user %s.", u.Name)
if updateUser.Name != nil { if updateUser.Name != nil {
u.Name = *updateUser.Name u.Name = *updateUser.Name
@ -146,7 +146,7 @@ func (h *UserHandler) CreateUser(ctx *gin.Context) {
return return
} }
log.Printf("Creating user %s.", createUser.Name) log.L.Printf("Creating user %s.", createUser.Name)
user, err := h.DB.CreateUser(createUser) user, err := h.DB.CreateUser(createUser)
@ -232,7 +232,7 @@ func (h *UserHandler) DeleteUser(ctx *gin.Context) {
} }
} }
log.Printf("Deleting user %s.", user.Name) log.L.Printf("Deleting user %s.", user.Name)
if err := h.deleteApplications(ctx, user); err != nil { if err := h.deleteApplications(ctx, user); err != nil {
return return

View file

@ -1,9 +1,8 @@
package credentials package credentials
import ( import (
"log"
"github.com/pushbits/server/internal/configuration" "github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/log"
"github.com/alexedwards/argon2id" "github.com/alexedwards/argon2id"
) )
@ -16,7 +15,7 @@ type Manager struct {
// CreateManager instanciates a credential manager. // CreateManager instanciates a credential manager.
func CreateManager(checkHIBP bool, c configuration.CryptoConfig) *Manager { func CreateManager(checkHIBP bool, c configuration.CryptoConfig) *Manager {
log.Println("Setting up credential manager.") log.L.Println("Setting up credential manager.")
argon2Params := &argon2id.Params{ argon2Params := &argon2id.Params{
Memory: c.Argon2.Memory, Memory: c.Argon2.Memory,

View file

@ -4,9 +4,10 @@ import (
"crypto/sha1" //#nosec G505 -- False positive, see the use below. "crypto/sha1" //#nosec G505 -- False positive, see the use below.
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"strings" "strings"
"github.com/pushbits/server/internal/log"
) )
const ( const (
@ -27,7 +28,7 @@ func IsPasswordPwned(password string) (bool, error) {
lookup := hashStr[0:5] lookup := hashStr[0:5]
match := hashStr[5:] match := hashStr[5:]
log.Printf("Checking HIBP for hashes starting with '%s'.", lookup) log.L.Printf("Checking HIBP for hashes starting with '%s'.", lookup)
resp, err := http.Get(pwnedHashesURL + lookup) resp, err := http.Get(pwnedHashesURL + lookup)
if err != nil { if err != nil {
@ -35,13 +36,13 @@ func IsPasswordPwned(password string) (bool, error) {
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
log.Fatalf("Request failed with HTTP %s.", resp.Status) log.L.Fatalf("Request failed with HTTP %s.", resp.Status)
} }
defer resp.Body.Close() defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body) bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
log.Fatal(err) log.L.Fatal(err)
} }
bodyStr := string(bodyText) bodyStr := string(bodyText)

View file

@ -2,9 +2,10 @@ package credentials
import ( import (
"errors" "errors"
"log"
"github.com/alexedwards/argon2id" "github.com/alexedwards/argon2id"
"github.com/pushbits/server/internal/log"
) )
// CreatePasswordHash returns a hashed version of the given password. // CreatePasswordHash returns a hashed version of the given password.
@ -21,7 +22,7 @@ func (m *Manager) CreatePasswordHash(password string) ([]byte, error) {
hash, err := argon2id.CreateHash(password, m.argon2Params) hash, err := argon2id.CreateHash(password, m.argon2Params)
if err != nil { if err != nil {
log.Fatal(err) log.L.Fatal(err)
panic(err) panic(err)
} }
@ -33,7 +34,7 @@ func ComparePassword(hash, password []byte) bool {
match, err := argon2id.ComparePasswordAndHash(string(password), string(hash)) match, err := argon2id.ComparePasswordAndHash(string(password), string(hash))
if err != nil { if err != nil {
log.Fatal(err) log.L.Fatal(err)
return false return false
} }

View file

@ -3,12 +3,12 @@ package database
import ( import (
"database/sql" "database/sql"
"errors" "errors"
"log"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"github.com/pushbits/server/internal/authentication/credentials" "github.com/pushbits/server/internal/authentication/credentials"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model" "github.com/pushbits/server/internal/model"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
@ -36,7 +36,7 @@ func createFileDir(file string) {
// Create instanciates a database connection. // Create instanciates a database connection.
func Create(cm *credentials.Manager, dialect, connection string) (*Database, error) { func Create(cm *credentials.Manager, dialect, connection string) (*Database, error) {
log.Println("Setting up database connection.") log.L.Println("Setting up database connection.")
maxOpenConns := 5 maxOpenConns := 5
@ -82,13 +82,13 @@ func Create(cm *credentials.Manager, dialect, connection string) (*Database, err
func (d *Database) Close() { func (d *Database) Close() {
err := d.sqldb.Close() err := d.sqldb.Close()
if err != nil { if err != nil {
log.Printf("Error while closing database: %s", err) log.L.Printf("Error while closing database: %s", err)
} }
} }
// Populate fills the database with initial information like the admin user. // Populate fills the database with initial information like the admin user.
func (d *Database) Populate(name, password, matrixID string) error { func (d *Database) Populate(name, password, matrixID string) error {
log.Print("Populating database.") log.L.Print("Populating database.")
var user model.User var user model.User
@ -104,7 +104,7 @@ func (d *Database) Populate(name, password, matrixID string) error {
return errors.New("user cannot be created") return errors.New("user cannot be created")
} }
} else { } else {
log.Printf("Priviledged user %s already exists.", name) log.L.Printf("Priviledged user %s already exists.", name)
} }
return nil return nil
@ -112,7 +112,7 @@ func (d *Database) Populate(name, password, matrixID string) error {
// RepairChannels resets channels that have been modified by a user. // RepairChannels resets channels that have been modified by a user.
func (d *Database) RepairChannels(dp Dispatcher) error { func (d *Database) RepairChannels(dp Dispatcher) error {
log.Print("Repairing application channels.") log.L.Print("Repairing application channels.")
users, err := d.GetUsers() users, err := d.GetUsers()
if err != nil { if err != nil {
@ -140,11 +140,11 @@ func (d *Database) RepairChannels(dp Dispatcher) error {
} }
if orphan { if orphan {
log.Printf("Found orphan channel for application %s (ID %d)", application.Name, application.ID) log.L.Printf("Found orphan channel for application %s (ID %d)", application.Name, application.ID)
if err = dp.RepairApplication(&application, &user); err != nil { if err = dp.RepairApplication(&application, &user); err != nil {
log.Printf("Unable to repair application %s (ID %d).", application.Name, application.ID) log.L.Printf("Unable to repair application %s (ID %d).", application.Name, application.ID)
log.Println(err) log.L.Println(err)
} }
} }
} }

View file

@ -2,11 +2,11 @@ package dispatcher
import ( import (
"fmt" "fmt"
"log"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model" "github.com/pushbits/server/internal/model"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
mId "maunium.net/go/mautrix/id" mId "maunium.net/go/mautrix/id"
) )
@ -17,7 +17,7 @@ func buildRoomTopic(id uint) string {
// RegisterApplication creates a channel for an application. // RegisterApplication creates a channel for an application.
func (d *Dispatcher) RegisterApplication(id uint, name, token, user string) (string, error) { func (d *Dispatcher) RegisterApplication(id uint, name, token, user string) (string, error) {
log.Printf("Registering application %s, notifications will be relayed to user %s.\n", name, user) log.L.Printf("Registering application %s, notifications will be relayed to user %s.\n", name, user)
resp, err := d.mautrixClient.CreateRoom(&mautrix.ReqCreateRoom{ resp, err := d.mautrixClient.CreateRoom(&mautrix.ReqCreateRoom{
Visibility: "private", Visibility: "private",
@ -28,18 +28,18 @@ func (d *Dispatcher) RegisterApplication(id uint, name, token, user string) (str
Topic: buildRoomTopic(id), Topic: buildRoomTopic(id),
}) })
if err != nil { if err != nil {
log.Print(err) log.L.Print(err)
return "", err return "", err
} }
log.Printf("Application %s is now relayed to room with ID %s.\n", name, resp.RoomID.String()) log.L.Printf("Application %s is now relayed to room with ID %s.\n", name, resp.RoomID.String())
return resp.RoomID.String(), err return resp.RoomID.String(), err
} }
// DeregisterApplication deletes a channel for an application. // DeregisterApplication deletes a channel for an application.
func (d *Dispatcher) DeregisterApplication(a *model.Application, u *model.User) error { func (d *Dispatcher) DeregisterApplication(a *model.Application, u *model.User) error {
log.Printf("Deregistering application %s (ID %d) with Matrix ID %s.\n", a.Name, a.ID, a.MatrixID) log.L.Printf("Deregistering application %s (ID %d) with Matrix ID %s.\n", a.Name, a.ID, a.MatrixID)
// The user might have left the channel, but we can still try to remove them. // The user might have left the channel, but we can still try to remove them.
@ -47,17 +47,17 @@ func (d *Dispatcher) DeregisterApplication(a *model.Application, u *model.User)
Reason: "This application was deleted", Reason: "This application was deleted",
UserID: mId.UserID(u.MatrixID), UserID: mId.UserID(u.MatrixID),
}); err != nil { }); err != nil {
log.Print(err) log.L.Print(err)
return err return err
} }
if _, err := d.mautrixClient.LeaveRoom(mId.RoomID(a.MatrixID)); err != nil { if _, err := d.mautrixClient.LeaveRoom(mId.RoomID(a.MatrixID)); err != nil {
log.Print(err) log.L.Print(err)
return err return err
} }
if _, err := d.mautrixClient.ForgetRoom(mId.RoomID(a.MatrixID)); err != nil { if _, err := d.mautrixClient.ForgetRoom(mId.RoomID(a.MatrixID)); err != nil {
log.Print(err) log.L.Print(err)
return err return err
} }
@ -66,7 +66,7 @@ func (d *Dispatcher) DeregisterApplication(a *model.Application, u *model.User)
func (d *Dispatcher) sendRoomEvent(roomID, eventType string, content interface{}) error { func (d *Dispatcher) sendRoomEvent(roomID, eventType string, content interface{}) error {
if _, err := d.mautrixClient.SendStateEvent(mId.RoomID(roomID), event.NewEventType(eventType), "", content); err != nil { if _, err := d.mautrixClient.SendStateEvent(mId.RoomID(roomID), event.NewEventType(eventType), "", content); err != nil {
log.Print(err) log.L.Print(err)
return err return err
} }
@ -75,7 +75,7 @@ func (d *Dispatcher) sendRoomEvent(roomID, eventType string, content interface{}
// UpdateApplication updates a channel for an application. // UpdateApplication updates a channel for an application.
func (d *Dispatcher) UpdateApplication(a *model.Application) error { func (d *Dispatcher) UpdateApplication(a *model.Application) error {
log.Printf("Updating application %s (ID %d) with Matrix ID %s.\n", a.Name, a.ID, a.MatrixID) log.L.Printf("Updating application %s (ID %d) with Matrix ID %s.\n", a.Name, a.ID, a.MatrixID)
content := map[string]interface{}{ content := map[string]interface{}{
"name": a.Name, "name": a.Name,

View file

@ -1,11 +1,11 @@
package dispatcher package dispatcher
import ( import (
"log"
"github.com/pushbits/server/internal/configuration"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/log"
) )
// Dispatcher holds information for sending notifications to clients. // Dispatcher holds information for sending notifications to clients.
@ -16,7 +16,7 @@ type Dispatcher struct {
// Create instanciates a dispatcher connection. // Create instanciates a dispatcher connection.
func Create(homeserver, username, password string, formatting configuration.Formatting) (*Dispatcher, error) { func Create(homeserver, username, password string, formatting configuration.Formatting) (*Dispatcher, error) {
log.Println("Setting up dispatcher.") log.L.Println("Setting up dispatcher.")
matrixClient, err := mautrix.NewClient(homeserver, "", "") matrixClient, err := mautrix.NewClient(homeserver, "", "")
if err != nil { if err != nil {
@ -39,14 +39,14 @@ func Create(homeserver, username, password string, formatting configuration.Form
// Close closes the dispatcher connection. // Close closes the dispatcher connection.
func (d *Dispatcher) Close() { func (d *Dispatcher) Close() {
log.Printf("Logging out.") log.L.Printf("Logging out.")
_, err := d.mautrixClient.Logout() _, err := d.mautrixClient.Logout()
if err != nil { if err != nil {
log.Printf("Error while logging out: %s", err) log.L.Printf("Error while logging out: %s", err)
} }
d.mautrixClient.ClearCredentials() d.mautrixClient.ClearCredentials()
log.Printf("Successfully logged out.") log.L.Printf("Successfully logged out.")
} }

View file

@ -3,15 +3,16 @@ package dispatcher
import ( import (
"fmt" "fmt"
"html" "html"
"log"
"strings" "strings"
"github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown"
"github.com/pushbits/server/internal/model"
"github.com/pushbits/server/internal/pberrors"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
mId "maunium.net/go/mautrix/id" mId "maunium.net/go/mautrix/id"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model"
"github.com/pushbits/server/internal/pberrors"
) )
// MessageFormat is a matrix message format // MessageFormat is a matrix message format
@ -31,9 +32,9 @@ type MessageEvent struct {
Body string `json:"body"` Body string `json:"body"`
FormattedBody string `json:"formatted_body"` FormattedBody string `json:"formatted_body"`
MsgType MsgType `json:"msgtype"` MsgType MsgType `json:"msgtype"`
RelatesTo RelatesTo `json:"m.relates_to,omitempty"` RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
Format MessageFormat `json:"format"` Format MessageFormat `json:"format"`
NewContent NewContent `json:"m.new_content,omitempty"` NewContent *NewContent `json:"m.new_content,omitempty"`
} }
// RelatesTo holds information about relations to other message events // RelatesTo holds information about relations to other message events
@ -53,7 +54,7 @@ type NewContent struct {
// SendNotification sends a notification to the specified user. // SendNotification sends a notification to the specified user.
func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notification) (eventId string, err error) { func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notification) (eventId string, err error) {
log.Printf("Sending notification to room %s.", a.MatrixID) log.L.Printf("Sending notification to room %s.", a.MatrixID)
plainMessage := strings.TrimSpace(n.Message) plainMessage := strings.TrimSpace(n.Message)
plainTitle := strings.TrimSpace(n.Title) plainTitle := strings.TrimSpace(n.Title)
@ -77,7 +78,7 @@ func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notificatio
// DeleteNotification sends a notification to the specified user that another notificaion is deleted // DeleteNotification sends a notification to the specified user that another notificaion is deleted
func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNotification) error { func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNotification) error {
log.Printf("Sending delete notification to room %s", a.MatrixID) log.L.Printf("Sending delete notification to room %s", a.MatrixID)
var oldFormattedBody string var oldFormattedBody string
var oldBody string var oldBody string
@ -85,7 +86,7 @@ func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNot
deleteMessage, err := d.getMessage(a, n.ID) deleteMessage, err := d.getMessage(a, n.ID)
if err != nil { if err != nil {
log.Println(err) log.L.Println(err)
return pberrors.ErrorMessageNotFound return pberrors.ErrorMessageNotFound
} }
@ -133,7 +134,7 @@ func (d *Dispatcher) getFormattedMessage(n *model.Notification) string {
if ok { if ok {
if contentTypeRaw, ok := optionsDisplay["contentType"]; ok { if contentTypeRaw, ok := optionsDisplay["contentType"]; ok {
contentType := fmt.Sprintf("%v", contentTypeRaw) contentType := fmt.Sprintf("%v", contentTypeRaw)
log.Printf("Message content type: %s", contentType) log.L.Printf("Message content type: %s", contentType)
switch contentType { switch contentType {
case "html", "text/html": case "html", "text/html":
@ -212,15 +213,15 @@ func (d *Dispatcher) replaceMessage(a *model.Application, newBody, newFormattedB
Body: oldBody, Body: oldBody,
FormattedBody: oldFormattedBody, FormattedBody: oldFormattedBody,
MsgType: MsgTypeText, MsgType: MsgTypeText,
NewContent: newMessage, NewContent: &newMessage,
RelatesTo: replaceRelation, RelatesTo: &replaceRelation,
Format: MessageFormatHTML, Format: MessageFormatHTML,
} }
sendEvent, err := d.mautrixClient.SendMessageEvent(mId.RoomID(a.MatrixID), event.EventMessage, &replaceEvent) sendEvent, err := d.mautrixClient.SendMessageEvent(mId.RoomID(a.MatrixID), event.EventMessage, &replaceEvent)
if err != nil { if err != nil {
log.Println(err) log.L.Println(err)
return nil, err return nil, err
} }
@ -252,7 +253,7 @@ func (d *Dispatcher) respondToMessage(a *model.Application, body, formattedBody
notificationRelation := RelatesTo{ notificationRelation := RelatesTo{
InReplyTo: notificationReply, InReplyTo: notificationReply,
} }
notificationEvent.RelatesTo = notificationRelation notificationEvent.RelatesTo = &notificationRelation
return d.mautrixClient.SendMessageEvent(mId.RoomID(a.MatrixID), event.EventMessage, &notificationEvent) return d.mautrixClient.SendMessageEvent(mId.RoomID(a.MatrixID), event.EventMessage, &notificationEvent)
} }

77
internal/log/ginlogrus.go Normal file
View file

@ -0,0 +1,77 @@
// Source: https://github.com/toorop/gin-logrus
package log
import (
"fmt"
"math"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
// GinLogger integrates logrus with gin
func GinLogger(logger logrus.FieldLogger, notLogged ...string) gin.HandlerFunc {
hostname, err := os.Hostname()
if err != nil {
hostname = "unknow"
}
var skip map[string]struct{}
if length := len(notLogged); length > 0 {
skip = make(map[string]struct{}, length)
for _, p := range notLogged {
skip[p] = struct{}{}
}
}
return func(c *gin.Context) {
path := c.Request.URL.Path
start := time.Now()
c.Next()
stop := time.Since(start)
latency := int(math.Ceil(float64(stop.Nanoseconds()) / 1000000.0))
statusCode := c.Writer.Status()
clientIP := c.ClientIP()
clientUserAgent := c.Request.UserAgent()
referer := c.Request.Referer()
dataLength := c.Writer.Size()
if dataLength < 0 {
dataLength = 0
}
if _, ok := skip[path]; ok {
return
}
entry := logger.WithFields(logrus.Fields{
"hostname": hostname,
"statusCode": statusCode,
"latency": latency,
"clientIP": clientIP,
"method": c.Request.Method,
"path": path,
"referer": referer,
"dataLength": dataLength,
"userAgent": clientUserAgent,
})
if len(c.Errors) > 0 {
entry.Error(c.Errors.ByType(gin.ErrorTypePrivate).String())
} else {
msg := fmt.Sprintf("%s [%d] %s %s", clientIP, statusCode, c.Request.Method, path)
if statusCode >= http.StatusInternalServerError {
entry.Error(msg)
} else if statusCode >= http.StatusBadRequest {
entry.Warn(msg)
} else {
entry.Info(msg)
}
}
}
}

22
internal/log/log.go Normal file
View file

@ -0,0 +1,22 @@
package log
import (
"os"
log "github.com/sirupsen/logrus"
)
var L *log.Logger
func init() {
L = log.New()
L.SetOutput(os.Stderr)
L.SetLevel(log.InfoLevel)
L.SetFormatter(&log.TextFormatter{
DisableTimestamp: true,
})
}
func SetDebug() {
L.SetLevel(log.DebugLevel)
}

View file

@ -1,9 +1,8 @@
package model package model
import ( import (
"log"
"github.com/pushbits/server/internal/authentication/credentials" "github.com/pushbits/server/internal/authentication/credentials"
"github.com/pushbits/server/internal/log"
) )
// User holds information like the name, the secret, and the applications of a user. // User holds information like the name, the secret, and the applications of a user.
@ -37,7 +36,7 @@ type CreateUser struct {
// NewUser creates a new user. // NewUser creates a new user.
func NewUser(cm *credentials.Manager, name, password string, isAdmin bool, matrixID string) (*User, error) { func NewUser(cm *credentials.Manager, name, password string, isAdmin bool, matrixID string) (*User, error) {
log.Printf("Creating user %s.", name) log.L.Printf("Creating user %s.", name)
passwordHash, err := cm.CreatePasswordHash(password) passwordHash, err := cm.CreatePasswordHash(password)
if err != nil { if err != nil {

View file

@ -1,7 +1,8 @@
package router package router
import ( import (
"log" "github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
"github.com/pushbits/server/internal/api" "github.com/pushbits/server/internal/api"
"github.com/pushbits/server/internal/api/alertmanager" "github.com/pushbits/server/internal/api/alertmanager"
@ -10,14 +11,12 @@ import (
"github.com/pushbits/server/internal/configuration" "github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/database" "github.com/pushbits/server/internal/database"
"github.com/pushbits/server/internal/dispatcher" "github.com/pushbits/server/internal/dispatcher"
"github.com/pushbits/server/internal/log"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
) )
// Create a Gin engine and setup all routes. // Create a Gin engine and setup all routes.
func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *dispatcher.Dispatcher, alertmanagerConfig *configuration.Alertmanager) *gin.Engine { func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *dispatcher.Dispatcher, alertmanagerConfig *configuration.Alertmanager) *gin.Engine {
log.Println("Setting up HTTP routes.") log.L.Println("Setting up HTTP routes.")
if !debug { if !debug {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
@ -34,7 +33,8 @@ func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *disp
MessageAnnotation: alertmanagerConfig.AnnotationMessage, MessageAnnotation: alertmanagerConfig.AnnotationMessage,
}} }}
r := gin.Default() r := gin.New()
r.Use(log.GinLogger(log.L), gin.Recovery())
r.Use(location.Default()) r.Use(location.Default())

View file

@ -3,17 +3,17 @@ package mockups
import ( import (
"errors" "errors"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"github.com/pushbits/server/internal/configuration" "github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/log"
) )
// ReadConfig copies the given filename to the current folder and parses it as a config file. RemoveFile indicates whether to remove the copied file or not // ReadConfig copies the given filename to the current folder and parses it as a config file. RemoveFile indicates whether to remove the copied file or not
func ReadConfig(filename string, removeFile bool) (config *configuration.Configuration, err error) { func ReadConfig(filename string, removeFile bool) (config *configuration.Configuration, err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
log.Println(r) log.L.Println(r)
err = errors.New("paniced while reading config") err = errors.New("paniced while reading config")
} }
}() }()