Compare commits
10 commits
4b7001765f
...
0e93e017f3
Author | SHA1 | Date | |
---|---|---|---|
Felix Niederwanger | 0e93e017f3 | ||
Felix Niederwanger | 9218427a0b | ||
Felix Niederwanger | fb036c40cc | ||
Felix Niederwanger | 87820e04cb | ||
Felix Niederwanger | 3a9c402698 | ||
Felix Niederwanger | ad5bdcfd11 | ||
Felix Niederwanger | 08c762fa47 | ||
Felix Niederwanger | 5025670328 | ||
Felix Niederwanger | 0d66b1569b | ||
Felix Niederwanger | 2ee99daf04 |
44
.github/workflows/ghcr.yml
vendored
Normal file
44
.github/workflows/ghcr.yml
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
# See https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#publishing-images-to-github-packages
|
||||
|
||||
name: Create and publish container
|
||||
|
||||
'on':
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
github-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
2
.github/workflows/pastad.yml
vendored
2
.github/workflows/pastad.yml
vendored
|
@ -15,7 +15,7 @@ jobs:
|
|||
- name: Setup go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.14'
|
||||
go-version: '1.16'
|
||||
- name: Install requirements
|
||||
run: make requirements
|
||||
- name: Compile binaries
|
||||
|
|
|
@ -7,10 +7,7 @@ ADD . /app
|
|||
RUN cd /app && make requirements && make pastad-static
|
||||
|
||||
FROM scratch
|
||||
#RUN mkdir /app
|
||||
#RUN mkdir /data
|
||||
WORKDIR /data
|
||||
COPY --from=build-env /app/pastad /app/pastad
|
||||
COPY --from=build-env /app/mime.types /app/mime.types
|
||||
COPY --from=build-env /app/pastad /app/mime.types /app/
|
||||
ENTRYPOINT ["/app/pastad", "-m", "/app/mime.types", "-c", "/data/pastad.toml"]
|
||||
VOLUME ["/data"]
|
||||
|
|
7
Makefile
7
Makefile
|
@ -22,9 +22,8 @@ test: pastad pasta
|
|||
# TODO: This syntax is horrible :-)
|
||||
bash -c 'cd test && ./test.sh'
|
||||
|
||||
docker: Dockerfile pasta pastad
|
||||
container-docker: Dockerfile pasta pastad
|
||||
docker build . -t feldspaten.org/pasta
|
||||
|
||||
deploy: Dockerfile pasta pastad
|
||||
docker build . -t grisu48/pasta
|
||||
docker push grisu48/pasta
|
||||
container-podman: Dockerfile pasta pastad
|
||||
podman build . -t feldspaten.org/pasta
|
||||
|
|
63
README.md
63
README.md
|
@ -4,21 +4,49 @@
|
|||
|
||||
Stupid simple pastebin service written in go.
|
||||
|
||||
Fastest way of deploying is via the [`deploy_pasta.sh`](deploy_pasta.sh) script.
|
||||
## Run via podman/docker
|
||||
|
||||
## Run via docker
|
||||
The easiest way of self-hosting a `pasta` server is via the provided container from `ghcr.io/grisu48/pasta:latest`. The container runs fine as rootless container. Setup your own `pasta` instance is as easy as:
|
||||
|
||||
The easiest way of self-hosting a `pasta` server is via the [docker image](https://hub.docker.com/r/grisu48/pasta/). All you need to do is
|
||||
* Create your `data` directory (holds config + data)
|
||||
* Create a [pastad.toml](pastad.toml.example) file therein
|
||||
* Start the container, mount the `data` directory as `/data` and publish port `8199`
|
||||
* Configure your reverse proxy (e.g. `nginx`) to forward requests to the `pasta` container
|
||||
|
||||
* Create your `data` directory
|
||||
* Put the [pastad.toml](pastad.toml.example) file there
|
||||
* Start the container, mount the `data` directory as `/data`
|
||||
Assuming you want your data directory be e.g. `/srv/pasta`, prepare your server:
|
||||
|
||||
Assuming your data is in `/srv/pasta/` you can do
|
||||
mkdir /srv/pasta
|
||||
cp pastad.toml.example /srv/pastsa/pastad.toml
|
||||
$EDITOR /srv/pastsa/pastad.toml # Modify the configuration to your needs
|
||||
|
||||
docker container run -d -v /srv/pasta:/data -p 127.0.0.1:8199:8199 grisu48/pasta
|
||||
And then create and run your container with your preferred container engine:
|
||||
|
||||
Configure your reverse proxy (e.g. `nginx`) then accordingly. I don't recomment to publish `pastad` on port 80 without a reverse proxy.
|
||||
docker container run -d --name pasta -v /srv/pasta:/data -p 127.0.0.1:8199:8199 ghcr.io/grisu48/pasta
|
||||
podman container run -d --name pasta -v /srv/pasta:/data -p 127.0.0.1:8199:8199 ghcr.io/grisu48/pasta
|
||||
|
||||
`pasta` listens here on port 8199 and all you need to do is to configure your reverse proxy (e.g. `nginx`) accordingly:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name my-awesome-pasta.server;
|
||||
|
||||
client_max_body_size 32M;
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8199/;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that the good old [dockerhub image](https://hub.docker.com/r/grisu48/pasta/) is deprecated. It still gets updates but will be removed one fine day.
|
||||
## Run on openSUSE
|
||||
|
||||
We build openSUSE package at [build.opensuse.org](https://build.opensuse.org/package/show/home%3Aph03nix%3Atools/pasta). To install follow the instructions from [software.opensuse.org](https://software.opensuse.org/download/package?package=pasta&project=home%3Aph03nix%3Atools) or the following snippet:
|
||||
|
||||
# Tumbleweed
|
||||
zypper addrepo zypper addrepo https://download.opensuse.org/repositories/home:ph03nix:tools/openSUSE_Tumbleweed/home:ph03nix:tools.repo
|
||||
zypper refresh && zypper install pasta
|
||||
|
||||
## Run on RancherOS
|
||||
|
||||
|
@ -35,14 +63,6 @@ $ sudo mkfs.ext4 /dev/sdb1
|
|||
$ sudo ros install -d /dev/sda -c cloud-init.yaml
|
||||
```
|
||||
|
||||
## Run on openSUSE
|
||||
|
||||
We build openSUSE package at [build.opensuse.org](https://build.opensuse.org/package/show/home%3Aph03nix%3Atools/pasta). To install follow the instructions from [software.opensuse.org](https://software.opensuse.org/download/package?package=pasta&project=home%3Aph03nix%3Atools) or the following snippet:
|
||||
|
||||
# Tumbleweed
|
||||
zypper addrepo zypper addrepo https://download.opensuse.org/repositories/home:ph03nix:tools/openSUSE_Tumbleweed/home:ph03nix:tools.repo
|
||||
zypper refresh && zypper install pasta
|
||||
|
||||
## Build and run from source
|
||||
|
||||
make pastad # Server
|
||||
|
@ -50,7 +70,7 @@ We build openSUSE package at [build.opensuse.org](https://build.opensuse.org/pac
|
|||
make # all
|
||||
make static # static binaries
|
||||
|
||||
Then create a `pastad.toml` file using the provided example (`pastad.toml.example`) and run the server with
|
||||
Create a `pastad.toml` file using the provided example (`pastad.toml.example`) and run the server with
|
||||
|
||||
./pastad
|
||||
|
||||
|
@ -93,9 +113,10 @@ Assuing the server runs on http://localhost:8199, you can use the `pasta` tool o
|
|||
|
||||
## pasta CLI
|
||||
|
||||
`pasta` is the CLI utility for easy handling. For instance, if you want to push the `README.md` file and create a pasta out of it:
|
||||
`pasta` is the CLI utility for making the creation of a pasta as easy as possible.
|
||||
For instance, if you want to push the `README.md` file and create a pasta out of it:
|
||||
|
||||
pasta README.md
|
||||
pasta -r http://localhost:8199 REAME.md
|
||||
pasta -r http://localhost:8199 REAME.md # Define a custom remote server
|
||||
|
||||
`pasta` reads the `~/.pasta.toml` file (see the [example file](pasta.toml.example))
|
||||
`pasta` reads the config from `~/.pasta.toml` (see the [example file](pasta.toml.example))
|
||||
|
|
|
@ -284,7 +284,7 @@ ServerError:
|
|||
fmt.Fprintf(w, "server error")
|
||||
}
|
||||
|
||||
func receiveBody(reader io.Reader, pasta *Pasta) error {
|
||||
func receive(reader io.Reader, pasta *Pasta) error {
|
||||
buf := make([]byte, 4096)
|
||||
file, err := os.OpenFile(pasta.Filename, os.O_RDWR|os.O_APPEND, 0640)
|
||||
if err != nil {
|
||||
|
@ -385,19 +385,31 @@ func ReceivePasta(r *http.Request) (Pasta, error) {
|
|||
}
|
||||
|
||||
if isMultipart(r) {
|
||||
// Close body, also if it's not set as reader
|
||||
defer r.Body.Close()
|
||||
reader, err = receiveMultibody(r, &pasta)
|
||||
if err != nil {
|
||||
pasta.Id = ""
|
||||
return pasta, err
|
||||
}
|
||||
} else {
|
||||
// Otherwise the message body is the upload content
|
||||
reader = r.Body
|
||||
// Check if the input is coming from the POST form
|
||||
inputs := r.URL.Query()["input"]
|
||||
if len(inputs) > 0 && inputs[0] == "form" {
|
||||
// Copy reader, as r.FromValue consumes it's contents
|
||||
defer r.Body.Close()
|
||||
reader = r.Body
|
||||
if content := r.FormValue("content"); content != "" {
|
||||
reader = io.NopCloser(strings.NewReader(content))
|
||||
} else {
|
||||
pasta.Id = "" // Empty pasta
|
||||
return pasta, nil
|
||||
}
|
||||
} else {
|
||||
reader = r.Body
|
||||
}
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
if err := receiveBody(reader, &pasta); err != nil {
|
||||
if err := receive(reader, &pasta); err != nil {
|
||||
return pasta, err
|
||||
}
|
||||
if pasta.Size >= cf.MaxPastaSize {
|
||||
|
@ -526,14 +538,15 @@ func handlerPost(w http.ResponseWriter, r *http.Request) {
|
|||
fmt.Fprintf(w, "<!doctype html><html><head><title>pasta</title></head>\n")
|
||||
fmt.Fprintf(w, "<body>\n")
|
||||
fmt.Fprintf(w, "<h1>pasta</h1>\n")
|
||||
fmt.Fprintf(w, "<p><a href=\"%s\">%s</a>", url, url)
|
||||
if pasta.ExpireDate > 0 {
|
||||
fmt.Fprintf(w, " (expires %s)", time.Unix(pasta.ExpireDate, 0).Format("2006-01-02-15:04:05"))
|
||||
}
|
||||
fmt.Fprintf(w, "</p>\n")
|
||||
deleteLink := fmt.Sprintf("%s/delete?id=%s&token=%s", cf.BaseUrl, pasta.Id, pasta.Token)
|
||||
fmt.Fprintf(w, "<p>Accidentally uploaded? <a href=\"%s\">Delete it</a> right away</p>\n", deleteLink)
|
||||
fmt.Fprintf(w, "<p>Modification token: <code>%s</code></p>\n", pasta.Token)
|
||||
fmt.Fprintf(w, "<p>Here is your pasta: <a href=\"%s\">%s</a>.<br/>", url, url)
|
||||
fmt.Fprintf(w, "<a href=\"%s\">Delete</a> it in case you don't want it anymore.</p>\n", deleteLink)
|
||||
fmt.Fprintf(w, "<pre>")
|
||||
if pasta.ExpireDate > 0 {
|
||||
fmt.Fprintf(w, "Expiration: %s\n", time.Unix(pasta.ExpireDate, 0).Format("2006-01-02-15:04:05"))
|
||||
}
|
||||
fmt.Fprintf(w, "Modification token: %s\n</pre>\n", pasta.Token)
|
||||
fmt.Fprintf(w, "<p>That was fun! Let's <a href=\"%s\">upload another one</a>.</p>\n", cf.BaseUrl)
|
||||
fmt.Fprintf(w, "</body></html>")
|
||||
} else if retFormat == "json" {
|
||||
// Dont use json package, the reply is simple enough to build it on-the-fly
|
||||
|
@ -628,19 +641,69 @@ func handlerDelete(w http.ResponseWriter, r *http.Request) {
|
|||
deletePasta(id, token, w)
|
||||
}
|
||||
|
||||
func timeHumanReadable(timestamp int64) string {
|
||||
if timestamp < 60 {
|
||||
return fmt.Sprintf("%d s", timestamp)
|
||||
}
|
||||
|
||||
minutes := timestamp / 60
|
||||
seconds := timestamp - (minutes * 60)
|
||||
if minutes < 60 {
|
||||
return fmt.Sprintf("%d:%d min", minutes, seconds)
|
||||
}
|
||||
|
||||
hours := minutes / 60
|
||||
minutes -= hours * 60
|
||||
if hours < 24 {
|
||||
return fmt.Sprintf("%d s", hours)
|
||||
}
|
||||
|
||||
days := hours / 24
|
||||
hours -= days * 24
|
||||
if days > 365 {
|
||||
years := float32(days) / 365.0
|
||||
return fmt.Sprintf("%.2f years", years)
|
||||
} else if days > 28 {
|
||||
weeks := days / 7
|
||||
if weeks > 4 {
|
||||
months := days / 30
|
||||
return fmt.Sprintf("%d months", months)
|
||||
}
|
||||
return fmt.Sprintf("%d weeks", weeks)
|
||||
} else {
|
||||
return fmt.Sprintf("%d days", days)
|
||||
}
|
||||
}
|
||||
|
||||
func handlerIndex(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "<!doctype html><html><head><title>pasta</title></head>\n")
|
||||
fmt.Fprintf(w, "<body>\n")
|
||||
fmt.Fprintf(w, "<h1>pasta</h1>\n")
|
||||
fmt.Fprintf(w, "<p>Stupid simple pastebin service written in go. Visit the project repo on <a href=\"https://github.com/grisu48/pasta\" target=\"_BLANK\">Github</a>.</p>\n")
|
||||
fmt.Fprintf(w, "<h3>Post a file</h3>\n")
|
||||
fmt.Fprintf(w, "<p>Just shove your file via POST request to the main page :</p>")
|
||||
fmt.Fprintf(w, "<pre>curl -X POST '%s' --data-binary @FILE</pre>\n", cf.BaseUrl)
|
||||
fmt.Fprintf(w, "<p>Or use the following upload form</p>")
|
||||
fmt.Fprintf(w, "<p>Stupid simple paste service written in <code>go</code><br/>\n")
|
||||
fmt.Fprintf(w, "Checkout our fresh CLI utilities in <a href=\"https://github.com/grisu48/pasta/releases/\" target=\"_BLANK\">releases</a> because you are amazing!</p>\n")
|
||||
fmt.Fprintf(w, "<h2>Post a new and fresh pasta</h2>\n")
|
||||
fmt.Fprintf(w, "<p>Just POST your file at the server, e.g. ")
|
||||
fmt.Fprintf(w, "<code>curl -X POST '%s' --data-binary @FILE</code></p>\n", cf.BaseUrl)
|
||||
if cf.DefaultExpire > 0 {
|
||||
fmt.Fprintf(w, "<p>pastas expire by default after %s - Enjoy them while they are fresh!</p>\n", timeHumanReadable(cf.DefaultExpire))
|
||||
}
|
||||
fmt.Fprintf(w, "<h3>File upload</h3>")
|
||||
fmt.Fprintf(w, "<p>Upload your file and make a fresh pasta out of it:</p>")
|
||||
fmt.Fprintf(w, "<form enctype=\"multipart/form-data\" method=\"post\" action=\"/?ret=html\">\n")
|
||||
fmt.Fprintf(w, "<input type=\"file\" name=\"file\">\n")
|
||||
fmt.Fprintf(w, "<input type=\"submit\" value=\"Upload\">\n")
|
||||
fmt.Fprintf(w, "</form>\n")
|
||||
fmt.Fprintf(w, "<h3>Text paste</h3>")
|
||||
fmt.Fprintf(w, "<p>Just paste your contents in the textfield and hit the <tt>pasta</tt> button below</p>\n")
|
||||
fmt.Fprintf(w, "<form method=\"post\" action=\"/?input=form&ret=html\">\n")
|
||||
if cf.MaxPastaSize > 0 {
|
||||
fmt.Fprintf(w, "<textarea name=\"content\" rows=\"10\" cols=\"80\" maxlength=\"%d\"></textarea><br/>\n", cf.MaxPastaSize)
|
||||
} else {
|
||||
fmt.Fprintf(w, "<textarea name=\"content\" rows=\"10\" cols=\"80\"></textarea><br/>\n")
|
||||
}
|
||||
fmt.Fprintf(w, "<input type=\"submit\" value=\"Pasta!\">\n")
|
||||
fmt.Fprintf(w, "</form>\n")
|
||||
fmt.Fprintf(w, "<p>project page: <a href=\"https://github.com/grisu48/pasta\" target=\"_BLANK\">github.com/grisu48/pasta</a></p>\n")
|
||||
fmt.Fprintf(w, "</body></html>")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Suggested deploy script for pasta
|
||||
#
|
||||
|
||||
CONTAINER_NAME="pasta" # Name of the container
|
||||
CONTAINER_MEMORY="64" # Memory limitations for the container in MB
|
||||
DATA="/srv/pasta" # Data directory for config file and pastas
|
||||
PORT="8199" # Exposed port of the container
|
||||
|
||||
|
||||
function usage() {
|
||||
echo "$0 - Simple pasta deployment script"
|
||||
echo "Usage: $0 [DATADIR] - deploy instance as docker container"
|
||||
echo " $0 --rm - remove container"
|
||||
echo ""
|
||||
echo "OPTIONS"
|
||||
echo " DATADIR - Directory where data will be stored"
|
||||
}
|
||||
|
||||
if [[ $# -ge 1 ]]; then
|
||||
DATA="$1"
|
||||
if [[ $DATA == "-h" || $DATA == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if [[ $CONTAINER_NAME != "" ]]; then
|
||||
docker container stop "$CONTAINER_NAME"
|
||||
docker container rm "$CONTAINER_NAME"
|
||||
fi
|
||||
|
||||
# Special use case: Remove container
|
||||
if [[ $DATA == "--rm" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
docker pull grisu48/pasta
|
||||
|
||||
|
||||
# Prepare data directory
|
||||
mkdir -p "$DATA"
|
||||
if [[ ! -s "$DATA/pastad.toml" ]]; then
|
||||
echo "Using default configuration file $DATA/pastad.toml"
|
||||
cp "pastad.toml.example" "$DATA/pastad.toml"
|
||||
else
|
||||
echo "Found existing pastad.toml configuration"
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
docker container create --name "$CONTAINER_NAME" -p "$PORT":8199 -v "$DATA" grisu48/pasta
|
||||
docker update --restart unless-stopped "$CONTAINER_NAME"
|
||||
if [[ "$CONTAINER_MEMORY" != "" ]]; then
|
||||
# Update memory, use double the memory as swap
|
||||
docker update --memory "${CONTAINER_MEMORY}M" --memory-swap "$(($CONTAINER_MEMORY * 2))M" "$CONTAINER_NAME"
|
||||
fi
|
||||
docker container start "$CONTAINER_NAME"
|
15
test/test.sh
15
test/test.sh
|
@ -31,7 +31,7 @@ fi
|
|||
|
||||
## Setup pasta server
|
||||
../pastad -c pastad.toml -m ../mime.types -B http://127.0.0.1:8200 -b 127.0.0.1:8200 &
|
||||
sleep 2 # TODO: Don't do sleep here you lazy ... :-)
|
||||
sleep 2
|
||||
|
||||
## Push a testfile
|
||||
echo "Testfile 123" > testfile
|
||||
|
@ -44,6 +44,19 @@ link=`../pasta -r http://127.0.0.1:8200 < testfile`
|
|||
curl -o testfile2 $link
|
||||
diff testfile testfile2
|
||||
echo "Testfile 2 matches"
|
||||
# Test also sending via curl
|
||||
url=`curl -X POST http://127.0.0.1:8200 --data-binary @testfile | grep -Eo 'http://.*'`
|
||||
echo "curl stored as $url"
|
||||
curl -o testfile3 "$url"
|
||||
diff testfile testfile3
|
||||
echo "Testfile 3 matches"
|
||||
# Test the POST form
|
||||
echo -n "testpasta" > testfile4
|
||||
url=`curl -X POST "http://127.0.0.1:8200?input=form&content=testpasta" | grep -Eo 'http://.*'`
|
||||
curl -o testfile5 "$url"
|
||||
diff testfile4 testfile5
|
||||
# Test different format in link
|
||||
curl -X POST http://127.0.0.1:8200?ret=json --data-binary @testfile
|
||||
|
||||
## Second pasta server with environment variables
|
||||
echo "Testing environment variables ... "
|
||||
|
|
Loading…
Reference in a new issue