mirror of
https://github.com/pldubouilh/gossa.git
synced 2026-09-01 06:46:50 -04:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eaf1342559 | ||
|
|
31c8ee518a | ||
|
|
cc703cd70d | ||
|
|
c963d1773b | ||
|
|
623acc30a6 | ||
|
|
746d6d55bc |
@@ -6,20 +6,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19.1
|
go-version: 1.23.0
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: deps
|
|
||||||
run: npm i -g standard
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
|
||||||
uses: actions/checkout@v1
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Run
|
- name: Run
|
||||||
run: make ci
|
run: make ci
|
||||||
|
|
||||||
|
|||||||
@@ -10,33 +10,25 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v1
|
|
||||||
with:
|
|
||||||
go-version: 1.19.1
|
|
||||||
id: go
|
|
||||||
|
|
||||||
- name: Set env
|
- name: Set env
|
||||||
run: echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
|
run: echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1.2.0
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1.6.0
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v1.10.0
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Build and release on dockerhub
|
- name: Build and release on dockerhub
|
||||||
uses: docker/build-push-action@v2.7.0
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
file: support/build.Dockerfile
|
file: support/build.Dockerfile
|
||||||
push: true
|
push: true
|
||||||
@@ -47,17 +39,19 @@ jobs:
|
|||||||
run: make build-all
|
run: make build-all
|
||||||
|
|
||||||
- name: "Release gh release versioned"
|
- name: "Release gh release versioned"
|
||||||
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: "builds/*"
|
artifacts: "builds/*"
|
||||||
|
bodyFile: "builds/buildout"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: "Release gh release latest"
|
- name: "Release gh release latest"
|
||||||
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
tag: latest
|
tag: latest
|
||||||
name: Latest
|
name: Latest
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: "builds/*"
|
artifacts: "builds/*"
|
||||||
|
bodyFile: "builds/buildout"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
name: rc
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'rc/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v1
|
|
||||||
with:
|
|
||||||
go-version: 1.19.1
|
|
||||||
id: go
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
|
||||||
uses: actions/checkout@v1
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Run
|
|
||||||
run: make ci
|
|
||||||
|
|
||||||
- name: Build all artifacts
|
|
||||||
run: make build-all
|
|
||||||
|
|
||||||
- name: "Release gh release prerelease"
|
|
||||||
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37
|
|
||||||
with:
|
|
||||||
allowUpdates: true
|
|
||||||
prerelease: true
|
|
||||||
tag: "rc"
|
|
||||||
artifacts: "builds/*"
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FLAGS := -ldflags "-s -w" -trimpath
|
FLAGS := -trimpath
|
||||||
NOCGO := CGO_ENABLED=0
|
NOCGO := CGO_ENABLED=0
|
||||||
|
|
||||||
build::
|
build::
|
||||||
@@ -17,10 +17,7 @@ run-ro::
|
|||||||
run-extra::
|
run-extra::
|
||||||
./gossa -verb=true -prefix="/fancy-path/" -k=false -symlinks=true test-fixture
|
./gossa -verb=true -prefix="/fancy-path/" -k=false -symlinks=true test-fixture
|
||||||
|
|
||||||
lint-js::
|
ci:: build-all test
|
||||||
standard
|
|
||||||
|
|
||||||
ci:: build-all test lint-js
|
|
||||||
echo "done"
|
echo "done"
|
||||||
|
|
||||||
test::
|
test::
|
||||||
@@ -59,13 +56,14 @@ watch-test::
|
|||||||
ls gossa.go gossa_test.go ui/* | entr -rc make test
|
ls gossa.go gossa_test.go ui/* | entr -rc make test
|
||||||
|
|
||||||
build-all:: build
|
build-all:: build
|
||||||
${NOCGO} GOOS=linux GOARCH=amd64 go build ${FLAGS} -o builds/gossa-linux-x64
|
go version
|
||||||
${NOCGO} GOOS=linux GOARCH=arm go build ${FLAGS} -o builds/gossa-linux-arm
|
${NOCGO} GOOS=linux GOARCH=amd64 go build ${FLAGS} -o builds/gossa-linux-x64
|
||||||
${NOCGO} GOOS=linux GOARCH=arm64 go build ${FLAGS} -o builds/gossa-linux-arm64
|
${NOCGO} GOOS=linux GOARCH=arm go build ${FLAGS} -o builds/gossa-linux-arm
|
||||||
${NOCGO} GOOS=darwin GOARCH=amd64 go build ${FLAGS} -o builds/gossa-mac-x64
|
${NOCGO} GOOS=linux GOARCH=arm64 go build ${FLAGS} -o builds/gossa-linux-arm64
|
||||||
${NOCGO} GOOS=darwin GOARCH=arm64 go build ${FLAGS} -o builds/gossa-mac-arm64
|
${NOCGO} GOOS=darwin GOARCH=amd64 go build ${FLAGS} -o builds/gossa-mac-x64
|
||||||
${NOCGO} GOOS=windows GOARCH=amd64 go build ${FLAGS} -o builds/gossa-windows.exe
|
${NOCGO} GOOS=darwin GOARCH=arm64 go build ${FLAGS} -o builds/gossa-mac-arm64
|
||||||
sha256sum builds/*
|
${NOCGO} GOOS=windows GOARCH=amd64 go build ${FLAGS} -o builds/gossa-windows.exe
|
||||||
|
sha256sum builds/* | tee builds/buildout
|
||||||
|
|
||||||
clean::
|
clean::
|
||||||
rm -f gossa
|
rm -f gossa
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module github.com/pldubouilh/gossa
|
module github.com/pldubouilh/gossa
|
||||||
|
|
||||||
go 1.16
|
go 1.23.0
|
||||||
|
|||||||
@@ -45,11 +45,6 @@ var verb = flag.Bool("verb", false, "verbosity")
|
|||||||
var skipHidden = flag.Bool("k", true, "\nskip hidden files")
|
var skipHidden = flag.Bool("k", true, "\nskip hidden files")
|
||||||
var ro = flag.Bool("ro", false, "read only mode (no upload, rename, move, etc...)")
|
var ro = flag.Bool("ro", false, "read only mode (no upload, rename, move, etc...)")
|
||||||
|
|
||||||
type rpcCall struct {
|
|
||||||
Call string `json:"call"`
|
|
||||||
Args []string `json:"args"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var rootPath = ""
|
var rootPath = ""
|
||||||
var handler http.Handler
|
var handler http.Handler
|
||||||
|
|
||||||
@@ -134,7 +129,7 @@ func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path str
|
|||||||
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.Header().Add("Content-Encoding", "gzip")
|
w.Header().Add("Content-Encoding", "gzip")
|
||||||
gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) // BestSpeed is Much Faster than default - base on a very unscientific local test, and only ~30% larger (compression remains still very effective, ~6x)
|
gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) // BestSpeed is Much Faster than default - base on a very unscientific local test
|
||||||
check(err)
|
check(err)
|
||||||
defer gz.Close()
|
defer gz.Close()
|
||||||
tmpl.Execute(gz, p)
|
tmpl.Execute(gz, p)
|
||||||
@@ -151,7 +146,8 @@ func doContent(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
path := html.UnescapeString(r.URL.Path)
|
path := html.UnescapeString(r.URL.Path)
|
||||||
defer exitPath(w, "get content", path)
|
defer exitPath(w, "get content", path)
|
||||||
fullPath := enforcePath(path)
|
fullPath, err := enforcePath(path)
|
||||||
|
check(err)
|
||||||
stat, errStat := os.Stat(fullPath)
|
stat, errStat := os.Stat(fullPath)
|
||||||
check(errStat)
|
check(errStat)
|
||||||
|
|
||||||
@@ -174,7 +170,9 @@ func upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil && err != io.EOF { // errs EOF when no more parts to process
|
if err != nil && err != io.EOF { // errs EOF when no more parts to process
|
||||||
check(err)
|
check(err)
|
||||||
}
|
}
|
||||||
dst, err := os.Create(enforcePath(path))
|
path, err = enforcePath(path)
|
||||||
|
check(err)
|
||||||
|
dst, err := os.Create(path)
|
||||||
check(err)
|
check(err)
|
||||||
io.Copy(dst, part)
|
io.Copy(dst, part)
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
@@ -184,8 +182,9 @@ func zipRPC(w http.ResponseWriter, r *http.Request) {
|
|||||||
zipPath := r.URL.Query().Get("zipPath")
|
zipPath := r.URL.Query().Get("zipPath")
|
||||||
zipName := r.URL.Query().Get("zipName")
|
zipName := r.URL.Query().Get("zipName")
|
||||||
defer exitPath(w, "zip", zipPath)
|
defer exitPath(w, "zip", zipPath)
|
||||||
zipFullPath := enforcePath(zipPath)
|
zipFullPath, err := enforcePath(zipPath)
|
||||||
_, err := os.Lstat(zipFullPath)
|
check(err)
|
||||||
|
_, err = os.Lstat(zipFullPath)
|
||||||
check(err)
|
check(err)
|
||||||
w.Header().Add("Content-Disposition", "attachment; filename=\""+zipName+".zip\"")
|
w.Header().Add("Content-Disposition", "attachment; filename=\""+zipName+".zip\"")
|
||||||
zipWriter := zip.NewWriter(w)
|
zipWriter := zip.NewWriter(w)
|
||||||
@@ -203,7 +202,7 @@ func zipRPC(w http.ResponseWriter, r *http.Request) {
|
|||||||
return nil // hidden files not allowed
|
return nil // hidden files not allowed
|
||||||
}
|
}
|
||||||
if f.Mode()&os.ModeSymlink != 0 {
|
if f.Mode()&os.ModeSymlink != 0 {
|
||||||
panic(errors.New("symlink not allowed in zip downloads")) // filepath.Walk doesnt support symlinks
|
check(errors.New("symlink not allowed in zip downloads")) // filepath.Walk doesnt support symlinks
|
||||||
}
|
}
|
||||||
|
|
||||||
header, err := zip.FileInfoHeader(f)
|
header, err := zip.FileInfoHeader(f)
|
||||||
@@ -224,39 +223,52 @@ func zipRPC(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func rpc(w http.ResponseWriter, r *http.Request) {
|
func rpc(w http.ResponseWriter, r *http.Request) {
|
||||||
var err error
|
type rpcCall struct {
|
||||||
|
Call string `json:"call"`
|
||||||
|
Args []string `json:"args"`
|
||||||
|
}
|
||||||
var rpc rpcCall
|
var rpc rpcCall
|
||||||
defer exitPath(w, "rpc", rpc)
|
defer exitPath(w, "rpc", rpc)
|
||||||
bodyBytes, err := io.ReadAll(r.Body)
|
bodyBytes, err := io.ReadAll(r.Body)
|
||||||
check(err)
|
check(err)
|
||||||
json.Unmarshal(bodyBytes, &rpc)
|
json.Unmarshal(bodyBytes, &rpc)
|
||||||
|
|
||||||
|
path0, err := enforcePath(rpc.Args[0])
|
||||||
|
path1 := ""
|
||||||
|
check(err)
|
||||||
|
if len(rpc.Args) > 1 {
|
||||||
|
path1, err = enforcePath(rpc.Args[1])
|
||||||
|
check(err)
|
||||||
|
}
|
||||||
|
|
||||||
if rpc.Call == "mkdirp" {
|
if rpc.Call == "mkdirp" {
|
||||||
err = os.MkdirAll(enforcePath(rpc.Args[0]), os.ModePerm)
|
err = os.MkdirAll(path0, os.ModePerm)
|
||||||
} else if rpc.Call == "mv" {
|
} else if rpc.Call == "mv" && len(rpc.Args) == 2 {
|
||||||
err = os.Rename(enforcePath(rpc.Args[0]), enforcePath(rpc.Args[1]))
|
err = os.Rename(path0, path1)
|
||||||
} else if rpc.Call == "rm" {
|
} else if rpc.Call == "rm" {
|
||||||
err = os.RemoveAll(enforcePath(rpc.Args[0]))
|
err = os.RemoveAll(path0)
|
||||||
|
} else {
|
||||||
|
err = errors.New("invalid rpc call")
|
||||||
}
|
}
|
||||||
|
|
||||||
check(err)
|
check(err)
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func enforcePath(p string) string {
|
func enforcePath(p string) (string, error) {
|
||||||
joined := filepath.Join(rootPath, strings.TrimPrefix(p, *extraPath))
|
joined := filepath.Join(rootPath, strings.TrimPrefix(p, *extraPath))
|
||||||
fp, err := filepath.Abs(joined)
|
fp, err := filepath.Abs(joined)
|
||||||
sl, _ := filepath.EvalSymlinks(fp) // err skipped as it would error for unexistent files (RPC check). The actual behaviour is tested below
|
sl, _ := filepath.EvalSymlinks(fp) // err skipped as it would error for inexistent files (RPC check). The actual behaviour is tested below
|
||||||
|
|
||||||
// panic if we had a error getting absolute path,
|
// panic if we had a error getting absolute path,
|
||||||
// ... or if path doesnt contain the prefix path we expect,
|
// ... or if path doesnt contain the prefix path we expect,
|
||||||
// ... or if we're skipping hidden folders, and one is requested,
|
// ... or if we're skipping hidden folders, and one is requested,
|
||||||
// ... or if we're skipping symlinks, path exists, and a symlink out of bound requested
|
// ... or if we're skipping symlinks, path exists, and a symlink out of bound requested
|
||||||
if err != nil || !strings.HasPrefix(fp, rootPath) || *skipHidden && strings.Contains(p, "/.") || !*symlinks && len(sl) > 0 && !strings.HasPrefix(sl, rootPath) {
|
if err != nil || !strings.HasPrefix(fp, rootPath) || *skipHidden && strings.Contains(p, "/.") || !*symlinks && len(sl) > 0 && !strings.HasPrefix(sl, rootPath) {
|
||||||
panic(errors.New("invalid path"))
|
return "", errors.New("invalid path")
|
||||||
}
|
}
|
||||||
|
|
||||||
return fp
|
return fp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ a simple UI comes as default, featuring :
|
|||||||
|
|
||||||
[nix](https://search.nixos.org/packages?channel=unstable&show=gossa&from=0&size=50&sort=relevance&type=packages&query=gossa) - e.g. `nix-shell -p gossa`
|
[nix](https://search.nixos.org/packages?channel=unstable&show=gossa&from=0&size=50&sort=relevance&type=packages&query=gossa) - e.g. `nix-shell -p gossa`
|
||||||
|
|
||||||
binaries are available on the [release page](https://github.com/pldubouilh/gossa/releases) - or simply `make build` this repo.
|
[mpr](https://mpr.makedeb.org/packages/gossa)
|
||||||
|
|
||||||
|
binaries are available on the [release page](https://github.com/pldubouilh/gossa/releases)
|
||||||
|
|
||||||
all builds are reproducible, checkout the hashes on the release page.
|
all builds are reproducible, checkout the hashes on the release page.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
FROM golang:1.18 as builder
|
FROM docker.io/library/golang:1.23.0-alpine AS builder
|
||||||
|
RUN apk add --no-cache make
|
||||||
WORKDIR /gossaSrc
|
WORKDIR /gossaSrc
|
||||||
COPY . /gossaSrc
|
COPY . /gossaSrc
|
||||||
RUN make
|
RUN make
|
||||||
|
|||||||
+2
-1
@@ -1 +1,2 @@
|
|||||||
B!!!
|
B!!!
|
||||||
|
test
|
||||||
Vendored
+23
-12
@@ -153,8 +153,11 @@ function rpc (call, args, cb) {
|
|||||||
xhr.open('POST', location.origin + window.extraPath + '/rpc')
|
xhr.open('POST', location.origin + window.extraPath + '/rpc')
|
||||||
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
|
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
|
||||||
xhr.send(JSON.stringify({ call, args }))
|
xhr.send(JSON.stringify({ call, args }))
|
||||||
xhr.onload = cb
|
xhr.onload = () => cb(false)
|
||||||
xhr.onerror = () => flicker(sadBadge)
|
xhr.onerror = () => {
|
||||||
|
flicker(sadBadge)
|
||||||
|
cb(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mkdirCall = (path, cb) => rpc('mkdirp', [prependPath(path)], cb)
|
const mkdirCall = (path, cb) => rpc('mkdirp', [prependPath(path)], cb)
|
||||||
@@ -315,28 +318,36 @@ const textTypes = ['.txt', '.rtf', '.md', '.markdown', '.log', '.yaml', '.yml']
|
|||||||
const isTextFile = src => src && textTypes.find(type => src.toLocaleLowerCase().includes(type))
|
const isTextFile = src => src && textTypes.find(type => src.toLocaleLowerCase().includes(type))
|
||||||
let fileEdited
|
let fileEdited
|
||||||
|
|
||||||
function saveText (quitting) {
|
function saveText (cb) {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append(fileEdited, editor.value)
|
formData.append(fileEdited, editor.value)
|
||||||
const path = encodeURIComponent(decodeURI(location.pathname) + fileEdited)
|
const fname = fileEdited + ".swp"
|
||||||
|
const path = encodeURIComponent(decodeURI(location.pathname) + fname)
|
||||||
upload(0, formData, path, () => {
|
upload(0, formData, path, () => {
|
||||||
toast.style.display = 'none'
|
toast.style.display = 'none'
|
||||||
if (!quitting) return
|
cb()
|
||||||
clearInterval(window.padTimer)
|
|
||||||
window.onbeforeunload = null
|
|
||||||
resetView()
|
|
||||||
softPrev()
|
|
||||||
refresh()
|
|
||||||
}, () => {
|
}, () => {
|
||||||
toast.style.display = 'block'
|
toast.style.display = 'block'
|
||||||
if (!quitting) return
|
|
||||||
alert('cant save!\r\nleave window open to resume saving\r\nwhen connection back up')
|
alert('cant save!\r\nleave window open to resume saving\r\nwhen connection back up')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function padOff () {
|
function padOff () {
|
||||||
if (!isEditorMode()) { return }
|
if (!isEditorMode()) { return }
|
||||||
saveText(true)
|
const swapfile = fileEdited + ".swp"
|
||||||
|
saveText(() => {
|
||||||
|
mvCall(prependPath(swapfile), prependPath(fileEdited), err => {
|
||||||
|
if (err) {
|
||||||
|
alert('cant save!\r\nleave window open to resume saving\r\nwhen connection back up')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clearInterval(window.padTimer)
|
||||||
|
window.onbeforeunload = null
|
||||||
|
resetView()
|
||||||
|
softPrev()
|
||||||
|
refresh()
|
||||||
|
})
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user