miki

Miki: personal wiki with the fmd markup format
git clone https://wtenison.com/repo/miki.git
Log | Files | Refs | README | LICENSE

commit c79f191be541a29dfafc0dd0b9799641918c79f1
parent cf473e898b4b52c2e21f840ef20f891b8b115878
Author: weetat <wtenison@protonmail.com>
Date:   Wed, 12 Feb 2025 20:50:31 +0000

Add HTTPS server with YAML config and templates

Read filesystem and server settings from the YAML files in sys/, check
that the TLS cert and key exist, and serve /web/ as static files. The /
handler collects the .html files under the web root and renders
index.html from them.

The run target now uses sudo so the server can bind a privileged port.

Diffstat:
MMakefile | 2+-
Mgo.mod | 2+-
Msrc/miki/miki.go | 173++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Dweb/blank.html | 0
Aweb/index.html | 12++++++++++++
5 files changed, 185 insertions(+), 4 deletions(-)

diff --git a/Makefile b/Makefile @@ -5,6 +5,6 @@ build: go build -o ./bin/$(PROJECT_NAME) ./src/$(PROJECT_NAME) run: - ./bin/$(PROJECT_NAME) + sudo ./bin/$(PROJECT_NAME) all: build run \ No newline at end of file diff --git a/go.mod b/go.mod @@ -2,4 +2,4 @@ module miki go 1.23.2 -require github.com/goccy/go-yaml v1.15.17 // indirect +require github.com/goccy/go-yaml v1.15.17 diff --git a/src/miki/miki.go b/src/miki/miki.go @@ -1,5 +1,175 @@ package main +import ( + "bytes" + "fmt" + "html/template" + "log" + "net/http" + "os" + "path/filepath" + "regexp" + + "github.com/goccy/go-yaml" +) + +type ResourceSignature struct { + directory string // The root directoy of the resource. + dependencyDepth int // The folder search depth of the resource. -1=inf, 0=single, >0=n-depth + dependencyPattern *regexp.Regexp // The pattern of valid resource dependency files. +} + +func MakeResourceSig(dir string, depth int, pattern *regexp.Regexp) (ResourceSignature, error) { + if dir == "" || depth < -1 || pattern == nil { + return ResourceSignature{}, fmt.Errorf("__ERROR:\tResource:Make(%s, %d, %s)", dir, depth, pattern) + } + + if _, _err := os.Stat(dir); _err != nil { + return ResourceSignature{}, _err + } + + return ResourceSignature{dir, depth, pattern}, nil +} + +type ResourcePackage struct { + directory string // The root directory of the resource. + dependencyFiles []string // The set of valid resource files. + dependencyData IResourceData // Any relevant resource data. +} + +func MakeResourcePkg(resSig ResourceSignature) (ResourcePackage, error) { + + // Check and collect resource signature directory files. + _files, _err := os.ReadDir(resSig.directory) + if _err != nil { + return ResourcePackage{}, _err + } + + _resPkg := ResourcePackage{ + directory: resSig.directory, + dependencyFiles: []string{}, + dependencyData: nil, + } + + _resFileDirDeps := []string{} + + // Collete all valid files and sub directory files. + for _, _f := range _files { + + if _f.IsDir() && (resSig.dependencyDepth > 0 || resSig.dependencyDepth == -1) { + // Recurse into valid directory files. + + _resDirSig, _err := MakeResourceSig(_f.Name(), resSig.dependencyDepth, resSig.dependencyPattern) + if _err != nil { + return ResourcePackage{}, _err + } + + _resDirPkg, _err := MakeResourcePkg(_resDirSig) + if _err != nil { + return ResourcePackage{}, _err + } + + _resFileDirDeps = append(_resFileDirDeps, _resDirPkg.dependencyFiles...) + + } else if resSig.dependencyPattern != nil && resSig.dependencyPattern.MatchString(_f.Name()) { + // Add files that match pattern to dependency list. + _resPkg.dependencyFiles = append(_resPkg.dependencyFiles, filepath.Join(_resPkg.directory, _f.Name())) + } else if resSig.dependencyPattern == nil { + // Add any file in directory to dependency list. + _resPkg.dependencyFiles = append(_resPkg.dependencyFiles, filepath.Join(_resPkg.directory, _f.Name())) + } + } + + // Add any sub directory file dependencies. + _resPkg.dependencyFiles = append(_resPkg.dependencyFiles, _resFileDirDeps...) + + return _resPkg, nil +} + +type IResourceData interface { +} + func main() { + fmt.Println("MIKI") + + _fsBuffer := bytes.NewBufferString("filesysteminfo: *fs\n") + + var _fs struct { + FileSystemInfo struct { + RootDir string + } + } + + decoder := yaml.NewDecoder(_fsBuffer, yaml.ReferenceDirs("sys")) + if err := decoder.Decode(&_fs); err != nil { + fmt.Println(err.Error()) + return + } + fmt.Printf("%+v\n", _fs) + + _siBuffer := bytes.NewBufferString("serverinfo: *si\n") + + var _si struct { + ServerInfo struct { + Port string + SslPath string + CertFile string + KeyFile string + } + } + + decoder = yaml.NewDecoder(_siBuffer, yaml.ReferenceDirs("sys")) + if err := decoder.Decode(&_si); err != nil { + fmt.Println(err.Error()) + return + } + fmt.Printf("%+v\n", _si) + + _tls_cert_path := filepath.Join(_si.ServerInfo.SslPath, _si.ServerInfo.CertFile) + if _, err := os.Stat(_tls_cert_path); err != nil { + log.Panicf("__PANIC\tmain:%s", err) + } + + _tls_key_path := filepath.Join(_si.ServerInfo.SslPath, _si.ServerInfo.KeyFile) + if _, err := os.Stat(_tls_key_path); err != nil { + log.Panicf("__PANIC\tmain:%s", err) + } + + _mux := http.NewServeMux() + _mux.Handle("/web/", http.StripPrefix("/web/", http.FileServer(http.Dir("/home/miki/")))) + _mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + + _r, _err := MakeResourceSig(_fs.FileSystemInfo.RootDir+"/", -1, regexp.MustCompile("^([a-zA-Z0-9.]+).html$")) + if _err != nil { + fmt.Printf("_ERROR\tmain():HandleFunc(\"/\"):MakeResourceSig():%s\n", _err) + http.NotFound(w, r) + return + } + + _rPkg, _err := MakeResourcePkg(_r) + if _err != nil { + fmt.Printf("_ERROR\tmain():HandleFunc(\"/\"):MakeResourcePkg():%s\n", _err) + http.NotFound(w, r) + return + } + + _tmpl, _err := template.ParseFiles(_rPkg.dependencyFiles...) + if _err != nil { + fmt.Printf("_ERROR\tmain():HandleFunc(\"/\"):MakeResourcePkg():%s\n", _err) + http.Error(w, "_ERROR:HandleFunc(\"/\"):template.ParseFiles()", 500) + return + } + + if _err = _tmpl.ExecuteTemplate(w, "index.html", _rPkg.dependencyData); _err != nil { + fmt.Printf("_ERROR\tmain():HandleFunc(\"/\"):MakeResourcePkg():%s\n", _err) + http.Error(w, "_ERROR:HandleFunc(\"/\"):template.ExecuteTemplate()", 500) + return + } + + fmt.Printf("INFO:\tmain():mux:Resource(%+v)\n", _rPkg) + }) -} -\ No newline at end of file + if _err := http.ListenAndServeTLS(":"+_si.ServerInfo.Port, _tls_cert_path, _tls_key_path, _mux); _err != nil { + fmt.Printf("_ERROR\tmain():ListenAndServeTLS():%s\n", _err) + } +} diff --git a/web/blank.html b/web/blank.html diff --git a/web/index.html b/web/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head> + <title>Miki</title> + <meta name="viewport" charset="utf-8" content="width=device-width, initial-scale=1.0"/> + </head> + + <body> + <h1>HELLO WORLD</h1> + </body> +</html> +\ No newline at end of file