Compare commits
10 commits
46b2bb796f
...
857eee205c
| Author | SHA1 | Date | |
|---|---|---|---|
| 857eee205c | |||
|
|
2b1c2a3c46 | ||
|
|
6f7994bfed | ||
|
|
61b5749682 | ||
|
|
bd98d78b8b | ||
|
|
a930028b33 | ||
|
|
84a56864c9 | ||
|
|
e1319660d9 | ||
|
|
6bae5d11c2 | ||
|
|
a10a04e1ce |
11 changed files with 249 additions and 159 deletions
|
|
@ -5,11 +5,11 @@
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
branches:
|
||||||
- 'v*'
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
basic:
|
||||||
runs-on: any
|
runs-on: any
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -45,6 +45,15 @@ jobs:
|
||||||
push: true
|
push: true
|
||||||
pull: true
|
pull: true
|
||||||
tags: git.ronmi.tw/ronmi/forgejo-pages:arm64,ronmi/forgejo-pages:arm64
|
tags: git.ronmi.tw/ronmi/forgejo-pages:arm64,ronmi/forgejo-pages:arm64
|
||||||
|
- name: Build arm64 image (git)
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: git.dockerfile
|
||||||
|
platforms: linux/arm64
|
||||||
|
push: true
|
||||||
|
pull: true
|
||||||
|
tags: git.ronmi.tw/ronmi/forgejo-pages:git-arm64,ronmi/forgejo-pages:git-arm64
|
||||||
- name: Build amd64 binary
|
- name: Build amd64 binary
|
||||||
run: GOARCH=amd64 go build
|
run: GOARCH=amd64 go build
|
||||||
- name: Build amd64 image
|
- name: Build amd64 image
|
||||||
|
|
@ -55,14 +64,24 @@ jobs:
|
||||||
push: true
|
push: true
|
||||||
pull: true
|
pull: true
|
||||||
tags: git.ronmi.tw/ronmi/forgejo-pages:amd64,ronmi/forgejo-pages:amd64
|
tags: git.ronmi.tw/ronmi/forgejo-pages:amd64,ronmi/forgejo-pages:amd64
|
||||||
|
- name: Build amd64 image (git)
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: git.dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
pull: true
|
||||||
|
tags: git.ronmi.tw/ronmi/forgejo-pages:git-amd64,ronmi/forgejo-pages:git-amd64
|
||||||
- name: Create multiarch image
|
- name: Create multiarch image
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create -t git.ronmi.tw/ronmi/forgejo-pages git.ronmi.tw/ronmi/forgejo-pages:arm64 git.ronmi.tw/ronmi/forgejo-pages:amd64
|
docker buildx imagetools create -t git.ronmi.tw/ronmi/forgejo-pages git.ronmi.tw/ronmi/forgejo-pages:arm64 git.ronmi.tw/ronmi/forgejo-pages:amd64
|
||||||
docker buildx imagetools create -t ronmi/forgejo-pages ronmi/forgejo-pages:arm64 ronmi/forgejo-pages:amd64
|
docker buildx imagetools create -t ronmi/forgejo-pages ronmi/forgejo-pages:arm64 ronmi/forgejo-pages:amd64
|
||||||
|
docker buildx imagetools create -t git.ronmi.tw/ronmi/forgejo-pages:git git.ronmi.tw/ronmi/forgejo-pages:git-arm64 git.ronmi.tw/ronmi/forgejo-pages:git-amd64
|
||||||
|
docker buildx imagetools create -t ronmi/forgejo-pages:git ronmi/forgejo-pages:git-arm64 ronmi/forgejo-pages:git-amd64
|
||||||
- name: Update readme to docker hub
|
- name: Update readme to docker hub
|
||||||
uses: https://github.com/peter-evans/dockerhub-description@v4
|
uses: https://github.com/peter-evans/dockerhub-description@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||||
repository: ronmi/forgejo-pages
|
repository: ronmi/forgejo-pages
|
||||||
|
|
||||||
|
|
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
forgejo-pages
|
||||||
|
|
@ -14,7 +14,7 @@ This mode is good for simple setup, like, you have small number of viewers, or y
|
||||||
|
|
||||||
### Webhook mode
|
### Webhook mode
|
||||||
|
|
||||||
Webhook mode is a tool which helps you to download latest content via git. You'll have to setup a webhook in forgejo server in order to notify it when to download new content.
|
Webhook mode is a tool which helps you to download latest content via git. You'll have to setup a webhook in forgejo server in order to notify it when to download new content. You have to have `git` binary in your path.
|
||||||
|
|
||||||
To serve downloaded pages, you'll have to use a web server like Nginx.
|
To serve downloaded pages, you'll have to use a web server like Nginx.
|
||||||
|
|
||||||
|
|
@ -41,9 +41,13 @@ Take care about permissions of the API token. For serve mode, repositories the k
|
||||||
```
|
```
|
||||||
docker run -p 8080:8080 --user 1000:1000 ronmi/forgejo-pages serve -s https://git.example.com -k my-secret-token
|
docker run -p 8080:8080 --user 1000:1000 ronmi/forgejo-pages serve -s https://git.example.com -k my-secret-token
|
||||||
|
|
||||||
docker run -p 8080:8080 --user 1000:1000 -v `pwd`/data:/data ronmi/forgejo-pages listen -u myuser -k my-secret-token -s https://git.example.com -a :8080 -b static-pages -d /data
|
docker run -p 8080:8080 --user 1000:1000 -v `pwd`/data:/data ronmi/forgejo-pages:git listen -u myuser -k my-secret-token -s https://git.example.com -a :8080 -b static-pages -d /data
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Serve mode could use `latest` tag, a minimal image contains only libc, CA certificates, timezone data and page server binary.
|
||||||
|
|
||||||
|
Webhook mode should use `git` tag.
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
### Can I use user.example.com/repo/path format?
|
### Can I use user.example.com/repo/path format?
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.ronmi.tw/ronmi/forgejo-pages/lib"
|
"git.ronmi.tw/ronmi/forgejo-pages/lib"
|
||||||
|
"github.com/raohwork/task"
|
||||||
|
"github.com/raohwork/task/httptask"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
@ -68,7 +70,7 @@ is up to you, eg. Nginx, Apache, or even a simple Go server.
|
||||||
GitPass: token,
|
GitPass: token,
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := lib.UseWebhook(bind, cfg)
|
s, r, err := lib.UseWebhook(bind, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("cannot create server: ", err)
|
fmt.Println("cannot create server: ", err)
|
||||||
return
|
return
|
||||||
|
|
@ -83,14 +85,11 @@ is up to you, eg. Nginx, Apache, or even a simple Go server.
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
fmt.Println("starting server")
|
fmt.Println("starting server")
|
||||||
go func() {
|
task.Wait(
|
||||||
<-ctx.Done()
|
httptask.Server(s, task.Timeout(10*time.Second)),
|
||||||
stop()
|
r.Run,
|
||||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
).Run(ctx)
|
||||||
defer cancel()
|
|
||||||
s.Shutdown(ctx)
|
|
||||||
}()
|
|
||||||
s.ListenAndServe()
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
11
cmd/serve.go
11
cmd/serve.go
|
|
@ -14,6 +14,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.ronmi.tw/ronmi/forgejo-pages/lib"
|
"git.ronmi.tw/ronmi/forgejo-pages/lib"
|
||||||
|
"github.com/raohwork/task"
|
||||||
|
"github.com/raohwork/task/httptask"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
@ -70,14 +72,7 @@ cache/protection layer like Cloudflare in front of the server.
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
fmt.Println("starting server")
|
fmt.Println("starting server")
|
||||||
go func() {
|
httptask.Server(s, task.Timeout(10*time.Second)).Run(ctx)
|
||||||
<-ctx.Done()
|
|
||||||
stop()
|
|
||||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
s.Shutdown(ctx)
|
|
||||||
}()
|
|
||||||
s.ListenAndServe()
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
13
git.dockerfile
Normal file
13
git.dockerfile
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
FROM debian:stable-slim
|
||||||
|
|
||||||
|
# Install git
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
ca-certificates git tzdata \
|
||||||
|
&& apt-get clean -y && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ADD forgejo-pages /usr/bin/forgejo-pages
|
||||||
|
ENTRYPOINT ["/usr/bin/forgejo-pages"]
|
||||||
1
go.mod
1
go.mod
|
|
@ -3,6 +3,7 @@ module git.ronmi.tw/ronmi/forgejo-pages
|
||||||
go 1.21.6
|
go 1.21.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/raohwork/task v0.3.0
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/spf13/viper v1.19.0
|
github.com/spf13/viper v1.19.0
|
||||||
)
|
)
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -26,6 +26,8 @@ github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/raohwork/task v0.3.0 h1:4j0jT1a+f5O+g6q22o42sFEdSlYGYtgUHLbg3cF/7VE=
|
||||||
|
github.com/raohwork/task v0.3.0/go.mod h1:QkVxY/Q/w6bW5Xjhcp8vn5qLgoS+70jUTyGueI0nzMo=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
|
|
||||||
159
lib/git.go
Normal file
159
lib/git.go
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type repoSpec struct {
|
||||||
|
user string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GitRunner struct {
|
||||||
|
ch <-chan repoSpec
|
||||||
|
cfg *WebhookCFG
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitRunner) Run(ctx context.Context) error {
|
||||||
|
// graceful: exit only if all tasks are done
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case repo := <-g.ch:
|
||||||
|
err := g.cfg.download(repo.user, repo.name)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to download repo: ", err)
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebhookCFG) pageDir(user, repo string) string {
|
||||||
|
return filepath.Join(c.PageDir, user, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebhookCFG) gitDir(user, repo string) string {
|
||||||
|
return filepath.Join(c.GitDir, user, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebhookCFG) checkout(user, repo string) (err error) {
|
||||||
|
pageDir := c.pageDir(user, repo)
|
||||||
|
gitDir := c.gitDir(user, repo)
|
||||||
|
|
||||||
|
git := exec.Command(
|
||||||
|
"git",
|
||||||
|
"--git-dir", gitDir,
|
||||||
|
"--work-tree", pageDir,
|
||||||
|
"checkout", "origin/"+c.Branch,
|
||||||
|
)
|
||||||
|
output, err := git.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("git checkout failed: ", err)
|
||||||
|
fmt.Println("=========== Dump output: ============")
|
||||||
|
fmt.Println(string(output))
|
||||||
|
fmt.Println("=====================================")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebhookCFG) fetch(user, repo string) (err error) {
|
||||||
|
gitDir := c.gitDir(user, repo)
|
||||||
|
git := exec.Command(
|
||||||
|
"git",
|
||||||
|
"--git-dir", gitDir,
|
||||||
|
"fetch", "origin", c.Branch,
|
||||||
|
)
|
||||||
|
output, err := git.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("git fetch failed: ", err)
|
||||||
|
fmt.Println("=========== Dump output: ============")
|
||||||
|
fmt.Println(string(output))
|
||||||
|
fmt.Println("=====================================")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebhookCFG) clone(user, repo string) (err error) {
|
||||||
|
pageDir := c.pageDir(user, repo)
|
||||||
|
err = os.MkdirAll(filepath.Dir(pageDir), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gitDir := c.gitDir(user, repo)
|
||||||
|
err = os.MkdirAll(filepath.Dir(gitDir), 0700)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := c.Server
|
||||||
|
uri.Path = "/" + path.Join(user, repo) + ".git"
|
||||||
|
uri.User = url.UserPassword(c.GitUser, c.GitPass)
|
||||||
|
|
||||||
|
git := exec.Command(
|
||||||
|
"git",
|
||||||
|
"clone",
|
||||||
|
"-b", c.Branch,
|
||||||
|
"--single-branch",
|
||||||
|
"--no-tags",
|
||||||
|
"--separate-git-dir", gitDir,
|
||||||
|
uri.String(),
|
||||||
|
pageDir,
|
||||||
|
)
|
||||||
|
output, err := git.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("git clone failed: ", err)
|
||||||
|
fmt.Println("=========== Dump output: ============")
|
||||||
|
fmt.Println(string(output))
|
||||||
|
fmt.Println("=====================================")
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Remove(filepath.Join(pageDir, ".git"))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoLock = &sync.Map{}
|
||||||
|
|
||||||
|
func (c *WebhookCFG) lock(user, repo string) func() {
|
||||||
|
key := user + "/" + repo
|
||||||
|
|
||||||
|
lock, _ := repoLock.LoadOrStore(key, &sync.Mutex{})
|
||||||
|
m := lock.(*sync.Mutex)
|
||||||
|
m.Lock()
|
||||||
|
return m.Unlock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebhookCFG) download(user, repo string) (err error) {
|
||||||
|
unlock := c.lock(user, repo)
|
||||||
|
defer unlock()
|
||||||
|
fmt.Println("Pulling ", user, repo)
|
||||||
|
|
||||||
|
gitDir := c.gitDir(user, repo)
|
||||||
|
if _, err = os.Stat(gitDir); os.IsNotExist(err) {
|
||||||
|
err = c.clone(user, repo)
|
||||||
|
} else {
|
||||||
|
err = c.fetch(user, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.checkout(user, repo)
|
||||||
|
return
|
||||||
|
}
|
||||||
138
lib/hook.go
138
lib/hook.go
|
|
@ -10,12 +10,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebhookCFG struct {
|
type WebhookCFG struct {
|
||||||
|
|
@ -24,123 +19,7 @@ type WebhookCFG struct {
|
||||||
GitDir string
|
GitDir string
|
||||||
GitUser string
|
GitUser string
|
||||||
GitPass string
|
GitPass string
|
||||||
}
|
ch chan<- repoSpec
|
||||||
|
|
||||||
func (c *WebhookCFG) pageDir(user, repo string) string {
|
|
||||||
return filepath.Join(c.PageDir, user, repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *WebhookCFG) gitDir(user, repo string) string {
|
|
||||||
return filepath.Join(c.GitDir, user, repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *WebhookCFG) checkout(user, repo string) (err error) {
|
|
||||||
pageDir := c.pageDir(user, repo)
|
|
||||||
gitDir := c.gitDir(user, repo)
|
|
||||||
|
|
||||||
git := exec.Command(
|
|
||||||
"git",
|
|
||||||
"--git-dir", gitDir,
|
|
||||||
"--work-tree", pageDir,
|
|
||||||
"checkout", "origin/"+c.Branch,
|
|
||||||
)
|
|
||||||
output, err := git.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("git checkout failed: ", err)
|
|
||||||
fmt.Println("=========== Dump output: ============")
|
|
||||||
fmt.Println(string(output))
|
|
||||||
fmt.Println("=====================================")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *WebhookCFG) fetch(user, repo string) (err error) {
|
|
||||||
gitDir := c.gitDir(user, repo)
|
|
||||||
git := exec.Command(
|
|
||||||
"git",
|
|
||||||
"--git-dir", gitDir,
|
|
||||||
"fetch", "origin", c.Branch,
|
|
||||||
)
|
|
||||||
output, err := git.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("git fetch failed: ", err)
|
|
||||||
fmt.Println("=========== Dump output: ============")
|
|
||||||
fmt.Println(string(output))
|
|
||||||
fmt.Println("=====================================")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *WebhookCFG) clone(user, repo string) (err error) {
|
|
||||||
pageDir := c.pageDir(user, repo)
|
|
||||||
err = os.MkdirAll(filepath.Dir(pageDir), 0755)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gitDir := c.gitDir(user, repo)
|
|
||||||
err = os.MkdirAll(filepath.Dir(gitDir), 0700)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uri := c.Server
|
|
||||||
uri.Path = "/" + path.Join(user, repo) + ".git"
|
|
||||||
uri.User = url.UserPassword(c.GitUser, c.GitPass)
|
|
||||||
|
|
||||||
git := exec.Command(
|
|
||||||
"git",
|
|
||||||
"clone",
|
|
||||||
"-b", c.Branch,
|
|
||||||
"--single-branch",
|
|
||||||
"--no-tags",
|
|
||||||
"--separate-git-dir", gitDir,
|
|
||||||
uri.String(),
|
|
||||||
pageDir,
|
|
||||||
)
|
|
||||||
output, err := git.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("git clone failed: ", err)
|
|
||||||
fmt.Println("=========== Dump output: ============")
|
|
||||||
fmt.Println(string(output))
|
|
||||||
fmt.Println("=====================================")
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Remove(filepath.Join(pageDir, ".git"))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var repoLock = &sync.Map{}
|
|
||||||
|
|
||||||
func (c *WebhookCFG) lock(user, repo string) func() {
|
|
||||||
key := user + "/" + repo
|
|
||||||
|
|
||||||
lock, _ := repoLock.LoadOrStore(key, &sync.Mutex{})
|
|
||||||
m := lock.(*sync.Mutex)
|
|
||||||
m.Lock()
|
|
||||||
return m.Unlock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *WebhookCFG) download(user, repo string) (err error) {
|
|
||||||
unlock := c.lock(user, repo)
|
|
||||||
defer unlock()
|
|
||||||
|
|
||||||
gitDir := c.gitDir(user, repo)
|
|
||||||
if _, err = os.Stat(gitDir); os.IsNotExist(err) {
|
|
||||||
err = c.clone(user, repo)
|
|
||||||
} else {
|
|
||||||
err = c.fetch(user, repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.checkout(user, repo)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type webhookPayload struct {
|
type webhookPayload struct {
|
||||||
|
|
@ -185,22 +64,19 @@ func (c *WebhookCFG) handle(w http.ResponseWriter, r *http.Request) {
|
||||||
user, repo := path.Split(payload.Repository.FullName)
|
user, repo := path.Split(payload.Repository.FullName)
|
||||||
user = user[:len(user)-1]
|
user = user[:len(user)-1]
|
||||||
go func() {
|
go func() {
|
||||||
fmt.Println("Pulling ", user, repo)
|
c.ch <- repoSpec{user: user, name: repo}
|
||||||
err = c.download(user, repo)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to download repository: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func UseWebhook(bind string, cfg *WebhookCFG) (*http.Server, error) {
|
func UseWebhook(bind string, cfg *WebhookCFG) (*http.Server, *GitRunner, error) {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil, errors.New("webhook config is nil")
|
return nil, nil, errors.New("webhook config is nil")
|
||||||
}
|
}
|
||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
Addr: bind,
|
Addr: bind,
|
||||||
Handler: http.HandlerFunc(cfg.handle),
|
Handler: http.HandlerFunc(cfg.handle),
|
||||||
}
|
}
|
||||||
return s, nil
|
ch := make(chan repoSpec, 5)
|
||||||
|
cfg.ch = ch
|
||||||
|
return s, &GitRunner{ch: ch, cfg: cfg}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
lib/web.go
31
lib/web.go
|
|
@ -12,6 +12,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"mime"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Forgejo struct {
|
type Forgejo struct {
|
||||||
|
|
@ -34,7 +36,7 @@ func (f *Forgejo) GetFile(ctx context.Context, headers map[string]string, user,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
if v == "" {
|
if v == "" {
|
||||||
continue
|
continue
|
||||||
|
|
@ -50,7 +52,7 @@ func (f *Forgejo) GetFile(ctx context.Context, headers map[string]string, user,
|
||||||
err = ErrNotFound
|
err = ErrNotFound
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,24 +104,43 @@ func (f *Forgejo) handle(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
file = strings.Join(arr[2:], "/")
|
file = strings.Join(arr[2:], "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
headers := map[string]string{}
|
headers := map[string]string{}
|
||||||
headers["If-None-Match"] = r.Header.Get("If-None-Match")
|
headers["If-None-Match"] = r.Header.Get("If-None-Match")
|
||||||
headers["If-Modified-Since"] = r.Header.Get("If-Modified-Since")
|
headers["If-Modified-Since"] = r.Header.Get("If-Modified-Since")
|
||||||
headers["If-Range"] = r.Header.Get("If-Range")
|
headers["If-Range"] = r.Header.Get("If-Range")
|
||||||
headers["Range"] = r.Header.Get("Range")
|
headers["Range"] = r.Header.Get("Range")
|
||||||
|
headers["X-Forwarded-For"] = r.Header.Get("X-Forwarded-For")
|
||||||
|
headers["X-Forwarded-Host"] = r.Header.Get("X-Forwarded-Host")
|
||||||
|
headers["X-Forwarded-Proto"] = r.Header.Get("X-Forwarded-Proto")
|
||||||
|
headers["X-Real-IP"] = r.Header.Get("X-Real-IP")
|
||||||
|
headers["X-Host"] = r.Header.Get("X-Host")
|
||||||
|
headers["CF-Connecting-IP"] = r.Header.Get("CF-Connecting-IP")
|
||||||
|
headers["CF-IPCountry"] = r.Header.Get("CF-IPCountry")
|
||||||
|
headers["CF-Visitor"] = r.Header.Get("CF-Visitor")
|
||||||
|
headers["CF-Request-ID"] = r.Header.Get("CF-Request-ID")
|
||||||
|
headers["CF-Ray"] = r.Header.Get("CF-Ray")
|
||||||
resp, err := f.GetFile(r.Context(), headers, user, repo, f.Branch, file)
|
resp, err := f.GetFile(r.Context(), headers, user, repo, f.Branch, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
trySet(w.Header(), "Etag", resp.Header)
|
trySet(w.Header(), "Etag", resp.Header)
|
||||||
trySet(w.Header(), "Last-Modified", resp.Header)
|
trySet(w.Header(), "Last-Modified", resp.Header)
|
||||||
trySet(w.Header(), "Content-Length", resp.Header)
|
trySet(w.Header(), "Content-Length", resp.Header)
|
||||||
trySet(w.Header(), "Content-Range", resp.Header)
|
trySet(w.Header(), "Content-Range", resp.Header)
|
||||||
|
contentType := resp.Header.Get("Content-Type")
|
||||||
|
if contentType == "" || strings.HasPrefix(contentType, "text/plain") {
|
||||||
|
if ct := mime.TypeByExtension(filepath.Ext(file)); ct != "" {
|
||||||
|
contentType = ct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if contentType != "" {
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
}
|
||||||
|
|
||||||
w.WriteHeader(resp.StatusCode)
|
w.WriteHeader(resp.StatusCode)
|
||||||
io.Copy(w, resp.Body)
|
io.Copy(w, resp.Body)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue