init
This commit is contained in:
commit
9e3aefc525
4 changed files with 193 additions and 0 deletions
10
README.md
Normal file
10
README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# docs.brut.systems
|
||||
|
||||
This program acts as a proxy to serve files directly from repositories.
|
||||
|
||||
It expects the following environment variables to be set:
|
||||
|
||||
- `API_URL` - Base Forgejo API url (ex. `https://git.brut.systems`)
|
||||
- `API_TOKEN` - Forgejo API authorization token
|
||||
- `REDIRECT_URL` - Redirect url when access fails or files do not exist (ex. `https://git.brut.systems`)
|
||||
- `PORT` - Port to listen on
|
||||
3
go.mod
Normal file
3
go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module git.brut.systems/judah/docs.brut.systems
|
||||
|
||||
go 1.25.0
|
||||
0
go.sum
Normal file
0
go.sum
Normal file
180
main.go
Normal file
180
main.go
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
API_URL string
|
||||
API_TOKEN string
|
||||
REDIRECT_URL string
|
||||
PORT string
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
http.ServeMux
|
||||
}
|
||||
|
||||
func (sv *Server) getfile(user, repo, path string) ([]byte, error) {
|
||||
var (
|
||||
endpoint = endpoint("/repos/%s/%s/contents/%s", user, repo, url.PathEscape(path))
|
||||
)
|
||||
|
||||
req, err := http.NewRequest("GET", endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("accept", "application/json")
|
||||
req.Header.Add("Authorization", API_TOKEN)
|
||||
|
||||
log.Printf("GET %q", endpoint)
|
||||
|
||||
var client http.Client
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("response had unexpected status %d - %s", res.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Encoding string `json:"encoding"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.Encoding != "base64" {
|
||||
return nil, fmt.Errorf("expected base64 encoding, found %q", response.Encoding)
|
||||
}
|
||||
|
||||
file, err := base64.StdEncoding.DecodeString(response.Content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
if API_URL, err = getvar("API_URL"); err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
if _, err := url.Parse(API_URL); err != nil {
|
||||
log.Fatalf("invalid API_URL %q", err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(API_URL, "http") {
|
||||
log.Fatalf("invalid API_URL %q - must being with http(s)://", API_URL)
|
||||
}
|
||||
}
|
||||
|
||||
if REDIRECT_URL, err = getvar("REDIRECT_URL"); err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
if _, err := url.Parse(REDIRECT_URL); err != nil {
|
||||
log.Fatalf("invalid REDIRECT_URL %q", err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(REDIRECT_URL, "http") {
|
||||
log.Fatalf("invalid REDIRECT_URL %q - must begin with \"http(s)://\"", REDIRECT_URL)
|
||||
}
|
||||
}
|
||||
|
||||
if token, err := getvar("API_TOKEN"); err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
API_TOKEN = "token " + token
|
||||
}
|
||||
|
||||
if PORT, err = getvar("PORT"); err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
if _, err = strconv.ParseInt(PORT, 10, 64); err != nil {
|
||||
log.Fatalf("invalid port number %q", PORT)
|
||||
}
|
||||
}
|
||||
|
||||
redirect := func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, REDIRECT_URL, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
sv := new(Server)
|
||||
sv.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
redirect(w, r)
|
||||
})
|
||||
|
||||
sv.HandleFunc("/{user}/{repo}/", func(w http.ResponseWriter, r *http.Request) {
|
||||
user := strings.TrimSpace(r.PathValue("user"))
|
||||
repo := strings.TrimSpace(r.PathValue("repo"))
|
||||
|
||||
if len(user) == 0 || len(repo) == 0 {
|
||||
redirect(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
path := r.URL.Path
|
||||
path = strings.TrimSpace(path[strings.Index(path, repo)+len(repo):])
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
|
||||
if len(path) == 0 {
|
||||
path = "docs/index.html"
|
||||
}
|
||||
|
||||
log.Printf("fetching file content %s:%s - %q", user, repo, path)
|
||||
|
||||
content, err := sv.getfile(user, repo, path)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
redirect(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(content); err != nil {
|
||||
log.Println(err)
|
||||
redirect(w, r)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
addr := fmt.Sprintf(":%s", PORT)
|
||||
log.Printf("Listening at %s", addr)
|
||||
|
||||
if err := http.ListenAndServe(addr, sv); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getvar(name string) (string, error) {
|
||||
envvar := strings.TrimSpace(os.Getenv(name))
|
||||
if len(envvar) == 0 {
|
||||
return "", fmt.Errorf("required environment variable %q was not set", name)
|
||||
}
|
||||
return envvar, nil
|
||||
}
|
||||
|
||||
func endpoint(endpoint string, args ...any) string {
|
||||
url, _ := url.JoinPath(API_URL, "api/v1", fmt.Sprintf(endpoint, args...))
|
||||
return url
|
||||
}
|
||||
Loading…
Reference in a new issue