Ente
This tutorial is for Debian users who want to setup Ente manually. I used this tutorial as a jumping off point and setup Ente as documented below. Since minio is now paywalled, I used Garage as the s3-compat bucket and just pointed it at the vm's virtiofs mountpoint which points to underlying btrfs r10 on the host (encrypted w/ LUKS).
Go / Node / Rust:
apt install golang-goapt install nodejs npmapt install rustup then rustup default stableAddresses:
8.28.86.1332604:fa40:0:10::36gnulinux.picsDNS A/AAAA for:
Setup base system,
sudo apt update && sudo apt upgrade -y sudo apt install -y ca-certificates curl wget git gnupg lsb-release build-essential pkg-config postgresql postgresql-contrib libsodium23 libsodium-dev caddy ufw sudo systemctl enable --now postgresql sudo ufw allow OpenSSH sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable git clone https://github.com/ente-io/ente.git /root/ente
Now, Go, Rust, and Node setup:
sudo apt install -y golang-go rustup nodejs npm rustup default stable go version rustc --version node -v npm -v
Setup PostgreSQL and create the db with sudo -u postgres psql and something like:
CREATE USER ente WITH ENCRYPTED PASSWORD 'ENTE_DB_PASS'; CREATE DATABASE ente_db OWNER ente; GRANT ALL PRIVILEGES ON DATABASE ente_db TO ente; \q
Next, lock access to db:
echo 'host ente_db ente 127.0.0.1/32 scram-sha-256' | sudo tee -a /etc/postgresql/*/main/pg_hba.conf echo 'host ente_db ente ::1/128 scram-sha-256' | sudo tee -a /etc/postgresql/*/main/pg_hba.conf sudo systemctl reload postgresql PGPASSWORD='ENTE_DB_PASS' psql -h 127.0.0.1 -U ente -d ente_db -c 'SELECT 1;'
Edit museum.yaml and configure it, build its web apps, start it, and create unit:
cd /root/ente/server go mod tidy go build cmd/museum/main.go go run tools/gen-random-keys/main.go cp config/example.yaml museum.yaml
Edit /root/ente/server/museum.yaml using keys from second to last step above. Now, create systemd unit.
sudo tee /etc/systemd/system/ente-museum.service >/dev/null << 'EOF' [Unit] Description=Ente Museum After=network-online.target postgresql.service Wants=network-online.target [Service] Type=simple WorkingDirectory=/root/ente/server ExecStart=/root/ente/server/main Restart=on-failure RestartSec=3 Environment=GIN_MODE=release [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable --now ente-museum curl -sS http://127.0.0.1:8080/ping
Build the Web apps and migrating content from repo
cd /root/ente/web npm ci export NEXT_PUBLIC_ENTE_ENDPOINT=https://api.gnulinux.pics npm run build npm run build:albums npm run build:accounts npm run build:auth npm run build:cast npm run build:share npm run build:embed npm run build:memories sudo mkdir -p /var/www/ente/apps sudo rm -rf /var/www/ente/apps/* sudo cp -a apps/photos/out /var/www/ente/apps/photos sudo cp -a apps/albums/out /var/www/ente/apps/albums sudo cp -a apps/accounts/out /var/www/ente/apps/accounts sudo cp -a apps/auth/out /var/www/ente/apps/auth sudo cp -a apps/cast/out /var/www/ente/apps/cast sudo cp -a apps/share/out /var/www/ente/apps/share sudo cp -a apps/embed/out /var/www/ente/apps/embed sudo cp -a apps/memories/out /var/www/ente/apps/memories sudo chown -R caddy:caddy /var/www/ente
Setup Caddy reverse proxy / load balancer, acme certs, and vhosts with nano /etc/caddy/Caddyfile and enter the following:
{
email admin@gnulinux.pics
}
api.gnulinux.pics {
reverse_proxy 127.0.0.1:8080
}
gnulinux.pics {
root * /var/www/ente/apps/photos
encode gzip
file_server
try_files {path} {path}.html /index.html
}
albums.gnulinux.pics {
root * /var/www/ente/apps/albums
encode gzip
file_server
try_files {path} {path}.html /index.html
}
accounts.gnulinux.pics {
root * /var/www/ente/apps/accounts
encode gzip
file_server
try_files {path} {path}.html /index.html
}
auth.gnulinux.pics {
root * /var/www/ente/apps/auth
encode gzip
file_server
try_files {path} {path}.html /index.html
}
share.gnulinux.pics {
root * /var/www/ente/apps/share
encode gzip
file_server
try_files {path} {path}.html /index.html
}
cast.gnulinux.pics {
root * /var/www/ente/apps/cast
encode gzip
file_server
try_files {path} {path}.html /index.html
}
embed.gnulinux.pics {
root * /var/www/ente/apps/embed
encode gzip
file_server
try_files {path} {path}.html /index.html
}
memories.gnulinux.pics {
root * /var/www/ente/apps/memories
encode gzip
file_server
try_files {path} {path}.html /index.html
}
legacy.gnulinux.pics {
root * /var/www/ente/apps/accounts
encode gzip
file_server
try_files {path} {path}.html /index.html
}
s3.gnulinux.pics {
header Access-Control-Allow-Origin *
header Access-Control-Allow-Methods "GET, PUT, POST, HEAD, DELETE, OPTIONS"
header Access-Control-Allow-Headers *
header Access-Control-Expose-Headers "ETag, Accept-Ranges, Content-Range, Content-Length, Content-Type"
header Access-Control-Max-Age 3600
@options method OPTIONS
handle @options {
respond 204
}
reverse_proxy 127.0.0.1:3900
}
Verify after making config, then restart service, debug as needed:
sudo caddy validate --config /etc/caddy/Caddyfile sudo systemctl enable --now caddy sudo systemctl reload caddy curl -sS http://127.0.0.1:8080/ping curl -sSI https://gnulinux.pics curl -sS https://api.gnulinux.pics/ping
End point works now, go to url in browser and create first user then use the id# to lock it as admin.
journalctl -u ente-museum -n 200 –no-pagerYou need to locate the id# by searching logs for your email in close proximity as per above. Or, alternately, make a one-liner:
journalctl -u ente-museum --no-pager | grep -F -A 80 'user@example.com' | grep -oE 'user_id=[1-9][0-9]*' | tail -1
Once that's done, go edit nano /root/ente/server/museum.yaml:
internal:
admins:
- USER_ID
trusted-client-ip-header: X-Forwarded-For
Restart service,
sudo systemctl restart ente-museum
How to edit admin user to give them more storage:
PGPASSWORD='ENTE_DB_PASS' psql -h 127.0.0.1 -U ente -d ente_db -c "UPDATE subscriptions SET storage = 25::bigint * 1024 * 1024 * 1024 WHERE user_id = USER_ID RETURNING user_id, storage;" # 100 250 500 750 → same, change 25 # 1TB = 1024 1.5TB = 1536 2TB = 2048
## 8. Garage (needed before uploads work) Warning: If you rerun layout/bucket/key it hoses the current bucket.
sudo mkdir -p /var/lib/garage/meta /mnt/warehouse/media/glpics curl -fL -o /tmp/garage https://garagehq.deuxfleurs.fr/_releases/v2.4.1/x86_64-unknown-linux-musl/garage sudo install -m 0755 /tmp/garage /usr/local/bin/garage garage --version
Create config .toml file and start garage service:
sudo tee /etc/garage.toml >/dev/null << 'EOF' metadata_dir = "/var/lib/garage/meta" data_dir = "/mnt/warehouse/media/glpics" db_engine = "sqlite" replication_factor = 1 rpc_bind_addr = "127.0.0.1:3901" rpc_public_addr = "127.0.0.1:3901" rpc_secret = "GARAGE_RPC_SECRET" [s3_api] s3_region = "garage" api_bind_addr = "127.0.0.1:3900" root_domain = ".s3.gnulinux.pics" [admin] api_bind_addr = "127.0.0.1:3903" admin_token = "GARAGE_ADMIN_TOKEN" EOF
Create systemd unit for garage to monitor and restart service:
sudo tee /etc/systemd/system/garage.service >/dev/null << 'EOF' [Unit] Description=Garage S3 After=network-online.target Wants=network-online.target [Service] ExecStart=/usr/local/bin/garage -c /etc/garage.toml server Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable --now garage sudo systemctl status garage --no-pager
Create bucket on the same host and name it / give perms for Ente's default s3-compatible endpoint:
garage -c /etc/garage.toml status NODE=$(garage -c /etc/garage.toml node id -q | head -1) garage -c /etc/garage.toml layout assign -z dc1 -c 1T "$NODE" garage -c /etc/garage.toml layout apply --version 1 garage -c /etc/garage.toml bucket create b2-eu-cen garage -c /etc/garage.toml key create ente-photos garage -c /etc/garage.toml bucket allow b2-eu-cen --read --write --owner --key ente-photos garage -c /etc/garage.toml key info ente-photos
Once the bucket is built, add the creds it spit out into /root/ente/server/museum.yaml and restart Museum.
s3:
are_local_buckets: false
use_path_style_urls: true
b2-eu-cen:
key: GARAGE_ACCESS_KEY
secret: GARAGE_SECRET_KEY
endpoint: s3.gnulinux.pics
region: garage
bucket: b2-eu-cen
Restart the service
sudo systemctl restart ente-museum
Draft Upgrade Garage Steps
VER=v2.4.1
ARCH=x86_64-unknown-linux-musl
curl -fL -o /tmp/garage "https://garagehq.deuxfleurs.fr/_releases/${VER}/${ARCH}/garage"
/tmp/garage --version
sudo systemctl stop ente-museum garage
sudo cp -a /usr/local/bin/garage /usr/local/bin/garage.bak-$(date +%F)
sudo install -m 0755 /tmp/garage /usr/local/bin/garage
sudo systemctl start garage ente-museum
garage -c /etc/garage.toml status
curl -sS https://api.gnulinux.pics/ping
Potential steps if garage upgrade fails:
sudo systemctl stop garage sudo cp /usr/local/bin/garage.bak-YYYY-MM-DD /usr/local/bin/garage sudo systemctl start garage
Do not layout/bucket/key on upgrade - it will hose current bucket. Example museum.yaml example config layout for base setup
db:
host: 127.0.0.1
port: 5432
name: ente_db
user: ente
password: ENTE_DB_PASS
s3:
are_local_buckets: false
use_path_style_urls: true
b2-eu-cen:
key: GARAGE_ACCESS_KEY
secret: GARAGE_SECRET_KEY
endpoint: s3.gnulinux.pics
region: garage
bucket: b2-eu-cen
apps:
public-albums: https://albums.gnulinux.pics
embed-albums: https://embed.gnulinux.pics
public-locker: https://share.gnulinux.pics
public-paste: https://share.gnulinux.pics
cast: https://cast.gnulinux.pics
accounts: https://accounts.gnulinux.pics
public-memories: https://memories.gnulinux.pics
legacy: https://gnulinux.pics
key:
encryption: MUSEUM_KEY_ENCRYPTION
hash: MUSEUM_KEY_HASH
jwt:
secret: MUSEUM_JWT_SECRET
internal:
admins:
- USER_ID
trusted-client-ip-header: X-Forwarded-For
smtp:
host: mail.example.com
port: 587
username: SMTP_USER
password: SMTP_PASS
email: SMTP_FROM
sender-name: gnulinux.pics
Draft update sequence
sudo apt update && sudo apt upgrade rustup update cd /root/ente && git pull cd /root/ente/server go mod tidy go build cmd/museum/main.go sudo systemctl restart ente-museum cd /root/ente/web npm ci export NEXT_PUBLIC_ENTE_ENDPOINT=https://api.gnulinux.pics npm run build npm run build:albums npm run build:accounts npm run build:auth npm run build:cast npm run build:share npm run build:embed npm run build:memories sudo rsync -a --delete apps/photos/out/ /var/www/ente/apps/photos/ sudo rsync -a --delete apps/albums/out/ /var/www/ente/apps/albums/ sudo rsync -a --delete apps/accounts/out/ /var/www/ente/apps/accounts/ sudo rsync -a --delete apps/auth/out/ /var/www/ente/apps/auth/ sudo rsync -a --delete apps/cast/out/ /var/www/ente/apps/cast/ sudo rsync -a --delete apps/share/out/ /var/www/ente/apps/share/ sudo rsync -a --delete apps/embed/out/ /var/www/ente/apps/embed/ sudo rsync -a --delete apps/memories/out/ /var/www/ente/apps/memories/ sudo chown -R caddy:caddy /var/www/ente sudo systemctl reload caddy curl -sS https://api.gnulinux.pics/ping
Store secrets properly
— oemb1905 2026/09/21 02:50