Add Debian package and Gitea APT repository support

debian/control, postinst, prerm, postrm — standard dpkg package lifecycle
debian/files/opt/dupfinder/dupfinder-setup.sh — interactive setup:
  checks Docker, detects NVIDIA GPU, prompts for photos/data paths,
  writes docker-compose.override.yml with GPU reservation if present,
  pulls image from registry (builds from source as fallback)
debian/files/usr/local/bin/dupfinder — CLI wrapper:
  setup / start / stop / restart / status / logs / open / update
debian/files/etc/systemd/system/dupfinder.service — systemd unit,
  guards against starting before setup has run
debian/build-deb.sh — builds .deb and uploads to Gitea package registry;
  prints the exact apt sources.list line on success

Install on any Debian/Ubuntu machine:
  echo "deb [trusted=yes] http://192.168.1.64:3000/api/packages/tocmo0nlord/debian bookworm main" \
    | sudo tee /etc/apt/sources.list.d/dupfinder.list
  sudo apt update && sudo apt install dupfinder
  sudo dupfinder setup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
tocmo
2026-04-05 01:42:45 -04:00
parent c110a8e4f9
commit f9164b4fa0
8 changed files with 482 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
[Unit]
Description=DupFinder Duplicate Photo Scanner
Documentation=http://192.168.1.64:3000/tocmo0nlord/duplicate-finder
After=docker.service network-online.target
Requires=docker.service
Wants=network-online.target
[Service]
Type=simple
Restart=on-failure
RestartSec=10
EnvironmentFile=-/etc/dupfinder.conf
WorkingDirectory=/opt/dupfinder
ExecStart=/usr/bin/docker compose \
-f /opt/dupfinder/docker-compose.yml \
-f /opt/dupfinder/docker-compose.override.yml \
up --no-build --remove-orphans
ExecStop=/usr/bin/docker compose \
-f /opt/dupfinder/docker-compose.yml \
-f /opt/dupfinder/docker-compose.override.yml \
down
# Don't start if override hasn't been created yet (setup not run)
ExecStartPre=/bin/test -f /opt/dupfinder/docker-compose.override.yml
StandardOutput=journal
StandardError=journal
SyslogIdentifier=dupfinder
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,167 @@
#!/bin/bash
# DupFinder first-time setup — configure paths, pull image, write override
set -e
CONF_FILE="/etc/dupfinder.conf"
COMPOSE_DIR="/opt/dupfinder"
OVERRIDE_YML="$COMPOSE_DIR/docker-compose.override.yml"
IMAGE_NAME="tocmo0nlord/dupfinder:latest"
DATA_DIR="/var/lib/dupfinder/data"
APP_PORT=8765
RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${CYAN}==> $*${NC}"; }
ok() { echo -e "${GREEN} OK $*${NC}"; }
err() { echo -e "${RED} !! $*${NC}"; }
# ── Root check ────────────────────────────────────────────────────────────────
if [[ $EUID -ne 0 ]]; then
err "Please run as root: sudo dupfinder setup"
exit 1
fi
echo ""
echo " ╔══════════════════════════════════════╗"
echo " ║ DupFinder Setup ║"
echo " ╚══════════════════════════════════════╝"
echo ""
# ── Load existing config as defaults ─────────────────────────────────────────
[[ -f "$CONF_FILE" ]] && source "$CONF_FILE"
: "${PHOTOS_PATH:=/mnt/photos}"
: "${DATA_PATH:=$DATA_DIR}"
: "${APP_PORT:=8765}"
# ── Check Docker ──────────────────────────────────────────────────────────────
info "Checking Docker..."
if ! command -v docker &>/dev/null; then
err "Docker is not installed."
echo " Install with: curl -fsSL https://get.docker.com | sh"
exit 1
fi
if ! docker info &>/dev/null; then
err "Docker daemon is not running."
echo " Start with: sudo systemctl start docker"
exit 1
fi
ok "Docker is running"
# ── Check docker compose ──────────────────────────────────────────────────────
if ! docker compose version &>/dev/null; then
err "docker compose (V2 plugin) not found. Update Docker or install docker-compose-plugin."
exit 1
fi
ok "docker compose V2 available"
# ── Check NVIDIA GPU ──────────────────────────────────────────────────────────
info "Checking GPU..."
if command -v nvidia-smi &>/dev/null && nvidia-smi &>/dev/null; then
GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1)
ok "NVIDIA GPU detected: $GPU_NAME"
GPU_AVAILABLE=true
else
echo " No NVIDIA GPU detected — will use CPU for perceptual hashing"
GPU_AVAILABLE=false
fi
# ── Photos path ───────────────────────────────────────────────────────────────
echo ""
info "Photos library path (mounted read-only):"
echo " Current: $PHOTOS_PATH"
read -rp " Path [Enter to keep]: " INPUT
INPUT="${INPUT%\"}" ; INPUT="${INPUT#\"}" # strip quotes
[[ -n "$INPUT" ]] && PHOTOS_PATH="$INPUT"
if [[ ! -d "$PHOTOS_PATH" ]]; then
err "Path not found: $PHOTOS_PATH"
echo " Create it or mount your drive first, then re-run setup."
exit 1
fi
ok "Photos: $PHOTOS_PATH"
# ── Data path ─────────────────────────────────────────────────────────────────
echo ""
info "Database storage path:"
echo " Current: $DATA_PATH"
read -rp " Path [Enter to keep]: " INPUT
INPUT="${INPUT%\"}" ; INPUT="${INPUT#\"}"
[[ -n "$INPUT" ]] && DATA_PATH="$INPUT"
mkdir -p "$DATA_PATH"
ok "Data: $DATA_PATH"
# ── Port ──────────────────────────────────────────────────────────────────────
echo ""
read -rp " Web port [$APP_PORT]: " INPUT
[[ -n "$INPUT" ]] && APP_PORT="$INPUT"
ok "Port: $APP_PORT"
# ── Pull Docker image ─────────────────────────────────────────────────────────
echo ""
info "Pulling Docker image ($IMAGE_NAME)..."
if docker pull "$IMAGE_NAME"; then
ok "Image pulled"
else
echo ""
echo " Could not pull from registry. Trying to build from source..."
if [[ -f "$COMPOSE_DIR/source/Dockerfile" ]]; then
docker build -t "$IMAGE_NAME" "$COMPOSE_DIR/source"
ok "Image built from source"
else
err "Neither pull nor local build succeeded."
echo " Make sure you have internet access or the source files are present."
exit 1
fi
fi
# ── Write config + override ───────────────────────────────────────────────────
info "Writing configuration..."
cat > "$CONF_FILE" <<EOF
PHOTOS_PATH=$PHOTOS_PATH
DATA_PATH=$DATA_PATH
APP_PORT=$APP_PORT
GPU_AVAILABLE=$GPU_AVAILABLE
EOF
chmod 600 "$CONF_FILE"
# Docker requires forward slashes
PHOTOS_DOCKER="${PHOTOS_PATH//\\//}"
DATA_DOCKER="${DATA_PATH//\\//}"
cat > "$OVERRIDE_YML" <<EOF
services:
dup-finder:
image: $IMAGE_NAME
ports:
- "${APP_PORT}:8000"
volumes:
- "$PHOTOS_DOCKER:/photos:ro"
- "$DATA_DOCKER:/data"
EOF
# Add GPU reservation if available
if [[ "$GPU_AVAILABLE" == "true" ]]; then
cat >> "$OVERRIDE_YML" <<EOF
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
EOF
fi
ok "Config saved to $CONF_FILE"
# ── Start service ─────────────────────────────────────────────────────────────
info "Starting DupFinder..."
systemctl daemon-reload
systemctl enable --now dupfinder.service
ok "Service started"
echo ""
echo -e "${GREEN} ╔══════════════════════════════════════════╗${NC}"
echo -e "${GREEN} ║ DupFinder is running! ║${NC}"
echo -e "${GREEN} ║ Open: http://localhost:$APP_PORT${NC}"
echo -e "${GREEN} ╚══════════════════════════════════════════╝${NC}"
echo ""

