cerrado @ 3fb9c66ffa0bf87cbd7cc1b5f4129f3447e94c13

feat: Initial http server code
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..7a0e125baadd8ba25b560106a0b1bd91ec30df17
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,5 @@
+build:
+	go build -o bin/cerrado
+
+run:
+	go run .
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..cca006e89444dbdcf6c81c9f4f0f9acf6ed0bb6b
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module git.gabrielgio.me/cerrado
+
+go 1.22.2
+
+require golang.org/x/sync v0.7.0 // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..e8ef4a360a1437536e8a3053e4ffc82fc69ce9c5
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..7c80564f57dfa106d4a4074f6c4586bc0a9c18ba
--- /dev/null
+++ b/main.go
@@ -0,0 +1,35 @@
+package main
+
+import (
+	"context"
+	"log/slog"
+	"net/http"
+	"os"
+	"os/signal"
+	"time"
+
+	"git.gabrielgio.me/cerrado/pkg/worker"
+)
+
+func main() {
+	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
+	defer stop()
+	if err := run(ctx); err != nil {
+		os.Exit(1)
+	}
+}
+
+func run(ctx context.Context) error {
+	mux := http.NewServeMux()
+	mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
+		if _, err := w.Write([]byte("Hello world!")); err != nil {
+			slog.Error("Error handling index", "error", err)
+		}
+	})
+	serverTask := worker.NewServerTask(&http.Server{Handler: mux, Addr: "0.0.0.0:8080"})
+
+	pool := worker.NewTaskPool()
+	pool.AddTask("http-server", 5*time.Second, serverTask)
+
+	return pool.Start(ctx)
+}
diff --git a/pkg/worker/http.go b/pkg/worker/http.go
new file mode 100644
index 0000000000000000000000000000000000000000..1d56f86e2587ccf59a047e278e1a7a45f11cb165
--- /dev/null
+++ b/pkg/worker/http.go
@@ -0,0 +1,26 @@
+package worker
+
+import (
+	"context"
+	"net/http"
+)
+
+type ServerTask struct {
+	server *http.Server
+}
+
+func NewServerTask(server *http.Server) *ServerTask {
+	return &ServerTask{
+		server: server,
+	}
+}
+
+func (self *ServerTask) Start(ctx context.Context) error {
+	go func() {
+		// nolint: errcheck
+		self.server.ListenAndServe()
+	}()
+
+	<-ctx.Done()
+	return self.server.Shutdown(ctx)
+}
diff --git a/pkg/worker/worker.go b/pkg/worker/worker.go
new file mode 100644
index 0000000000000000000000000000000000000000..6b5c21cd67bc557b2b8bd1fa048229dc4b098efb
--- /dev/null
+++ b/pkg/worker/worker.go
@@ -0,0 +1,94 @@
+package worker
+
+import (
+	"context"
+	"errors"
+	"log/slog"
+	"time"
+
+	"golang.org/x/sync/errgroup"
+)
+
+type (
+	Task interface {
+		Start(context.Context) error
+	}
+
+	Work struct {
+		Name string
+		Task Task
+		wait time.Duration
+	}
+
+	TaskPool struct {
+		tasks []*Work
+	}
+)
+
+const (
+	format = "2006.01.02 15:04:05"
+)
+
+func NewTaskPool() *TaskPool {
+	return &TaskPool{}
+}
+
+func (w *Work) run(ctx context.Context) error {
+	// first time fire from the get go
+	timer := time.NewTimer(time.Nanosecond)
+
+	for {
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case <-timer.C:
+			if err := w.Task.Start(ctx); err != nil && !errors.Is(err, context.Canceled) {
+				return err
+			}
+		}
+		timer.Reset(w.wait)
+	}
+}
+
+func (self *TaskPool) AddTask(name string, wait time.Duration, task Task) {
+	self.tasks = append(self.tasks, &Work{
+		Name: name,
+		Task: task,
+		wait: wait,
+	})
+}
+
+func (self *TaskPool) Start(ctx context.Context) error {
+	var g errgroup.Group
+
+	for _, w := range self.tasks {
+		g.Go(func(w *Work) func() error {
+			return func() error {
+				slog.Info("Process starting", "time", time.Now().Format(format), "name", w.Name)
+				now := time.Now()
+				if err := w.run(ctx); err != nil && !errors.Is(context.Canceled, err) {
+					since := time.Since(now)
+					slog.Error(
+						"Process erred",
+						"time", time.Now().Format(format),
+						"name", w.Name,
+						"error", err,
+						"duration", since,
+					)
+					return err
+				} else {
+					since := time.Since(now)
+					slog.Info(
+						"Process ended",
+						"time", time.Now().Format(format),
+						"name", w.Name,
+						"duration", since,
+					)
+				}
+				return nil
+			}
+		}(w))
+	}
+
+	return g.Wait()
+}