generated from templates/go-generic
feat: project setup
- Set the project's placeholder name to indieauth-server. - Update the magefiles and CI workflow. - Add the version command to print the app's build information. - Add the serve command to run the HTTP server.
This commit is contained in:
parent
7751cf2fe8
commit
4fdad2d805
16 changed files with 312 additions and 39 deletions
|
@ -1,26 +1,48 @@
|
|||
---
|
||||
name: Workflow
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
test:
|
||||
tests:
|
||||
name: Tests
|
||||
if: ${{ ! github.event.pull_request.draft }}
|
||||
runs-on: docker
|
||||
env:
|
||||
GO_TEST_VERBOSE: "1"
|
||||
GO_TEST_COVER: "1"
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- name: Test
|
||||
- name: Run tests
|
||||
uses: https://codeflow.dananglin.me.uk/actions/mage-ci@main
|
||||
with:
|
||||
target: test
|
||||
env:
|
||||
PROJECT_TEST_VERBOSE: "1"
|
||||
PROJECT_TEST_COVER: "1"
|
||||
- name: Run gosec
|
||||
uses: https://codeflow.dananglin.me.uk/actions/mage-ci@main
|
||||
with:
|
||||
target: gosec
|
||||
|
||||
style:
|
||||
name: Style
|
||||
if: ${{ ! github.event.pull_request.draft }}
|
||||
runs-on: docker
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: https://code.forgejo.org/actions/checkout@v4
|
||||
- name: Check formatting
|
||||
uses: https://codeflow.dananglin.me.uk/actions/mage-ci@main
|
||||
with:
|
||||
target: gofmt
|
||||
env:
|
||||
PROJECT_FAIL_ON_FORMATTING: 1
|
||||
- name: Run staticcheck
|
||||
uses: https://codeflow.dananglin.me.uk/actions/mage-ci@main
|
||||
with:
|
||||
target: staticcheck
|
||||
|
|
|
@ -13,6 +13,14 @@ output:
|
|||
sort-results: true
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
files:
|
||||
- $all
|
||||
allow:
|
||||
- $gostd
|
||||
- codeflow.dananglin.me.uk/apollo/indieauth-server
|
||||
lll:
|
||||
line-length: 140
|
||||
|
||||
|
|
23
cmd/indieauth-server/main.go
Normal file
23
cmd/indieauth-server/main.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/executors"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Set up logging
|
||||
loggingLevel := new(slog.LevelVar)
|
||||
|
||||
slogOpts := slog.HandlerOptions{Level: loggingLevel}
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, &slogOpts))
|
||||
slog.SetDefault(logger)
|
||||
loggingLevel.Set(slog.LevelInfo)
|
||||
|
||||
if err := executors.Execute(os.Args[1:]); err != nil {
|
||||
slog.Error(err.Error())
|
||||
}
|
||||
}
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
|||
module codeflow.dananglin.me.uk/apollo/indieauth-server
|
||||
|
||||
go 1.23.2
|
24
internal/executors/command.go
Normal file
24
internal/executors/command.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package executors
|
||||
|
||||
type command struct {
|
||||
name string
|
||||
args []string
|
||||
}
|
||||
|
||||
func newCommand(args []string) command {
|
||||
if len(args) == 0 {
|
||||
return command{
|
||||
name: "help",
|
||||
args: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
return command{
|
||||
name: args[0],
|
||||
args: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
return command{name: args[0], args: args[1:]}
|
||||
}
|
9
internal/executors/errors.go
Normal file
9
internal/executors/errors.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package executors
|
||||
|
||||
type UnrecognisedCommandError struct {
|
||||
command string
|
||||
}
|
||||
|
||||
func (e UnrecognisedCommandError) Error() string {
|
||||
return "unrecognised command: " + e.command
|
||||
}
|
17
internal/executors/executors.go
Normal file
17
internal/executors/executors.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package executors
|
||||
|
||||
func Execute(args []string) error {
|
||||
command := newCommand(args)
|
||||
|
||||
executorFuncMap := map[string]func(args []string) error{
|
||||
"serve": executeServeCommand,
|
||||
"version": executeVersionCommand,
|
||||
}
|
||||
|
||||
executeFunc, ok := executorFuncMap[command.name]
|
||||
if !ok {
|
||||
return UnrecognisedCommandError{command.name}
|
||||
}
|
||||
|
||||
return executeFunc(command.args)
|
||||
}
|
1
internal/executors/help.go
Normal file
1
internal/executors/help.go
Normal file
|
@ -0,0 +1 @@
|
|||
package executors
|
55
internal/executors/serve.go
Normal file
55
internal/executors/serve.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package executors
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/info"
|
||||
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/router"
|
||||
)
|
||||
|
||||
type serveExecutor struct {
|
||||
*flag.FlagSet
|
||||
|
||||
address string
|
||||
}
|
||||
|
||||
func executeServeCommand(args []string) error {
|
||||
executorName := "serve"
|
||||
|
||||
executor := serveExecutor{
|
||||
FlagSet: flag.NewFlagSet(executorName, flag.ExitOnError),
|
||||
}
|
||||
|
||||
executor.StringVar(&executor.address, "address", "0.0.0.0:8080", "The address that the server will listen on")
|
||||
|
||||
if err := executor.Parse(args); err != nil {
|
||||
return fmt.Errorf("(%s) flag parsing error: %w", executorName, err)
|
||||
}
|
||||
|
||||
if err := executor.execute(); err != nil {
|
||||
return fmt.Errorf("(%s) execution error: %w", executorName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *serveExecutor) execute() error {
|
||||
server := http.Server{
|
||||
Addr: e.address,
|
||||
Handler: router.NewServeMux(),
|
||||
ReadHeaderTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
slog.Info(info.ApplicationName+" is listening for web requests", "address", e.address)
|
||||
|
||||
err := server.ListenAndServe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running the server: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
58
internal/executors/version.go
Normal file
58
internal/executors/version.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package executors
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/info"
|
||||
)
|
||||
|
||||
type versionExecutor struct {
|
||||
*flag.FlagSet
|
||||
|
||||
showFullVersion bool
|
||||
}
|
||||
|
||||
func executeVersionCommand(args []string) error {
|
||||
executorName := "version"
|
||||
|
||||
executor := versionExecutor{
|
||||
FlagSet: flag.NewFlagSet(executorName, flag.ExitOnError),
|
||||
}
|
||||
|
||||
executor.BoolVar(&executor.showFullVersion, "full", false, "Print the applications full build information")
|
||||
|
||||
if err := executor.Parse(args); err != nil {
|
||||
return fmt.Errorf("(%s) flag parsing error: %w", executorName, err)
|
||||
}
|
||||
|
||||
executor.printVersion()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *versionExecutor) printVersion() {
|
||||
if !e.showFullVersion {
|
||||
fmt.Fprintf(os.Stdout, "%s %s\n", info.ApplicationName, info.BinaryVersion)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
|
||||
builder.WriteString(info.ApplicationName + "\n\n")
|
||||
|
||||
tableWriter := tabwriter.NewWriter(&builder, 0, 4, 1, ' ', 0)
|
||||
|
||||
_, _ = tableWriter.Write([]byte("Version:" + "\t" + info.BinaryVersion + "\n"))
|
||||
_, _ = tableWriter.Write([]byte("Git commit:" + "\t" + info.GitCommit + "\n"))
|
||||
_, _ = tableWriter.Write([]byte("Go version:" + "\t" + info.GoVersion + "\n"))
|
||||
_, _ = tableWriter.Write([]byte("Build date:" + "\t" + info.BuildTime + "\n"))
|
||||
|
||||
_ = tableWriter.Flush()
|
||||
|
||||
_, _ = os.Stdout.WriteString(builder.String())
|
||||
}
|
12
internal/info/info.go
Normal file
12
internal/info/info.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package info
|
||||
|
||||
const (
|
||||
ApplicationName string = "indieauth-server"
|
||||
)
|
||||
|
||||
var (
|
||||
BinaryVersion string //nolint:gochecknoglobals
|
||||
BuildTime string //nolint:gochecknoglobals
|
||||
GoVersion string //nolint:gochecknoglobals
|
||||
GitCommit string //nolint:gochecknoglobals
|
||||
)
|
9
internal/router/router.go
Normal file
9
internal/router/router.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package router
|
||||
|
||||
import "net/http"
|
||||
|
||||
func NewServeMux() *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
return mux
|
||||
}
|
5
magefiles/go.mod
Normal file
5
magefiles/go.mod
Normal file
|
@ -0,0 +1,5 @@
|
|||
module codeflow.dananglin.me.uk/apollo/indieauth-server/magefiles
|
||||
|
||||
go 1.23.2
|
||||
|
||||
require github.com/magefile/mage v1.15.0
|
2
magefiles/go.sum
Normal file
2
magefiles/go.sum
Normal file
|
@ -0,0 +1,2 @@
|
|||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
|
@ -7,6 +7,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/magefile/mage/mg"
|
||||
|
@ -14,13 +15,15 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
app = "binary"
|
||||
app = "indieauth-server"
|
||||
defaultInstallPrefix = "/usr/local"
|
||||
|
||||
envInstallPrefix = "PROJECT_INSTALL_PREFIX"
|
||||
envTestVerbose = "PROJECT_TEST_VERBOSE"
|
||||
envTestCover = "PROJECT_TEST_COVER"
|
||||
envBuildRebuildAll = "PROJECT_BUILD_REBUILD_ALL"
|
||||
envBuildVerbose = "PROJECT_BUILD_VERBOSE"
|
||||
envFailOnFormatting = "PROJECT_FAIL_ON_FORMATTING"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -52,11 +55,46 @@ func Lint() error {
|
|||
return sh.RunV("golangci-lint", "run", "--color", "always")
|
||||
}
|
||||
|
||||
func Gosec() error {
|
||||
return sh.RunV("gosec", "./...")
|
||||
}
|
||||
|
||||
func Staticcheck() error {
|
||||
return sh.RunV("staticcheck", "./...")
|
||||
}
|
||||
|
||||
// Gofmt checks the code for formatting.
|
||||
// To fail on formatting set PROJECT_FAIL_ON_FORMATTING=1
|
||||
func Gofmt() error {
|
||||
output, err := sh.Output("go", "fmt", "./...")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
formattedFiles := ""
|
||||
|
||||
for _, file := range strings.Split(output, "\n") {
|
||||
formattedFiles += "\n- " + file
|
||||
}
|
||||
|
||||
if os.Getenv(envFailOnFormatting) != "1" {
|
||||
fmt.Println(formattedFiles)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(output) != 0 {
|
||||
return fmt.Errorf("The following files needed to be formatted: %s", formattedFiles)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build build the executable.
|
||||
// To rebuild packages that are already up-to-date set PROJECT_BUILD_REBUILD_ALL=1
|
||||
// To enable verbose mode set PROJECT_BUILD_VERBOSE=1
|
||||
func Build() error {
|
||||
main := "main.go"
|
||||
main := "./cmd/" + app
|
||||
flags := ldflags()
|
||||
build := sh.RunCmd("go", "build")
|
||||
args := []string{"-ldflags=" + flags, "-o", binary}
|
||||
|
@ -110,10 +148,21 @@ func Clean() error {
|
|||
|
||||
// ldflags returns the build flags.
|
||||
func ldflags() string {
|
||||
ldflagsfmt := "-s -w -X main.binaryVersion=%s -X main.gitCommit=%s -X main.goVersion=%s -X main.buildTime=%s"
|
||||
versionPackage := "codeflow.dananglin.me.uk/apollo/indieauth-server/internal/info"
|
||||
binaryVersionVar := versionPackage + "." + "BinaryVersion"
|
||||
gitCommitVar := versionPackage + "." + "GitCommit"
|
||||
goVersionVar := versionPackage + "." + "GoVersion"
|
||||
buildTimeVar := versionPackage + "." + "BuildTime"
|
||||
ldflagsfmt := "-s -w -X %s=%s -X %s=%s -X %s=%s -X %s=%s"
|
||||
buildTime := time.Now().UTC().Format(time.RFC3339)
|
||||
|
||||
return fmt.Sprintf(ldflagsfmt, version(), gitCommit(), runtime.Version(), buildTime)
|
||||
return fmt.Sprintf(
|
||||
ldflagsfmt,
|
||||
binaryVersionVar, version(),
|
||||
gitCommitVar, gitCommit(),
|
||||
goVersionVar, runtime.Version(),
|
||||
buildTimeVar, buildTime,
|
||||
)
|
||||
}
|
||||
|
||||
// version returns the latest git tag using git describe.
|
||||
|
|
24
main.go
24
main.go
|
@ -1,24 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
binaryVersion string
|
||||
buildTime string
|
||||
goVersion string
|
||||
gitCommit string
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Printf("ERROR: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
return nil
|
||||
}
|
Loading…
Reference in a new issue