81
debian/files/usr/local/bin/dupfinder vendored Normal file
View File

@@ -0,0 +1,81 @@
#!/bin/bash
# DupFinder CLI wrapper
CONF_FILE="/etc/dupfinder.conf"
COMPOSE_DIR="/opt/dupfinder"
COMPOSE_YML="$COMPOSE_DIR/docker-compose.yml"
OVERRIDE_YML="$COMPOSE_DIR/docker-compose.override.yml"
[[ -f "$CONF_FILE" ]] && source "$CONF_FILE"
: "${APP_PORT:=8765}"
_compose() {
docker compose -f "$COMPOSE_YML" -f "$OVERRIDE_YML" "$@"
}
_require_conf() {
if [[ ! -f "$CONF_FILE" ]]; then
echo "DupFinder is not configured. Run: sudo dupfinder setup"
exit 1
fi
}
case "${1:-help}" in
setup)
exec bash /opt/dupfinder/dupfinder-setup.sh
;;
start)
_require_conf
sudo systemctl start dupfinder.service
echo "DupFinder started — http://localhost:$APP_PORT"
;;
stop)
sudo systemctl stop dupfinder.service
echo "DupFinder stopped."
;;
restart)
_require_conf
sudo systemctl restart dupfinder.service
echo "DupFinder restarted — http://localhost:$APP_PORT"
;;
status)
systemctl status dupfinder.service --no-pager
;;
logs)
_compose logs -f --tail=100
;;
open)
_require_conf
# Wait for service to be ready then open browser
for i in $(seq 1 15); do
curl -sf "http://localhost:$APP_PORT/" -o /dev/null && break
sleep 1
done
xdg-open "http://localhost:$APP_PORT" 2>/dev/null || \
echo "Open in browser: http://localhost:$APP_PORT"
;;
update)
_require_conf
echo "Pulling latest image..."
docker pull tocmo0nlord/dupfinder:latest
sudo systemctl restart dupfinder.service
echo "Updated and restarted."
;;
uninstall)
echo "To fully remove DupFinder: sudo apt remove dupfinder"
echo "To also remove data: sudo apt purge dupfinder"
;;
help|--help|-h|*)
echo "Usage: dupfinder <command>"
echo ""
echo "Commands:"
echo " setup Configure photos path, data path, pull image"
echo " start Start the service"
echo " stop Stop the service"
echo " restart Restart the service"
echo " status Show systemd service status"
echo " logs Tail container logs"
echo " open Open in browser"
echo " update Pull latest image and restart"
echo " uninstall Show removal instructions"
;;
esac