Compare commits
18 Commits
644063b987
...
1d6eec3291
Author | SHA1 | Date |
---|---|---|
blek | 1d6eec3291 | |
blek | 9764ee5964 | |
blek | e00dd4b8d7 | |
blek | 3687d8c907 | |
blek | 434906047f | |
blek | bc608863cf | |
blek | d299ea6e76 | |
blek | 27e386fb35 | |
blek | 84fa988538 | |
blek | 9463b24a4d | |
blek | 45c47d9b0c | |
blek | b06f0a3f75 | |
blek | 13c2bb6407 | |
blek | 280f1a038b | |
blek | 3f859dac77 | |
blek | ed77a9fc27 | |
blek | 67f69aa361 | |
blek | a3f821a0d0 |
|
@ -4,4 +4,8 @@
|
|||
uri * strip_prefix /qr
|
||||
reverse_proxy http://qr
|
||||
}
|
||||
handle /resource/* {
|
||||
uri * strip_prefix /resource
|
||||
reverse_proxy http://resourced
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
FROM golang:alpine
|
||||
|
||||
RUN go install github.com/cosmtrek/air@latest
|
||||
|
||||
WORKDIR /opt/code
|
||||
|
||||
# The directory will be mounted
|
||||
# COPY . .
|
||||
|
||||
CMD [ "air", "-c", ".air.toml" ]
|
|
@ -7,6 +7,6 @@ RUN cargo install cargo-watch && \
|
|||
|
||||
RUN apt update && \
|
||||
apt install nodejs npm -y --no-install-recommends && \
|
||||
npm i -g uglify-js
|
||||
npm i -g uglify-js html-minifier
|
||||
|
||||
CMD [ "/opt/code/dev-entry.sh" ]
|
||||
|
|
|
@ -21,6 +21,15 @@ services:
|
|||
volumes:
|
||||
- './janitor:/opt/code'
|
||||
- './volatile/files:/opt/user_uploads'
|
||||
resourced:
|
||||
build:
|
||||
context: containers
|
||||
dockerfile: go-dev.Dockerfile
|
||||
networks:
|
||||
bfile:
|
||||
volumes:
|
||||
- './resource:/opt/code'
|
||||
- '/opt/code/tmp'
|
||||
caddy:
|
||||
image: caddy:alpine
|
||||
volumes:
|
||||
|
|
|
@ -19,6 +19,15 @@ services:
|
|||
volumes:
|
||||
- './janitor:/config:ro'
|
||||
- './volatile/files:/opt/user_uploads'
|
||||
resourced:
|
||||
build:
|
||||
context: resource
|
||||
dockerfile: Dockerfile.prod
|
||||
networks:
|
||||
bfile:
|
||||
pid: host # prefork
|
||||
volumes:
|
||||
- './resource:/opt/cont'
|
||||
caddy:
|
||||
image: caddy:alpine
|
||||
volumes:
|
||||
|
|
|
@ -8,8 +8,24 @@
|
|||
const root_drag_rop = document.getElementsByClassName('file-drag-n-drop')[0];
|
||||
|
||||
// make the root drag&drop element an ideal circle
|
||||
root_drag_rop.style.width = root_drag_rop.offsetWidth + 'px';
|
||||
root_drag_rop.style.height = root_drag_rop.offsetWidth + 'px';
|
||||
|
||||
function updateDragNDrop() {
|
||||
|
||||
if (document.body.scrollWidth < 667) {
|
||||
// mobile
|
||||
delete root_drag_rop.style.width;
|
||||
delete root_drag_rop.style.height;
|
||||
return
|
||||
}
|
||||
|
||||
const width = root_drag_rop.offsetWidth;
|
||||
|
||||
root_drag_rop.style.width = width + 'px';
|
||||
root_drag_rop.style.height = width + 'px';
|
||||
}
|
||||
|
||||
updateDragNDrop();
|
||||
document.onresize = updateDragNDrop();
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
const drag_rop = document.getElementsByClassName('file-drag-n-drop-inside')[0];
|
||||
|
|
|
@ -16,6 +16,22 @@
|
|||
<title>{{ conf.brand.instance_name }}</title>
|
||||
|
||||
<style> .footer svg { height: 32px; margin: 10px 0 } </style>
|
||||
<style>
|
||||
.header .header-text, .header .header-home, .header .header-bg { transition: 250ms ease; }
|
||||
@media (max-width:667px) {
|
||||
|
||||
.header .header-text { text-align: center }
|
||||
.header .header-home { width: 100% }
|
||||
.header .header-bg { left: 50% !important; transform: translateX(-50%); -webkit-mask-image: linear-gradient(90deg, #0000, #000, #0000); }
|
||||
|
||||
{%- if conf.brand.instance_motto.len() != 0 -%}
|
||||
.header .header-home-motto {
|
||||
display:none
|
||||
}
|
||||
{%- endif -%}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
{%- if cfg!(debug_assertions) -%}
|
||||
<link rel="stylesheet" href="/alert.css" />
|
||||
|
@ -33,7 +49,7 @@
|
|||
{{- conf.brand.instance_name -}}
|
||||
{%- if conf.brand.instance_motto.len() != 0 -%}
|
||||
{#- Whitespace control is stupid -#}
|
||||
{{- " - " -}}{{- conf.brand.instance_motto -}}
|
||||
<span class="header-home-motto">{{- " - " -}}{{- conf.brand.instance_motto -}}</span>
|
||||
{%- endif -%}
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./tmp/main"
|
||||
cmd = "go build -o ./tmp/main ."
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||
exclude_file = []
|
||||
exclude_regex = [ ".*\\.md" ]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
|
@ -0,0 +1,5 @@
|
|||
resourced.toml
|
||||
resourced
|
||||
.DS_Store
|
||||
.env
|
||||
tmp
|
|
@ -0,0 +1,22 @@
|
|||
FROM golang:alpine3.17 as builder
|
||||
|
||||
WORKDIR /opt/build
|
||||
COPY . .
|
||||
|
||||
RUN apk add --no-cache git musl-dev upx binutils
|
||||
|
||||
RUN go build . && \
|
||||
strip resourced && \
|
||||
upx resourced
|
||||
|
||||
FROM alpine:3.17
|
||||
|
||||
WORKDIR /opt/code
|
||||
COPY --from=builder /opt/build/resourced /usr/bin/resourced
|
||||
|
||||
# Note
|
||||
# -----
|
||||
# Since this is running with prefork, don't
|
||||
# forget to set --pid=host when running this app
|
||||
|
||||
CMD [ "sh", "-c", "/usr/bin/resourced" ]
|
|
@ -0,0 +1,15 @@
|
|||
# resourceD
|
||||
A simple microservice for serving resources
|
||||
|
||||
## Building
|
||||
```sh
|
||||
go build .
|
||||
```
|
||||
|
||||
## Running
|
||||
Either run it the normal way via the provided docker compose file, or `go run .`.
|
||||
|
||||
Also dont forget to create `resourced.toml`
|
||||
|
||||
## Configuration
|
||||
The file `resourced.toml.example` serves as a both an example and reference, since it has a lot of comments.
|
|
@ -0,0 +1,48 @@
|
|||
openapi: 3.0.3
|
||||
info:
|
||||
title: ResourceD API
|
||||
description: |-
|
||||
This is the ResourceD API docs.
|
||||
license:
|
||||
name: GPLv3
|
||||
url: https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
version: "1.0"
|
||||
servers:
|
||||
- url: http://localhost/resource
|
||||
tags:
|
||||
- name: Data API
|
||||
description: API for serving data
|
||||
- name: System API
|
||||
description: API for serving system data
|
||||
|
||||
paths:
|
||||
/{id}:
|
||||
get:
|
||||
description: Get a resource
|
||||
summary: Get a resource. Send browser requests to this URL
|
||||
tags:
|
||||
- Data API
|
||||
responses:
|
||||
200:
|
||||
description: Returns a resource in its binary data
|
||||
302:
|
||||
description: Resource is an external (http) link and the redirect is being forwarded to that link
|
||||
404:
|
||||
description: Not found
|
||||
500:
|
||||
description: Internal error
|
||||
/info/is_enabled:
|
||||
get:
|
||||
description: Check if resourceD is enabled
|
||||
summary: Check if resourceD is enabled
|
||||
tags:
|
||||
- System API
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
example: true
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
module blek/resourced
|
||||
|
||||
go 1.21.3
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gofiber/fiber/v2 v2.50.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.50.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
)
|
|
@ -0,0 +1,31 @@
|
|||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/gofiber/fiber/v2 v2.50.0 h1:ia0JaB+uw3GpNSCR5nvC5dsaxXjRU5OEu36aytx+zGw=
|
||||
github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
|
||||
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
@ -0,0 +1,158 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
import (
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
Url string `toml:"url"`
|
||||
Proxied bool `toml:"proxied",default:false`
|
||||
Mime string `toml:"mime"`
|
||||
}
|
||||
func (self Resource) Get() ([]byte, error) {
|
||||
return ioutil.ReadFile(self.Url[7:])
|
||||
}
|
||||
|
||||
type ResourceDConfig struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
ListenURL string `toml:"listen_url"`
|
||||
ProxyCacheMinSize string `toml:"proxy_cache_min_size",default:5MB`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
ResourceD ResourceDConfig `toml:"resourceD"`
|
||||
Resource map[string]Resource `toml:"resource"`
|
||||
}
|
||||
|
||||
func (self Config) Validate() int {
|
||||
re, err := regexp.Compile(`^(file|http(s|))://`)
|
||||
if err != nil { panic(err) }
|
||||
|
||||
for key, res := range self.Resource {
|
||||
if ! re.MatchString(res.Url) {
|
||||
panic(fmt.Sprintf("Resource %s has invalid URL: %s\nOnly file://, http:// and https:// URLs are allowed", key, res.Url))
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
func (self Resource) GetProxied() ([]byte, error) {
|
||||
|
||||
cached, exists := ProxyResourceCache[self.Url];
|
||||
if exists {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
resp, err := http.Get(self.Url)
|
||||
if err != nil { return make([]byte, 0, 0), err }
|
||||
|
||||
buf, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil { return make([]byte, 0, 0), err }
|
||||
|
||||
// cache only those that are less than 5 mb
|
||||
if len(buf) > ProxyCacheMinSize {
|
||||
ProxyResourceCache[self.Url] = buf
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
var ProxyResourceCache map[string][]byte = make(map[string][]byte)
|
||||
var ProxyCacheMinSize int
|
||||
|
||||
func main() {
|
||||
var conf Config
|
||||
|
||||
data, err := ioutil.ReadFile("resourced.toml")
|
||||
if err != nil { panic(err) }
|
||||
|
||||
a, err := toml.Decode(string(data), &conf)
|
||||
if err != nil { panic(err) }
|
||||
_ = a
|
||||
|
||||
cache_min, err := humanize.ParseBytes(conf.ResourceD.ProxyCacheMinSize)
|
||||
if err != nil { panic(err) }
|
||||
ProxyCacheMinSize = int(cache_min)
|
||||
|
||||
conf.Validate()
|
||||
|
||||
if len(conf.Resource) == 0 {
|
||||
fmt.Println("\x1b[33m[warn] No resources are specified\x1b[0m");
|
||||
} else {
|
||||
fmt.Println(fmt.Sprintf("[info] Loaded %d resources", len(conf.Resource)))
|
||||
}
|
||||
|
||||
if ! conf.ResourceD.Enabled {
|
||||
fmt.Println("\x1b[33m[warn] resourceD is disabled. No resources will be served\x1b[0m")
|
||||
}
|
||||
|
||||
app := fiber.New(fiber.Config {
|
||||
Prefork: true,
|
||||
CaseSensitive: false,
|
||||
StrictRouting: true,
|
||||
ServerHeader: "",
|
||||
AppName: "blek! File resourceD",
|
||||
})
|
||||
|
||||
app.Get("/info/is_enabled", func (c *fiber.Ctx) error {
|
||||
return c.JSON(conf.ResourceD.Enabled)
|
||||
})
|
||||
|
||||
app.Use(func (c *fiber.Ctx) error {
|
||||
if ! conf.ResourceD.Enabled {
|
||||
return c.Status(fiber.StatusNotFound).SendString("ResourceD is disabled")
|
||||
}
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
app.Get("/:id", func (c *fiber.Ctx) error {
|
||||
res, exists := conf.Resource[c.Params("id")]
|
||||
if ! exists {
|
||||
return c.Status(fiber.StatusNotFound).SendString("Resource not found")
|
||||
}
|
||||
|
||||
if ! strings.HasPrefix(res.Url, "file://") {
|
||||
|
||||
if res.Proxied {
|
||||
data, err := res.GetProxied()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
// we failed, send a redirect instead
|
||||
// the next line would be the one with
|
||||
// c.Location(res.Url)
|
||||
} else {
|
||||
c.Response().Header.SetContentType(res.Mime)
|
||||
c.Response().Header.SetContentLength(len(data))
|
||||
return c.Send(data)
|
||||
}
|
||||
}
|
||||
|
||||
c.Location(res.Url)
|
||||
c.Status(302)
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := res.Get()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Response().Header.SetContentType(res.Mime)
|
||||
c.Response().Header.SetContentLength(len(data))
|
||||
|
||||
return c.Send(data)
|
||||
})
|
||||
|
||||
log.Fatal(app.Listen(conf.ResourceD.ListenURL))
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
# The resourceD config
|
||||
[resourceD]
|
||||
|
||||
# Whether to enable the resourceD.
|
||||
# If this is false, resourceD will start but respond to
|
||||
# all requests with 404
|
||||
# It is false by default because resourceD is not required in a default installation.
|
||||
enabled=true
|
||||
|
||||
# URL to listen on
|
||||
listen_url="0.0.0.0:80"
|
||||
|
||||
# Minibal size of a file to be cached
|
||||
# File size is parsed via this library:
|
||||
# https://github.com/dustin/go-humanize
|
||||
proxy_cache_min_size="5MB"
|
||||
|
||||
# Resource ID must be like a java package name
|
||||
# At least one X.X. is required
|
||||
#
|
||||
# Examples:
|
||||
# org.university.logo
|
||||
# dev.indie_guy.logo
|
||||
# com.pany.logo
|
||||
# Test your names here: https://regex101.com/r/wQdOup/2
|
||||
[resource."com.example.logo"]
|
||||
|
||||
# Can also be an external link
|
||||
# If an external link is specified,
|
||||
# the resource will be returned as a 302 redirect to the link
|
||||
url="file:///some/where"
|
||||
|
||||
# File type, as according to this:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||
mime="image/jpg"
|
||||
|
||||
# (if url is an http(s) url))
|
||||
# Whether to proxy the resource.
|
||||
# true - Send the resource like a regular file
|
||||
# false - Send a 302 HTTP redirect to the URL (default)
|
||||
#
|
||||
# It is not recommended to set this to `true`
|
||||
# unless you are referring to a resource that is
|
||||
# available only in the local network
|
||||
#
|
||||
# proxied=true
|
Loading…
Reference in New Issue