Skip to content

Commit

Permalink
feat(server): create a minimal server
Browse files Browse the repository at this point in the history
Signed-off-by: Tristan Partin <tristan@partin.io>
  • Loading branch information
tristan957 committed Dec 1, 2024
1 parent 2e5204d commit 289aca7
Show file tree
Hide file tree
Showing 20 changed files with 697 additions and 6 deletions.
41 changes: 41 additions & 0 deletions .github/workflows/build-server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Tristan Partin <tristan@partin.io>

name: Build Server

on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- "**.go"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true

jobs:
build-server:
runs-on: ubuntu-latest

steps:
- name: Checkout gosplitsies
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
check-latest: true
go-version: stable

- name: Install Just
run: |
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh |
bash -s -- --to /usr/local/bin
- name: Build the Server
run: |
just debug
32 changes: 32 additions & 0 deletions .github/workflows/golangci-lint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Tristan Partin <tristan@partin.io>

name: golangci-lint

on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- "**.go"

permissions:
contents: read

jobs:
golangci-lint:
runs-on: ubuntu-latest

steps:
- name: Checkout gosplitsies
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: stable

- name: Lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
2 changes: 1 addition & 1 deletion .github/workflows/reuse.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: Checkout splitsies
- name: Checkout gosplitsies
uses: actions/checkout@v4

- name: Lint
Expand Down
60 changes: 60 additions & 0 deletions .github/workflows/vacuum.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Tristan Partin <tristan@partin.io>

name: vacuum

on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- "openapi.yaml"
- "vacuum.conf.yaml"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true

jobs:
vacuum:
runs-on: ubuntu-latest
env:
VACUUM_VERSION: "v0.14.3"

steps:
- name: Checkout gosplitsies
uses: actions/checkout@v4

- name: Get the various paths for Go caching
id: go-paths
run: |
echo "GOBIN=$(go env GOBIN)" >> "$GITHUB_OUTPUT"
echo "GOCACHE=$(go env GOCACHE)" >> "$GITHUB_OUTPUT"
echo "GOMODCACHE=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"
- name: Cache Go Modules
uses: actions/cache@v4
with:
path: |
${{ steps.go-paths.outputs.GOBIN }}
${{ steps.go-paths.outputs.GOCACHE }}
${{ needs.go-paths.outputs.GOMODCACHE }}
key: ${{ env.VACUUM_VERSION }}

- name: Setup Go
uses: actions/setup-go@v5
with:
cache: false
check-latest: true
go-version: stable

- name: Install vacuum
run: |
go install "github.com/daveshanley/vacuum@$VACUUM_VERSION"
- name: Lint the OpenAPI Description
run: |
vacuum lint --no-style openapi.yaml
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ go.work.sum
.env

!.vscode/

# Go ignores
gsplit
gsplit.debug
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ SPDX-FileCopyrightText: 2024 Tristan Partin <tristan@partin.io>

# gosplitsies

## server

### Building

```shell
# Release
just build
# Debug
just debug
```

## ui

### Setup
Expand Down
16 changes: 11 additions & 5 deletions REUSE.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Tristan Partin <tristan@partin.io>

version = 1
SPDX-HomePage = "https://github.com/return0software/gosplitsies"
SPDX-PackageName = "gosplitsies"

[[annotations]]
path = [
".vscode/copyright.code-snippets",
"ui/package.json",
"ui/package-lock.json",
]
precedence = "override"
SPDX-FileCopyrightText = "2024 Joseph Martinsen <joseph@martinsen.com>"
SPDX-License-Identifier = "AGPL-3.0-or-later"

[[annotations]]
path = [
".vscode/copyright.code-snippets",
"go.mod",
"go.sum",
"VERSION",
]
precedence = "override"
SPDX-FileCopyrightText = "2024 Tristan Partin <tristan@partin.io>"
SPDX-License-Identifier = "AGPL-3.0-or-later"
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.0.0
135 changes: 135 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later
*
* SPDX-FileCopyrightText: 2024 Tristan Partin <tristan@partin.io>
*/

package cmd

