Skip to content

Instantly share code, notes, and snippets.

@palaniraja
Last active October 26, 2026 02:28
Show Gist options
  • Select an option

  • Save palaniraja/7d293297d7fead35603b13f92a5b8a85 to your computer and use it in GitHub Desktop.

Select an option

Save palaniraja/7d293297d7fead35603b13f92a5b8a85 to your computer and use it in GitHub Desktop.
Gemini screwed up: initial commits create/formats the enc diskimage on start instead of just setup
.DEFAULT_GOAL := help
help:
@echo "=================================================================="
@echo " Immich Vault & Stack Management Makefile"
@echo "=================================================================="
@echo "Available Targets:"
@echo " make start - Unlock vault, mount filesystem, and start Docker stack"
@echo " make stop - Stop Docker stack, unmount, and lock encrypted vault"
@echo " make status - Check Docker container status and vault mount state"
@echo " make resize - Expand encrypted vault (Usage: make resize +20G)"
@echo " make setup - [ONE-TIME] Initialize brand new encrypted vault image"
@echo "=================================================================="
-include .env
export
# --- CRITICAL SAFETY GUARD: Ensure required .env variables exist ---
ifndef ENCRYPTED_IMAGE
$(error ERROR: ENCRYPTED_IMAGE is not defined. Please check your .env file.)
endif
ifndef MOUNT_DIR
$(error ERROR: MOUNT_DIR is not defined. Please check your .env file.)
endif
ifndef UPLOAD_LOCATION
$(error ERROR: UPLOAD_LOCATION is not defined. Please check your .env file.)
endif
ifndef DB_DATA_LOCATION
$(error ERROR: DB_DATA_LOCATION is not defined. Please check your .env file.)
endif
# Default vault size in MB if not defined in .env (50000 MB = ~50GB)
VAULT_SIZE ?= 50000
# Fixed secure mapper target name
FIXED_MAPPER_NAME = immich_crypt
# Safely capture the actual host user's UID/GID, even if 'sudo make' was invoked
HOST_UID := $(shell if [ -n "$$SUDO_UID" ]; then echo $$SUDO_UID; else id -u; fi)
HOST_GID := $(shell if [ -n "$$SUDO_GID" ]; then echo $$SUDO_GID; else id -g; fi)
.PHONY: setup start stop status resize
setup:
@if [ -f $(ENCRYPTED_IMAGE) ]; then \
echo "=================================================================="; \
echo " FATAL ERROR: Encrypted vault already exists at $(ENCRYPTED_IMAGE)."; \
echo "=================================================================="; \
echo " 'make setup' is strictly for brand-new installations."; \
echo " Running it again would overwrite and destroy your existing data."; \
echo "=================================================================="; \
exit 1; \
fi
@echo "==> Creating parent directories..."
sudo mkdir -p $$(dirname $(ENCRYPTED_IMAGE))
@echo "==> Creating $(VAULT_SIZE)MB encrypted vault file..."
sudo dd if=/dev/zero of=$(ENCRYPTED_IMAGE) bs=1M count=$(VAULT_SIZE) status=progress
@echo "==> Formatting LUKS container..."
sudo cryptsetup luksFormat $(ENCRYPTED_IMAGE)
sudo cryptsetup luksOpen $(ENCRYPTED_IMAGE) $(FIXED_MAPPER_NAME)
sudo mkfs.ext4 -F /dev/mapper/$(FIXED_MAPPER_NAME)
sudo cryptsetup luksClose $(FIXED_MAPPER_NAME)
@echo "==> Setup complete! You can now run 'make start'."
start:
@if [ ! -b /dev/mapper/$(FIXED_MAPPER_NAME) ]; then \
echo "==> Unlocking encrypted vault..."; \
sudo cryptsetup luksOpen $(ENCRYPTED_IMAGE) $(FIXED_MAPPER_NAME); \
fi
@if ! mountpoint -q $(MOUNT_DIR); then \
if [ ! -d $(MOUNT_DIR) ]; then sudo mkdir -p $(MOUNT_DIR); fi; \
echo "==> Mounting filesystem..."; \
sudo mount /dev/mapper/$(FIXED_MAPPER_NAME) $(MOUNT_DIR); \
fi
@echo "==> Verifying permissions using host UID ($(HOST_UID):$(HOST_GID))..."
sudo mkdir -p $(UPLOAD_LOCATION) $(DB_DATA_LOCATION)
sudo chown -R $(HOST_UID):$(HOST_GID) $(UPLOAD_LOCATION)
sudo chown -R 70:70 $(DB_DATA_LOCATION)
sudo chmod 700 $(DB_DATA_LOCATION)
@echo "==> Starting Immich..."
docker compose up -d
stop:
@echo "==> Stopping Immich containers..."
docker compose down
@echo "==> Waiting for file handles to release..."
@if mountpoint -q $(MOUNT_DIR); then \
echo "==> Unmounting directory with retry loop..."; \
RETRIES=10; \
while [ $$RETRIES -gt 0 ]; do \
if sudo umount $(MOUNT_DIR) 2>/dev/null; then \
echo "==> Unmounted successfully."; \
break; \
fi; \
RETRIES=$$((RETRIES - 1)); \
if [ $$RETRIES -eq 0 ]; then \
echo "=================================================================="; \
echo " ERROR: Failed to unmount $(MOUNT_DIR) after 10 attempts."; \
echo "=================================================================="; \
echo " TIP: Check if any open terminal session has its current directory (pwd)"; \
echo " set inside $(MOUNT_DIR). If so, 'cd ~' out of it."; \
echo ""; \
echo " Processes currently locking files:"; \
if command -v lsof >/dev/null 2>&1; then \
sudo lsof +D $(MOUNT_DIR) || echo " (No active file handles detected by lsof)"; \
else \
echo " (lsof utility not installed)"; \
fi; \
echo "=================================================================="; \
exit 1; \
fi; \
sleep 1; \
done; \
fi
@if [ -b /dev/mapper/$(FIXED_MAPPER_NAME) ]; then \
echo "==> Locking vault..."; \
sudo cryptsetup luksClose $(FIXED_MAPPER_NAME); \
fi
@echo "==> Vault successfully secured and locked."
status:
@echo "=== Docker Status ==="
docker compose ps
@echo "\n=== Mount & Vault Status ==="
@mountpoint -q $(MOUNT_DIR) && echo "Filesystem: MOUNTED ($(MOUNT_DIR))" || echo "Filesystem: UNMOUNTED"
@if [ -b /dev/mapper/$(FIXED_MAPPER_NAME) ]; then echo "LUKS Vault: UNLOCKED"; else echo "LUKS Vault: LOCKED"; fi
# If the first goal is resize, catch any extra arguments (like +20G) so Make doesn't error out
ifeq (resize,$(firstword $(MAKECMDGOALS)))
RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
$(eval $(RUN_ARGS):;@:)
endif
resize:
@if [ -z "$(RUN_ARGS)" ]; then \
echo "=================================================================="; \
echo " ERROR: Missing size argument for resize."; \
echo "=================================================================="; \
echo "Usage:"; \
echo " make resize +20G (to expand by 20 Gigabytes)"; \
echo " make resize +500M (to expand by 500 Megabytes)"; \
echo ""; \
echo "Current Vault Image: $(ENCRYPTED_IMAGE)"; \
echo "Host Disk Space Available:"; \
df -h $$(dirname $(ENCRYPTED_IMAGE)) | awk 'NR==1 || NR==2 {print $$0}'; \
echo "=================================================================="; \
exit 1; \
fi
@# Guard against dangerous negative arguments that would destroy data
@case "$(RUN_ARGS)" in \
-+*) ;; \
+*) ;; \
*) \
echo "=================================================================="; \
echo " ERROR: Invalid resize argument '$(RUN_ARGS)'."; \
echo " Shrinking volumes via Makefile is disabled to prevent data loss."; \
echo " Please use a positive expansion format, e.g., 'make resize +20G'"; \
echo "=================================================================="; \
exit 1 ;; \
esac
@echo "==> [1/6] Running pre-flight checks for expansion target: $(RUN_ARGS)..."
@if [ ! -f $(ENCRYPTED_IMAGE) ]; then \
echo "Error: Encrypted image file not found at $(ENCRYPTED_IMAGE). Run 'make setup' first."; \
exit 1; \
fi
@echo ""
@echo "=== Current Host Disk Usage ==="
@df -h $$(dirname $(ENCRYPTED_IMAGE))
@echo ""
@echo "=== Current Vault / Filesystem Usage (if mounted) ==="
@if mountpoint -q $(MOUNT_DIR); then \
df -h $(MOUNT_DIR); \
else \
echo "Vault is currently unmounted (will check after unlocking)."; \
fi
@echo ""
@read -p "==> Proceed with expanding container by $(RUN_ARGS)? [y/N] " confirm && [ "$$confirm" = "y" ] || { echo "Aborted."; exit 1; }
@echo "==> [2/6] Ensuring stack is fully stopped and unmounted safely..."
@$(MAKE) stop
@echo "==> [3/6] Expanding underlying image file ($(ENCRYPTED_IMAGE)) by $(RUN_ARGS)..."
sudo truncate -s $(RUN_ARGS) $(ENCRYPTED_IMAGE)
@echo "==> [4/6] Unlocking LUKS container..."
sudo cryptsetup luksOpen $(ENCRYPTED_IMAGE) $(FIXED_MAPPER_NAME)
@echo "==> [5/6] Resizing LUKS mapping layer..."
sudo cryptsetup resize $(FIXED_MAPPER_NAME)
@echo "==> [6/6] Temporarily mounting and resizing filesystem to claim space..."
sudo mkdir -p $(MOUNT_DIR)
sudo mount /dev/mapper/$(FIXED_MAPPER_NAME) $(MOUNT_DIR)
sudo resize2fs /dev/mapper/$(FIXED_MAPPER_NAME)
sudo umount $(MOUNT_DIR)
sudo cryptsetup luksClose $(FIXED_MAPPER_NAME)
@echo "==> Expansion operation completed successfully! Run 'make start' to boot Immich."
@palaniraja

Copy link
Copy Markdown
Author

After Gemini screwed up my start on initial commit resulting in me loosing data. I was experimenting by asking it with $100-$250 bounty on each iteration to fix defects.

An exercise to never trust these results because first happy path succeeds. I still had to review all these again just to have a peace of mind is an example why token/budget based limits is not going to solve software development.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment