package main import ( "bufio" "context" "errors" "fmt" "io" "os" "os/signal" "strings" "go.opentelemetry.io/otel/metric" "go.sour.is/xt/internal/otel" ) const name = "go.sour.is/xt" var m_up metric.Int64Gauge func main() { dotEnv() // load .env ctx, console := newConsole(args{ dbtype: env("XT_DBTYPE", "sqlite3"), dbfile: env("XT_DBFILE", "file:twt.db"), baseFeed: env("XT_BASE_FEED", "feed"), Nick: env("XT_NICK", "xuu"), URI: env("XT_URI", "https://txt.sour.is/user/xuu/twtxt.txt"), Listen: env("XT_LISTEN", ":8080"), }) finish, err := otel.Init(ctx, name) console.IfFatal(err) console.AddCancel(finish) m_up, err = otel.Meter().Int64Gauge("up") console.IfFatal(err) m_up.Record(ctx, 1) defer m_up.Record(context.Background(), 0) err = run(console) if !errors.Is(err, context.Canceled) { console.IfFatal(err) } } type console struct { io.Reader io.Writer err io.Writer context.Context abort func() cancelfns []func(context.Context) error } func newConsole(args args) (context.Context, *console) { ctx := context.Background() ctx, abort := context.WithCancel(ctx) ctx, stop := signal.NotifyContext(ctx, os.Interrupt) go func() { <-ctx.Done(); stop() }() // restore interrupt function console := &console{Reader: os.Stdin, Writer: os.Stdout, err: os.Stderr, Context: ctx, abort: abort} console.Set("console", console) console.Set("args", args) return ctx, console } func (c *console) Args() args { v, ok := c.Get("args").(args) if !ok { return args{} } return v } func (c *console) Shutdown() error { fmt.Fprintln(c.err, "shutting down ", len(c.cancelfns), " cancel functions...") defer fmt.Fprintln(c.err, "done") c.abort() var err error for _, fn := range c.cancelfns { err = errors.Join(err, fn(c.Context)) } return err } func (c *console) AddCancel(fn func(context.Context) error) { c.cancelfns = append(c.cancelfns, fn) } func (c *console) IfFatal(err error) { if err == nil { return } fmt.Fprintln(c.err, err) c.abort() os.Exit(1) } type contextKey struct{ name string } func (c *console) Set(name string, value any) { c.Context = context.WithValue(c.Context, contextKey{name}, value) } func (c *console) Get(name string) any { return c.Context.Value(contextKey{name}) } type args struct { dbtype string dbfile string baseFeed string Nick string URI string Listen string } func env(key, def string) string { if v, ok := os.LookupEnv(key); ok { return v } return def } func dotEnv() { fd, err := os.Open(".env") if err != nil { return } scan := bufio.NewScanner(fd) for scan.Scan() { line := scan.Text() if strings.HasPrefix(line, "#") { continue } key, val, ok := strings.Cut(line, "=") if !ok { continue } os.Setenv(strings.TrimSpace(key), strings.TrimSpace(val)) } }