- Add .gitignore: exclude compiled binaries, build artifacts, and Helm values files containing real secrets (authentik, prometheus) - Add all Kubernetes deployment manifests (deployment/) - Add services source code: ha-sync, device-inventory, games-console, paperclip, parts-inventory - Add Ansible orchestration: playbooks, roles, inventory, cloud-init - Add hardware specs, execution plans, scripts, HOMELAB.md - Add skills/homelab/SKILL.md + skills/install.sh to preserve Copilot skill - Remove previously-tracked inventory-cli binary from git index Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
86 lines
2.5 KiB
Go
86 lines
2.5 KiB
Go
package opslog
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// LogEntry is the detailed record written to .jsonl files and flushed to the DB.
|
|
type LogEntry struct {
|
|
IterationID int64 `json:"iteration_id"`
|
|
DryRun bool `json:"dry_run"`
|
|
Operation string `json:"operation"`
|
|
Filepath string `json:"filepath"`
|
|
SizeBefore int64 `json:"size_before"`
|
|
SizeAfter int64 `json:"size_after"`
|
|
MD5Before string `json:"md5_before"`
|
|
MD5After string `json:"md5_after"`
|
|
StartedAt time.Time `json:"started_at"`
|
|
EndedAt time.Time `json:"ended_at"`
|
|
Status string `json:"status"`
|
|
ErrorMessage string `json:"error_message"`
|
|
}
|
|
|
|
// OpRecord describes a single file operation performed during a sync pass.
|
|
type OpRecord struct {
|
|
IterID int64 `json:"iter_id"`
|
|
RelPath string `json:"rel_path"`
|
|
Action string `json:"action"` // create | update | delete | skip | fail
|
|
Bytes int64 `json:"bytes,omitempty"`
|
|
ErrMsg string `json:"error,omitempty"`
|
|
At time.Time `json:"at"`
|
|
Owner string `json:"owner,omitempty"`
|
|
}
|
|
|
|
// Writer serialises OpRecords as newline-delimited JSON.
|
|
type Writer struct {
|
|
file *os.File // non-nil when opened via Open
|
|
enc *json.Encoder
|
|
}
|
|
|
|
// NewWriter wraps any io.Writer (useful for testing).
|
|
func NewWriter(w io.Writer) *Writer {
|
|
return &Writer{enc: json.NewEncoder(w)}
|
|
}
|
|
|
|
// Open creates logDir if needed and opens a new .jsonl log file named after
|
|
// pair, direction and the current UTC timestamp.
|
|
func Open(logDir, pair, direction string) (*Writer, error) {
|
|
if err := os.MkdirAll(logDir, 0o755); err != nil {
|
|
return nil, fmt.Errorf("opslog: create dir: %w", err)
|
|
}
|
|
ts := strings.ReplaceAll(time.Now().UTC().Format(time.RFC3339), ":", "-")
|
|
name := fmt.Sprintf("opslog-%s-%s-%s.jsonl", pair, direction, ts)
|
|
f, err := os.Create(filepath.Join(logDir, name))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opslog: open file: %w", err)
|
|
}
|
|
return &Writer{file: f, enc: json.NewEncoder(f)}, nil
|
|
}
|
|
|
|
// WriteOp stamps rec with the current UTC time and encodes it as JSON.
|
|
func (w *Writer) WriteOp(rec OpRecord) error {
|
|
rec.At = time.Now().UTC()
|
|
return w.enc.Encode(rec)
|
|
}
|
|
|
|
// Close closes the underlying file if the Writer was created with Open.
|
|
func (w *Writer) Close() error {
|
|
if w.file != nil {
|
|
return w.file.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Path returns the log file path when the Writer was created with Open.
|
|
func (w *Writer) Path() string {
|
|
if w.file != nil {
|
|
return w.file.Name()
|
|
}
|
|
return ""
|
|
}
|