Compare commits

..

12 Commits
main ... mine

Author SHA1 Message Date
Raymaekers Luca
a4d9291ee3 use storage, with max size for upload dir 2023-04-11 12:27:09 +02:00
Raymaekers Luca
129101ac2e use arguments or variables 2023-04-11 12:25:32 +02:00
root
c3f133b211 Merge branch 'installation-fix' into mine 2023-04-05 14:16:17 +02:00
root
4d3cac8851 added error detection 2023-04-05 14:15:54 +02:00
root
574b7ce5f1 use id command instead of variable 2023-04-05 14:10:59 +02:00
Raymaekers Luca
3873729ae7 Merge branch 'readme' into mine 2023-04-05 13:36:00 +02:00
Raymaekers Luca
4342fa4a56 added updating to readme 2023-04-05 13:35:31 +02:00
Raymaekers Luca
aa1dff7015 Merge branch 'installation' into mine 2023-04-05 13:15:16 +02:00
Raymaekers Luca
69f19a1888 Merge branch 'installation' into mine 2023-04-05 13:01:12 +02:00
Raymaekers Luca
a5df50f43c Merge branch 'installation' into mine 2023-04-04 18:53:40 +02:00
Raymaekers Luca
9f8afe5054 use italic font in <code> 2023-04-04 18:01:56 +02:00
Raymaekers Luca
edc99c3481 changed colors 2023-04-04 17:50:35 +02:00
24 changed files with 353 additions and 886 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
__pycache__
upload
venv
.git

2
.envrc
View File

@ -1,2 +0,0 @@
watch_file default.nix shell.nix
use flake

6
.gitignore vendored
View File

@ -1,7 +1,3 @@
__pycache__
upload
env
.env
files
/gomod2nix-template
.direnv
.pre-commit-config.yaml

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM python:3.10-alpine
WORKDIR /usr/src/app
COPY requirements.txt ./
RUN apk add --no-cache --update libmagic \
&& pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers", "--forwarded-allow-ips='*'"]

131
README.md
View File

