embed web files for build
This commit is contained in:
@@ -122,7 +122,8 @@ gobsidian/
|
|||||||
To build a standalone binary:
|
To build a standalone binary:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go build -o gobsidian cmd/main.go
|
go mod tidy
|
||||||
|
go build -o gobsidian ./cmd
|
||||||
```
|
```
|
||||||
## Image storing trying to follow Obsidian settings
|
## Image storing trying to follow Obsidian settings
|
||||||
Image storing modes:
|
Image storing modes:
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ type Config struct {
|
|||||||
DBPath string
|
DBPath string
|
||||||
|
|
||||||
// Auth settings
|
// Auth settings
|
||||||
RequireAdminActivation bool
|
RequireAdminActivation bool
|
||||||
RequireEmailConfirmation bool
|
RequireEmailConfirmation bool
|
||||||
MFAEnabledByDefault bool
|
MFAEnabledByDefault bool
|
||||||
|
|
||||||
// Email (SMTP) settings
|
// Email (SMTP) settings
|
||||||
SMTPHost string
|
SMTPHost string
|
||||||
@@ -54,11 +54,11 @@ type Config struct {
|
|||||||
SMTPUseTLS bool
|
SMTPUseTLS bool
|
||||||
|
|
||||||
// Security settings (failed-login thresholds and auto-ban config)
|
// Security settings (failed-login thresholds and auto-ban config)
|
||||||
PwdFailuresThreshold int
|
PwdFailuresThreshold int
|
||||||
MFAFailuresThreshold int
|
MFAFailuresThreshold int
|
||||||
FailuresWindowMinutes int
|
FailuresWindowMinutes int
|
||||||
AutoBanDurationHours int
|
AutoBanDurationHours int
|
||||||
AutoBanPermanent bool
|
AutoBanPermanent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultConfig = map[string]map[string]string{
|
var defaultConfig = map[string]map[string]string{
|
||||||
@@ -91,9 +91,9 @@ var defaultConfig = map[string]map[string]string{
|
|||||||
"PATH": "data/gobsidian.db",
|
"PATH": "data/gobsidian.db",
|
||||||
},
|
},
|
||||||
"AUTH": {
|
"AUTH": {
|
||||||
"REQUIRE_ADMIN_ACTIVATION": "true",
|
"REQUIRE_ADMIN_ACTIVATION": "true",
|
||||||
"REQUIRE_EMAIL_CONFIRMATION": "true",
|
"REQUIRE_EMAIL_CONFIRMATION": "true",
|
||||||
"MFA_ENABLED_BY_DEFAULT": "false",
|
"MFA_ENABLED_BY_DEFAULT": "false",
|
||||||
},
|
},
|
||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
"SMTP_HOST": "",
|
"SMTP_HOST": "",
|
||||||
@@ -112,8 +112,20 @@ var defaultConfig = map[string]map[string]string{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// exeDir returns the directory containing the running executable.
|
||||||
|
func exeDir() string {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
return wd
|
||||||
|
}
|
||||||
|
return filepath.Dir(exe)
|
||||||
|
}
|
||||||
|
|
||||||
func Load() (*Config, error) {
|
func Load() (*Config, error) {
|
||||||
configPath := "settings.ini"
|
baseDir := exeDir()
|
||||||
|
// settings.ini lives next to the executable
|
||||||
|
configPath := filepath.Join(baseDir, "settings.ini")
|
||||||
|
|
||||||
// Ensure config file exists
|
// Ensure config file exists
|
||||||
if err := ensureConfigFile(configPath); err != nil {
|
if err := ensureConfigFile(configPath); err != nil {
|
||||||
@@ -167,15 +179,23 @@ func Load() (*Config, error) {
|
|||||||
config.ImageStoragePath = notesSection.Key("IMAGE_STORAGE_PATH").String()
|
config.ImageStoragePath = notesSection.Key("IMAGE_STORAGE_PATH").String()
|
||||||
config.ImageSubfolderName = notesSection.Key("IMAGE_SUBFOLDER_NAME").String()
|
config.ImageSubfolderName = notesSection.Key("IMAGE_SUBFOLDER_NAME").String()
|
||||||
|
|
||||||
// Convert relative paths to absolute
|
// Convert relative paths to be next to the executable
|
||||||
if !filepath.IsAbs(config.NotesDir) {
|
if !filepath.IsAbs(config.NotesDir) {
|
||||||
wd, _ := os.Getwd()
|
config.NotesDir = filepath.Join(baseDir, config.NotesDir)
|
||||||
config.NotesDir = filepath.Join(wd, config.NotesDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filepath.IsAbs(config.ImageStoragePath) && config.ImageStorageMode == 2 {
|
if !filepath.IsAbs(config.ImageStoragePath) && config.ImageStorageMode == 2 {
|
||||||
wd, _ := os.Getwd()
|
config.ImageStoragePath = filepath.Join(baseDir, config.ImageStoragePath)
|
||||||
config.ImageStoragePath = filepath.Join(wd, config.ImageStoragePath)
|
}
|
||||||
|
|
||||||
|
// Ensure these directories exist
|
||||||
|
if err := os.MkdirAll(config.NotesDir, 0o755); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create notes directory: %w", err)
|
||||||
|
}
|
||||||
|
if config.ImageStorageMode == 2 {
|
||||||
|
if err := os.MkdirAll(config.ImageStoragePath, 0o755); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create image storage directory: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load DATABASE section
|
// Load DATABASE section
|
||||||
@@ -184,8 +204,7 @@ func Load() (*Config, error) {
|
|||||||
config.DBPath = dbSection.Key("PATH").String()
|
config.DBPath = dbSection.Key("PATH").String()
|
||||||
if config.DBType == "sqlite" {
|
if config.DBType == "sqlite" {
|
||||||
if !filepath.IsAbs(config.DBPath) {
|
if !filepath.IsAbs(config.DBPath) {
|
||||||
wd, _ := os.Getwd()
|
config.DBPath = filepath.Join(baseDir, config.DBPath)
|
||||||
config.DBPath = filepath.Join(wd, config.DBPath)
|
|
||||||
}
|
}
|
||||||
// ensure parent dir exists
|
// ensure parent dir exists
|
||||||
if err := os.MkdirAll(filepath.Dir(config.DBPath), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(config.DBPath), 0o755); err != nil {
|
||||||
@@ -222,6 +241,10 @@ func Load() (*Config, error) {
|
|||||||
func ensureConfigFile(configPath string) error {
|
func ensureConfigFile(configPath string) error {
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||||
|
// ensure parent dir exists
|
||||||
|
if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return createDefaultConfigFile(configPath)
|
return createDefaultConfigFile(configPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +313,7 @@ func parseCommaSeparated(value string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) SaveSetting(section, key, value string) error {
|
func (c *Config) SaveSetting(section, key, value string) error {
|
||||||
configPath := "settings.ini"
|
configPath := filepath.Join(exeDir(), "settings.ini")
|
||||||
cfg, err := ini.Load(configPath)
|
cfg, err := ini.Load(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ package server
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
|
||||||
|
webassets "gobsidian/web"
|
||||||
"gobsidian/internal/auth"
|
"gobsidian/internal/auth"
|
||||||
"gobsidian/internal/config"
|
"gobsidian/internal/config"
|
||||||
"gobsidian/internal/handlers"
|
"gobsidian/internal/handlers"
|
||||||
@@ -173,8 +176,14 @@ func (s *Server) setupRoutes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) setupStaticFiles() {
|
func (s *Server) setupStaticFiles() {
|
||||||
s.router.Static("/static", "./web/static")
|
// Serve /static from embedded web/static
|
||||||
s.router.StaticFile("/favicon.ico", "./web/static/favicon.ico")
|
sub, err := fs.Sub(webassets.StaticFS, "static")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
s.router.StaticFS("/static", http.FS(sub))
|
||||||
|
// Favicon from same sub FS
|
||||||
|
s.router.StaticFileFS("/favicon.ico", "favicon.ico", http.FS(sub))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) setupTemplates() {
|
func (s *Server) setupTemplates() {
|
||||||
@@ -244,8 +253,12 @@ func (s *Server) setupTemplates() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load templates - make sure base.html is loaded with all the other templates
|
// Load templates from embedded FS
|
||||||
templates := template.Must(template.New("").Funcs(funcMap).ParseGlob("web/templates/*.html"))
|
tplFS, err := fs.Sub(webassets.TemplatesFS, "templates")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
templates := template.Must(template.New("").Funcs(funcMap).ParseFS(tplFS, "*.html"))
|
||||||
s.router.SetHTMLTemplate(templates)
|
s.router.SetHTMLTemplate(templates)
|
||||||
|
|
||||||
fmt.Printf("DEBUG: Templates loaded successfully\n")
|
fmt.Printf("DEBUG: Templates loaded successfully\n")
|
||||||
@@ -253,10 +266,10 @@ func (s *Server) setupTemplates() {
|
|||||||
|
|
||||||
// startAccessLogCleanup deletes access logs older than 7 days once at startup and then daily.
|
// startAccessLogCleanup deletes access logs older than 7 days once at startup and then daily.
|
||||||
func (s *Server) startAccessLogCleanup() {
|
func (s *Server) startAccessLogCleanup() {
|
||||||
// initial cleanup
|
// initial cleanup
|
||||||
_, _ = s.auth.DB.Exec(`DELETE FROM access_logs WHERE created_at < DATETIME('now', '-7 days')`)
|
_, _ = s.auth.DB.Exec(`DELETE FROM access_logs WHERE created_at < DATETIME('now', '-7 days')`)
|
||||||
ticker := time.NewTicker(24 * time.Hour)
|
ticker := time.NewTicker(24 * time.Hour)
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
_, _ = s.auth.DB.Exec(`DELETE FROM access_logs WHERE created_at < DATETIME('now', '-7 days')`)
|
_, _ = s.auth.DB.Exec(`DELETE FROM access_logs WHERE created_at < DATETIME('now', '-7 days')`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
web/assets_embed.go
Normal file
11
web/assets_embed.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package webassets
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
// TemplatesFS embeds all HTML templates under web/templates.
|
||||||
|
//go:embed templates/*.html
|
||||||
|
var TemplatesFS embed.FS
|
||||||
|
|
||||||
|
// StaticFS embeds all static assets under web/static.
|
||||||
|
//go:embed static/*
|
||||||
|
var StaticFS embed.FS
|
||||||
Reference in New Issue
Block a user