mirror of
https://codeberg.org/git-pages/git-pages.git
synced 2026-05-14 11:11:35 +00:00
Add a configuration file.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/bin
|
||||
/data
|
||||
/config.toml
|
||||
|
||||
@@ -21,7 +21,8 @@ You will need [Go](https://go.dev/) 1.24 or newer. Run:
|
||||
|
||||
```console
|
||||
$ mkdir -p data
|
||||
$ go run . data :3333
|
||||
$ cp config.toml.example config.toml
|
||||
$ go run ./src
|
||||
```
|
||||
|
||||
This starts an HTTP server on `0.0.0.0:3333` whose behavior is fully determined by the `data` directory. It will accept requests to any virtual host, but must first be provisioned. For example:
|
||||
|
||||
5
config.toml.example
Normal file
5
config.toml.example
Normal file
@@ -0,0 +1,5 @@
|
||||
data-dir = "./data"
|
||||
|
||||
[listen]
|
||||
protocol = "tcp"
|
||||
address = ":3333"
|
||||
1
go.mod
1
go.mod
@@ -20,6 +20,7 @@ require (
|
||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pjbgf/sha1cd v0.4.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -36,6 +36,8 @@ github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7Dmvb
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=
|
||||
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
27
src/config.go
Normal file
27
src/config.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
DataDir string `toml:"data-dir"`
|
||||
Listen struct {
|
||||
Protocol string `toml:"protocol"`
|
||||
Address string `toml:"address"`
|
||||
} `toml:"listen"`
|
||||
}
|
||||
|
||||
func readConfig(path string, config *Config) error {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
decoder := toml.NewDecoder(file)
|
||||
decoder.DisallowUnknownFields()
|
||||
return decoder.Decode(config)
|
||||
}
|
||||
15
src/fetch.go
15
src/fetch.go
@@ -37,7 +37,6 @@ func splitHash(hash plumbing.Hash) string {
|
||||
}
|
||||
|
||||
func fetch(
|
||||
dataDir string,
|
||||
webRoot string,
|
||||
repoURL string,
|
||||
branch string,
|
||||
@@ -61,10 +60,10 @@ func fetch(
|
||||
}
|
||||
head := ref.Hash()
|
||||
|
||||
destDir := filepath.Join(dataDir, "tree", splitHash(head))
|
||||
destDir := filepath.Join(config.DataDir, "tree", splitHash(head))
|
||||
if _, err := os.Stat(destDir); errors.Is(err, os.ErrNotExist) {
|
||||
// check out to a temporary directory to avoid TOCTTOU race on destDir
|
||||
tempDir, err := os.MkdirTemp(dataDir, ".tree")
|
||||
tempDir, err := os.MkdirTemp(config.DataDir, ".tree")
|
||||
if err != nil {
|
||||
return FetchResult{err: fmt.Errorf("mkdir temp: %s", err)}
|
||||
}
|
||||
@@ -96,10 +95,10 @@ func fetch(
|
||||
}
|
||||
}
|
||||
|
||||
webLink := filepath.Join(dataDir, "www", webRoot)
|
||||
webLink := filepath.Join(config.DataDir, "www", webRoot)
|
||||
destDirRel, _ := filepath.Rel(filepath.Dir(webLink), destDir)
|
||||
|
||||
tempLink := filepath.Join(dataDir,
|
||||
tempLink := filepath.Join(config.DataDir,
|
||||
fmt.Sprintf(".link.%s.%s", strings.ReplaceAll(webRoot, "/", ".."), head.String()))
|
||||
if err := os.Symlink(destDirRel, tempLink); err != nil {
|
||||
return FetchResult{err: fmt.Errorf("symlink temp: %s", err)}
|
||||
@@ -131,13 +130,12 @@ func fetch(
|
||||
}
|
||||
|
||||
func Fetch(
|
||||
dataDir string,
|
||||
webRoot string,
|
||||
repoURL string,
|
||||
branch string,
|
||||
) FetchResult {
|
||||
log.Println("fetch:", webRoot, repoURL, branch)
|
||||
result := fetch(dataDir, webRoot, repoURL, branch)
|
||||
result := fetch(webRoot, repoURL, branch)
|
||||
if result.err == nil {
|
||||
status := ""
|
||||
switch result.outcome {
|
||||
@@ -156,7 +154,6 @@ func Fetch(
|
||||
}
|
||||
|
||||
func FetchWithTimeout(
|
||||
dataDir string,
|
||||
webRoot string,
|
||||
repoURL string,
|
||||
branch string,
|
||||
@@ -165,7 +162,7 @@ func FetchWithTimeout(
|
||||
// fetch the updated content with a timeout
|
||||
c := make(chan FetchResult, 1)
|
||||
go func() {
|
||||
result := Fetch(dataDir, webRoot, repoURL, branch)
|
||||
result := Fetch(webRoot, repoURL, branch)
|
||||
c <- result
|
||||
}()
|
||||
select {
|
||||
|
||||
23
src/main.go
23
src/main.go
@@ -1,18 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dataDir := os.Args[1]
|
||||
listenAddr := os.Args[2]
|
||||
var config Config
|
||||
|
||||
http.HandleFunc("/", Serve(dataDir))
|
||||
err := http.ListenAndServe(listenAddr, nil)
|
||||
func main() {
|
||||
configPath := flag.String("config", "config.toml", "path to configuration file")
|
||||
flag.Parse()
|
||||
|
||||
if err := readConfig(*configPath, &config); err != nil {
|
||||
log.Fatalln("failed to read configuration:", err)
|
||||
}
|
||||
|
||||
listener, err := net.Listen(config.Listen.Protocol, config.Listen.Address)
|
||||
if err != nil {
|
||||
log.Fatalln("failed to listen:", err)
|
||||
}
|
||||
|
||||
http.HandleFunc("/", Serve)
|
||||
if err := http.Serve(listener, nil); err != nil {
|
||||
log.Fatalln("failed to serve:", err)
|
||||
}
|
||||
}
|
||||
|
||||
52
src/serve.go
52
src/serve.go
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
const fetchTimeout = 30 * time.Second
|
||||
|
||||
func getPage(dataDir string, w http.ResponseWriter, r *http.Request) error {
|
||||
func getPage(w http.ResponseWriter, r *http.Request) error {
|
||||
host := getHost(r)
|
||||
|
||||
// if the first directory of the path exists under `www/$host`, use it as the root,
|
||||
@@ -29,26 +29,26 @@ func getPage(dataDir string, w http.ResponseWriter, r *http.Request) error {
|
||||
requestPath := path
|
||||
if projectName, projectPath, found := strings.Cut(path, "/"); found {
|
||||
projectRoot := filepath.Join("www", host, projectName)
|
||||
if file, _ := securejoin.OpenInRoot(dataDir, projectRoot); file != nil {
|
||||
if file, _ := securejoin.OpenInRoot(config.DataDir, projectRoot); file != nil {
|
||||
file.Close()
|
||||
wwwRoot, requestPath = projectRoot, projectPath
|
||||
}
|
||||
}
|
||||
|
||||
// try to serve `$root/$path` first
|
||||
file, err := securejoin.OpenInRoot(dataDir, filepath.Join(wwwRoot, requestPath))
|
||||
file, err := securejoin.OpenInRoot(config.DataDir, filepath.Join(wwwRoot, requestPath))
|
||||
if err == nil {
|
||||
// if it's a directory, serve `$root/$path/index.html`
|
||||
stat, statErr := file.Stat()
|
||||
if statErr == nil && stat.IsDir() {
|
||||
defer file.Close()
|
||||
file, err = securejoin.OpenInRoot(dataDir,
|
||||
file, err = securejoin.OpenInRoot(config.DataDir,
|
||||
filepath.Join(wwwRoot, requestPath, "index.html"))
|
||||
}
|
||||
}
|
||||
// if whatever we were serving doesn't exist, try to serve `$root/404.html`
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
file, _ = securejoin.OpenInRoot(dataDir, filepath.Join(wwwRoot, "404.html"))
|
||||
file, _ = securejoin.OpenInRoot(config.DataDir, filepath.Join(wwwRoot, "404.html"))
|
||||
}
|
||||
|
||||
// acquire read capability to the file being served (if possible)
|
||||
@@ -103,7 +103,7 @@ func getProjectName(w http.ResponseWriter, r *http.Request) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func putPage(dataDir string, w http.ResponseWriter, r *http.Request) error {
|
||||
func putPage(w http.ResponseWriter, r *http.Request) error {
|
||||
host := getHost(r)
|
||||
|
||||
err := authorize(w, r)
|
||||
@@ -130,7 +130,7 @@ func putPage(dataDir string, w http.ResponseWriter, r *http.Request) error {
|
||||
branch = "pages"
|
||||
}
|
||||
|
||||
result := FetchWithTimeout(dataDir, webRoot, repoURL, branch, fetchTimeout)
|
||||
result := FetchWithTimeout(webRoot, repoURL, branch, fetchTimeout)
|
||||
if result.err == nil {
|
||||
w.Header().Add("Content-Location", r.URL.String())
|
||||
}
|
||||
@@ -155,7 +155,7 @@ func putPage(dataDir string, w http.ResponseWriter, r *http.Request) error {
|
||||
return result.err
|
||||
}
|
||||
|
||||
func postPage(dataDir string, w http.ResponseWriter, r *http.Request) error {
|
||||
func postPage(w http.ResponseWriter, r *http.Request) error {
|
||||
host := getHost(r)
|
||||
|
||||
err := authorize(w, r)
|
||||
@@ -199,7 +199,7 @@ func postPage(dataDir string, w http.ResponseWriter, r *http.Request) error {
|
||||
webRoot := fmt.Sprintf("%s/%s", host, projectName)
|
||||
repoURL := event["repository"].(map[string]any)["clone_url"].(string)
|
||||
|
||||
result := FetchWithTimeout(dataDir, webRoot, repoURL, "pages", fetchTimeout)
|
||||
result := FetchWithTimeout(webRoot, repoURL, "pages", fetchTimeout)
|
||||
switch result.outcome {
|
||||
case FetchError:
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
@@ -214,23 +214,21 @@ func postPage(dataDir string, w http.ResponseWriter, r *http.Request) error {
|
||||
return result.err
|
||||
}
|
||||
|
||||
func Serve(dataDir string) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("serve:", r.Method, r.Host, r.URL)
|
||||
err := error(nil)
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
err = getPage(dataDir, w, r)
|
||||
case http.MethodPut:
|
||||
err = putPage(dataDir, w, r)
|
||||
case http.MethodPost:
|
||||
err = postPage(dataDir, w, r)
|
||||
default:
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
err = fmt.Errorf("method %s not allowed", r.Method)
|
||||
}
|
||||
if err != nil {
|
||||
log.Println("serve err:", err)
|
||||
}
|
||||
func Serve(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("serve:", r.Method, r.Host, r.URL)
|
||||
err := error(nil)
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
err = getPage(w, r)
|
||||
case http.MethodPut:
|
||||
err = putPage(w, r)
|
||||
case http.MethodPost:
|
||||
err = postPage(w, r)
|
||||
default:
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
err = fmt.Errorf("method %s not allowed", r.Method)
|
||||
}
|
||||
if err != nil {
|
||||
log.Println("serve err:", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user