3 Commits

Author SHA1 Message Date
fb686b591a Merge 013be7aa24 into ee7e9ef2a3 2025-05-27 22:10:26 -05:00
ee7e9ef2a3 build: setup docker-compose.dev.yml dev iteration
This is based off of the 5-commit branch at
https://github.com/neobooru/szurubooru/blob/docker-development-setup.

Compared to said branch, we
* Exclude extraneous changes such as
    * Any formatting
    * The use of deprecated/ineffectual top-level `version:` in composer files
* Support controlling $THREADS (modernizing the branch to upstream)
* Integrate into master more cleanly

However, client/docker-start-dev uses a temporary hack -- due to
volume mounting overwriting node_modules at arbitrary points during the
`docker compose build` step, we run `npm i` before any given
`npm run watch`.

To see the effects of this commit in action, run:

    docker compose -f ./docker-compose.dev.yml up
2025-05-23 20:05:15 +02:00
013be7aa24 Added the possiblility for mass-deletion of posts via CLI 2024-11-16 21:12:26 +01:00
10 changed files with 196 additions and 6 deletions

View File

@ -1,4 +1,5 @@
node_modules/* node_modules/*
public/
Dockerfile Dockerfile
.dockerignore .dockerignore
**/.gitignore **/.gitignore

View File

@ -1,3 +1,26 @@
FROM --platform=$BUILDPLATFORM node:lts-alpine as development
WORKDIR /opt/app
RUN apk --no-cache add \
dumb-init \
nginx \
git
RUN ln -sf /opt/app/nginx.conf.docker /etc/nginx/nginx.conf
RUN rm -rf /var/www
RUN ln -sf /opt/app/public/ /var/www
COPY package.json package-lock.json ./
RUN npm install
ARG BUILD_INFO="docker-development"
ENV BUILD_INFO=${BUILD_INFO}
ENV BACKEND_HOST="server"
CMD ["/opt/app/docker-start-dev.sh"]
VOLUME ["/data"]
FROM --platform=$BUILDPLATFORM node:lts as builder FROM --platform=$BUILDPLATFORM node:lts as builder
WORKDIR /opt/app WORKDIR /opt/app

View File

