Compare commits

...

20 Commits

Author SHA1 Message Date
blek aae05764f5
mention the contributing guide in readme 2023-12-07 20:20:42 +10:00
blek f4e3aef32e
add contributing guide 2023-12-07 20:19:57 +10:00
blek d018c0c2ba
print warning in resourceD if no resources are specified and report number of resources on startup 2023-11-16 19:03:51 +10:00
blek 6e500cf07b add animation to the header 2023-11-11 09:28:01 +01:00
blek 2efdeabf77 add html-minifier to rust dev dockerfile 2023-11-11 09:28:01 +01:00
blek 64edbd48d5 improve dragndrop zone size handling on mobile 2023-11-11 09:28:01 +01:00
blek 3c30f964f0 alternative display for header in mobile mode 2023-11-11 09:28:01 +01:00
blek f976c85e48 print a warning if resourced is disabled 2023-11-11 09:09:48 +01:00
blek cdcecad84d deploy resourced to production dockerfile
Note: I did not test the compose file on my machine before submitting the commit. May or may not work.
2023-11-11 09:09:48 +01:00
blek edf0591bf0 deploy resourceD within dev compose file 2023-11-11 09:09:48 +01:00
blek 24d1318c9f add air to resourceD
more info: https://github.com/cosmtrek/air
2023-11-11 09:09:48 +01:00
blek 75ce4b1311 add swagger reference 2023-11-11 09:09:48 +01:00
blek 15d47c8fea add route to get if the resourced is enabled 2023-11-11 09:09:48 +01:00
blek 8a6c411bf8 fix wrong symbols in the example config 2023-11-11 09:09:48 +01:00
blek e4d3331823 add option to configure min size to cache proxy 2023-11-11 09:09:48 +01:00
blek cc8c9b911a cached proxied resources 2023-11-11 09:09:48 +01:00
blek 915552e62c proxied resources 2023-11-11 09:09:48 +01:00
blek ee450d237a support for http(s):// urls 2023-11-11 09:09:48 +01:00
blek 90d4e8c93f actually serve the resources 2023-11-11 09:09:48 +01:00
blek 4997750fc6 init repo 2023-11-11 09:09:48 +01:00
18 changed files with 480 additions and 4 deletions

17
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,17 @@
# Contributing to blek! File
blek! File is a project that aims for high quality standarts,
which means that contributions will be reviewed accordingly.
This doesnt mean that you shouldn't contribute if you have low skills;
it means that you should be ready to expect to make changes in your code as it is reviewed.
## Argument rules
The BDFL of the project is: `blek! <me@blek.codes>`. BDFL's role is to make a final call in case if an argument gets stalled or too controversial.
Based criticism and analysis is the only things that matter within an argument inside this project. Basic your argument on personal emotions against another person will not be tolerated.
## Expected workflow
The expected workflow is:
1. Fork
2. Commit changes
3. Open a PR
4. Merge

View File

@ -24,6 +24,9 @@ To qualify, your instance must be having:
3. Come up with a unique name 3. Come up with a unique name
4. Have a public administrator email 4. Have a public administrator email
## Contributing
Contributing guide is available [here](./CONTRIBUTING.md) (the CONTRIBUTING.md file).
## Licensing ## Licensing
This software is released under GPL3 license, a copyleft license that protects users' freedom by ensuring that all future copies of this software are open source as well. This software is released under GPL3 license, a copyleft license that protects users' freedom by ensuring that all future copies of this software are open source as well.

View File

@ -4,4 +4,8 @@
uri * strip_prefix /qr uri * strip_prefix /qr
reverse_proxy http://qr reverse_proxy http://qr
} }
handle /resource/* {
uri * strip_prefix /resource
reverse_proxy http://resourced
}
} }

View File

@ -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" ]

View File

@ -7,6 +7,6 @@ RUN cargo install cargo-watch && \
RUN apt update && \ RUN apt update && \
apt install nodejs npm -y --no-install-recommends && \ 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" ] CMD [ "/opt/code/dev-entry.sh" ]

View File

@ -21,6 +21,15 @@ services:
volumes: volumes:
- './janitor:/opt/code' - './janitor:/opt/code'
- './volatile/files:/opt/user_uploads' - './volatile/files:/opt/user_uploads'
resourced:
build:
context: containers
dockerfile: go-dev.Dockerfile
networks:
bfile:
volumes:
- './resource:/opt/code'
- '/opt/code/tmp'
caddy: caddy:
image: caddy:alpine image: caddy:alpine
volumes: volumes:

View File

@ -19,6 +19,15 @@ services:
volumes: volumes:
- './janitor:/config:ro' - './janitor:/config:ro'
- './volatile/files:/opt/user_uploads' - './volatile/files:/opt/user_uploads'
resourced:
build:
context: resource
dockerfile: Dockerfile.prod
networks:
bfile:
pid: host # prefork
volumes:
- './resource:/opt/cont'
caddy: caddy:
image: caddy:alpine image: caddy:alpine
volumes: volumes:

View File

@ -8,8 +8,24 @@
const root_drag_rop = document.getElementsByClassName('file-drag-n-drop')[0]; const root_drag_rop = document.getElementsByClassName('file-drag-n-drop')[0];
// make the root drag&drop element an ideal circle // 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} */ /** @type {HTMLElement} */
const drag_rop = document.getElementsByClassName('file-drag-n-drop-inside')[0]; const drag_rop = document.getElementsByClassName('file-drag-n-drop-inside')[0];

View File

@ -16,6 +16,22 @@
<title>{{ conf.brand.instance_name }}</title> <title>{{ conf.brand.instance_name }}</title>
<style> .footer svg { height: 32px; margin: 10px 0 } </style> <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) -%} {%- if cfg!(debug_assertions) -%}
<link rel="stylesheet" href="/alert.css" /> <link rel="stylesheet" href="/alert.css" />
@ -33,7 +49,7 @@
{{- conf.brand.instance_name -}} {{- conf.brand.instance_name -}}
{%- if conf.brand.instance_motto.len() != 0 -%} {%- if conf.brand.instance_motto.len() != 0 -%}
{#- Whitespace control is stupid -#} {#- Whitespace control is stupid -#}
{{- " - " -}}{{- conf.brand.instance_motto -}} <span class="header-home-motto">{{- " - " -}}{{- conf.brand.instance_motto -}}</span>
{%- endif -%} {%- endif -%}
</a> </a>
</div> </div>

46
resource/.air.toml Normal file
View File

@ -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

5
resource/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
resourced.toml
resourced
.DS_Store
.env
tmp

22
resource/Dockerfile.prod Normal file
View File

@ -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" ]

15
resource/README.md Normal file
View File

@ -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.

View File

@ -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

20
resource/go.mod Normal file
View File

@ -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
)

31
resource/go.sum Normal file
View File

@ -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=

158
resource/main.go Normal file
View File

@ -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))
}

View File

@ -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