This commit is contained in:
Jan-Lukas Else 2023-12-27 11:37:58 +01:00
parent 5220c497cf
commit ad7536034a
31 changed files with 247 additions and 245 deletions

View File

@ -13,7 +13,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -157,7 +156,7 @@ func (a *goBlog) apCheckMentions(p *post) {
links, err := allLinksFromHTML(pr, a.fullPostURL(p)) links, err := allLinksFromHTML(pr, a.fullPostURL(p))
_ = pr.CloseWithError(err) _ = pr.CloseWithError(err)
if err != nil { if err != nil {
log.Println("Failed to extract links from post: " + err.Error()) a.error("ActivityPub: Failed to extract links from post", err)
return return
} }
apc := a.apHttpClients[p.Blog] apc := a.apHttpClients[p.Blog]
@ -486,12 +485,12 @@ func (a *goBlog) apUndelete(p *post) {
func (a *goBlog) apAccept(blogName string, blog *configBlog, follow *ap.Activity) { func (a *goBlog) apAccept(blogName string, blog *configBlog, follow *ap.Activity) {
newFollower := follow.Actor.GetLink() newFollower := follow.Actor.GetLink()
log.Println("New follow request from follower id:", newFollower.String()) a.info("AcitivyPub: New follow request from follower", "id", newFollower.String())
// Get remote actor // Get remote actor
follower, err := a.apGetRemoteActor(newFollower, blogName) follower, err := a.apGetRemoteActor(newFollower, blogName)
if err != nil || follower == nil { if err != nil || follower == nil {
// Couldn't retrieve remote actor info // Couldn't retrieve remote actor info
log.Println("Failed to retrieve remote actor info:", newFollower) a.error("ActivityPub: Failed to retrieve remote actor info", "actor", newFollower)
return return
} }
// Add or update follower // Add or update follower
@ -529,7 +528,7 @@ func (a *goBlog) apSendProfileUpdates() {
func (a *goBlog) apSendToAllFollowers(blog string, activity *ap.Activity, mentions ...string) { func (a *goBlog) apSendToAllFollowers(blog string, activity *ap.Activity, mentions ...string) {
inboxes, err := a.db.apGetAllInboxes(blog) inboxes, err := a.db.apGetAllInboxes(blog)
if err != nil { if err != nil {
log.Println("Failed to retrieve follower inboxes:", err.Error()) a.error("ActivityPub: Failed to retrieve follower inboxes", "err", err)
return return
} }
for _, m := range mentions { for _, m := range mentions {
@ -583,7 +582,7 @@ func (a *goBlog) loadActivityPubPrivateKey() error {
if keyData, err := a.db.retrievePersistentCache("activitypub_key"); err == nil && keyData != nil { if keyData, err := a.db.retrievePersistentCache("activitypub_key"); err == nil && keyData != nil {
privateKeyDecoded, _ := pem.Decode(keyData) privateKeyDecoded, _ := pem.Decode(keyData)
if privateKeyDecoded == nil { if privateKeyDecoded == nil {
log.Println("failed to decode cached private key") a.error("ActivityPub: failed to decode cached private key")
// continue // continue
} else { } else {
key, err := x509.ParsePKCS1PrivateKey(privateKeyDecoded.Bytes) key, err := x509.ParsePKCS1PrivateKey(privateKeyDecoded.Bytes)

View File

@ -6,7 +6,6 @@ import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"time" "time"
@ -26,7 +25,7 @@ func (a *goBlog) initAPSendQueue() {
a.listenOnQueue("ap", 30*time.Second, func(qi *queueItem, dequeue func(), reschedule func(time.Duration)) { a.listenOnQueue("ap", 30*time.Second, func(qi *queueItem, dequeue func(), reschedule func(time.Duration)) {
var r apRequest var r apRequest
if err := gob.NewDecoder(bytes.NewReader(qi.content)).Decode(&r); err != nil { if err := gob.NewDecoder(bytes.NewReader(qi.content)).Decode(&r); err != nil {
log.Println("activitypub queue:", err.Error()) a.error("Activitypub queue", "err", err)
dequeue() dequeue()
return return
} }
@ -40,7 +39,7 @@ func (a *goBlog) initAPSendQueue() {
bufferpool.Put(buf) bufferpool.Put(buf)
return return
} }
log.Println("AP request failed for the 20th time:", r.To) a.info("AP request failed for the 20th time", "to", r.To)
_ = a.db.apRemoveInbox(r.To) _ = a.db.apRemoveInbox(r.To)
} }
dequeue() dequeue()

6
app.go
View File

@ -2,6 +2,7 @@ package main
import ( import (
"crypto/rsa" "crypto/rsa"
"log/slog"
"net/http" "net/http"
"sync" "sync"
@ -66,8 +67,11 @@ type goBlog struct {
inLoad sync.Once inLoad sync.Once
// IndieAuth // IndieAuth
ias *indieauth.Server ias *indieauth.Server
// Logs // Logs (HTTP)
logf *rotatelogs.RotateLogs logf *rotatelogs.RotateLogs
// Logs (Program)
logger *slog.Logger
logLevel *slog.LevelVar
// Markdown // Markdown
md, absoluteMd, titleMd goldmark.Markdown md, absoluteMd, titleMd goldmark.Markdown
// Media // Media

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"context" "context"
"io" "io"
"log"
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
@ -25,7 +24,7 @@ func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
return a.getBlogrollOutlines(blog) return a.getBlogrollOutlines(blog)
}) })
if err != nil { if err != nil {
log.Printf("Failed to get outlines: %v", err) a.error("Blogroll: Failed to get outlines", "err", err)
a.serveError(w, r, "", http.StatusInternalServerError) a.serveError(w, r, "", http.StatusInternalServerError)
return return
} }
@ -48,7 +47,7 @@ func (a *goBlog) serveBlogrollExport(w http.ResponseWriter, r *http.Request) {
return a.getBlogrollOutlines(blog) return a.getBlogrollOutlines(blog)
}) })
if err != nil { if err != nil {
log.Printf("Failed to get outlines: %v", err) a.error("Blogroll: Failed to get outlines", "err", err)
a.serveError(w, r, "", http.StatusInternalServerError) a.serveError(w, r, "", http.StatusInternalServerError)
return return
} }

View File

@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"log"
"net/http" "net/http"
"net/url" "net/url"
"sort" "sort"
@ -41,7 +40,7 @@ func (a *goBlog) initCache() (err error) {
ticker := time.NewTicker(15 * time.Minute) ticker := time.NewTicker(15 * time.Minute)
for range ticker.C { for range ticker.C {
met := a.cache.c.Metrics met := a.cache.c.Metrics
log.Println("\nCache:", met.String()) a.info("Cache metrics", "metrics", met.String())
} }
}() }()
return return

View File

@ -3,7 +3,6 @@ package main
import ( import (
"encoding/base64" "encoding/base64"
"io" "io"
"log"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -104,8 +103,6 @@ func Test_captchaMiddleware(t *testing.T) {
_, captchaSolved := session.Values["captcha"].(bool) _, captchaSolved := session.Values["captcha"].(bool)
assert.False(t, captchaSolved) assert.False(t, captchaSolved)
log.Println("Captcha ID:", captchaId)
// Check form values // Check form values
doc, err := goquery.NewDocumentFromReader(res.Body) doc, err := goquery.NewDocumentFromReader(res.Body)
_ = res.Body.Close() _ = res.Body.Close()
@ -157,8 +154,6 @@ func Test_captchaMiddleware(t *testing.T) {
_, captchaSolved = session.Values["captcha"].(bool) _, captchaSolved = session.Values["captcha"].(bool)
assert.False(t, captchaSolved) assert.False(t, captchaSolved)
log.Println("Captcha ID:", captchaId)
// Check form values // Check form values
doc, err = goquery.NewDocumentFromReader(res.Body) doc, err = goquery.NewDocumentFromReader(res.Body)
_ = res.Body.Close() _ = res.Body.Close()

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"strings" "strings"
"sync/atomic" "sync/atomic"
@ -27,24 +26,24 @@ func (a *goBlog) checkAllExternalLinks() error {
if err != nil { if err != nil {
return err return err
} }
return a.checkLinks(log.Writer(), posts...) return a.checkLinks(posts...)
} }
func (a *goBlog) checkLinks(w io.Writer, posts ...*post) error { func (a *goBlog) checkLinks(posts ...*post) error {
// Get all links // Get all links
allLinks, err := a.allLinksToCheck(posts...) allLinks, err := a.allLinksToCheck(posts...)
if err != nil { if err != nil {
return err return err
} }
// Print some info // Print some info
fmt.Fprintln(w, "Checking", len(allLinks), "links") fmt.Println("Checking", len(allLinks), "links")
// Cancel context // Cancel context
cancelContext, cancelFunc := context.WithCancel(context.Background()) cancelContext, cancelFunc := context.WithCancel(context.Background())
var done atomic.Bool var done atomic.Bool
a.shutdown.Add(func() { a.shutdown.Add(func() {
done.Store(true) done.Store(true)
cancelFunc() cancelFunc()
fmt.Fprintln(w, "Cancelled link check") fmt.Println("Cancelled link check")
}) })
// Create HTTP cache // Create HTTP cache
cache, err := ristretto.NewCache(&ristretto.Config{ cache, err := ristretto.NewCache(&ristretto.Config{
@ -106,11 +105,12 @@ func (a *goBlog) checkLinks(w io.Writer, posts ...*post) error {
continue continue
} }
if r.err != nil { if r.err != nil {
fmt.Fprintf(w, "%s in %s: %s\n", r.link, r.in, r.err.Error()) fmt.Printf("%s in %s: %s\n", r.link, r.in, r.err.Error())
} else if !successStatus(r.status) { } else if !successStatus(r.status) {
fmt.Fprintf(w, "%s in %s: %d (%s)\n", r.link, r.in, r.status, http.StatusText(r.status)) fmt.Printf("%s in %s: %d (%s)\n", r.link, r.in, r.status, http.StatusText(r.status))
} }
} }
fmt.Println("Finished link check")
return nil return nil
} }

View File

@ -2,7 +2,6 @@ package main
import ( import (
"errors" "errors"
"log"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@ -386,6 +385,8 @@ func (a *goBlog) initConfig(logging bool) error {
if a.cfg.initialized { if a.cfg.initialized {
return nil return nil
} }
// Update log level
a.updateLogLevel()
// Init database // Init database
if err := a.initDatabase(logging); err != nil { if err := a.initDatabase(logging); err != nil {
return err return err
@ -561,7 +562,7 @@ func (a *goBlog) initConfig(logging bool) error {
} }
// Log success // Log success
a.cfg.initialized = true a.cfg.initialized = true
log.Println("Initialized configuration") a.info("Initialized configuration")
return nil return nil
} }

View File

@ -2,7 +2,6 @@ package main
import ( import (
"fmt" "fmt"
"log"
"net/http" "net/http"
"time" "time"
@ -58,7 +57,7 @@ func (a *goBlog) sendContactSubmission(w http.ResponseWriter, r *http.Request) {
// Send submission // Send submission
go func() { go func() {
if err := a.sendContactEmail(bc.Contact, message.String(), formEmail); err != nil { if err := a.sendContactEmail(bc.Contact, message.String(), formEmail); err != nil {
log.Println(err.Error()) a.error("Failed to send contact email", "err", err)
} }
}() }()
// Send notification // Send notification

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"log"
"os" "os"
"strings" "strings"
"sync" "sync"
@ -17,6 +16,7 @@ import (
) )
type database struct { type database struct {
a *goBlog
// Basic things // Basic things
db *sql.DB // database db *sql.DB // database
em sync.Mutex // command execution (insert, update, delete ...) em sync.Mutex // command execution (insert, update, delete ...)
@ -35,7 +35,7 @@ func (a *goBlog) initDatabase(logging bool) (err error) {
return return
} }
if logging { if logging {
log.Println("Initialize database...") a.info("Initialize database")
} }
// Setup db // Setup db
db, err := a.openDatabase(a.cfg.Db.File, logging) db, err := a.openDatabase(a.cfg.Db.File, logging)
@ -46,9 +46,9 @@ func (a *goBlog) initDatabase(logging bool) (err error) {
a.db = db a.db = db
a.shutdown.Add(func() { a.shutdown.Add(func() {
if err := db.close(); err != nil { if err := db.close(); err != nil {
log.Printf("Failed to close database: %v", err) a.error("Failed to close database", "err", err)
} else { } else {
log.Println("Closed database") a.info("Closed database")
} }
}) })
if a.cfg.Db.DumpFile != "" { if a.cfg.Db.DumpFile != "" {
@ -58,7 +58,7 @@ func (a *goBlog) initDatabase(logging bool) (err error) {
db.dump(a.cfg.Db.DumpFile) db.dump(a.cfg.Db.DumpFile)
} }
if logging { if logging {
log.Println("Initialized database") a.info("Initialized database")
} }
return nil return nil
} }
@ -116,7 +116,7 @@ func (a *goBlog) openDatabase(file string, logging bool) (*database, error) {
return nil, errors.New("sqlite not compiled with FTS5") return nil, errors.New("sqlite not compiled with FTS5")
} }
// Migrate DB // Migrate DB
err = migrateDb(db, logging) err = a.migrateDb(db, logging)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -144,6 +144,7 @@ func (a *goBlog) openDatabase(file string, logging bool) (*database, error) {
return nil, err return nil, err
} }
return &database{ return &database{
a: a,
db: db, db: db,
debug: debug, debug: debug,
psc: psc, psc: psc,
@ -163,11 +164,11 @@ func (db *database) dump(file string) {
// Dump database // Dump database
f, err := os.Create(file) f, err := os.Create(file)
if err != nil { if err != nil {
log.Println("Error while dump db:", err.Error()) db.a.error("Error while dump db", "err", err)
return return
} }
if err = sqlite3dump.DumpDB(db.db, f, sqlite3dump.WithTransaction(true)); err != nil { if err = sqlite3dump.DumpDB(db.db, f, sqlite3dump.WithTransaction(true)); err != nil {
log.Println("Error while dump db:", err.Error()) db.a.error("Error while dump db", "err", err)
} }
} }
@ -202,7 +203,7 @@ func (db *database) prepare(query string, args ...any) (*sql.Stmt, []any, error)
}) })
if err != nil { if err != nil {
if db.debug { if db.debug {
log.Printf(`Failed to prepare query "%s": %s`, query, err.Error()) db.a.error("Failed to prepare query", "query", query, "err", err)
} }
return nil, args, err return nil, args, err
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"log"
"time" "time"
"github.com/spf13/cast" "github.com/spf13/cast"
@ -25,34 +24,27 @@ func (db *database) dbAfter(ctx context.Context, query string, args ...any) {
return return
} }
dur := time.Since(ctx.Value(dbHooksBegin).(time.Time)) dur := time.Since(ctx.Value(dbHooksBegin).(time.Time))
logBuilder := builderpool.Get() argsBuilder := builderpool.Get()
logBuilder.WriteString("\nQuery: ")
logBuilder.WriteString(`"`)
logBuilder.WriteString(query)
logBuilder.WriteString(`"`)
if len(args) > 0 { if len(args) > 0 {
logBuilder.WriteString("\nArgs: ")
for i, arg := range args { for i, arg := range args {
if i > 0 { if i > 0 {
logBuilder.WriteString(", ") argsBuilder.WriteString(", ")
} }
if named, ok := arg.(sql.NamedArg); ok && named.Name != "" { if named, ok := arg.(sql.NamedArg); ok && named.Name != "" {
logBuilder.WriteString("(") argsBuilder.WriteString("(")
logBuilder.WriteString(named.Name) argsBuilder.WriteString(named.Name)
logBuilder.WriteString(`) "`) argsBuilder.WriteString(`) '`)
logBuilder.WriteString(argToString(named.Value)) argsBuilder.WriteString(argToString(named.Value))
logBuilder.WriteString(`"`) argsBuilder.WriteString(`'`)
} else { } else {
logBuilder.WriteString(`"`) argsBuilder.WriteString(`'`)
logBuilder.WriteString(argToString(arg)) argsBuilder.WriteString(argToString(arg))
logBuilder.WriteString(`"`) argsBuilder.WriteString(`'`)
} }
} }
} }
logBuilder.WriteString("\nDuration: ") db.a.debug("Database query", "query", query, "args", argsBuilder.String(), "duration", dur.String())
logBuilder.WriteString(dur.String()) builderpool.Put(argsBuilder)
log.Println(logBuilder.String())
builderpool.Put(logBuilder)
} }
func argToString(arg any) string { func argToString(arg any) string {

View File

@ -3,8 +3,8 @@ package main
import ( import (
"database/sql" "database/sql"
"embed" "embed"
"fmt"
"io/fs" "io/fs"
"log"
"strings" "strings"
"github.com/lopezator/migrator" "github.com/lopezator/migrator"
@ -13,7 +13,7 @@ import (
//go:embed dbmigrations/* //go:embed dbmigrations/*
var dbMigrations embed.FS var dbMigrations embed.FS
func migrateDb(db *sql.DB, logging bool) error { func (a *goBlog) migrateDb(db *sql.DB, logging bool) error {
var sqlMigrations []any var sqlMigrations []any
err := fs.WalkDir(dbMigrations, "dbmigrations", func(path string, d fs.DirEntry, err error) error { err := fs.WalkDir(dbMigrations, "dbmigrations", func(path string, d fs.DirEntry, err error) error {
if err != nil || d.Type().IsDir() { if err != nil || d.Type().IsDir() {
@ -41,7 +41,7 @@ func migrateDb(db *sql.DB, logging bool) error {
m, err := migrator.New( m, err := migrator.New(
migrator.WithLogger(migrator.LoggerFunc(func(s string, i ...any) { migrator.WithLogger(migrator.LoggerFunc(func(s string, i ...any) {
if logging { if logging {
log.Printf(s, i) a.info(fmt.Sprintf(s, i...))
} }
})), })),
migrator.Migrations(sqlMigrations...), migrator.Migrations(sqlMigrations...),

View File

@ -1,9 +0,0 @@
package main
import "log"
func (a *goBlog) debug(msg ...any) {
if a.cfg.Debug {
log.Println(append([]any{"Debug:"}, msg...)...)
}
}

View File

@ -3,7 +3,6 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"log"
"math" "math"
"github.com/tkrajina/gpxgo/gpx" "github.com/tkrajina/gpxgo/gpx"
@ -51,7 +50,7 @@ func (a *goBlog) getTrack(p *post, withMapFeatures bool) (result *trackResult, e
parseResult, err := trackParseGPX(gpxString) parseResult, err := trackParseGPX(gpxString)
if err != nil { if err != nil {
// Failed to parse, but just log error // Failed to parse, but just log error
log.Printf("failed to parse GPX: %v", err) a.error("failed to parse GPX", "err", err)
return nil, nil return nil, nil
} }

View File

@ -2,7 +2,6 @@ package main
import ( import (
"html/template" "html/template"
"log"
"os/exec" "os/exec"
"time" "time"
@ -14,7 +13,7 @@ func (a *goBlog) preStartHooks() {
cfg := a.cfg.Hooks cfg := a.cfg.Hooks
for _, cmd := range cfg.PreStart { for _, cmd := range cfg.PreStart {
func(cmd string) { func(cmd string) {
executeHookCommand("pre-start", cfg.Shell, cmd) a.executeHookCommand("pre-start", cfg.Shell, cmd)
}(cmd) }(cmd)
} }
} }
@ -26,7 +25,7 @@ func (a *goBlog) postPostHooks(p *post) {
if hc := a.cfg.Hooks; hc != nil { if hc := a.cfg.Hooks; hc != nil {
for _, cmdTmplString := range hc.PostPost { for _, cmdTmplString := range hc.PostPost {
go func(p *post, cmdTmplString string) { go func(p *post, cmdTmplString string) {
a.cfg.Hooks.executeTemplateCommand("post-post", cmdTmplString, map[string]any{ a.executeHookTemplateCommand("post-post", cmdTmplString, map[string]any{
"URL": a.fullPostURL(p), "URL": a.fullPostURL(p),
"Post": p, "Post": p,
}) })
@ -46,7 +45,7 @@ func (a *goBlog) postUpdateHooks(p *post) {
if hc := a.cfg.Hooks; hc != nil { if hc := a.cfg.Hooks; hc != nil {
for _, cmdTmplString := range hc.PostUpdate { for _, cmdTmplString := range hc.PostUpdate {
go func(p *post, cmdTmplString string) { go func(p *post, cmdTmplString string) {
a.cfg.Hooks.executeTemplateCommand("post-update", cmdTmplString, map[string]any{ a.executeHookTemplateCommand("post-update", cmdTmplString, map[string]any{
"URL": a.fullPostURL(p), "URL": a.fullPostURL(p),
"Post": p, "Post": p,
}) })
@ -65,7 +64,7 @@ func (a *goBlog) postDeleteHooks(p *post) {
if hc := a.cfg.Hooks; hc != nil { if hc := a.cfg.Hooks; hc != nil {
for _, cmdTmplString := range hc.PostDelete { for _, cmdTmplString := range hc.PostDelete {
go func(p *post, cmdTmplString string) { go func(p *post, cmdTmplString string) {
a.cfg.Hooks.executeTemplateCommand("post-delete", cmdTmplString, map[string]any{ a.executeHookTemplateCommand("post-delete", cmdTmplString, map[string]any{
"URL": a.fullPostURL(p), "URL": a.fullPostURL(p),
"Post": p, "Post": p,
}) })
@ -84,7 +83,7 @@ func (a *goBlog) postUndeleteHooks(p *post) {
if hc := a.cfg.Hooks; hc != nil { if hc := a.cfg.Hooks; hc != nil {
for _, cmdTmplString := range hc.PostUndelete { for _, cmdTmplString := range hc.PostUndelete {
go func(p *post, cmdTmplString string) { go func(p *post, cmdTmplString string) {
a.cfg.Hooks.executeTemplateCommand("post-undelete", cmdTmplString, map[string]any{ a.executeHookTemplateCommand("post-undelete", cmdTmplString, map[string]any{
"URL": a.fullPostURL(p), "URL": a.fullPostURL(p),
"Post": p, "Post": p,
}) })
@ -96,19 +95,20 @@ func (a *goBlog) postUndeleteHooks(p *post) {
} }
} }
func (cfg *configHooks) executeTemplateCommand(hookType string, tmpl string, data map[string]any) { func (a *goBlog) executeHookTemplateCommand(hookType string, tmpl string, data map[string]any) {
cfg := a.cfg.Hooks
cmdTmpl, err := template.New("cmd").Parse(tmpl) cmdTmpl, err := template.New("cmd").Parse(tmpl)
if err != nil { if err != nil {
log.Println("Failed to parse cmd template:", err.Error()) a.error("Failed to parse cmd template", "err", err)
return return
} }
cmdBuf := bufferpool.Get() cmdBuf := bufferpool.Get()
defer bufferpool.Put(cmdBuf) defer bufferpool.Put(cmdBuf)
if err = cmdTmpl.Execute(cmdBuf, data); err != nil { if err = cmdTmpl.Execute(cmdBuf, data); err != nil {
log.Println("Failed to execute cmd template:", err.Error()) a.error("Failed to execute cmd template", "err", err)
return return
} }
executeHookCommand(hookType, cfg.Shell, cmdBuf.String()) a.executeHookCommand(hookType, cfg.Shell, cmdBuf.String())
} }
type hourlyHookFunc func() type hourlyHookFunc func()
@ -119,7 +119,7 @@ func (a *goBlog) startHourlyHooks() {
for _, cmd := range cfg.Hourly { for _, cmd := range cfg.Hourly {
c := cmd c := cmd
f := func() { f := func() {
executeHookCommand("hourly", cfg.Shell, c) a.executeHookCommand("hourly", cfg.Shell, c)
} }
a.hourlyHooks = append(a.hourlyHooks, f) a.hourlyHooks = append(a.hourlyHooks, f)
} }
@ -135,7 +135,7 @@ func (a *goBlog) startHourlyHooks() {
ticker := time.NewTicker(1 * time.Hour) ticker := time.NewTicker(1 * time.Hour)
a.shutdown.Add(func() { a.shutdown.Add(func() {
ticker.Stop() ticker.Stop()
log.Println("Stopped hourly hooks") a.info("Stopped hourly hooks")
}) })
for range ticker.C { for range ticker.C {
for _, f := range a.hourlyHooks { for _, f := range a.hourlyHooks {
@ -145,19 +145,19 @@ func (a *goBlog) startHourlyHooks() {
}) })
a.shutdown.Add(func() { a.shutdown.Add(func() {
if tr.Stop() { if tr.Stop() {
log.Println("Canceled hourly hooks") a.info("Canceled hourly hooks")
} }
}) })
} }
} }
func executeHookCommand(hookType, shell, cmd string) { func (a *goBlog) executeHookCommand(hookType, shell, cmd string) {
log.Printf("Executing %v hook: %v", hookType, cmd) a.info("Executing hook", "type", hookType, "cmd", cmd)
out, err := exec.Command(shell, "-c", cmd).CombinedOutput() out, err := exec.Command(shell, "-c", cmd).CombinedOutput()
if err != nil { if err != nil {
log.Println("Failed to execute command:", err.Error()) a.error("Failed to execute command", "err", err, "cmd", cmd)
} }
if len(out) > 0 { if len(out) > 0 {
log.Printf("Output:\n%v", string(out)) a.info("Hook output", "out", string(out))
} }
} }

17
http.go
View File

@ -4,7 +4,6 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"log"
"net" "net"
"net/http" "net/http"
"sort" "sort"
@ -33,7 +32,7 @@ const (
) )
func (a *goBlog) startServer() (err error) { func (a *goBlog) startServer() (err error) {
log.Println("Start server(s)...") a.info("Start server(s)...")
// Load router // Load router
a.reloadRouter() a.reloadRouter()
// Set basic middlewares // Set basic middlewares
@ -63,7 +62,7 @@ func (a *goBlog) startServer() (err error) {
if a.cfg.Server.Tor { if a.cfg.Server.Tor {
go func() { go func() {
if err := a.startOnionService(finalHandler); err != nil { if err := a.startOnionService(finalHandler); err != nil {
log.Println("Tor failed:", err.Error()) a.error("Tor failed", "err", err)
} }
}() }()
} }
@ -82,9 +81,9 @@ func (a *goBlog) startServer() (err error) {
ReadTimeout: 5 * time.Minute, ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute, WriteTimeout: 5 * time.Minute,
} }
a.shutdown.Add(shutdownServer(httpServer, "http server")) a.shutdown.Add(a.shutdownServer(httpServer, "http server"))
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Println("Failed to start HTTP server:", err.Error()) a.error("Failed to start HTTP server", "err", err)
} }
}() }()
} }
@ -94,7 +93,7 @@ func (a *goBlog) startServer() (err error) {
ReadTimeout: 5 * time.Minute, ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute, WriteTimeout: 5 * time.Minute,
} }
a.shutdown.Add(shutdownServer(s, "main server")) a.shutdown.Add(a.shutdownServer(s, "main server"))
s.Addr = ":" + strconv.Itoa(a.cfg.Server.Port) s.Addr = ":" + strconv.Itoa(a.cfg.Server.Port)
if a.cfg.Server.PublicHTTPS { if a.cfg.Server.PublicHTTPS {
s.TLSConfig = a.getAutocertManager().TLSConfig() s.TLSConfig = a.getAutocertManager().TLSConfig()
@ -110,14 +109,14 @@ func (a *goBlog) startServer() (err error) {
return err return err
} }
func shutdownServer(s *http.Server, name string) func() { func (a *goBlog) shutdownServer(s *http.Server, name string) func() {
return func() { return func() {
toc, c := context.WithTimeout(context.Background(), 5*time.Second) toc, c := context.WithTimeout(context.Background(), 5*time.Second)
defer c() defer c()
if err := s.Shutdown(toc); err != nil { if err := s.Shutdown(toc); err != nil {
log.Printf("Error on server shutdown (%v): %v", name, err) a.error("Error on server shutdown (%v): %v", name, err)
} }
log.Println("Stopped server:", name) a.info("Stopped server", "name", name)
} }
} }

View File

@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"log"
"net/http" "net/http"
"github.com/carlmjohnson/requests" "github.com/carlmjohnson/requests"
@ -50,7 +49,7 @@ func (a *goBlog) indexNow(url string) {
} }
key := a.indexNowKey() key := a.indexNowKey()
if len(key) == 0 { if len(key) == 0 {
log.Println("Skipping IndexNow") a.info("Skipping IndexNow")
return return
} }
err := requests.URL("https://api.indexnow.org/indexnow"). err := requests.URL("https://api.indexnow.org/indexnow").
@ -59,10 +58,10 @@ func (a *goBlog) indexNow(url string) {
Param("key", string(key)). Param("key", string(key)).
Fetch(context.Background()) Fetch(context.Background())
if err != nil { if err != nil {
log.Println("Sending IndexNow request failed:", err.Error()) a.error("Sending IndexNow request failed", "err", err)
return return
} else { } else {
log.Println("IndexNow request sent for", url) a.info("IndexNow request sent", "url", url)
} }
} }
@ -71,7 +70,7 @@ func (a *goBlog) indexNowKey() []byte {
// Try to load key from database // Try to load key from database
keyBytes, err := a.db.retrievePersistentCache("indexnowkey") keyBytes, err := a.db.retrievePersistentCache("indexnowkey")
if err != nil { if err != nil {
log.Println("Failed to retrieve cached IndexNow key:", err.Error()) a.error("Failed to retrieve cached IndexNow key", "err", err)
return return
} }
if keyBytes == nil { if keyBytes == nil {
@ -80,7 +79,7 @@ func (a *goBlog) indexNowKey() []byte {
// Store key in database // Store key in database
err = a.db.cachePersistently("indexnowkey", keyBytes) err = a.db.cachePersistently("indexnowkey", keyBytes)
if err != nil { if err != nil {
log.Println("Failed to cache IndexNow key:", err.Error()) a.error("Failed to cache IndexNow key", "err", err)
return return
} }
} }

39
log.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"log/slog"
"os"
)
func (a *goBlog) initLog() {
a.logLevel = new(slog.LevelVar)
a.logger = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: a.logLevel,
}))
}
func (a *goBlog) updateLogLevel() {
if a.logLevel == nil {
a.initLog()
}
if a.cfg.Debug {
a.logLevel.Set(slog.LevelDebug)
}
}
func (a *goBlog) debug(msg string, args ...any) {
a.logger.Debug(msg, args...)
}
func (a *goBlog) info(msg string, args ...any) {
a.logger.Info(msg, args...)
}
func (a *goBlog) error(msg string, args ...any) {
a.logger.Error(msg, args...)
}
func (a *goBlog) fatal(msg string, args ...any) {
a.error(msg, args...)
os.Exit(1)
}

64
main.go
View File

@ -2,7 +2,7 @@ package main
import ( import (
"flag" "flag"
"log" "fmt"
"net" "net"
"net/http" "net/http"
netpprof "net/http/pprof" netpprof "net/http/pprof"
@ -22,17 +22,23 @@ func main() {
memprofile := flag.String("memprofile", "", "write memory profile to `file`") memprofile := flag.String("memprofile", "", "write memory profile to `file`")
configfile := flag.String("config", "", "use a specific config file") configfile := flag.String("config", "", "use a specific config file")
// Init app and logger
app := &goBlog{
httpClient: newHttpClient(),
}
app.initLog()
// Init CPU and memory profiling // Init CPU and memory profiling
flag.Parse() flag.Parse()
if *cpuprofile != "" { if *cpuprofile != "" {
f, err := os.Create(*cpuprofile) f, err := os.Create(*cpuprofile)
if err != nil { if err != nil {
log.Fatalln("could not create CPU profile: ", err) app.fatal("could not create CPU profile", "err", err)
return return
} }
defer f.Close() defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil { if err := pprof.StartCPUProfile(f); err != nil {
log.Fatalln("could not start CPU profile: ", err) app.fatal("could not start CPU profile", "err", err)
return return
} }
defer pprof.StopCPUProfile() defer pprof.StopCPUProfile()
@ -41,29 +47,25 @@ func main() {
defer func() { defer func() {
f, err := os.Create(*memprofile) f, err := os.Create(*memprofile)
if err != nil { if err != nil {
log.Fatalln("could not create memory profile: ", err.Error()) app.fatal("could not create memory profile", "err", err)
return return
} }
defer f.Close() defer f.Close()
runtime.GC() runtime.GC()
if err := pprof.WriteHeapProfile(f); err != nil { if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatalln("could not write memory profile: ", err.Error()) app.fatal("could not write memory profile", "err", err)
return return
} }
}() }()
} }
app := &goBlog{
httpClient: newHttpClient(),
}
// Initialize config // Initialize config
if err = app.loadConfigFile(*configfile); err != nil { if err = app.loadConfigFile(*configfile); err != nil {
app.logErrAndQuit("Failed to load config file:", err.Error()) app.logErrAndQuit("Failed to load config file", "err", err)
return return
} }
if err = app.initConfig(false); err != nil { if err = app.initConfig(false); err != nil {
app.logErrAndQuit("Failed to init config:", err.Error()) app.logErrAndQuit("Failed to init config", "err", err)
return return
} }
@ -83,17 +85,17 @@ func main() {
AccountName: app.cfg.User.Nick, AccountName: app.cfg.User.Nick,
}) })
if err != nil { if err != nil {
app.logErrAndQuit(err.Error()) app.logErrAndQuit("Failed to generate TOTP secret", "err", err)
return return
} }
log.Println("TOTP-Secret:", key.Secret()) fmt.Println("TOTP-Secret:", key.Secret())
app.shutdown.ShutdownAndWait() app.shutdown.ShutdownAndWait()
return return
} }
// Initialize plugins // Initialize plugins
if err = app.initPlugins(); err != nil { if err = app.initPlugins(); err != nil {
app.logErrAndQuit("Failed to init plugins:", err.Error()) app.logErrAndQuit("Failed to init plugins", "err", err)
return return
} }
@ -119,13 +121,13 @@ func main() {
} }
listener, err := net.Listen("tcp", pprofServer.Addr) listener, err := net.Listen("tcp", pprofServer.Addr)
if err != nil { if err != nil {
log.Fatalln("Failed to start pprof server:", err.Error()) app.fatal("Failed to start pprof server", "err", err)
return return
} }
log.Println("Pprof server listening on", listener.Addr().String()) app.info("Pprof server listening", "addr", listener.Addr().String())
// Start server // Start server
if err := pprofServer.Serve(listener); err != nil { if err := pprofServer.Serve(listener); err != nil {
log.Fatalln("Failed to start pprof server:", err.Error()) app.fatal("Failed to start pprof server", "err", err)
return return
} }
}() }()
@ -139,11 +141,11 @@ func main() {
app.initMarkdown() app.initMarkdown()
err = app.initTemplateStrings() err = app.initTemplateStrings()
if err != nil { if err != nil {
app.logErrAndQuit("Failed to start check:", err.Error()) app.logErrAndQuit("Failed to start check", "err", err)
} }
err = app.checkAllExternalLinks() err = app.checkAllExternalLinks()
if err != nil { if err != nil {
app.logErrAndQuit("Failed to start check:", err.Error()) app.logErrAndQuit("Failed to start check", "err", err)
} }
app.shutdown.ShutdownAndWait() app.shutdown.ShutdownAndWait()
return return
@ -157,7 +159,7 @@ func main() {
} }
err = app.exportMarkdownFiles(dir) err = app.exportMarkdownFiles(dir)
if err != nil { if err != nil {
app.logErrAndQuit("Failed to export markdown files:", err.Error()) app.logErrAndQuit("Failed to export markdown files", "err", err)
return return
} }
app.shutdown.ShutdownAndWait() app.shutdown.ShutdownAndWait()
@ -173,7 +175,7 @@ func main() {
// Start the server // Start the server
err = app.startServer() err = app.startServer()
if err != nil { if err != nil {
app.logErrAndQuit("Failed to start server(s):", err.Error()) app.logErrAndQuit("Failed to start server(s)", "err", err)
return return
} }
@ -184,31 +186,31 @@ func main() {
func (app *goBlog) initComponents() { func (app *goBlog) initComponents() {
var err error var err error
log.Println("Initialize components...") app.info("Initialize components...")
app.initMarkdown() app.initMarkdown()
if err = app.initTemplateAssets(); err != nil { // Needs minify if err = app.initTemplateAssets(); err != nil { // Needs minify
app.logErrAndQuit("Failed to init template assets:", err.Error()) app.logErrAndQuit("Failed to init template assets", "err", err)
return return
} }
if err = app.initTemplateStrings(); err != nil { if err = app.initTemplateStrings(); err != nil {
app.logErrAndQuit("Failed to init template translations:", err.Error()) app.logErrAndQuit("Failed to init template translations", "err", err)
return return
} }
if err = app.initCache(); err != nil { if err = app.initCache(); err != nil {
app.logErrAndQuit("Failed to init HTTP cache:", err.Error()) app.logErrAndQuit("Failed to init HTTP cache", "err", err)
return return
} }
if err = app.initRegexRedirects(); err != nil { if err = app.initRegexRedirects(); err != nil {
app.logErrAndQuit("Failed to init redirects:", err.Error()) app.logErrAndQuit("Failed to init redirects", "err", err)
return return
} }
if err = app.initHTTPLog(); err != nil { if err = app.initHTTPLog(); err != nil {
app.logErrAndQuit("Failed to init HTTP logging:", err.Error()) app.logErrAndQuit("Failed to init HTTP logging", "err", err)
return return
} }
if err = app.initActivityPub(); err != nil { if err = app.initActivityPub(); err != nil {
app.logErrAndQuit("Failed to init ActivityPub:", err.Error()) app.logErrAndQuit("Failed to init ActivityPub", "err", err)
return return
} }
app.initWebmention() app.initWebmention()
@ -221,11 +223,11 @@ func (app *goBlog) initComponents() {
app.initPostsDeleter() app.initPostsDeleter()
app.initIndexNow() app.initIndexNow()
log.Println("Initialized components") app.info("Initialized components")
} }
func (a *goBlog) logErrAndQuit(v ...any) { func (a *goBlog) logErrAndQuit(msg string, args ...any) {
log.Println(v...) a.error(msg, args...)
a.shutdown.ShutdownAndWait() a.shutdown.ShutdownAndWait()
os.Exit(1) os.Exit(1)
} }

View File

@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"image/png" "image/png"
"io" "io"
"log"
"net/http" "net/http"
"github.com/carlmjohnson/requests" "github.com/carlmjohnson/requests"
@ -42,17 +41,18 @@ func (a *goBlog) initMediaCompressors() {
} }
config := a.cfg.Micropub.MediaStorage config := a.cfg.Micropub.MediaStorage
if key := config.TinifyKey; key != "" { if key := config.TinifyKey; key != "" {
a.compressors = append(a.compressors, &tinify{key}) a.compressors = append(a.compressors, &tinify{a: a, key: key})
} }
if config.CloudflareCompressionEnabled { if config.CloudflareCompressionEnabled {
a.compressors = append(a.compressors, &cloudflare{}) a.compressors = append(a.compressors, &cloudflare{})
} }
if config.LocalCompressionEnabled { if config.LocalCompressionEnabled {
a.compressors = append(a.compressors, &localMediaCompressor{}) a.compressors = append(a.compressors, &localMediaCompressor{a: a})
} }
} }
type tinify struct { type tinify struct {
a *goBlog
key string key string
} }
@ -78,12 +78,12 @@ func (tf *tinify) compress(url string, upload mediaStorageSaveFunc, hc *http.Cli
ToHeaders(headers). ToHeaders(headers).
Fetch(context.Background()) Fetch(context.Background())
if err != nil { if err != nil {
log.Println("Tinify error:", err.Error()) tf.a.error("Tinify error", "err", err)
return "", tinifyErr return "", tinifyErr
} }
compressedLocation := headers.Get("Location") compressedLocation := headers.Get("Location")
if compressedLocation == "" { if compressedLocation == "" {
log.Println("Tinify error: location header missing") tf.a.error("Tinify error: location header missing")
return "", tinifyErr return "", tinifyErr
} }
// Resize and download image // Resize and download image
@ -134,9 +134,11 @@ func (*cloudflare) compress(url string, upload mediaStorageSaveFunc, hc *http.Cl
return res, err return res, err
} }
type localMediaCompressor struct{} type localMediaCompressor struct {
a *goBlog
}
func (*localMediaCompressor) compress(url string, upload mediaStorageSaveFunc, hc *http.Client) (string, error) { func (lc *localMediaCompressor) compress(url string, upload mediaStorageSaveFunc, hc *http.Client) (string, error) {
// Check url // Check url
fileExtension, allowed := urlHasExt(url, "jpg", "jpeg", "png") fileExtension, allowed := urlHasExt(url, "jpg", "jpeg", "png")
if !allowed { if !allowed {
@ -150,7 +152,7 @@ func (*localMediaCompressor) compress(url string, upload mediaStorageSaveFunc, h
img, err := imaging.Decode(pr, imaging.AutoOrientation(true)) img, err := imaging.Decode(pr, imaging.AutoOrientation(true))
_ = pr.CloseWithError(err) _ = pr.CloseWithError(err)
if err != nil { if err != nil {
log.Println("Local compressor error:", err.Error()) lc.a.error("Local compressor error", "err", err)
return "", errors.New("failed to compress image using local compressor") return "", errors.New("failed to compress image using local compressor")
} }
// Resize image // Resize image

View File

@ -3,7 +3,6 @@ package main
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"log"
"net/http" "net/http"
"reflect" "reflect"
"strconv" "strconv"
@ -29,7 +28,7 @@ func (a *goBlog) sendNotification(text string) {
Text: text, Text: text,
} }
if err := a.db.saveNotification(n); err != nil { if err := a.db.saveNotification(n); err != nil {
log.Println("Failed to save notification:", err.Error()) a.error("Failed to save notification", "err", err)
} }
if cfg := a.cfg.Notifications; cfg != nil { if cfg := a.cfg.Notifications; cfg != nil {
p := pool.New().WithErrors() p := pool.New().WithErrors()
@ -45,7 +44,7 @@ func (a *goBlog) sendNotification(text string) {
return err return err
}) })
if err := p.Wait(); err != nil { if err := p.Wait(); err != nil {
log.Println("Failed to send notification:", err.Error()) a.error("Failed to send notification", "err", err)
} }
} }
} }

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"log"
"time" "time"
"github.com/araddon/dateparse" "github.com/araddon/dateparse"
@ -13,21 +12,24 @@ func (a *goBlog) initPostsDeleter() {
}) })
} }
const deletedPostParam = "deleted"
func (a *goBlog) checkDeletedPosts() { func (a *goBlog) checkDeletedPosts() {
// Get all posts with `deleted` parameter and a deleted status // Get all posts with `deleted` parameter and a deleted status
postsToDelete, err := a.getPosts(&postsRequestConfig{ postsToDelete, err := a.getPosts(&postsRequestConfig{
status: []postStatus{statusPublishedDeleted, statusDraftDeleted, statusScheduledDeleted}, status: []postStatus{statusPublishedDeleted, statusDraftDeleted, statusScheduledDeleted},
parameter: "deleted", parameter: deletedPostParam,
}) })
if err != nil { if err != nil {
log.Println("Error getting deleted posts:", err) a.error("Error getting deleted posts", "err", err)
return return
} }
for _, post := range postsToDelete { for _, post := range postsToDelete {
// Check if post is deleted for more than 7 days // Check if post is deleted for more than 7 days
if deleted, err := dateparse.ParseLocal(post.firstParameter("deleted")); err == nil && deleted.Add(time.Hour*24*7).Before(time.Now()) { if deleted, err := dateparse.ParseLocal(post.firstParameter(deletedPostParam)); err == nil &&
deleted.Add(time.Hour*24*7).Before(time.Now()) {
if err := a.deletePost(post.Path); err != nil { if err := a.deletePost(post.Path); err != nil {
log.Println("Error deleting post:", err) a.error("Error deleting post", "err", err)
} }
} }
} }

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"log"
"time" "time"
) )
@ -21,7 +20,7 @@ func (a *goBlog) startPostsScheduler() {
a.shutdown.Add(func() { a.shutdown.Add(func() {
ticker.Stop() ticker.Stop()
done <- struct{}{} done <- struct{}{}
log.Println("Posts scheduler stopped") a.info("Posts scheduler stopped")
}) })
} }
@ -31,16 +30,16 @@ func (a *goBlog) checkScheduledPosts() {
publishedBefore: time.Now(), publishedBefore: time.Now(),
}) })
if err != nil { if err != nil {
log.Println("Error getting scheduled posts:", err) a.error("Error getting scheduled posts", "err", err)
return return
} }
for _, post := range postsToPublish { for _, post := range postsToPublish {
post.Status = statusPublished post.Status = statusPublished
err := a.replacePost(post, post.Path, statusScheduled, post.Visibility) err := a.replacePost(post, post.Path, statusScheduled, post.Visibility)
if err != nil { if err != nil {
log.Println("Error publishing scheduled post:", err) a.error("Error publishing scheduled post", "err", err)
continue continue
} }
log.Println("Published scheduled post:", post.Path) a.info("Published scheduled post", "path", post.Path)
} }
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"log"
"sync" "sync"
"time" "time"
@ -101,7 +100,7 @@ func (a *goBlog) listenOnQueue(queueName string, wait time.Duration, process que
} }
qi, err := a.peekQueue(queueContext, queueName) qi, err := a.peekQueue(queueContext, queueName)
if err != nil { if err != nil {
log.Println("queue peek error:", err.Error()) a.error("queue peek error", "err", err)
continue queueLoop continue queueLoop
} }
if qi == nil { if qi == nil {
@ -117,17 +116,17 @@ func (a *goBlog) listenOnQueue(queueName string, wait time.Duration, process que
qi, qi,
func() { func() {
if err := a.dequeue(qi); err != nil { if err := a.dequeue(qi); err != nil {
log.Println("queue dequeue error:", err.Error()) a.error("queue dequeue error", "err", err)
} }
}, },
func(dur time.Duration) { func(dur time.Duration) {
if err := a.reschedule(qi, dur); err != nil { if err := a.reschedule(qi, dur); err != nil {
log.Println("queue reschedule error:", err.Error()) a.error("queue reschedule error", "err", err)
} }
}, },
) )
} }
log.Println("stopped queue:", queueName) a.info("stopped queue", "name", queueName)
wg.Done() wg.Done()
}() }()
} }

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"database/sql" "database/sql"
"encoding/gob" "encoding/gob"
"log"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -27,7 +26,7 @@ func (a *goBlog) initSessions() {
"delete from sessions where expires < @now", "delete from sessions where expires < @now",
sql.Named("now", utcNowString()), sql.Named("now", utcNowString()),
); err != nil { ); err != nil {
log.Println("Failed to delete expired sessions:", err.Error()) a.error("Failed to delete expired sessions", "err", err)
} }
} }
deleteExpiredSessions() deleteExpiredSessions()

View File

@ -2,7 +2,6 @@ package main
import ( import (
"errors" "errors"
"log"
"net/url" "net/url"
"strconv" "strconv"
@ -11,10 +10,10 @@ import (
) )
func (a *goBlog) initTelegram() { func (a *goBlog) initTelegram() {
a.pPostHooks = append(a.pPostHooks, a.tgPost(false)) a.pPostHooks = append(a.pPostHooks, func(p *post) { a.tgPost(p, false) })
a.pUpdateHooks = append(a.pUpdateHooks, a.tgUpdate) a.pUpdateHooks = append(a.pUpdateHooks, a.tgUpdate)
a.pDeleteHooks = append(a.pDeleteHooks, a.tgDelete) a.pDeleteHooks = append(a.pDeleteHooks, a.tgDelete)
a.pUndeleteHooks = append(a.pUndeleteHooks, a.tgPost(true)) a.pUndeleteHooks = append(a.pUndeleteHooks, func(p *post) { a.tgPost(p, true) })
} }
func (tg *configTelegram) enabled() bool { func (tg *configTelegram) enabled() bool {
@ -24,39 +23,37 @@ func (tg *configTelegram) enabled() bool {
return true return true
} }
func (a *goBlog) tgPost(silent bool) func(*post) { func (a *goBlog) tgPost(p *post, silent bool) {
return func(p *post) { if tg := a.getBlogFromPost(p).Telegram; tg.enabled() && p.isPublicPublishedSectionPost() {
if tg := a.getBlogFromPost(p).Telegram; tg.enabled() && p.isPublicPublishedSectionPost() { tgChat := p.firstParameter("telegramchat")
tgChat := p.firstParameter("telegramchat") tgMsg := p.firstParameter("telegrammsg")
tgMsg := p.firstParameter("telegrammsg") if tgChat != "" && tgMsg != "" {
if tgChat != "" && tgMsg != "" { // Already posted
// Already posted return
return }
} // Generate HTML
// Generate HTML html := tg.generateHTML(p.RenderedTitle, a.fullPostURL(p), a.shortPostURL(p))
html := tg.generateHTML(p.RenderedTitle, a.fullPostURL(p), a.shortPostURL(p)) if html == "" {
if html == "" { return
return }
} // Send message
// Send message chatId, msgId, err := a.sendTelegram(tg, html, tgbotapi.ModeHTML, silent)
chatId, msgId, err := a.sendTelegram(tg, html, tgbotapi.ModeHTML, silent) if err != nil {
if err != nil { a.error("Failed to send post to Telegram", "err", err)
log.Printf("Failed to send post to Telegram: %v", err) return
return }
} if chatId == 0 || msgId == 0 {
if chatId == 0 || msgId == 0 { // Not sent
// Not sent return
return }
} // Save chat and message id to post
// Save chat and message id to post err = a.db.replacePostParam(p.Path, "telegramchat", []string{strconv.FormatInt(chatId, 10)})
err = a.db.replacePostParam(p.Path, "telegramchat", []string{strconv.FormatInt(chatId, 10)}) if err != nil {
if err != nil { a.error("Failed to save Telegram chat id", "err", err)
log.Printf("Failed to save Telegram chat id: %v", err) }
} err = a.db.replacePostParam(p.Path, "telegrammsg", []string{strconv.Itoa(msgId)})
err = a.db.replacePostParam(p.Path, "telegrammsg", []string{strconv.Itoa(msgId)}) if err != nil {
if err != nil { a.error("Failed to save Telegram message id", "err", err)
log.Printf("Failed to save Telegram message id: %v", err)
}
} }
} }
} }
@ -72,13 +69,13 @@ func (a *goBlog) tgUpdate(p *post) {
// Parse tgChat to int64 // Parse tgChat to int64
chatId, err := strconv.ParseInt(tgChat, 10, 64) chatId, err := strconv.ParseInt(tgChat, 10, 64)
if err != nil { if err != nil {
log.Printf("Failed to parse Telegram chat ID: %v", err) a.error("Failed to parse Telegram chat ID", "err", err)
return return
} }
// Parse tgMsg to int // Parse tgMsg to int
messageId, err := strconv.Atoi(tgMsg) messageId, err := strconv.Atoi(tgMsg)
if err != nil { if err != nil {
log.Printf("Failed to parse Telegram message ID: %v", err) a.error("Failed to parse Telegram message ID", "err", err)
return return
} }
// Generate HTML // Generate HTML
@ -89,7 +86,7 @@ func (a *goBlog) tgUpdate(p *post) {
// Send update // Send update
err = a.updateTelegram(tg, chatId, messageId, html, "HTML") err = a.updateTelegram(tg, chatId, messageId, html, "HTML")
if err != nil { if err != nil {
log.Printf("Failed to send update to Telegram: %v", err) a.error("Failed to send update to Telegram", "err", err)
} }
} }
} }
@ -105,28 +102,28 @@ func (a *goBlog) tgDelete(p *post) {
// Parse tgChat to int64 // Parse tgChat to int64
chatId, err := strconv.ParseInt(tgChat, 10, 64) chatId, err := strconv.ParseInt(tgChat, 10, 64)
if err != nil { if err != nil {
log.Printf("Failed to parse Telegram chat ID: %v", err) a.error("Failed to parse Telegram chat ID", "err", err)
return return
} }
// Parse tgMsg to int // Parse tgMsg to int
messageId, err := strconv.Atoi(tgMsg) messageId, err := strconv.Atoi(tgMsg)
if err != nil { if err != nil {
log.Printf("Failed to parse Telegram message ID: %v", err) a.error("Failed to parse Telegram message ID", "err", err)
return return
} }
// Delete message // Delete message
err = a.deleteTelegram(tg, chatId, messageId) err = a.deleteTelegram(tg, chatId, messageId)
if err != nil { if err != nil {
log.Printf("Failed to delete Telegram message: %v", err) a.error("Failed to delete Telegram message", "err", err)
} }
// Delete chat and message id from post // Delete chat and message id from post
err = a.db.replacePostParam(p.Path, "telegramchat", []string{}) err = a.db.replacePostParam(p.Path, "telegramchat", []string{})
if err != nil { if err != nil {
log.Printf("Failed to remove Telegram chat id: %v", err) a.error("Failed to remove Telegram chat id", "err", err)
} }
err = a.db.replacePostParam(p.Path, "telegrammsg", []string{}) err = a.db.replacePostParam(p.Path, "telegrammsg", []string{})
if err != nil { if err != nil {
log.Printf("Failed to remove Telegram message id: %v", err) a.error("Failed to remove Telegram message id", "err", err)
} }
} }
} }

7
tor.go
View File

@ -6,7 +6,6 @@ import (
"crypto/ed25519" "crypto/ed25519"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"log"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@ -34,7 +33,7 @@ func (a *goBlog) startOnionService(h http.Handler) error {
return err return err
} }
// Start tor // Start tor
log.Println("Starting and registering onion service, please wait a couple of minutes...") a.info("Starting and registering onion service")
t, err := tor.Start(context.Background(), &tor.StartConf{ t, err := tor.Start(context.Background(), &tor.StartConf{
TempDataDirBase: os.TempDir(), TempDataDirBase: os.TempDir(),
NoAutoSocksPort: true, NoAutoSocksPort: true,
@ -64,7 +63,7 @@ func (a *goBlog) startOnionService(h http.Handler) error {
a.torAddress = "http://" + onion.String() a.torAddress = "http://" + onion.String()
torUrl, _ := url.Parse(a.torAddress) torUrl, _ := url.Parse(a.torAddress)
a.torHostname = torUrl.Hostname() a.torHostname = torUrl.Hostname()
log.Println("Onion service published on " + a.torAddress) a.info("Onion service published", "address", a.torAddress)
// Clear cache // Clear cache
a.cache.purge() a.cache.purge()
// Serve handler // Serve handler
@ -74,7 +73,7 @@ func (a *goBlog) startOnionService(h http.Handler) error {
ReadTimeout: 5 * time.Minute, ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute, WriteTimeout: 5 * time.Minute,
} }
a.shutdown.Add(shutdownServer(s, "tor")) a.shutdown.Add(a.shutdownServer(s, "tor"))
if err = s.Serve(onion); err != nil && err != http.ErrServerClosed { if err = s.Serve(onion); err != nil && err != http.ErrServerClosed {
return err return err
} }

11
tts.go
View File

@ -8,7 +8,6 @@ import (
"fmt" "fmt"
"html" "html"
"io" "io"
"log"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
@ -38,7 +37,7 @@ func (a *goBlog) initTTS() {
// Create TTS audio // Create TTS audio
err := a.createPostTTSAudio(p) err := a.createPostTTSAudio(p)
if err != nil { if err != nil {
log.Printf("create post audio for %s failed: %v", p.Path, err) a.error("create post audio failed", "path", p.Path, "err", err)
} }
} }
a.pPostHooks = append(a.pPostHooks, createOrUpdate) a.pPostHooks = append(a.pPostHooks, createOrUpdate)
@ -47,7 +46,7 @@ func (a *goBlog) initTTS() {
a.pDeleteHooks = append(a.pDeleteHooks, func(p *post) { a.pDeleteHooks = append(a.pDeleteHooks, func(p *post) {
// Try to delete the audio file // Try to delete the audio file
if a.deletePostTTSAudio(p) { if a.deletePostTTSAudio(p) {
log.Println("deleted tts audio for", p.Path) a.info("deleted tts audio", "path", p.Path)
} }
}) })
} }
@ -131,7 +130,7 @@ func (a *goBlog) createPostTTSAudio(p *post) error {
// Already has tts audio, but with different location // Already has tts audio, but with different location
// Try to delete the old audio file // Try to delete the old audio file
if a.deletePostTTSAudio(p) { if a.deletePostTTSAudio(p) {
log.Println("deleted old tts audio for", p.Path) a.info("deleted old tts audio", "path", p.Path)
} }
} }
@ -158,7 +157,7 @@ func (a *goBlog) deletePostTTSAudio(p *post) bool {
fileUrl, err := url.Parse(audio) fileUrl, err := url.Parse(audio)
if err != nil { if err != nil {
// Failed to parse audio url // Failed to parse audio url
log.Println("failed to parse audio url:", err) a.error("failed to parse audio url", "err", err, "audio", audio)
return false return false
} }
fileName := path.Base(fileUrl.Path) fileName := path.Base(fileUrl.Path)
@ -169,7 +168,7 @@ func (a *goBlog) deletePostTTSAudio(p *post) bool {
// Try to delete the audio file // Try to delete the audio file
err = a.deleteMediaFile(fileName) err = a.deleteMediaFile(fileName)
if err != nil { if err != nil {
log.Println("failed to delete audio file:", err) a.error("failed to delete audio file", "err", err, "file", fileName)
return false return false
} }
return true return true

View File

@ -52,35 +52,35 @@ func (a *goBlog) initWebmention() {
func (a *goBlog) handleWebmention(w http.ResponseWriter, r *http.Request) { func (a *goBlog) handleWebmention(w http.ResponseWriter, r *http.Request) {
m, err := a.extractMention(r) m, err := a.extractMention(r)
if err != nil { if err != nil {
a.debug("Error extracting webmention:", err.Error()) a.debug("Error extracting webmention", "err", err)
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return
} }
hasShortPrefix := a.cfg.Server.ShortPublicAddress != "" && strings.HasPrefix(m.Target, a.cfg.Server.ShortPublicAddress) hasShortPrefix := a.cfg.Server.ShortPublicAddress != "" && strings.HasPrefix(m.Target, a.cfg.Server.ShortPublicAddress)
hasLongPrefix := strings.HasPrefix(m.Target, a.cfg.Server.PublicAddress) hasLongPrefix := strings.HasPrefix(m.Target, a.cfg.Server.PublicAddress)
if !hasShortPrefix && !hasLongPrefix { if !hasShortPrefix && !hasLongPrefix {
a.debug("Webmention target not allowed:", m.Target) a.debug("Webmention target not allowed", "target", m.Target)
a.serveError(w, r, "target not allowed", http.StatusBadRequest) a.serveError(w, r, "target not allowed", http.StatusBadRequest)
return return
} }
if m.Target == m.Source { if m.Target == m.Source {
a.debug("Webmention target and source are the same:", m.Target) a.debug("Webmention target and source are the same", "target", m.Target)
a.serveError(w, r, "target and source are the same", http.StatusBadRequest) a.serveError(w, r, "target and source are the same", http.StatusBadRequest)
return return
} }
if err = a.queueMention(m); err != nil { if err = a.queueMention(m); err != nil {
a.debug("Failed to queue webmention", err.Error()) a.debug("Failed to queue webmention", "err", err)
a.serveError(w, r, err.Error(), http.StatusInternalServerError) a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
w.WriteHeader(http.StatusAccepted) w.WriteHeader(http.StatusAccepted)
_, _ = fmt.Fprint(w, "Webmention accepted") _, _ = fmt.Fprint(w, "Webmention accepted")
a.debug("Accepted webmention:", m.Source, m.Target) a.debug("Accepted webmention", "source", m.Source, "target", m.Target)
} }
func (a *goBlog) extractMention(r *http.Request) (*mention, error) { func (a *goBlog) extractMention(r *http.Request) (*mention, error) {
if ct := r.Header.Get(contentType); !strings.Contains(ct, contenttype.WWWForm) { if ct := r.Header.Get(contentType); !strings.Contains(ct, contenttype.WWWForm) {
a.debug("New webmention request with wrong content type:", ct) a.debug("New webmention request with wrong content type", "ct", ct)
return nil, errors.New("unsupported Content-Type") return nil, errors.New("unsupported Content-Type")
} }
err := r.ParseForm() err := r.ParseForm()
@ -90,7 +90,7 @@ func (a *goBlog) extractMention(r *http.Request) (*mention, error) {
source := r.Form.Get("source") source := r.Form.Get("source")
target := r.Form.Get("target") target := r.Form.Get("target")
if source == "" || target == "" || !isAbsoluteURL(source) || !isAbsoluteURL(target) { if source == "" || target == "" || !isAbsoluteURL(source) || !isAbsoluteURL(target) {
a.debug("Invalid webmention request, source:", source, "target:", target) a.debug("Invalid webmention request", "source", source, "target", target)
return nil, errors.New("invalid request") return nil, errors.New("invalid request")
} }
return &mention{ return &mention{

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@ -49,7 +48,7 @@ func (a *goBlog) sendWebmentions(p *post) error {
if strings.HasPrefix(link, a.cfg.Server.PublicAddress) { if strings.HasPrefix(link, a.cfg.Server.PublicAddress) {
// Save mention directly // Save mention directly
if err := a.createWebmention(a.fullPostURL(p), link); err != nil { if err := a.createWebmention(a.fullPostURL(p), link); err != nil {
log.Println("Failed to create webmention:", err.Error()) a.error("Failed to create webmention", "err", err)
} }
continue continue
} }
@ -64,10 +63,10 @@ func (a *goBlog) sendWebmentions(p *post) error {
continue continue
} }
if err = a.sendWebmention(endpoint, a.fullPostURL(p), link); err != nil { if err = a.sendWebmention(endpoint, a.fullPostURL(p), link); err != nil {
log.Println("Sending webmention to " + link + " failed") a.error("Sending webmention failed", "link", link)
continue continue
} }
log.Println("Sent webmention to " + link) a.info("Sent webmention", "link", link)
} }
return nil return nil
} }

View File

@ -7,7 +7,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -21,12 +20,12 @@ func (a *goBlog) initWebmentionQueue() {
a.listenOnQueue("wm", 30*time.Second, func(qi *queueItem, dequeue func(), reschedule func(time.Duration)) { a.listenOnQueue("wm", 30*time.Second, func(qi *queueItem, dequeue func(), reschedule func(time.Duration)) {
var m mention var m mention
if err := gob.NewDecoder(bytes.NewReader(qi.content)).Decode(&m); err != nil { if err := gob.NewDecoder(bytes.NewReader(qi.content)).Decode(&m); err != nil {
log.Println("webmention queue:", err.Error()) a.error("webmention queue error", "err", err)
dequeue() dequeue()
return return
} }
if err := a.verifyMention(&m); err != nil { if err := a.verifyMention(&m); err != nil {
log.Printf("Failed to verify webmention from %s to %s: %s", m.Source, m.Target, err.Error()) a.error("Failed to verify webmention", "source", m.Source, "target", m.Target, "err", err)
} }
dequeue() dequeue()
}) })
@ -59,9 +58,7 @@ func (a *goBlog) verifyMention(m *mention) error {
_ = targetResp.Body.Close() _ = targetResp.Body.Close()
// Check if target has a valid status code // Check if target has a valid status code
if targetResp.StatusCode != http.StatusOK { if targetResp.StatusCode != http.StatusOK {
if a.cfg.Debug { a.debug("Webmention for unknown path", "target", m.Target)
a.debug(fmt.Sprintf("Webmention for unknown path: %s", m.Target))
}
return a.db.deleteWebmention(m) return a.db.deleteWebmention(m)
} }
// Check if target has a redirect // Check if target has a redirect
@ -94,9 +91,7 @@ func (a *goBlog) verifyMention(m *mention) error {
} }
// Check if source has a valid status code // Check if source has a valid status code
if sourceResp.StatusCode != http.StatusOK { if sourceResp.StatusCode != http.StatusOK {
if a.cfg.Debug { a.debug("Delete webmention because source doesn't have valid status code", "source", m.Source)
a.debug(fmt.Sprintf("Delete webmention because source doesn't have valid status code: %s", m.Source))
}
return a.db.deleteWebmention(m) return a.db.deleteWebmention(m)
} }
// Check if source has a redirect // Check if source has a redirect
@ -108,17 +103,13 @@ func (a *goBlog) verifyMention(m *mention) error {
// Parse response body // Parse response body
err = a.verifyReader(m, sourceResp.Body) err = a.verifyReader(m, sourceResp.Body)
if err != nil { if err != nil {
if a.cfg.Debug { a.debug("Delete webmention because verifying source threw error", "source", m.Source, "err", err)
a.debug(fmt.Sprintf("Delete webmention because verifying %s threw error: %s", m.Source, err.Error()))
}
return a.db.deleteWebmention(m) return a.db.deleteWebmention(m)
} }
newStatus := webmentionStatusVerified newStatus := webmentionStatusVerified
// Update or insert webmention // Update or insert webmention
if a.db.webmentionExists(m) { if a.db.webmentionExists(m) {
if a.cfg.Debug { a.debug("Update webmention", "source", m.Source, "target", m.Target)
a.debug(fmt.Sprintf("Update webmention: %s => %s", m.Source, m.Target))
}
// Update webmention // Update webmention
err = a.db.updateWebmention(m, newStatus) err = a.db.updateWebmention(m, newStatus)
if err != nil { if err != nil {