Compare commits

...

22 Commits

Author SHA1 Message Date
ge
71e1d87d32 feat: Improve and make scripts POSIX compliant 2022-09-29 10:47:48 +03:00
ge
921dd5d29d feat: Add dark theme via prefers-color-scheme 2022-09-29 10:46:51 +03:00
ge
cde343d159 update README 2022-09-29 10:44:41 +03:00
ge
cd258011be fix: Update link 2022-09-29 08:31:36 +03:00
aed21e14a3 Merge pull request #2 from gechandesu/dependabot/pip/bottle-0.12.20
build(deps): Bump bottle from 0.12.19 to 0.12.20
2022-07-27 08:40:34 +03:00
85b2664483 build(deps): Bump bottle from 0.12.19 to 0.12.20
Bumps [bottle](https://github.com/bottlepy/bottle) from 0.12.19 to 0.12.20.
- [Release notes](https://github.com/bottlepy/bottle/releases)
- [Changelog](https://github.com/bottlepy/bottle/blob/master/docs/changelog.rst)
- [Commits](https://github.com/bottlepy/bottle/compare/0.12.19...0.12.20)

---
updated-dependencies:
- dependency-name: bottle
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-03 22:50:24 +00:00
ge
dbdbf2a3d9 feat: Remove WSGI server from requirements 2022-03-17 21:42:47 +03:00
ge
3a8bef4ddc feat: Reduce image size from ~930M to ~85M 2022-03-17 21:42:06 +03:00
ge
8122de2c52 fix: Naitilus script fixed 2022-01-29 02:43:50 +03:00
ge
2eea050bf8 fix: Fix typo in README 2022-01-23 16:47:49 +03:00
gd
2f7fc76573 fix: Fix Nginx vhost example 2022-01-05 13:22:13 +03:00
gd
dd570f7f93 feat: Add Dockerfile 2022-01-05 13:12:51 +03:00
gd
312efc9047 feat: Update requirements 2022-01-05 11:18:10 +03:00
gd
fc80d9e661 fix: Fixed WSGI app startup 2022-01-05 09:31:27 +03:00
gd
721fda8b22 feat: Update README 2022-01-05 08:33:43 +03:00
gd
331a377c6b feat: Update imgs.ini 2022-01-05 08:33:18 +03:00
gd
12f4433e8e feat: Add shell-scripts 2022-01-05 08:32:46 +03:00
gd
0898b1f593 feat: Update version 2022-01-05 08:18:54 +03:00
gd
4ff6362c64 feat: Remove run() options 2022-01-05 07:52:05 +03:00
gd
8e4ab793f9 feat: Refactored uplocad_image(), MIME check skipping added 2022-01-05 07:16:24 +03:00
gd
87f6c3a048 feat: Add favicon 2022-01-05 07:14:47 +03:00
gd
397aeed123 feat: Update LICENSE 2022-01-05 07:13:50 +03:00
11 changed files with 286 additions and 78 deletions

9
Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM alpine:latest
RUN apk update && apk add python3 py3-pip
ADD . /opt/imgs
RUN mkdir -p /opt/imgs/uploads
WORKDIR /opt/imgs
RUN pip install --upgrade pip && pip install --requirement requirements.txt
RUN pip install gunicorn
EXPOSE 5000
CMD gunicorn imgs:app --bind :5000

View File

@ -1,4 +1,4 @@
Copyright (c) 2021 gd <gechandev@gmail.com> Copyright (c) 2022 ge <gechandev@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View File

@ -5,8 +5,74 @@ imgs is a minimalictic image sharing web app written with Bottle framework.
No database. No image compression. No time limits. No additional dependencies. No database. No image compression. No time limits. No additional dependencies.
Features: Features:
* Upload images via Drag&Drop * Upload images via Drag&Drop
* Easy copy share link * Easy copy share link
* MIME type detecting * MIME type detecting
See deployment options in Bottle documentation: https://bottlepy.org/docs/dev/deployment.html See deployment options in Bottle documentation: https://bottlepy.org/docs/dev/deployment.html
# Run imgs in Docker
Clone repository and edit **imgs.ini**.
Build Docker image:
```shell
docker build --tag imgs .
```
Run container from image. Replace **/path/to/your/uploads/dir** with path to directory where you want to store images:
```shell
docker run -d \
--name imgs \
--publish 127.0.0.1:5000:5000 \
--volume /path/to/your/uploads/dir:/opt/imgs/uploads \
imgs
```
imgs will launched on `127.0.0.1:5000`. Set up reverse proxy server. I recommed to use basic authentication to prevent abuses. Nginx virtual host example:
```nginx
server {
listen 80;
server_name yourdomain.tld;
root /path/to/imgs/root;
location / {
auth_basic "Authentication required";
auth_basic_user_file /path/to/.htpasswd;
proxy_pass http://127.0.0.1:5000;
}
location ~* ^/favicon.ico$ {
try_files $uri $uri/ =404;
}
location ~* \..* {
auth_basic off;
proxy_pass http://127.0.0.1:5000;
}
}
```
# Additional
## imgs client with CLI
imgs has a simple CLI tool based on curl. Copy **imgs** script to your PATH.
```shell
sudo cp imgs /usr/bin/imgs
```
## Nautilus integration
Push files to your imgs instance via GNOME Files (former name: Nautilus). Depends on: curl, libnotify (notify-send utility).
Just place **Upload to imgs** script into **~/.local/share/nautilus/scripts/** directory.
```shell
DIR=~/.local/share/nautilus/scripts/; mkdir -p $DIR && cp Upload\ to\ imgs $DIR
```

32
Upload to imgs Executable file
View File

@ -0,0 +1,32 @@
#!/bin/sh
# This is an imgs https://git.nxhs.cloud/ge/imgs "integration" for Nautilus.
# Place this script into path: $HOME/.local/share/nautilus/scripts
# See more info at: <https://help.ubuntu.com/community/NautilusScriptsHowto>
IMGSLOG="${IMGSLOG:-$HOME/imgs_debug.log}"
[ -n "$IMGSREMOTE" ] && return 0 # exit from func if variable is set
if [ -f "$HOME"/.imgsremote ]; then
# shellcheck source=/dev/null
. "$HOME"/.imgsremote
fi
if [ -z "$IMGSREMOTE" ]; then
echo "$0: Error: IMGSREMOTE variable is not set." >&2; exit 1
fi
[ -n "$IMGSDEBUG" ] && date +"[%d %b %Y %H:%M:%S] Started" >> "$IMGSLOG"
echo "$NAUTILUS_SCRIPT_SELECTED_FILE_PATHS" | while read -r file; do
[ -z "$file" ] && break
if [ -n "$IMGSDEBUG" ]; then
image="$(curl -v -L -F "image=@$file" "$IMGSREMOTE" 2>&1 | tee -a "$IMGSLOG")"
image="$(echo "$image" | tail -n 1)"
else
image="$(curl -L -F "image=@$file" "$IMGSREMOTE")"
fi
[ -n "$IMGSDEBUG" ] && echo "$(date +"[%d %b %Y %H:%M:%S]") $file --> $image" >> "$IMGSLOG"
notify-send "File uploaded to imgs!" "$image"
done
[ -n "$IMGSDEBUG" ] && date +"[%d %b %Y %H:%M:%S] Finished" >> "$IMGSLOG"

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

74
imgs Executable file
View File

@ -0,0 +1,74 @@
#!/bin/sh
# imgs CLI https://git.nxhs.cloud/ge/imgs
imgs_usage() {
cat <<- EOF
Upload images to remote imgs server.
Usage: imgs [-rvh] <file>...
Options:
-r, --remote remote imgs instance URI e.g. https://user:password@example.org
-v, --version print version and exit.
-h, --help print this help message and exit.
Environment variables:
IMGSREMOTE remote imgs instance URI.
IMGSDEBUG enables verbose mode and logging.
IMGSLOG path to logfile. Default: ~/imgs_debug.log
You can set variables in ~/.imgsremote file instead of ~/.bashrc
See <https://git.nxhs.cloud/ge/imgs> for more info.
EOF
}
[ "$#" -eq 0 ] && { imgs_usage; exit 1; }
# Transform long options to short ones
for arg in "$@"; do
shift
case "$arg" in
--remote) set -- "$@" "-r";;
--help) set -- "$@" "-h";;
--version) set -- "$@" "-v";;
*) set -- "$@" "$arg";;
esac
done
while getopts r:vh OPT; do
case "$OPT" in
r) IMGSREMOTE="$OPTARG";;
v) echo 'imgs CLI 1.1'; exit 0;;
h) imgs_usage; exit 0;;
*) echo "$0: Unknown option: $OPT" >&2; exit 1;;
esac
done
shift $((OPTIND - 1)) # shift for parse positional args
# Check variables
IMGSLOG="${IMGSLOG:-$HOME/imgs_debug.log}"
[ -n "$IMGSREMOTE" ] && return 0 # exit from func if variable is set
if [ -f "$HOME"/.imgsremote ]; then
# shellcheck source=/dev/null
. "$HOME"/.imgsremote
fi
if [ -z "$IMGSREMOTE" ]; then
echo "$0: Error: IMGSREMOTE variable is not set." >&2; exit 1
fi
[ -n "$IMGSDEBUG" ] && date +"[%d %b %Y %H:%M:%S] Started" | tee -a "$IMGSLOG"
for file in "$@"; do
filepath="$(realpath "$file")"
if [ -n "$IMGSDEBUG" ]; then
echo "Uploading $filepath ..." | tee -a "$IMGSLOG"
curl -v -L -F "image=@/$filepath" "$IMGSREMOTE" 2>&1 | tee -a "$IMGSLOG"
else
curl -L -F "image=@/$filepath" "$IMGSREMOTE"
fi
done
[ -n "$IMGSDEBUG" ] && date +"[%d %b %Y %H:%M:%S] Finished" | tee -a "$IMGSLOG"

View File

@ -1,6 +1,6 @@
[imgs] [imgs]
debug = False
base_url = http://localhost:5000/ base_url = http://localhost:5000/
image_name_lenght = 3 image_name_lenght = 3
uploads_dir = uploads uploads_dir = uploads
#allowed_mime_types = * # allow any MIME type
allowed_mime_types = ['image/bmp', 'image/gif', 'image/jpeg', 'image/png', 'image/svg+xml', 'image/tiff', 'image/webp'] allowed_mime_types = ['image/bmp', 'image/gif', 'image/jpeg', 'image/png', 'image/svg+xml', 'image/tiff', 'image/webp']

38
imgs.py
View File

@ -1,3 +1,5 @@
__version__ = '1.1'
import os import os
import random import random
import string import string
@ -48,30 +50,38 @@ def upload_image():
# Handle request from CLI # Handle request from CLI
if request.files.get('image'): if request.files.get('image'):
file = request.files.get('image') file = request.files.get('image')
if file.content_type in config['imgs.allowed_mime_types']: rq = 'cli'
image_name = upload_file(file)
return get_image_url(image_name) + '\n'
else:
# Prevent recource leek. Force close buffered file
request.body.close()
response.status = 415
return 'Error: bad file MIME type\n'
# Handle request from web-browser # Handle request from web-browser
elif request.files.get('image_web'): elif request.files.get('image_web'):
file = request.files.get('image_web') file = request.files.get('image_web')
if file.content_type in config['imgs.allowed_mime_types']: rq = 'web'
if config['imgs.allowed_mime_types'] == '*':
# Skip MIME checking.
image_name = upload_file(file) image_name = upload_file(file)
return template('index.tpl',
uploaded = True, not_found = False, bad_mime_type = False,
base_url = get_base_url(), image_url = get_image_url(image_name))
else: else:
if file.content_type in config['imgs.allowed_mime_types']:
# Upload file!
image_name = upload_file(file)
else:
# Show MIME type error!
# Prevent recource leek. Force close buffered file # Prevent recource leek. Force close buffered file
request.body.close() request.body.close()
response.status = 415 response.status = 415
if rq == 'cli':
return 'Error: bad file MIME type\n'
else:
return template('index.tpl', return template('index.tpl',
uploaded = False, not_found = False, bad_mime_type = True, uploaded = False, not_found = False, bad_mime_type = True,
allowed_mime_types = config['imgs.allowed_mime_types'], allowed_mime_types = config['imgs.allowed_mime_types'],
base_url = get_base_url(), image_url = 'None') base_url = get_base_url(), image_url = 'None')
# Return 200 OK
if rq == 'cli':
return get_image_url(image_name) + '\n'
else:
return template('index.tpl',
uploaded = True, not_found = False, bad_mime_type = False,
base_url = get_base_url(), image_url = get_image_url(image_name))
@get('/<image_name>') @get('/<image_name>')
def send_image(image_name): def send_image(image_name):
@ -81,5 +91,7 @@ def send_image(image_name):
def send_style(): def send_style():
return static_file('style.css', root = './') return static_file('style.css', root = './')
app = app() # Create WSGI application
if __name__ == '__main__': if __name__ == '__main__':
run(debug = config['imgs.debug']) run()

View File

@ -77,7 +77,7 @@
<div class="logo"> <div class="logo">
<pre> __<br>|__| _____ ____ ______<br>| |/ \ / ___\/ ___/<br>| | Y Y / /_/ \___ \<br>|__|__|_| \___ /____ ><br> \/_____/ \/</pre> <pre> __<br>|__| _____ ____ ______<br>| |/ \ / ___\/ ___/<br>| | Y Y / /_/ \___ \<br>|__|__|_| \___ /____ ><br> \/_____/ \/</pre>
</div> </div>
<p><a href="https://gitea.gch.icu/gd/imgs" target="_blank">v1.0</a></p> <p><a href="https://git.nxhs.cloud/ge/imgs" target="_blank">v1.1</a></p>
</main> </main>
</body> </body>

View File

@ -1 +1 @@
bottle==0.12.19 bottle==0.12.20

View File

@ -1,5 +1,18 @@
:root {
--b: #000;
--w: #fff;
}
@media (prefers-color-scheme: dark) {
:root {
--b: #fff;
--w: #000;
}
}
body { body {
background-color: #fff; color: var(--b);
background-color: var(--w);
font-family: 'Ubuntu Mono', monospace; font-family: 'Ubuntu Mono', monospace;
max-width: 720px; max-width: 720px;
margin: 0 auto; margin: 0 auto;
@ -7,11 +20,14 @@ body {
} }
main { margin: 4rem 2rem; } main { margin: 4rem 2rem; }
.not-found, .bad-mime-type { margin-bottom: 2rem; } a, a:visited { color: var(--b); }
a, a:visited { color: #000; }
img { width: 100%; } img { width: 100%; }
.not-found, .bad-mime-type { margin-bottom: 2rem; }
.logo pre {
display: flex;
justify-content: center;
text-align: left;
}
/* Drag and Drop */ /* Drag and Drop */
.drop-area { .drop-area {
@ -23,9 +39,7 @@ img { width: 100%; }
padding: 25px; padding: 25px;
border: 3px dashed #e1e1e1; border: 3px dashed #e1e1e1;
} }
.drop-area.dragover { border-color: var(--b); }
.drop-area.dragover { border-color: #000; }
.file-input { .file-input {
position: absolute; position: absolute;
left: 0; left: 0;
@ -36,7 +50,6 @@ img { width: 100%; }
align-items: center; align-items: center;
opacity: 0; opacity: 0;
} }
.file-input-label { .file-input-label {
display: block; display: block;
margin-top: 1rem; margin-top: 1rem;
@ -46,17 +59,17 @@ img { width: 100%; }
.copy-to-clipboard { .copy-to-clipboard {
display: flex; display: flex;
margin: 2rem 0; margin: 2rem 0;
border: 1px solid #000000; border: 1px solid var(--b);
} }
.copy-to-clipboard input[type=text] { .copy-to-clipboard input[type=text] {
flex: 50%; flex: 50%;
width: 100%; width: 100%;
padding: 12px 20px; padding: 12px 20px;
border: none; border: none;
outline: none; outline: none;
background-color: var(--w);
color: var(--b);
} }
.copy-to-clipboard button { .copy-to-clipboard button {
padding: 12px 20px; padding: 12px 20px;
margin: 0; margin: 0;
@ -64,8 +77,8 @@ img { width: 100%; }
cursor: pointer; cursor: pointer;
text-align: center; text-align: center;
border: none; border: none;
background-color: #000000; background-color: var(--b);
color: #ffffff; color: var(--w);
} }
/* cURL command */ /* cURL command */
@ -73,13 +86,15 @@ img { width: 100%; }
text-align: left; text-align: left;
padding: 12px 20px; padding: 12px 20px;
font-size: 14px; font-size: 14px;
background: #000000; background: var(--b);
color: #ffffff; color: var(--w);
overflow-x: auto; overflow-x: auto;
} }
.logo pre { /* SVG */
display: flex; svg {
justify-content: center; color: var(--b);
text-align: left; }
svg path {
stroke: currentcolor;
} }