@ -315,7 +315,7 @@ function makeOutputDirs() {
} }
function watch() { function watch() {
let wss = new WebSocket.Server({ port: 8080 }); let wss = new WebSocket.Server({ port: environment === "development" ? 8081 : 8080 });
const liveReload = !process.argv.includes('--no-live-reload'); const liveReload = !process.argv.includes('--no-live-reload');
function emitReload() { function emitReload() {

17
client/docker-start-dev.sh Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/dumb-init /bin/sh
# Integrate environment variables
sed -i "s|__BACKEND__|${BACKEND_HOST}|" \
/etc/nginx/nginx.conf
# Start server
nginx &
# Watch source for changes and build app
# FIXME: It's not ergonomic to run `npm i` outside of the build step.
# However, the mounting of different directories into the
# client container's /opt/app causes node_modules to disappear
# (the mounting causes client/Dockerfile's RUN npm install
# to silently clobber).
# Find a way to move `npm i` into client/Dockerfile.
npm i && npm run watch -- --polling

View File

@ -2,10 +2,10 @@
# Integrate environment variables # Integrate environment variables
sed -i "s|__BACKEND__|${BACKEND_HOST}|" \ sed -i "s|__BACKEND__|${BACKEND_HOST}|" \
/etc/nginx/nginx.conf /etc/nginx/nginx.conf
sed -i "s|__BASEURL__|${BASE_URL:-/}|g" \ sed -i "s|__BASEURL__|${BASE_URL:-/}|g" \
/var/www/index.htm \ /var/www/index.htm \
/var/www/manifest.json /var/www/manifest.json
# Start server # Start server
exec nginx exec nginx

View File

@ -3,7 +3,7 @@
const config = require("./config.js"); const config = require("./config.js");
if (config.environment == "development") { if (config.environment == "development") {
var ws = new WebSocket("ws://" + location.hostname + ":8080"); var ws = new WebSocket("ws://" + location.hostname + ":8081");
ws.addEventListener("open", function (event) { ws.addEventListener("open", function (event) {
console.log("Live-reloading websocket connected."); console.log("Live-reloading websocket connected.");
}); });

54
docker-compose.dev.yml Normal file
View File

@ -0,0 +1,54 @@
## Docker Compose configuration for dev iteration
##
## Data is transient by using named vols.
## Run: docker-compose -f ./docker-compose.dev.yml up
services:
server:
build:
context: ./server
target: development
depends_on:
- sql
environment:
## These should be the names of the dependent containers listed below,
## or FQDNs/IP addresses if these services are running outside of Docker
POSTGRES_HOST: sql
## Credentials for database:
POSTGRES_USER:
POSTGRES_PASSWORD:
## Commented Values are Default:
#POSTGRES_DB: defaults to same as POSTGRES_USER
#POSTGRES_PORT: 5432
#LOG_SQL: 0 (1 for verbose SQL logs)
THREADS:
volumes:
- "data:/data"
- "./server/:/opt/app/"
client:
build:
context: ./client
target: development
depends_on:
- server
volumes:
- "data:/data:ro"
- "./client/:/opt/app/"
- "/opt/app/public/"
ports:
- "${PORT}:80"
- "8081:8081"
sql:
image: postgres:11-alpine
restart: unless-stopped
environment:
POSTGRES_USER:
POSTGRES_PASSWORD:
volumes:
- "sql:/var/lib/postgresql/data"
volumes:
data:
sql:

View File

@ -61,7 +61,42 @@ ENTRYPOINT ["pytest", "--tb=short"]
CMD ["szurubooru/"] CMD ["szurubooru/"]
FROM prereqs as development
WORKDIR /opt/app
ARG PUID=1000
ARG PGID=1000
RUN apk --no-cache add \
dumb-init \
py3-pip \
py3-setuptools \
py3-waitress \
&& pip3 install --no-cache-dir --disable-pip-version-check \
hupper \
&& mkdir -p /opt/app /data \
&& addgroup -g ${PGID} app \
&& adduser -SDH -h /opt/app -g '' -G app -u ${PUID} app \
&& chown -R app:app /opt/app /data
USER app
CMD ["/opt/app/docker-start-dev.sh"]
ARG PORT=6666
ENV PORT=${PORT}
EXPOSE ${PORT}
ARG THREADS=4
ENV THREADS=${THREADS}
VOLUME ["/data/"]
FROM prereqs as release FROM prereqs as release
COPY ./ /opt/app/
RUN rm -rf /opt/app/szurubooru/tests
WORKDIR /opt/app WORKDIR /opt/app
ARG PUID=1000 ARG PUID=1000

8
server/docker-start-dev.sh Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/dumb-init /bin/sh
set -e
cd /opt/app
alembic upgrade head
echo "Starting szurubooru API on port ${PORT} - Running on ${THREADS} threads"
exec hupper -m waitress --port ${PORT} --threads ${THREADS} szurubooru.facade:app

View File

@ -13,7 +13,7 @@ from getpass import getpass
from sys import stderr from sys import stderr
from szurubooru import config, db, errors, model from szurubooru import config, db, errors, model
from szurubooru.func import files, images from szurubooru.func import files, images, snapshots
from szurubooru.func import posts as postfuncs from szurubooru.func import posts as postfuncs
from szurubooru.func import users as userfuncs from szurubooru.func import users as userfuncs
@ -100,6 +100,48 @@ def regenerate_thumbnails() -> None:
pass pass
def delete_posts(parameters: list) -> None:
verification: str = input("Do you really want to delete all posts with the given ID's [y/n]: ").lower()
if "y" != verification:
return
def delete_one_post(post_id: int) -> None:
print("Deleting post %d" % post_id)
try:
post: model.Post = postfuncs.get_post_by_id(post_id)
except postfuncs.PostNotFoundError:
print("Post with ID %d not found" % post_id)
return
postfuncs.delete(post)
snapshots.delete(post, None)
def delete_multiple_posts(start_id: int, end_id: int) -> None:
if start_id > end_id:
start_id, end_id = end_id, start_id
for post_id in range(start_id, end_id + 1):
delete_one_post(post_id)
for parameter in parameters:
try:
if "-" not in parameter:
delete_one_post(int(parameter))
continue
post_range: list = [int(number) for number in parameter.split("-", 2)]
delete_multiple_posts(*post_range)
except ValueError:
print("One of the specified parameters is not a number")
return
db.get_session().commit()
print("All posts were deleted")
def main() -> None: def main() -> None:
parser_top = ArgumentParser( parser_top = ArgumentParser(
description="Collection of CLI commands for an administrator to use", description="Collection of CLI commands for an administrator to use",
@ -129,6 +171,14 @@ def main() -> None:
help="regenerate the thumbnails for posts if the " help="regenerate the thumbnails for posts if the "
"thumbnail files are missing", "thumbnail files are missing",
) )
parser.add_argument(
"--delete-posts",
metavar="<post_ids>",
nargs='+',
help="Delete all posts with the specified ID, separated by a space. "
"Multiple posts can be deleted at once by specifying them as follows, "
"including the upper and lower limits: 37-47"
)
command = parser_top.parse_args() command = parser_top.parse_args()
try: try:
@ -140,6 +190,8 @@ def main() -> None:
reset_filenames() reset_filenames()
elif command.regenerate_thumbnails: elif command.regenerate_thumbnails:
regenerate_thumbnails() regenerate_thumbnails()
elif command.delete_posts:
delete_posts(command.delete_posts)
except errors.BaseError as e: except errors.BaseError as e:
print(e, file=stderr) print(e, file=stderr)