import (
"fmt"
"os"
"runtime"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

var debug string
var configFile string
var closeLogFiles func()
var logLevel zap.AtomicLevel

var rootCmd = &cobra.Command{
Use: "gsplit",
Short: "GoSplitsies is a pay splitting application",
}

func init() {
// This will be set to something real if we are logging to a file
closeLogFiles = func() {}

rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(serverCmd)

rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "", "Path to a config file")
rootCmd.PersistentFlags().StringP("log-filepath", "l", "", "Path to log to if log-location is file")
rootCmd.PersistentFlags().String("log-location", "stderr", "Location to send logs to")

serverCmd.Flags().Uint16P("port", "p", 5431, "Port to start the server on")

flags := map[string]*pflag.Flag{
"log.filepath": rootCmd.PersistentFlags().Lookup("log-filepath"),
"log.location": rootCmd.PersistentFlags().Lookup("log-location"),

"server.port": serverCmd.Flags().Lookup("port"),
}
for key, flag := range flags {
if err := viper.BindPFlag(key, flag); err != nil {
fmt.Fprintf(os.Stderr, "failed to bind flags for configuration purposes: %s\n", err)
os.Exit(1)
}
}

cobra.OnInitialize(initConfig)
cobra.OnFinalize(func() { _ = zap.S().Sync() }, closeLogFiles)
}

func initConfig() {
if configFile != "" {
viper.SetConfigFile(configFile)
} else {
viper.SetConfigName("gosplitsies")
viper.SetConfigType("yaml")

// TODO: Windows and macOS?
viper.AddConfigPath("/etc")
}

viper.SetEnvPrefix("gsplit")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
viper.AutomaticEnv()

if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok && configFile != "" {
fmt.Fprintf(os.Stderr, "failed to read config: %s\n", err)
os.Exit(1)
}
}

var encoder zapcore.Encoder
var output zapcore.WriteSyncer

if debug == "true" {
encoderConfig := zap.NewDevelopmentEncoderConfig()
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
logLevel = zap.NewAtomicLevelAt(zapcore.DebugLevel)
encoder = zapcore.NewConsoleEncoder(encoderConfig)
} else {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeLevel = zapcore.LowercaseLevelEncoder
logLevel = zap.NewAtomicLevelAt(zapcore.InfoLevel)
encoder = zapcore.NewJSONEncoder(encoderConfig)
}

logLocation := viper.GetString("log.location")
if logLocation == "file" {
logFilepath := viper.GetString("log.filepath")
if logFilepath == "" {
fmt.Fprintln(os.Stderr, "no log file path provided")
os.Exit(1)
}

var err error
output, closeLogFiles, err = zap.Open(logFilepath)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to open %s: %s", logFilepath, err)
os.Exit(1)
}
} else if logLocation == "stdout" {
output = zapcore.AddSync(os.Stdout)
} else if logLocation == "stderr" {
output = zapcore.AddSync(os.Stderr)
} else if logLocation == "syslog" { //nolint:staticcheck
if runtime.GOOS == "windows" || runtime.GOOS == "macos" {
fmt.Fprintf(os.Stderr, "log.location cannot be set to syslog on %s\n", runtime.GOOS)
os.Exit(1)
}

fmt.Fprintln(os.Stderr, "log.location set to syslog is currently unsupported")
os.Exit(1)
}

core := zapcore.NewCore(encoder, output, logLevel)
logger := zap.New(core)
zap.ReplaceGlobals(logger)
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
zap.S().Fatal("%s", err)
}
}
45 changes: 45 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later
*
* SPDX-FileCopyrightText: 2024 Tristan Partin <tristan@partin.io>
*/

package cmd

import (
"fmt"
"net"
"net/http"

"github.com/Return0Software/gosplitsies/middleware"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tristan957/go-sd-notify"
"go.uber.org/zap"
)

var serverCmd = &cobra.Command{
Use: "server",
Short: "Start the server",
Run: func(cmd *cobra.Command, args []string) {
adminMux := http.NewServeMux()
adminMux.HandleFunc("/logs/level", logLevel.ServeHTTP)

rootMux := http.NewServeMux()
rootMux.Handle("/admin", adminMux)

port := viper.GetUint16("server.port")
zap.S().Infof("Starting server on port %d", port)

listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
zap.S().Fatalf("failed to begin listening on port %d: %s", port, err)
}

_ = notify.Ready()

if err := http.Serve(listener, middleware.NewLogger(rootMux)); err != nil {
_ = notify.Stopping()
zap.S().Fatalf("failed to start the server: %s", err)
}
},
}
Loading

0 comments on commit 289aca7

Please sign in to comment.