@ -1,125 +1,62 @@
# UpFast
The new and improwed version of upfast, now writen in GO!
Simple tool for uploading and sharing files that you can self-host.
This one is gonna be a lot easier to deploy since all you need to do is download the upfast binary and run it in a directory.
## How to host
To change the port or the ip adress to listen for you can use `-p` and `-a` options respectivelly.
### Regular system
There is alse the `-d` option that will set what domain name upfast will return when returning the file path when uploaded.
To run on a regular system I recommend to use a virtual environment to install the dependencies and run the project from there.
> example
We will also be setting up an specific user to run the app as safe as possible with a systemd service file for startup
#### Installing
```bash
./upfast -p 8080 -a 0.0.0.0 -d https://upfast.cronyakatsuki.xyz
./install.sh
```
By default upfast listen's on 127.0.0.1 adress so if you wan't to access it outside your network you will either need to listen to 0.0.0.0 or use a reverse proxy. I recommend the usage of a reverse proxy.
## Note for the users of the python version
You will need to rename the `upload` folder to `files`, since I changed the naming a bit.
## Installation
This installation step's will also harden our installation and limit the size of how much can be uploaded at the same time so your system can't be flooded.
### Creating user
#### Running
```bash
# Create upfast user with no login shell, no home directory and as a system user
useradd --shell /usr/sbin/nologin --system -M upfast
# Resctrict login to upfast user
usermod -L upfast
systemctl start upfast.service
```
### Creating virtual filesystem
This is done in order to limit the amount of storage the upfast installation, so people can't storage dos you.
Creating virtual filesystem to limit amount people can upload like chad's, using dd to create an empty file of size you choose.
Change the size output file to whatever you wan't and size to whatever you wan't.
``` bash
# make sure to first change into a directory where you wan't to save the virtual filesystem
dd if=/dev/zero of=20gb bs=1M count=20480
# make that file into a filesystem
mkfs.ext4 20gb
# create directory to mount the filesystem, I recommend /usr/local/upfast because that's where the included systemd service looks for upfast binary
mkdir /usr/local/upfast
# mounting the filesystem
mount -o loop,rw /home/ivek/20gb /usr/local/upfast
```
Now to make it mount on system reboot we need to add this line to fstab.
``` fstab
/home/amir/mydatafile /usr/local/upfast ext4 loop,rw,usrquota,grpquota 0 0
```
### Downloading the binary
#### Start on boot
```bash
# go to upfast's user's home and curl the upfast binary, make sure to run the next command's as root
cd /usr/local/upfast
curl -O https://code.cronyakatsuki.xyz/crony/UpFast/releases/download/v1.2/upfast
chmod +x upfast
systemctl enable upfast.service
```
#### Updating
To update, you only need to pull the changes
```bash
# Own the directory with upfast user and group
chown upfast:upfast -R /usr/local/upfast
chmod 700 /usr/local/upfast
su upfast -c 'cd && git pull --no-rebase'
```
## Updating
### Docker
When new update of upfast come's out all you will need to change to upfast user and curl the new binary in it's home directory overwritting the old one like this.
In the repo there is an included dockerfile to generate an image from the latest version of everything, to generate an image just run `docker build . -t upfast` (You need root privileges or to be in the docker group).
To run the docker container check the container id with `docker images` command.
> sample docker run command
```bash
su upfast
cd /usr/local/upfast
curl -O https://code.cronyakatsuki.xyz/crony/UpFast/releases/download/v1.2/upfast
sudo docker run -p 8000:8000 -v ./upload:/usr/src/app/upload CONTAINER_ID
```
## Startup automaticallly on system restart
The sample command will need an upload directory, you can replace `./upload` with a different path to save uploaded stuff.
If you use the old version of upfast you just need to change values in the systemd service located `/etc/systemd/system/upfast.service`, otherwise create a file in that path with this content.
### Nginx Proxy setup
> /etc/systemd/system/upfast.service
```systemd-service
[Unit]
Description=UpFast service
Documentation=https://code.cronyakatsuki.xyz/crony/upfast
[Service]
User=upfast
Group=upfast
WorkingDirectory=/usr/local/upfast/
ExecStart=/usr/local/upfast/upfast -p 8000 -a 127.0.0.1 -d https://upfast.cronyakatsuki.xyz
Restart=on-failure
[Install]
WantedBy=multi-user.target
This is an example nginx proxy config for http
```bash
cp ./upfast-nginx /etc/nginx/sites-available/upfast
ln -sf /etc/nginx/sites-available/upfast /etc/nginx/sites-enabled/
```
Remember to change the port if you need to, the listening adress and the domain name.
After that run `systemctl daemon-reload` as root or with sudo.
## Nginx proxy setup
To setup nginx as the reverse proxy you will need to add this server block to either the main nginx config file or in a separate file depending on your version of nginx.
```nginx
server {
listen 80;
listen [::]:80;
server_name upfast.example.xyz;
location / {
proxy_pass https://127.0.0.1:8000;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
> Load config
```bash
systemctl reload nginx
```
# Contributions
Thanks [TLasT](https://craftmenners.men) on his help with testing and documentation.

View File

@ -1,21 +0,0 @@
{ pkgs ? (
let
inherit (builtins) fetchTree fromJSON readFile;
inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix;
in
import (fetchTree nixpkgs.locked) {
overlays = [
(import "${fetchTree gomod2nix.locked}/overlay.nix")
];
}
)
, buildGoApplication ? pkgs.buildGoApplication
}:
buildGoApplication {
pname = "upfast";
version = "1.3";
pwd = ./.;
src = ./.;
modules = ./gomod2nix.toml;
}

View File

@ -1,210 +0,0 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1709126324,
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1703887061,
"narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"gomod2nix": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1705314449,
"narHash": "sha256-yfQQ67dLejP0FLK76LKHbkzcQqNIrux6MFe32MMFGNQ=",
"owner": "nix-community",
"repo": "gomod2nix",
"rev": "30e3c3a9ec4ac8453282ca7f67fca9e1da12c3e6",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "gomod2nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1709237383,
"narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1704874635,
"narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1704842529,
"narHash": "sha256-OTeQA+F8d/Evad33JMfuXC89VMetQbsU4qcaePchGr4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "eabe8d3eface69f5bb16c18f8662a702f50c20d5",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils_2",
"gitignore": "gitignore",
"nixpkgs": "nixpkgs_2",
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1708018599,
"narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,31 +0,0 @@
{
description = "A basic gomod2nix flake";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.gomod2nix.url = "github:nix-community/gomod2nix";
inputs.gomod2nix.inputs.nixpkgs.follows = "nixpkgs";
inputs.gomod2nix.inputs.flake-utils.follows = "flake-utils";
inputs.pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
outputs = { self, nixpkgs, flake-utils, gomod2nix, pre-commit-hooks }:
(flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = nixpkgs.legacyPackages.${system};
# The current default sdk for macOS fails to compile go projects, so we use a newer one for now.
# This has no effect on other platforms.
callPackage = pkgs.darwin.apple_sdk_11_0.callPackage or pkgs.callPackage;
in
{
packages.default = callPackage ./. {
inherit (gomod2nix.legacyPackages.${system}) buildGoApplication;
};
devShells.default = callPackage ./shell.nix {
inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix;
inherit pre-commit-hooks;
};
})
);
}

19
go.mod
View File

@ -1,19 +0,0 @@
module cronyakatsuki/upfast
go 1.21.5
require github.com/labstack/echo/v4 v4.11.3
require (
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
)

47
go.sum
View File

@ -1,47 +0,0 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM=
github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,39 +0,0 @@
schema = 3
[mod]
[mod."github.com/golang-jwt/jwt"]
version = "v3.2.2+incompatible"
hash = "sha256-LOkpuXhWrFayvVf1GOaOmZI5YKEsgqVSb22aF8LnCEM="
[mod."github.com/labstack/echo/v4"]
version = "v4.11.3"
hash = "sha256-rdqH4HQB/vZyEsoymrEsQ8izjf0m7jhrIxbF6r5ZmBo="
[mod."github.com/labstack/gommon"]
version = "v0.4.0"
hash = "sha256-xISAIJEu2xh0hoWsORbgjnz3rDK3ft3hrvmxt0wfHVw="
[mod."github.com/mattn/go-colorable"]
version = "v0.1.13"
hash = "sha256-qb3Qbo0CELGRIzvw7NVM1g/aayaz4Tguppk9MD2/OI8="
[mod."github.com/mattn/go-isatty"]
version = "v0.0.19"
hash = "sha256-wYQqGxeqV3Elkmn26Md8mKZ/viw598R4Ych3vtt72YE="
[mod."github.com/valyala/bytebufferpool"]
version = "v1.0.0"
hash = "sha256-I9FPZ3kCNRB+o0dpMwBnwZ35Fj9+ThvITn8a3Jr8mAY="
[mod."github.com/valyala/fasttemplate"]
version = "v1.2.2"
hash = "sha256-gp+lNXE8zjO+qJDM/YbS6V43HFsYP6PKn4ux1qa5lZ0="
[mod."golang.org/x/crypto"]
version = "v0.14.0"
hash = "sha256-UUSt3X/i34r1K0mU+Y5IzljX5HYy07JcHh39Pm1MU+o="
[mod."golang.org/x/net"]
version = "v0.17.0"
hash = "sha256-qRawHWLSsJ06QNbLhUWPXGVSO1eaioeC9xZlUEWN8J8="
[mod."golang.org/x/sys"]
version = "v0.13.0"
hash = "sha256-/+RDZ0a0oEfJ0k304VqpJpdrl2ZXa3yFlOxy4mjW7w0="
[mod."golang.org/x/text"]
version = "v0.13.0"
hash = "sha256-J34dbc8UNVIdRJUZP7jPt11oxuwG8VvrOOylxE7V3oA="
[mod."golang.org/x/time"]
version = "v0.3.0"
hash = "sha256-/hmc9skIswMYbivxNS7R8A6vCTUF9k2/7tr/ACkcEaM="

39
install.sh Executable file
View File

@ -0,0 +1,39 @@
#!/bin/sh
# quick install (and run) script for upfast using systemd
if [ "$(id -u)" -ne 0 ]
then
echo "Please run as root"
exit 1
fi
SIZE="5G"
UFDIR=${1:=/srv/upfast}
USER=${1:=upfast}
useradd --shell /bin/sh --system --home-dir $UFDIR $UFUSER ||
exit 1
mkdir -p "$UFDIR" || # dodge copying of skeletons
exit 1
chown "$UFUSER:$UFUSER" "$UFDIR"
chmod 700 "$UFDIR"
su "$UFUSER" -c"
cd
git clone https://code.cronyakatsuki.xyz/tlast/upfast . ||
exit 1
python3 -m venv env
. ./env/bin/activate ||
exit1
pip install -r requirements.txt ||
exit 1
fallocate -l $SIZE storage
mkfs.ext4 storage
mount storage upload
rm -fd /storage/*" ||
exit 1
cp ./upfast.service /etc/systemd/system/upfast.service
systemctl daemon-reload

247
main.go
View File

@ -1,247 +0,0 @@
package main
import (
"embed"
"errors"
"flag"
"html/template"
"io"
"log"
"net/http"
"os"
"regexp"
"strconv"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
//go:embed all:public/views/*.html
var templates embed.FS
//go:embed all:static/*
var static embed.FS
var domain string
func main() {
t := &Template{
templates: template.Must(template.ParseFS(templates, "public/views/*.html")),
}
p := flag.Int("p", 1323, "upfast port to listen on.")
a := flag.String("a", "127.0.0.1", "upfast ip to listen to")
d := flag.String("d", "127.0.0.1", "upfast domain")
flag.Parse()
host := *a + ":" + strconv.Itoa(*p)
domain = *d
e := echo.New()
e.Renderer = t
e.Use(middleware.Logger())
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
Root: "static",
Browse: false,
HTML5: true,
Filesystem: http.FS(static),
}))
files := "files"
if _, err := os.Stat(files); errors.Is(err, os.ErrNotExist) {
err := os.Mkdir(files, os.ModePerm)
if err != nil {
log.Println(err)
}
}
e.Static("/files", files)
e.GET("/", Index)
e.POST("/", Upload)
e.GET("/files/", Files)
e.DELETE("/files/:file", Delete)
e.Logger.Fatal(e.Start(host))
}
type Template struct {
templates *template.Template
}
type File struct {
Name string
FileType string
Content string
}
type FilesData struct {
Files []File
}
type IndexData struct {
Host string
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
func Index(c echo.Context) error {
data := IndexData{
Host: domain,
}
return c.Render(http.StatusOK, "index", data)
}
func GetFileContentType(ouput *os.File) (string, error) {
buf := make([]byte, 512)
_, err := ouput.Read(buf)
if err != nil {
return "", err
}
contentType := http.DetectContentType(buf)
return contentType, nil
}
func Files(c echo.Context) error {
var files FilesData
filelist, err := os.ReadDir("./files/")
if err != nil {
log.Fatal(err)
}
UserAgent := c.Request().UserAgent()
log.Print(UserAgent)
match, err := regexp.MatchString("^curl/.*", UserAgent)
if err != nil {
log.Fatal(err)
}
if match {
out := ""
for _, f := range filelist {
out += f.Name() + "\n"
}
return c.String(http.StatusOK, out)
}
var Type string
var Content string
ImageMatch := regexp.MustCompile("^image/.*")
VideoMatch := regexp.MustCompile("^video/.*")
JsonMatch := regexp.MustCompile("application/json")
TextMatch := regexp.MustCompile("^text/.*")
for _, f := range filelist {
filePath := "files/" + f.Name()
file, err := os.Open(filePath)
if err != nil {
panic(err)
}
defer file.Close()
contentType, err := GetFileContentType(file)
switch {
case ImageMatch.MatchString(contentType):
Type = "image"
Content = ""
case VideoMatch.MatchString(contentType):
Type = "video"
Content = ""
case JsonMatch.MatchString(contentType):
Type = "text"
b, _ := os.ReadFile(filePath)
Content = string(b)
case TextMatch.MatchString(contentType):
Type = "text"
b, _ := os.ReadFile(filePath)
Content = string(b)
default:
Type = "else"
Content = ""
}
log.Print(contentType)
files.Files = append(
files.Files,
File{Name: f.Name(), FileType: Type, Content: Content},
)
}
return c.Render(http.StatusOK, "files", files)
}
func Upload(c echo.Context) error {
file, err := c.FormFile("file")
if err != nil {
return err
}
if _, err := os.Stat("files/" + file.Filename); err == nil {
return c.String(http.StatusOK, "A file with the same name already exist's on the server.\n")
}
src, err := file.Open()
if err != nil {
return err
}
dst, err := os.Create("files/" + file.Filename)
if err != nil {
return err
}
defer dst.Close()
if _, err = io.Copy(dst, src); err != nil {
return err
}
fileUrl := domain + "/files/" + file.Filename + "\n"
UserAgent := c.Request().UserAgent()
log.Print(UserAgent)
match, err := regexp.MatchString("^curl/.*", UserAgent)
if err != nil {
log.Fatal(err)
}
if match {
return c.String(http.StatusOK, fileUrl)
}
return c.HTML(http.StatusOK, "File uploaded at url: <strong>"+fileUrl+"</strong>")
}
func Delete(c echo.Context) error {
file := c.Param("file")
filePath := "files/" + file
if _, err := os.Stat(filePath); err != nil {
return c.String(http.StatusOK, "That file doesn't exist on the server\n")
}
err := os.Remove(filePath)
if err != nil {
return c.String(http.StatusOK, "Error while deleting "+file+"\n")
}
return c.String(http.StatusOK, "Deleted file from server\n")
}

113
main.py Normal file
View File

@ -0,0 +1,113 @@
import shutil, magic, re
from typing import Annotated, Union
from os import listdir, remove
from os.path import abspath, dirname, exists
from fastapi import FastAPI, Request, UploadFile, File, Header
from fastapi.responses import HTMLResponse, PlainTextResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
# file class
class _File:
def __init__(self, name: str, fileType: str, content=None):
self.name = name
self.fileType = fileType
self.content = content if content is not None else ''
# File list generator with filetype assignemt and content reading for previews
def file_list_generator(path: str, file_list: list[str]):
files = []
for file in file_list:
file_type = magic.from_file(f"{path}{file}", mime=True)
if re.search("^image/.*", file_type):
_file = _File(file, "image")
elif re.search("^video/.*", file_type):
_file = _File(file, "video")
elif re.search("^text/.*", file_type):
with open(f"{path}{file}") as f:
content = f.read()
_file = _File(file, "text", content)
elif file_type == 'application/json':
with open(f"{path}{file}") as f:
content = f.read()
_file = _File(file, "text", content)
else:
_file = _File(file, "else")
files.append(_file)
return files
# Load jinja2 for templating
templates = Jinja2Templates(directory="templates")
# Create fastapi template
app = FastAPI()
# Mount all uploaded files at the /files path
app.mount("/files", StaticFiles(directory="upload"), name='upload')
# Mount static files like css
app.mount("/static", StaticFiles(directory="static"), name='static')
# show the homepage when just getting the website
@app.get("/", response_class=HTMLResponse)
async def index(request: Request, user_agent: Annotated[Union[str, None], Header()] = None):
if re.search("^curl/.*", str(user_agent)):
return PlainTextResponse("It fucking works!\n")
else:
context = {'request': request, "user_agent": user_agent}
return templates.TemplateResponse("index.html", context)
# get the file user want's to upload and save it
@app.post("/")
async def upload( request: Request, file: UploadFile = File(...), user_agent: Annotated[Union[str, None], Header()] = None):
file_location = f"upload/{file.filename}"
if exists(file_location):
context = f'''File: "{request.url._url}files/{file.filename}" already exists on the server!
Please rename the file or delete the one on the server\n'''
return PlainTextResponse(context)
with open(file_location, "wb+") as file_object:
shutil.copyfileobj(file.file, file_object)
if re.search("^curl/.*", str(user_agent)):
return PlainTextResponse(f"{request.url._url}files/{file.filename}\n")
else:
return {"filename": file.filename, "path": f"{request.url._url}files/{file.filename}"}
# show the files page with the list of all files or if run with curl list all files in url format
@app.get("/files")
async def files( request: Request, user_agent: Annotated[Union[str, None], Header()] = None):
path = f"{dirname(abspath(__file__))}/upload/"
file_list = listdir(path)
files = file_list_generator(path, file_list)
context = { 'request': request, "files": files}
if re.search("^curl/.*", str(user_agent)):
context = ""
for file in files:
context += f"{request.url._url}/{file.name}\n"
return PlainTextResponse(f"{context}")
else:
return templates.TemplateResponse("files.html", context)
# delete specific file when getting this request
@app.get("/delete/{file}")
async def delete(request: Request, file: str, user_agent: Annotated[Union[str, None], Header()] = None):
file_path = f"{dirname(abspath(__file__))}/upload/{file}"
if exists(file_path):
remove(file_path)
if re.search("^curl/.*", str(user_agent)):
return PlainTextResponse(f"file {file} deleted from the server\n")
else:
return RedirectResponse(request.url_for('files'))
if re.search("^curl/.*", str(user_agent)):
return PlainTextResponse(f"file {file} doesn't exist on the server\n")
else:
return RedirectResponse(request.url_for('files'))

View File

@ -1,32 +0,0 @@
{{define "files"}}
<!doctype html>
<html lang="en">
<head>
<title>Files - UpFast</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/css/files.css" rel="stylesheet" />
</head>
<body>
<h1>File List</h1>
{{range .Files}}
<hr />
<div class="file">
{{if eq .FileType "image"}}
<img src="/files/{{.Name}}" alt="{{.Name}}" />
{{else if eq .FileType "video"}}
<video controls src="/files/{{.Name}}" />
{{else if eq .FileType "text"}}
<pre>{{.Content}}</pre>
{{end}}
<div class="info">
<a href="/files/{{.Name}}" download>{{.Name}}</a>
</div>
</div>
{{end}}
</body>
</html>
{{end}}

View File

@ -1,55 +0,0 @@
{{define "index"}}
<!doctype html>
<html lang="en">
<head>
<title>UpFast</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/css/style.css" rel="stylesheet" />
</head>
<body>
<h1>UpFast: Minimal file upload and sharing tool</h1>
<p>
<a href="https://0x0.st">0x0.st</a> Influenced file upload and sharing
tool
</p>
<p>
To upload files use curl:
<code>curl -F "file=@/path/to/file" "{{.Host}}"</code>
</p>
<p>
Or for <a href="https://poggerer.xyz/">Tulg</a> on a windows pc ;)
<form action="/" method="post" enctype="multipart/form-data">
File: <input type="file" name="file"> <input type="submit" value="submit">
</form>
</p>
<p>
To delete a file using curl:
<code>curl -X DELETE "{{.Host}}/files/:file"</code>
</p>
<p>
To get of all files hosted on the instance check
<a href="/files">files</a>
</p>
<p>
Created and maintained by
<a href="https://cronyakatsuki.xyz">Crony Akatsuki</a>
</p>
<p>
Code is hosted on
<a href="https://code.cronyakatsuki.xyz/crony/upfast">https://code.cronyakatsuki.xyz/crony/upfast</a>
</p>
</body>
</html>
{{end}}

14
requirements.txt Normal file
View File

@ -0,0 +1,14 @@
anyio==3.6.2
click==8.1.3
fastapi==0.95.0
h11==0.14.0
idna==3.4
Jinja2==3.1.2
MarkupSafe==2.1.2
pydantic==1.10.7
python-magic==0.4.27
python-multipart==0.0.6
sniffio==1.3.0
starlette==0.26.1
typing_extensions==4.5.0
uvicorn==0.21.1

View File

@ -1,32 +0,0 @@
{ pkgs ? (
let
inherit (builtins) fetchTree fromJSON readFile;
inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix;
in
import (fetchTree nixpkgs.locked) {
overlays = [
(import "${fetchTree gomod2nix.locked}/overlay.nix")
];
}
)
, mkGoEnv ? pkgs.mkGoEnv
, gomod2nix ? pkgs.gomod2nix
, pre-commit-hooks
}:
let
goEnv = mkGoEnv { pwd = ./.; };
pre-commit-check = pre-commit-hooks.lib.${pkgs.system}.run {
src = ./.;
hooks = {
gofmt.enable = true;
};
};
in
pkgs.mkShell {
inherit (pre-commit-check) shellHook;
packages = [
goEnv
gomod2nix
];
}

View File

@ -1,66 +1,66 @@
html {
padding-bottom: 10%;
padding-bottom: 10%;
}
body {
max-width: 900px;
margin: auto;
background: #303446;
color: #c6d0f5;
max-width: 900px;
margin: auto;
background: #81a1c1;
}
h1 {
font-size: 250%;
text-align: center;
font-size: 250%;
text-align: center;
}
hr {
margin: 40px;
color: #626880;
margin: 40px;
color: #2e3440;
}
img {
display: block;
margin: auto;
max-width: 800px;
max-height: 800px;
display: block;
margin: auto;
max-width: 800px;
max-height: 800px;
}
video {
display: block;
margin: auto;
max-width: 800px;
max-height: 800px;
display: block;
margin: auto;
max-width: 800px;
max-height: 800px;
}
a {
color: #f2d5cf;
text-decoration: none;
font-weight: bold;
padding: 5px;
color: #ebcb8b;
text-decoration: none;
font-weight: bold;
padding: 5px;
}
a:hover {
text-decoration: underline;
text-decoration: underline;
}
pre {
background: #292c3c;
font-size: 1am;
padding: 10px;
max-width: 800;
max-height: 10rem;
overflow-y: auto;
overflow-x: auto;
border: none;
border-radius: 1rem;
background: #2e3440;
font-size: 1am;
padding: 10px;
max-width: 800;
max-height: 800;
overflow-y: scroll;
overflow-x: scroll;
border: none;
border-radius: 1rem;
color: #88c0d0;
}
.file {
display: block;
display: block;
}
.info {
display: block;
text-align: center;
display: block;
text-align: center;
}

View File

@ -1,29 +1,30 @@
body {
max-width: 900px;
margin: auto;
background: #303446;
color: #c6d0f5;
max-width: 900px;
margin: auto;
background: #81a1c1;
}
h1 {
font-size: 250%;
text-align: center;
font-size: 250%;
text-align: center;
}
a {
color: #f2d5cf;
text-decoration: none;
font-weight: bold;
padding: 5px;
color: #ebcb8b;
text-decoration: none;
font-weight: bold;
padding: 5px;
}
a:hover {
text-decoration: underline;
text-decoration: underline;
}
code {
background: #414559;
border-radius: 10px;
font-size: 105%;
padding: 2px;
background: #88c0d0;
border-radius: 10px;
font-size: 105%;
font-style: italic;
padding: 2px;
}

31
templates/files.html Normal file
View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{ url_for('static', path='/css/files.css') }}" rel="stylesheet">
<title>UpFast: File List</title>
</head>
<body>
<h1>File List</h1>
{% for file in files %}
<hr>
<div class="file">
{% if file.fileType == "image" %}
<img src="/files/{{ file.name }}">
{% elif file.fileType == "video" %}
<video controls src="/files/{{ file.name }}"></video>
{% elif file.fileType == "text" %}
<pre>{{ file.content }}</pre>
{% endif %}
<div class="info">
<a href="/files/{{ file.name }}" download>{{ file.name }}</a>
<a href="/delete/{{ file.name }}">delete!</a>
</div>
</div>
{% endfor %}
</body>
</html>

26
templates/index.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{ url_for('static', path='/css/style.css') }}" rel="stylesheet">
<title>UpFast</title>
</head>
<body>
<h1>UpFast: Minimal file upload and sharing tool</h1>
<p><a href="https://0x0.st">0x0.st</a> Influenced file upload and sharing tool</p>
<p>To upload files use curl: <code>curl -F "file=@/path/to/file" "{{ request.url }}"</code></p>
<p>To delete a file using curl: <code>curl "{{ request.url }}delete/file_name"</code></p>
<p>To get of all files hosted on the instance check <a href="/files">files</a></p>
<p>Created and maintained by <a href="https://cronyakatsuki.xyz">Crony Akatsuki</a></p>
<p>Code is hosted on <a href="https://code.cronyakatsuki.xyz/crony/upfast">https://code.cronyakatsuki.xyz/crony/upfast</a></p>
</body>
</html>

15
upfast-nginx Normal file
View File

@ -0,0 +1,15 @@
server {
listen 80;
listen [::]:80;
server_name upfast.example.com ;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_buffering off;
}
}

13
upfast.service Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=UpFast service
Documentation=https://code.cronyakatsuki.xyz/crony/upfast
[Service]
User=upfast
Group=upfast
WorkingDirectory=/usr/local/upfast/
ExecStart=/usr/local/upfast/env/bin/uvicorn main:app --host 127.0.0.1 --port 8000 --proxy-headers --forwarded-allow-ips='*'
Restart=on-failure
[Install]
WantedBy=multi-user.target