Files
opsx-vpn/backend/internal/tlsservice/renewals.go
T

64 lines
1.5 KiB
Go

package tlsservice
import (
"errors"
"io"
"os"
"strings"
"time"
)
// RenewalLogPath is written only by the Certbot deploy hook after Nginx reload.
const RenewalLogPath = "/var/log/opsx-vpn/cert-renewals.log"
type RenewalEvent struct {
Time time.Time `json:"time"`
Host string `json:"host"`
}
// ReadRenewalEvents returns a bounded, validated view of successful renewals.
// Missing history does not imply a renewal failure: the certificate may not be due.
func ReadRenewalEvents() []RenewalEvent {
return readRenewalEvents(RenewalLogPath)
}
func readRenewalEvents(path string) []RenewalEvent {
f, err := os.Open(path)
if err != nil {
return nil
}
defer f.Close()
const limit int64 = 65536
info, err := f.Stat()
if err != nil {
return nil
}
start := info.Size() - limit
if start < 0 {
start = 0
}
if _, err := f.Seek(start, io.SeekStart); err != nil {
return nil
}
data, err := io.ReadAll(io.LimitReader(f, limit))
if err != nil && !errors.Is(err, io.EOF) {
return nil
}
lines := strings.Split(string(data), "\n")
if start > 0 && len(lines) > 0 {
lines = lines[1:] // ignore a partial line from the bounded tail
}
var events []RenewalEvent
for i := len(lines) - 1; i >= 0 && len(events) < 20; i-- {
fields := strings.Split(lines[i], "\t")
if len(fields) != 2 || (!ValidPublicHost("https", fields[1]) && !ValidDomain(fields[1])) {
continue
}
stamp, err := time.Parse(time.RFC3339, fields[0])
if err == nil {
events = append(events, RenewalEvent{Time: stamp, Host: fields[1]})
}
}
return events
}