├── .activate.sh ├── .coveragerc ├── .deactivate.sh ├── .dockerignore ├── .github └── workflows │ ├── ci.yaml │ ├── publish-cli.yaml │ └── publish.yaml ├── .gitignore ├── .goreleaser.yaml ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── cli ├── fpb │ ├── fpb.go │ └── fpb_test.go ├── fput │ └── fput.go └── internal │ └── cli │ └── cli.go ├── fluffy ├── __init__.py ├── app.py ├── component │ ├── __init__.py │ ├── assets.py │ ├── backends.py │ ├── highlighting.py │ ├── markdown.py │ └── styles.py ├── models.py ├── run.py ├── run_prod.py ├── static │ ├── img │ │ ├── favicon.ico │ │ ├── loading-32.gif │ │ └── mime │ │ │ └── small │ │ │ ├── 7z.png │ │ │ ├── README │ │ │ ├── ai.png │ │ │ ├── bmp.png │ │ │ ├── doc.png │ │ │ ├── docx.png │ │ │ ├── gif.png │ │ │ ├── gz.png │ │ │ ├── html.png │ │ │ ├── jpeg.png │ │ │ ├── jpg.png │ │ │ ├── midi.png │ │ │ ├── mp3.png │ │ │ ├── odf.png │ │ │ ├── odt.png │ │ │ ├── paste-generic.png │ │ │ ├── pdf.png │ │ │ ├── png.png │ │ │ ├── psd.png │ │ │ ├── rar.png │ │ │ ├── rtf.png │ │ │ ├── svg.png │ │ │ ├── tar.png │ │ │ ├── txt.png │ │ │ ├── unknown.png │ │ │ ├── wav.png │ │ │ ├── xls.png │ │ │ └── zip.png │ ├── js │ │ ├── base.js │ │ ├── home-inline.js │ │ ├── home.js │ │ ├── paste-inline-start.js │ │ ├── paste-inline.js │ │ └── upload-history.js │ └── scss │ │ ├── app.scss │ │ ├── details.scss │ │ ├── home.scss │ │ ├── markdown.scss │ │ ├── paste.scss │ │ ├── reset.scss │ │ ├── text.scss │ │ └── upload-history.scss ├── templates │ ├── details.html │ ├── home.html │ ├── layouts │ │ ├── base.html │ │ └── text.html │ ├── markdown.html │ ├── paste.html │ └── upload-history.html ├── utils.py └── views.py ├── go.mod ├── go.sum ├── requirements-dev-minimal.txt ├── requirements-dev.txt ├── requirements-minimal.txt ├── requirements.txt ├── settings ├── dev_files.py ├── dev_s3.py ├── prod_s3.py └── test_files.py ├── setup.py ├── testing ├── __init__.py └── files │ ├── ansi-color │ ├── code.py │ ├── markdown.md │ └── python.diff ├── tests ├── __init__.py ├── cli │ ├── __init__.py │ ├── conftest.py │ ├── paste_test.py │ └── upload_test.py ├── conftest.py ├── integration │ ├── __init__.py │ ├── paste_test.py │ └── upload_test.py └── unit │ ├── __init__.py │ ├── component │ ├── __init__.py │ └── highlighting_test.py │ └── utils_test.py └── tox.ini /.activate.sh: -------------------------------------------------------------------------------- 1 | venv/bin/activate -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | parallel = True 3 | branch = True 4 | data_file = .coverage 5 | source = 6 | cli 7 | fluffy 8 | tests 9 | testing 10 | omit = 11 | cli/setup.py 12 | 13 | [report] 14 | show_missing = True 15 | -------------------------------------------------------------------------------- /.deactivate.sh: -------------------------------------------------------------------------------- 1 | deactivate 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | venv 2 | .git 3 | .tox 4 | *.swp 5 | playground 6 | *.pyc 7 | /testing 8 | /tests 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | build-and-test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-python@v4 13 | with: 14 | python-version: '3.11' 15 | - name: Install dependencies 16 | run: | 17 | pip install virtualenv 18 | make minimal 19 | - name: Run tests 20 | run: make test 21 | -------------------------------------------------------------------------------- /.github/workflows/publish-cli.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Release CLI 2 | 3 | on: 4 | push: 5 | tags: 6 | - cli/v* 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Set up Go 18 | uses: actions/setup-go@v5 19 | - name: Set VERSION 20 | run: | 21 | echo VERSION=${TAG#cli/v} >> $GITHUB_ENV 22 | env: 23 | TAG: ${{ github.ref_name }} 24 | - name: Build release artifacts 25 | run: | 26 | make release-cli 27 | - name: Upload assets 28 | uses: actions/upload-artifact@v4 29 | with: 30 | name: release 31 | path: dist/* 32 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | jobs: 7 | build-and-publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-python@v4 12 | with: 13 | python-version: '3.11' 14 | - name: Install dependencies 15 | run: pip install virtualenv build 16 | - name: Build assets 17 | run: make assets 18 | - name: Upload assets 19 | run: | 20 | ln -s settings/prod_s3.py settings.py 21 | echo y | make upload-assets 22 | rm settings.py 23 | env: 24 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }} 25 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 26 | AWS_DEFAULT_REGION: us-west-1 27 | - name: Build Python artifacts 28 | run: python -m build --sdist --wheel --outdir dist 29 | - name: Publish to PyPI 30 | uses: pypa/gh-action-pypi-publish@release/v1 31 | with: 32 | password: ${{ secrets.PYPI_TOKEN }} 33 | skip-existing: true 34 | - name: Login to GitHub Container Registry 35 | uses: docker/login-action@v2 36 | with: 37 | registry: ghcr.io 38 | username: ${{ github.actor }} 39 | password: ${{ secrets.GITHUB_TOKEN }} 40 | - name: Build and push Docker image 41 | uses: docker/build-push-action@v4 42 | with: 43 | tags: 'ghcr.io/chriskuehl/fluffy-server:latest,ghcr.io/chriskuehl/fluffy-server:${{ github.ref_name }}' 44 | push: true 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.egg-info 3 | *.py[cod] 4 | /*.build 5 | /*.buildinfo 6 | /*.changes 7 | /*.deb 8 | /.coverage 9 | /.coverage.* 10 | /.pytest_cache 11 | /.tox 12 | /build 13 | /dist 14 | /fluffy/static/**/*.hash 15 | /fluffy/static/app.css 16 | /fluffy/static/pygments.css 17 | /settings.py 18 | /tmp 19 | /venv 20 | /fpb 21 | /fput 22 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | snapshot: 3 | name_template: '{{.Env.VERSION}}' 4 | builds: 5 | - id: fput 6 | main: ./cli/fput 7 | no_unique_dist_dir: true 8 | binary: fput-{{.Os}}-{{.Arch}} 9 | - id: fpb 10 | main: ./cli/fpb 11 | no_unique_dist_dir: true 12 | binary: fpb-{{.Os}}-{{.Arch}} 13 | archives: 14 | - format: binary 15 | nfpms: 16 | - id: cli 17 | package_name: fluffy 18 | builds: 19 | - fpb 20 | - fput 21 | homepage: 'https://github.com/chriskuehl/fluffy' 22 | maintainer: 'Chris Kuehl ' 23 | description: 'command-line tools for uploading to fluffy servers' 24 | license: Apache-2.0 25 | formats: 26 | - deb 27 | - rpm 28 | - archlinux 29 | contents: 30 | - src: /usr/bin/fpb-{{.Os}}-{{.Arch}} 31 | dst: /usr/bin/fpb 32 | type: symlink 33 | - src: /usr/bin/fput-{{.Os}}-{{.Arch}} 34 | dst: /usr/bin/fput 35 | type: symlink 36 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.6.0 4 | hooks: 5 | - id: check-added-large-files 6 | - id: check-docstring-first 7 | - id: check-executables-have-shebangs 8 | - id: check-json 9 | - id: check-merge-conflict 10 | - id: check-xml 11 | - id: check-yaml 12 | - id: debug-statements 13 | - id: detect-private-key 14 | - id: double-quote-string-fixer 15 | - id: end-of-file-fixer 16 | - id: name-tests-test 17 | - id: requirements-txt-fixer 18 | - id: trailing-whitespace 19 | - repo: https://github.com/pycqa/flake8 20 | rev: 7.1.1 21 | hooks: 22 | - id: flake8 23 | - repo: https://github.com/hhatto/autopep8 24 | rev: v2.3.1 25 | hooks: 26 | - id: autopep8 27 | - repo: https://github.com/asottile/reorder-python-imports 28 | rev: v3.13.0 29 | hooks: 30 | - id: reorder-python-imports 31 | - repo: https://github.com/asottile/add-trailing-comma 32 | rev: v3.1.0 33 | hooks: 34 | - id: add-trailing-comma 35 | args: [--py36-plus] 36 | - repo: https://github.com/asottile/pyupgrade 37 | rev: v3.17.0 38 | hooks: 39 | - id: pyupgrade 40 | args: [--py310-plus] 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Release process (server) 2 | 3 | 1. Bump version in `fluffy/__init__.py` 4 | 2. Commit, tag as "vX.Y.Z", and push to main. 5 | 3. A GitHub Actions workflow will build and publish to PyPI. 6 | 7 | 8 | ## Release process (cli) 9 | 10 | 1. Bump version in `cli/fluffy_cli/__init__.py` 11 | 2. cd to `cli` and use `dch` to update the Debian changelog 12 | 3. Run `make release` and upload a new deb to GitHub releases. 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye 2 | 3 | RUN apt-get update \ 4 | && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \ 5 | && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ 6 | ca-certificates \ 7 | curl \ 8 | dumb-init \ 9 | gcc \ 10 | libxml2-dev \ 11 | libxslt1-dev \ 12 | zlib1g-dev \ 13 | zstd \ 14 | && apt-get clean 15 | 16 | RUN curl \ 17 | -sLo /tmp/python.tar.zst \ 18 | 'https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1+20230116-x86_64_v3-unknown-linux-gnu-pgo+lto-full.tar.zst' \ 19 | && echo '8e279b25388e47124a422f300db710cdc98c64cf24bf6903f6f6e8ddbc52d743 */tmp/python.tar.zst' | sha256sum --check \ 20 | && tar -C /opt -xf /tmp/python.tar.zst \ 21 | && rm /tmp/python.tar.zst 22 | ENV PATH=/opt/python/install/bin:$PATH 23 | 24 | COPY . /opt/fluffy 25 | 26 | RUN install --owner=nobody -d /srv/fluffy 27 | RUN python3.11 -m venv /srv/fluffy/venv \ 28 | && /srv/fluffy/venv/bin/pip install -r /opt/fluffy/requirements.txt /opt/fluffy gunicorn==20.1.0 29 | 30 | USER nobody 31 | EXPOSE 8000 32 | ENV FLUFFY_SETTINGS /opt/fluffy/settings/prod_s3.py 33 | ENV PYTHONUNBUFFERED TRUE 34 | CMD [ \ 35 | "/usr/bin/dumb-init", "--", \ 36 | "/srv/fluffy/venv/bin/gunicorn", \ 37 | "-b", "0.0.0.0:8000", \ 38 | "-w", "4", \ 39 | "fluffy.run:app" \ 40 | ] 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Summary: 2 | 3 | This repository contains some third-party assets which are licensed 4 | individually. All other code and images are licensed under Apache 2.0; 5 | 6 | =============================================================================== 7 | 8 | All original code and images are copyright (c) 2016 Chris Kuehl. 9 | 10 | All code and images which do not have their own copyright attached (e.g. as a 11 | comment block or a separate file), are released under the Apache 2.0 license, a 12 | copy of which is included below. 13 | 14 | Licensed under the Apache License, Version 2.0 (the "License"); 15 | you may not use this file except in compliance with the License. 16 | You may obtain a copy of the License at 17 | 18 | https://www.apache.org/licenses/LICENSE-2.0 19 | 20 | Unless required by applicable law or agreed to in writing, software 21 | distributed under the License is distributed on an "AS IS" BASIS, 22 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | See the License for the specific language governing permissions and 24 | limitations under the License. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements-minimal.txt 2 | recursive-include fluffy/static * 3 | recursive-include fluffy/templates * 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VENV := venv 2 | BIN := $(VENV)/bin 3 | export FLUFFY_SETTINGS := $(CURDIR)/settings.py 4 | 5 | .PHONY: minimal 6 | minimal: $(VENV) fpb fput assets settings.py install-hooks 7 | 8 | fpb: cli/fpb/fpb.go cli/internal/cli/cli.go go.mod 9 | go build ./cli/fpb 10 | 11 | fput: cli/fput/fput.go cli/internal/cli/cli.go go.mod 12 | go build ./cli/fput 13 | 14 | .PHONY: release-cli 15 | release-cli: export GORELEASER_CURRENT_TAG ?= 0.0.0 16 | release-cli: export VERSION ?= 0.0.0 17 | release-cli: 18 | go run github.com/goreleaser/goreleaser/v2@latest release --clean --snapshot --verbose 19 | rm -v dist/*.txt dist/*.yaml dist/*.json 20 | 21 | $(VENV): setup.py requirements.txt requirements-dev.txt 22 | rm -rf $@ 23 | virtualenv -ppython3.11 $@ 24 | $@/bin/pip install -r requirements.txt -r requirements-dev.txt -e . 25 | ln -fs ../../fput $@/bin/fput 26 | ln -fs ../../fpb $@/bin/fpb 27 | 28 | fluffy/static/app.css: $(VENV) $(wildcard fluffy/static/scss/*.scss) 29 | $(BIN)/pysassc fluffy/static/scss/app.scss $@ 30 | 31 | fluffy/static/pygments.css: $(VENV) fluffy/component/styles.py 32 | $(BIN)/python -m fluffy.component.styles > $@ 33 | 34 | ASSET_FILES := $(shell find fluffy/static -type f -not -name '*.hash') 35 | ASSET_HASHES := $(addsuffix .hash,$(ASSET_FILES)) 36 | 37 | fluffy/static/%.hash: fluffy/static/% 38 | sha256sum $^ | awk '{print $$1}' > $@ 39 | 40 | .PHONY: assets 41 | assets: fluffy/static/app.css.hash fluffy/static/pygments.css.hash $(ASSET_HASHES) 42 | 43 | .PHONY: upload-assets 44 | upload-assets: assets $(VENV) 45 | $(BIN)/fluffy-upload-assets 46 | 47 | settings.py: 48 | ln -fs settings/dev_files.py settings.py 49 | 50 | .PHONY: watch-assets 51 | watch-assets: 52 | while :; do \ 53 | find fluffy/static -type f | \ 54 | inotifywait --fromfile - -e modify; \ 55 | make assets; \ 56 | done 57 | 58 | .PHONY: dev 59 | dev: $(VENV) fluffy/static/app.css 60 | $(BIN)/python -m fluffy.run 61 | 62 | .PHONY: test 63 | test: $(VENV) 64 | cd cli && go test -v ./... 65 | $(BIN)/coverage erase 66 | COVERAGE_PROCESS_START=$(CURDIR)/.coveragerc \ 67 | $(BIN)/py.test --tb=native -vv tests/ 68 | $(BIN)/coverage combine 69 | $(BIN)/coverage report 70 | 71 | .PHONY: install-hooks 72 | install-hooks: $(VENV) 73 | $(BIN)/pre-commit install -f --install-hooks 74 | 75 | .PHONY: pre-commit 76 | pre-commit: $(VENV) 77 | $(BIN)/pre-commit run --all-files 78 | 79 | .PHONY: clean 80 | clean: 81 | rm -rf $(VENV) 82 | 83 | .PHONY: upgrade-requirements 84 | upgrade-requirements: venv 85 | upgrade-requirements 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## fluffy: a file sharing web app that doesn't suck. 2 | 3 | | Overall build status | [![GitHub Actions CI](https://github.com/chriskuehl/fluffy/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/chriskuehl/fluffy/actions/workflows/ci.yaml) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/chriskuehl/fluffy/main.svg)](https://results.pre-commit.ci/latest/github/chriskuehl/fluffy/main) | 4 | | -------------------- | ----------------------------- | 5 | | `fluffy-server` | [![PyPI version](https://badge.fury.io/py/fluffy-server.svg)](https://pypi.python.org/pypi/fluffy-server) | 6 | | `fluffy` | [![PyPI version](https://badge.fury.io/py/fluffy.svg)](https://pypi.python.org/pypi/fluffy) | 7 | 8 | 9 | ![fluffy screenshots](https://i.fluffy.cc/sx8c22NDDBw2hG0slzZVLM2ZW2FHw0j5.png) 10 | 11 | 12 | ### What is fluffy? 13 | 14 | fluffy is a Flask-based web application that allows you to upload arbitrary 15 | files to the web. Once you upload the files, you get a link to the file which 16 | you can share. 17 | 18 | The reference instance of fluffy is online at [fluffy.cc](https://fluffy.cc/). 19 | You can also run your own! 20 | 21 | 22 | ### What isn't fluffy? 23 | 24 | * **fluffy isn't social.** Files are given a long, random (unguessable) name. 25 | There's no upload feed or list of files. 26 | * **fluffy isn't intrusive.** Your files aren't resized, compressed, stripped, 27 | or modified in any way. 28 | * **fluffy isn't annoying.** A simple, modern page for uploading your files. No 29 | ads, no memes, and no comments. 30 | 31 | 32 | ### Philosophy and motivation 33 | 34 | fluffy was created out of frustration from seeing hundreds of files (mostly 35 | images) be lost or deleted over the years from popular image hosts such as 36 | imageshack which either deleted files or closed their doors entirely. Fluffy is 37 | designed so that it is easy to stop accepting uploads while still serving 38 | existing files, with the hope being that a "shut down" would involve no longer 39 | accepting uploads, but still continuing to serve existing uploads. 40 | 41 | fluffy only handles uploading and storing your files. There's no database, and 42 | it's up to you to figure out how you serve the uploaded files. Once fluffy 43 | stores a file, it forgets about it. 44 | 45 | This not only makes the code simple, but also makes maintenance easy. If you 46 | wish to stop accepting uploads, you can easily throw the existing uploads on S3 47 | or any web server to ensure their continued availability. 48 | 49 | This does make some features hard or impossible to implement, however, so if 50 | you want to do anything post-upload at the application level, fluffy probably 51 | isn't for you. 52 | 53 | 54 | #### Storing files 55 | 56 | fluffy hands off uploaded files to a *storage backend*, which is responsible 57 | for saving the file. The following backends are currently available: 58 | 59 | * **File.** Storage on the local filesystem. You can easily serve these with 60 | any web server. 61 | * **Amazon S3.** Storage on Amazon S3. You can serve these with S3 static 62 | websites or with CloudFront (if you want a CDN). 63 | 64 | Writing a storage backend is dead simple and requires you to implement only a 65 | single method. The current backends are both about ten lines of code. 66 | 67 | 68 | #### Serving files 69 | 70 | fluffy won't serve your files, period. It's up to you to figure this part out. 71 | Depending on which backend you use, you may get it easily. For example, Amazon 72 | S3 makes it easy to serve uploaded files via the web. 73 | 74 | 75 | ### Run your own fluffy 76 | 77 | There's a public "reference implementation" of fluffy at 78 | [fluffy.cc](https://fluffy.cc/). 79 | 80 | To host your own copy of fluffy, just adjust `settings.py` to your needs, being 81 | sure to uncomment whichever storage backend you wish to use. There's no 82 | database, so setup is very simple. 83 | 84 | Once you've adjusted the configuration, you can deploy fluffy the way you 85 | deploy any Flask app. fluffy is tested with Python versions 3.5 and 3.6. 86 | 87 | 88 | ### Command-line uploading tools 89 | 90 | Two tools, `fput` and `fpb`, are provided. They can be installed with `pip 91 | install fluffy` and used from the command line. Use `--help` with either tool 92 | for more information. 93 | 94 | Additionally, Debian packages for the command-line tools are available in the 95 | [GitHub releases tab](https://github.com/chriskuehl/fluffy/releases). These 96 | packages contain no binary components and should be compatible with most 97 | releases of Debian and Ubuntu. 98 | 99 | 100 | ### Contributing, license, and credits 101 | 102 | Contributions to fluffy are welcome! Send your pull requests or file an issue. 103 | Thanks for the help! 104 | 105 | fluffy is released under the MIT license; see LICENSE for full details. 106 | 107 | 108 | #### Running locally for development 109 | 110 | To run fluffy during development, run `make dev`. 111 | You should now have fluffy running at `http://localhost:5000`. 112 | 113 | 114 | ### FAQ 115 | #### Why are there only certain languages in the dropdown? Can I add more? 116 | 117 | Since it's just a normal `