Compare commits

..

54 Commits

Author SHA1 Message Date
Michael Mayer
dd3c80bd62 Frontend: Reformat gettext.config.js with eslint
Signed-off-by: Michael Mayer <michael@photoprism.app>
2025-03-24 09:26:51 +01:00
Anastasiia
fb65bb7935 Frontend: add dynamic loading language json file 2025-03-21 09:46:11 +01:00
Anastasiia
78698abcfa Frontend: gettext config copying files to assets folder 2025-03-21 09:46:11 +01:00
Anastasiia
c81890f86a Frontend: delete translations 2025-03-21 09:46:11 +01:00
Michael Mayer
9cb7be07d0 Merge branch 'develop' into scratch/frontend 2025-03-20 12:56:35 +01:00
Michael Mayer
d834e5058b Merge branch 'develop' into scratch/frontend 2025-03-20 12:03:57 +01:00
Michael Mayer
05b9bab4e3 Merge branch 'develop' into scratch/frontend 2025-03-20 11:24:48 +01:00
Michael Mayer
b3d8448927 Merge branch 'develop' into scratch/frontend 2025-03-19 16:51:58 +01:00
Michael Mayer
6fc66dec36 Merge branch 'develop' into scratch/frontend 2025-03-19 16:47:18 +01:00
Michael Mayer
8f531d5a56 Merge branch 'develop' into scratch/frontend 2025-03-19 14:52:37 +01:00
Michael Mayer
334185bb0f Merge branch 'develop' into scratch/frontend 2025-03-19 12:01:18 +01:00
Michael Mayer
b84505f5d6 Merge branch 'develop' into scratch/frontend 2025-03-18 18:12:46 +01:00
Michael Mayer
312d230f02 Merge branch 'develop' into scratch/frontend 2025-03-18 16:51:23 +01:00
Michael Mayer
fdd92d3a0a Merge branch 'develop' into scratch/frontend 2025-03-18 14:57:59 +01:00
Michael Mayer
feb2402c0a Merge branch 'develop' into scratch/frontend 2025-03-18 11:56:09 +01:00
Michael Mayer
df892d1f81 Merge branch 'develop' into scratch/frontend 2025-03-18 09:11:58 +01:00
Michael Mayer
5ef580fc42 Merge branch 'develop' into scratch/frontend 2025-03-18 06:28:21 +01:00
Michael Mayer
d52c17da7a Merge branch 'develop' into scratch/frontend 2025-03-17 19:29:48 +01:00
Michael Mayer
fba42f21d1 Merge branch 'develop' into scratch/frontend 2025-03-17 17:53:53 +01:00
Michael Mayer
38cea8dc09 Merge branch 'develop' into scratch/frontend 2025-03-17 13:29:16 +01:00
Michael Mayer
2a14a8d958 Merge branch 'develop' into scratch/frontend 2025-03-17 13:24:38 +01:00
Michael Mayer
3d3d170e33 Merge branch 'develop' into scratch/frontend 2025-03-16 17:03:09 +01:00
Michael Mayer
6882d0ce14 Merge branch 'develop' into scratch/frontend 2025-03-16 16:25:07 +01:00
Michael Mayer
62e1ff9f48 Merge branch 'develop' into scratch/frontend 2025-03-16 16:11:05 +01:00
Michael Mayer
26500ee80e Merge branch 'develop' into scratch/frontend 2025-03-16 13:25:52 +01:00
Michael Mayer
6c90fb5b81 Merge branch 'develop' into scratch/frontend 2025-03-15 16:31:34 +01:00
Michael Mayer
4dfa6d9062 Merge branch 'develop' into scratch/frontend 2025-03-15 16:07:07 +01:00
Michael Mayer
3ff71ebb00 Merge branch 'develop' into scratch/frontend 2025-03-15 14:53:01 +01:00
Michael Mayer
2fcfc702a9 Merge branch 'develop' into scratch/frontend 2025-03-14 16:16:54 +01:00
Michael Mayer
5c4dcbcce4 Merge branch 'develop' into scratch/frontend 2025-03-14 16:05:23 +01:00
Michael Mayer
6848a5cbee Merge branch 'develop' into scratch/frontend 2025-03-14 12:56:10 +01:00
Michael Mayer
d6a6fa2ed9 Merge branch 'develop' into scratch/frontend 2025-03-14 12:39:16 +01:00
Michael Mayer
0369121421 Merge branch 'develop' into scratch/frontend 2025-03-14 12:34:55 +01:00
Michael Mayer
63528d1090 Merge branch 'develop' into scratch/frontend 2025-03-13 11:31:32 +01:00
Michael Mayer
afe5034772 UX: Increase font size of .v-table footer in css/vuetify.css
Signed-off-by: Michael Mayer <michael@photoprism.app>
2025-03-13 07:53:32 +01:00
Michael Mayer
5e32205bda Merge branch 'develop' into scratch/frontend 2025-03-13 07:39:02 +01:00
Michael Mayer
81a8dd28f8 Merge branch 'develop' into scratch/frontend 2025-03-12 04:48:36 +01:00
Michael Mayer
68084bb365 Merge branch 'develop' into scratch/frontend 2025-03-11 23:46:03 +01:00
Michael Mayer
24f4705351 Merge branch 'develop' into scratch/frontend 2025-03-11 21:01:03 +01:00
Michael Mayer
0b9719fc44 Merge branch 'develop' into scratch/frontend 2025-03-11 20:39:06 +01:00
Michael Mayer
bcae07ae46 Merge branch 'develop' into scratch/frontend
# Conflicts:
#	frontend/package-lock.json
#	frontend/package.json
2025-03-11 18:12:34 +01:00
Michael Mayer
7d950cb269 Merge branch 'develop' into scratch/frontend 2025-03-11 10:46:03 +01:00
Michael Mayer
9fee538925 Merge branch 'develop' into scratch/frontend 2025-03-10 18:31:54 +01:00
Michael Mayer
a01317fa1a Merge branch 'develop' into scratch/frontend 2025-03-10 15:22:20 +01:00
Michael Mayer
6f36a9913d Merge branch 'develop' into scratch/frontend 2025-03-10 13:17:08 +01:00
Michael Mayer
173f6a065c Merge branch 'develop' into scratch/frontend 2025-03-10 12:51:40 +01:00
Michael Mayer
0449e4cc8c Merge branch 'develop' into scratch/frontend 2025-03-04 08:35:49 +01:00
Michael Mayer
e9a9e9f436 Merge branch 'develop' into scratch/frontend 2025-03-04 08:28:51 +01:00
Michael Mayer
c7cbc920e8 Merge branch 'develop' into scratch/frontend 2025-03-03 21:05:22 +01:00
Michael Mayer
7cf730ff20 Merge branch 'develop' into scratch/frontend 2025-03-03 20:28:37 +01:00
Michael Mayer
1345d471eb Merge branch 'develop' into scratch/frontend 2025-03-03 20:13:08 +01:00
Michael Mayer
81e0502bdd Merge branch 'develop' into scratch/frontend 2025-03-03 20:06:22 +01:00
Anastasiia
746729607b Frontend: add gettext.config.js for vue3-gettext 2025-03-03 11:36:34 +01:00
Anastasiia
df7df30161 Frontend: add loading places-page chunk 2025-03-03 11:35:09 +01:00
1879 changed files with 57192 additions and 130948 deletions

View File

@@ -1,9 +0,0 @@
[codespell]
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
skip = .git*,*.svg,go.sum,package-lock.json,*.css,.codespellrc,locales,stopwords.*,shortwords.*,countries.*,keywords.json,smallwords.*
check-hidden = true
# ACRONYMS and generally mixed case/Capitalized words
# Also ignore some German
ignore-regex = ^(SMethod=shal|biuld: build)$|https://\S+|\b([A-Z][a-zA-Z]+|ist auf)\b|"(datin|alis|hel)"|\bfixe == false\b
# common variables or other tricky cases
ignore-words-list = renderd,nd,folde,crate,ue,fo,ist,alo,mot,te,admiraal

View File

@@ -1,4 +1,4 @@
# Local build files and directories:
# Local build files and directories
/photos/*
/frontend/node_modules/*
/node_modules
@@ -7,63 +7,32 @@
/assets/facenet
/assets/nasnet
/assets/nsfw
/assets/efficientnet
/assets/imagenet
/assets/resnet
/assets/vision
/assets/models
/storage
/build
/photoprism
/photoprism-*
/coverage.*
/frontend/tests/acceptance/screenshots
/test/
/specs/
/docs/
/bin/
# Docker configuration files:
# Custom config, database, log, and temporary files
/tmp/
.env
.dockerignore
Dockerfile
docker-compose*
compose.yaml
compose.*.yaml
# Customization, database, log, and temporary files:
*.socket
*.lock
*.sock
*.pid
*.log
*.jsonl
*.db
*.db-journal
*.sqlite
Dockerfile
docker-compose*
compose.yaml
compose.*.yaml
*.override.yml
*.tmp.yml
*.override.yaml
*.tmp.yml
*.tmp.yaml
*.out
*.test
*.exe
*.exe~
*.dll
*.so
*.dylib
*.tmp
*.img
*.img.xz
*.img.gz
/*.zip
/coverage.*
__pycache__
venv
.venv
.env
.tmp
.nv
.eslintcache
.gocache
# Automatically generated files, e.g. by editors and operating systems
.DS_Store
.DS_Store?
._*
@@ -73,12 +42,10 @@ ehthumbs.db
Thumbs.db
.heartbeat
.idea
.codex
.local
.glide
*~
.goutputstream*
.c9revisions
.settings
.swp
.glide
/tmp/
.tmp

View File

@@ -1,18 +1,18 @@
---
name: Question
about: You have a general question or need assistance
title: 'PLEASE DO NOT OPEN AN ISSUE FOR YOUR QUESTION, AND USE GITHUB DISCUSSIONS OR OUR COMMUNITY CHAT INSTEAD'
labels: question
title: 'STOP! DO NOT PROCEED, USE GITHUB DISCUSSIONS INSTEAD - THANK YOU'
labels: technical-support
assignees: ''
---
FOR GENERAL QUESTIONS, TECHNICAL SUPPORT, AND TO GET TO KNOW OTHER COMMUNITY MEMBERS:
<https://github.com/photoprism/photoprism/discussions>
<https://github.com/photoprism/photoprism/discussions>
OUR TROUBLESHOOTING CHECKLISTS HELP YOU QUICKLY DIAGNOSE AND FIX COMMON PROBLEMS:
<https://docs.photoprism.app/getting-started/troubleshooting/>
<https://docs.photoprism.app/getting-started/troubleshooting/>
PLEASE DO NOT OPEN AN ISSUE FOR YOUR QUESTION, AND USE GITHUB DISCUSSIONS OR OUR COMMUNITY CHAT INSTEAD. THANK YOU VERY MUCH!
DO NOT PROCEED, THANK YOU!

View File

@@ -1,34 +1,33 @@
### Description
<!--
We appreciate your interest in contributing! Please provide a brief description of your changes so that we know what is included in this pull request, and confirm that it meets the acceptance criteria:
What does it aim to implement, fix or improve? Why?
Thank you for your interest in contributing!
Because we want to create the best possible product for our users, we have a set of criteria to ensure that all submissions are acceptable, see https://docs.photoprism.app/developer-guide/pull-requests/ for details.
(1) Please provide a concise description of your pull request.
- What does it implement / fix / improve? Why?
- Are the changes related to an existing issue?
(2) After you submit your first pull request, you will be asked to accept our CLA, see https://www.photoprism.app/cla.
(3) Finally, please confirm that the following criteria are met by replacing "[ ]" with "[x]" (also possible at a later time).
-->
These changes implement/fix/improve...
Acceptance Criteria:
#### Related Issues
- Links to issues that this PR fixes, implements, or is otherwise related to...
### Acceptance Criteria
<!-- You may add additional criteria and/or remove criteria that do not apply, e.g. because your PR does not include frontend changes: -->
- [ ] New features or enhancements are fully implemented and do not break existing functionality, so that they can be released at any time without requiring additional work
- [ ] Automated unit and/or acceptance tests are included to ensure that changes work as expected and to reduce repetitive manual work
- [ ] Documentation has been / will be updated, especially as it relates to new configuration options or potentially disruptive changes
- [ ] The user interface has been tested on Chrome, Safari, and Firefox and is fully responsive for use on phones, tablets, and desktop computers
- [ ] Database-related changes have been successfully tested with SQLite 3 and MariaDB 10.5.12+
- [ ] Features and enhancements must be fully implemented so that they can be released at any time without additional work
- [ ] Automated unit and/or acceptance tests are mandatory to ensure the changes work as expected and to reduce repetitive manual work
- [ ] Frontend components must be responsive to work and look properly on phones, tablets, and desktop computers; you must have tested them on all major browsers and different devices
- [ ] Documentation and translation updates should be provided if needed
- [ ] In case you submit database-related changes, they must be tested and compatible with SQLite 3 and MariaDB 10.5.12+
<!--
Contribution Agreement:
After submitting your first pull request, you will be asked to confirm our contribution agreement. This allows us to safely use your Contribution in all our projects without risking unexpected legal disputes or having to repeatedly ask for permission.
The agreement is solely for our protection and that of our users. It does not grant us exclusive rights to your code.
Since reviewing, testing and finally merging pull requests requires significant resources on our side, this can take several months if it's not just a small fix, especially if extensive testing is required to prevent bugs from getting into our stable version.
PhotoPrism UG ("PhotoPrism", "we" or "us") hereby confirms to you that, to the fullest extent permitted by applicable law, this Contribution is provided "AS IS" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES OR CONDITIONS OF NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. You have no obligation to provide support, maintenance, or other services for your Contribution.
We thank you for your patience! :)
-->
Thank you very much! 🌈💎✨
-->

View File

@@ -1,86 +0,0 @@
# Instructions for GitHub Copilot
## Purpose
- Provide Copilot with the single sources of truth for building, testing, and contributing to PhotoPrism.
- Improve PR reviews and code suggestions by aligning them with our documented workflows and style.
## Single Sources of Truth (SOT)
- Makefile targets (always prefer existing targets): https://github.com/photoprism/photoprism/blob/develop/Makefile
- Developer Guide Setup: https://docs.photoprism.app/developer-guide/setup/
- Developer Guide Tests: https://docs.photoprism.app/developer-guide/tests/
- Contributing: https://github.com/photoprism/photoprism/blob/develop/CONTRIBUTING.md
- Security: https://github.com/photoprism/photoprism/blob/develop/SECURITY.md
- REST API (Swagger): https://docs.photoprism.dev/
- REST API Guide: https://docs.photoprism.app/developer-guide/api/
- Agents reference for tools/commands: https://github.com/photoprism/photoprism/blob/develop/AGENTS.md
## Build & Run (local dev; use Makefile first)
- Show tasks: `make help`
- Build local image: `make docker-build`
- Start dev env: `docker compose up` (add `-d` for detached)
- Logs: `docker compose logs -f --tail=100 photoprism`
- Open app: http://localhost:2342/ (HTTP) / https://app.localssl.dev/ (TLS via Traefik when enabled)
- From the dev container:
- Install deps: `make dep`
- Build frontend: `make build-js` (or `cd frontend && npm run build`)
- Build backend: `make build-go`
- Watch frontend: `make watch-js` (stop with Ctrl+C)
- Run server binary: `./photoprism start`
## Tests & Lint
- Full tests: `make test`
- Frontend tests (Vitest): `make test-js` (watch: `make vitest-watch`, coverage: `make vitest-coverage`)
- Backend tests: `make test-go`
- Formatting:
- Go: `go fmt`, `goimports` (see `make fmt`, `make fmt-go`)
- JS/Vue: use scripts in `frontend/package.json` (ESLint + Prettier)
## Project Structure & Languages
- Backend: Go (`internal/`, `pkg/`, `cmd/`) + MariaDB/SQLite
- Frontend: Vue 3 + Vuetify 3 (`frontend/`)
- Docker/compose for dev/CI; Traefik used for local TLS in dev profile when enabled.
## Code Review Instructions (for Copilot)
- Respect SOT above; do not invent flags, env vars, or Compose options. If a command/env var is not in the docs/Makefile/CLI help, say “not documented” and suggest checking the SOT.
- Prefer minimal, surgical diffs. Propose changes as concrete patches and reference the relevant Makefile target or doc section.
- Before suggesting refactors, check tests and build tasks exist and can pass with the change. If tests are missing, suggest specific Vitest/Go test snippets.
- Security: never suggest committing secrets; prefer env vars and `.env` in dev only. Point to SECURITY.md for disclosures.
- Data safety: never run or recommend destructive CLI operations in examples without explicit backups and `--yes`. Avoid `photoprism reset`, `photoprism users reset`, `photoprism auth reset`, or `photoprism audit reset` in PR comments unless the change is specifically about those commands; if unavoidable, add bold warnings and backup steps.
- Database/schema: if a change touches persistence, check for migrations and mention `photoprism migrate` / `migrations` commands.
- API changes: align with the REST API docs/spec; include curl examples only if they match current endpoints and auth notes.
- UX/i18n: keep UI strings concise, translatable, and consistent; avoid hard-coded language constructs; prefer existing patterns/components.
## Style & Patterns
- Go: idiomatic Go, clear error handling, small functions, packages with focused responsibilities. Keep public surface minimal. Add/adjust unit tests.
- Vue/JS: options API, store patterns as in existing code, avoid breaking translations. Keep ESLint/Prettier clean.
- Config & flags: suggest `photoprism --help`, `photoprism show config-options` or `photoprism show config-yaml` to verify names before using them.
## Performance & Reliability
- Prefer using existing caches, workers, and batching strategies referenced in code and Makefile. Consider memory/CPU impact; suggest benchmarks or profiling only when justified.
## When Unsure
- Ask for the exact Makefile target or doc link you need, then proceed. Defer to SOT if any conflict arises.
## References To Help Copilot Answer Questions
- Show supported formats/filters: run `photoprism show file-formats` and `photoprism show search-filters` (use results rather than guessing).
- For CI/dev containers, assume Linux/Unix shell by default; link Windows specifics in Developer Guide FAQ.
## Output Expectations
- Prefer short, actionable comments with code blocks that pass tests locally: `make test-js` (frontend) / `make test-go` (backend)
- If a suggestion requires additional context (e.g., DB access, external service), call it out explicitly.
## Safety Checklist Before Proposing a CLI Command
- Include a dry-run or non-destructive variant if possible.
- Recommend creating/using backups before any reset/migrate.

View File

@@ -1,13 +0,0 @@
---
applyTo:
- "internal/**"
- "pkg/**"
- "cmd/**"
---
For backend changes:
- Maintain Go style; keep functions small, errors wrapped with context.
- Add/update unit tests; ensure `make test-go` passes.
- Touching schema? Mention migrations and verify with `photoprism migrations ls`/`migrate`.
- For CLI/config, confirm flags/envs via `photoprism --help` / `photoprism show config-options` before suggesting.

View File

@@ -1,12 +0,0 @@
---
applyTo:
- "frontend/**"
---
For frontend changes:
- Keep Vue 3 idioms; prefer options API and existing components.
- Run `make build-js` and `make test-js`; fix ESLint/Prettier before proposing changes.
- Avoid introducing global state; follow existing store patterns.
- Keep strings translatable; dont hardcode locales.
- For performance, avoid unnecessary reactivity and re-renders; justify new deps.

24
.gitignore vendored
View File

@@ -18,10 +18,6 @@
*.dll
*.so
*.dylib
*.tmp
*.img
*.img.xz
*.img.gz
/*.zip
/coverage.*
__pycache__
@@ -29,11 +25,7 @@ venv
.venv
.env
.tmp
.nv
.eslintcache
.gocache
/pro
/plus
/tmp/
/test/
*-lock.json
@@ -45,7 +37,6 @@ venv
/frontend/src/locales/*.mo
/frontend/tests_output
frontend/coverage/
**/__screenshots__/
/photoprism
/photoprism-*
/photos/originals/*
@@ -57,12 +48,8 @@ frontend/coverage/
/assets/nasnet
/assets/nsfw
/assets/static/build/
/assets/static/maps/
/assets/*net
/assets/vision
/specs/
/docs/
/bin/
/pro
/plus
# Files created automatically by editors and/or operating systems:
.DS_Store
@@ -73,13 +60,8 @@ frontend/coverage/
ehthumbs.db
Thumbs.db
.heartbeat
.glide
.idea
.codex
.local
.project
.vscode
*.tmproj
.glide
*~
.goutputstream*
.c9revisions

View File

@@ -1,335 +0,0 @@
log_level: INFO
# Logging configuration
# Qdrant logs to stdout. You may configure to also write logs to a file on disk.
# Be aware that this file may grow indefinitely.
# logger:
# # Logging format, supports `text` and `json`
# format: text
# on_disk:
# enabled: true
# log_file: path/to/log/file.log
# log_level: INFO
# # Logging format, supports `text` and `json`
# format: text
storage:
# Where to store all the data
storage_path: ./storage
# Where to store snapshots
snapshots_path: ./snapshots
snapshots_config:
# "local" or "s3" - where to store snapshots
snapshots_storage: local
# s3_config:
# bucket: ""
# region: ""
# access_key: ""
# secret_key: ""
# Where to store temporary files
# If null, temporary snapshots are stored in: storage/snapshots_temp/
temp_path: null
# If true - point payloads will not be stored in memory.
# It will be read from the disk every time it is requested.
# This setting saves RAM by (slightly) increasing the response time.
# Note: those payload values that are involved in filtering and are indexed - remain in RAM.
#
# Default: true
on_disk_payload: true
# Maximum number of concurrent updates to shard replicas
# If `null` - maximum concurrency is used.
update_concurrency: null
# Write-ahead-log related configuration
wal:
# Size of a single WAL segment
wal_capacity_mb: 32
# Number of WAL segments to create ahead of actual data requirement
wal_segments_ahead: 0
# Normal node - receives all updates and answers all queries
node_type: "Normal"
# Listener node - receives all updates, but does not answer search/read queries
# Useful for setting up a dedicated backup node
# node_type: "Listener"
performance:
# Number of parallel threads used for search operations. If 0 - auto selection.
max_search_threads: 0
# Max number of threads (jobs) for running optimizations across all collections, each thread runs one job.
# If 0 - have no limit and choose dynamically to saturate CPU.
# Note: each optimization job will also use `max_indexing_threads` threads by itself for index building.
max_optimization_threads: 0
# CPU budget, how many CPUs (threads) to allocate for an optimization job.
# If 0 - auto selection, keep 1 or more CPUs unallocated depending on CPU size
# If negative - subtract this number of CPUs from the available CPUs.
# If positive - use this exact number of CPUs.
optimizer_cpu_budget: 0
# Prevent DDoS of too many concurrent updates in distributed mode.
# One external update usually triggers multiple internal updates, which breaks internal
# timings. For example, the health check timing and consensus timing.
# If null - auto selection.
update_rate_limit: null
# Limit for number of incoming automatic shard transfers per collection on this node, does not affect user-requested transfers.
# The same value should be used on all nodes in a cluster.
# Default is to allow 1 transfer.
# If null - allow unlimited transfers.
#incoming_shard_transfers_limit: 1
# Limit for number of outgoing automatic shard transfers per collection on this node, does not affect user-requested transfers.
# The same value should be used on all nodes in a cluster.
# Default is to allow 1 transfer.
# If null - allow unlimited transfers.
#outgoing_shard_transfers_limit: 1
# Enable async scorer which uses io_uring when rescoring.
# Only supported on Linux, must be enabled in your kernel.
# See: <https://qdrant.tech/articles/io_uring/#and-what-about-qdrant>
#async_scorer: false
optimizers:
# The minimal fraction of deleted vectors in a segment, required to perform segment optimization
deleted_threshold: 0.2
# The minimal number of vectors in a segment, required to perform segment optimization
vacuum_min_vector_number: 1000
# Target amount of segments optimizer will try to keep.
# Real amount of segments may vary depending on multiple parameters:
# - Amount of stored points
# - Current write RPS
#
# It is recommended to select default number of segments as a factor of the number of search threads,
# so that each segment would be handled evenly by one of the threads.
# If `default_segment_number = 0`, will be automatically selected by the number of available CPUs
default_segment_number: 0
# Do not create segments larger this size (in KiloBytes).
# Large segments might require disproportionately long indexation times,
# therefore it makes sense to limit the size of segments.
#
# If indexation speed have more priority for your - make this parameter lower.
# If search speed is more important - make this parameter higher.
# Note: 1Kb = 1 vector of size 256
# If not set, will be automatically selected considering the number of available CPUs.
max_segment_size_kb: null
# Maximum size (in KiloBytes) of vectors to store in-memory per segment.
# Segments larger than this threshold will be stored as read-only memmapped file.
# To enable memmap storage, lower the threshold
# Note: 1Kb = 1 vector of size 256
# To explicitly disable mmap optimization, set to `0`.
# If not set, will be disabled by default.
memmap_threshold_kb: null
# Maximum size (in KiloBytes) of vectors allowed for plain index.
# Default value based on https://github.com/google-research/google-research/blob/master/scann/docs/algorithms.md
# Note: 1Kb = 1 vector of size 256
# To explicitly disable vector indexing, set to `0`.
# If not set, the default value will be used.
indexing_threshold_kb: 20000
# Interval between forced flushes.
flush_interval_sec: 5
# Max number of threads (jobs) for running optimizations per shard.
# Note: each optimization job will also use `max_indexing_threads` threads by itself for index building.
# If null - have no limit and choose dynamically to saturate CPU.
# If 0 - no optimization threads, optimizations will be disabled.
max_optimization_threads: null
# This section has the same options as 'optimizers' above. All values specified here will overwrite the collections
# optimizers configs regardless of the config above and the options specified at collection creation.
#optimizers_overwrite:
# deleted_threshold: 0.2
# vacuum_min_vector_number: 1000
# default_segment_number: 0
# max_segment_size_kb: null
# memmap_threshold_kb: null
# indexing_threshold_kb: 20000
# flush_interval_sec: 5
# max_optimization_threads: null
# Default parameters of HNSW Index. Could be overridden for each collection or named vector individually
hnsw_index:
# Number of edges per node in the index graph. Larger the value - more accurate the search, more space required.
m: 16
# Number of neighbours to consider during the index building. Larger the value - more accurate the search, more time required to build index.
ef_construct: 100
# Minimal size (in KiloBytes) of vectors for additional payload-based indexing.
# If payload chunk is smaller than `full_scan_threshold_kb` additional indexing won't be used -
# in this case full-scan search should be preferred by query planner and additional indexing is not required.
# Note: 1Kb = 1 vector of size 256
full_scan_threshold_kb: 10000
# Number of parallel threads used for background index building.
# If 0 - automatically select.
# Best to keep between 8 and 16 to prevent likelihood of building broken/inefficient HNSW graphs.
# On small CPUs, less threads are used.
max_indexing_threads: 0
# Store HNSW index on disk. If set to false, index will be stored in RAM. Default: false
on_disk: false
# Custom M param for hnsw graph built for payload index. If not set, default M will be used.
payload_m: null
# Default shard transfer method to use if none is defined.
# If null - don't have a shard transfer preference, choose automatically.
# If stream_records, snapshot or wal_delta - prefer this specific method.
# More info: https://qdrant.tech/documentation/guides/distributed_deployment/#shard-transfer-method
shard_transfer_method: null
# Default parameters for collections
collection:
# Number of replicas of each shard that network tries to maintain
replication_factor: 1
# How many replicas should apply the operation for us to consider it successful
write_consistency_factor: 1
# Default parameters for vectors.
vectors:
# Whether vectors should be stored in memory or on disk.
on_disk: null
# shard_number_per_node: 1
# Default quantization configuration.
# More info: https://qdrant.tech/documentation/guides/quantization
quantization: null
# Default strict mode parameters for newly created collections.
strict_mode:
# Whether strict mode is enabled for a collection or not.
enabled: false
# Max allowed `limit` parameter for all APIs that don't have their own max limit.
max_query_limit: null
# Max allowed `timeout` parameter.
max_timeout: null
# Allow usage of unindexed fields in retrieval based (eg. search) filters.
unindexed_filtering_retrieve: null
# Allow usage of unindexed fields in filtered updates (eg. delete by payload).
unindexed_filtering_update: null
# Max HNSW value allowed in search parameters.
search_max_hnsw_ef: null
# Whether exact search is allowed or not.
search_allow_exact: null
# Max oversampling value allowed in search.
search_max_oversampling: null
service:
# Maximum size of POST data in a single request in megabytes
max_request_size_mb: 32
# Number of parallel workers used for serving the api. If 0 - equal to the number of available cores.
# If missing - Same as storage.max_search_threads
max_workers: 0
# Host to bind the service on
host: 0.0.0.0
# HTTP(S) port to bind the service on
http_port: 6333
# gRPC port to bind the service on.
# If `null` - gRPC is disabled. Default: null
# Comment to disable gRPC:
grpc_port: 6334
# Enable CORS headers in REST API.
# If enabled, browsers would be allowed to query REST endpoints regardless of query origin.
# More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
# Default: true
enable_cors: true
# Enable HTTPS for the REST and gRPC API
enable_tls: false
# Check user HTTPS client certificate against CA file specified in tls config
verify_https_client_certificate: false
# Set an api-key.
# If set, all requests must include a header with the api-key.
# example header: `api-key: <API-KEY>`
#
# If you enable this you should also enable TLS.
# (Either above or via an external service like nginx.)
# Sending an api-key over an unencrypted channel is insecure.
#
# Uncomment to enable.
# api_key: your_secret_api_key_here
# Set an api-key for read-only operations.
# If set, all requests must include a header with the api-key.
# example header: `api-key: <API-KEY>`
#
# If you enable this you should also enable TLS.
# (Either above or via an external service like nginx.)
# Sending an api-key over an unencrypted channel is insecure.
#
# Uncomment to enable.
# read_only_api_key: your_secret_read_only_api_key_here
# Uncomment to enable JWT Role Based Access Control (RBAC).
# If enabled, you can generate JWT tokens with fine-grained rules for access control.
# Use generated token instead of API key.
#
# jwt_rbac: true
# Hardware reporting adds information to the API responses with a
# hint on how many resources were used to execute the request.
#
# Uncomment to enable.
# hardware_reporting: true
cluster:
# Use `enabled: true` to run Qdrant in distributed deployment mode
enabled: false
# Configuration of the inter-cluster communication
p2p:
# Port for internal communication between peers
port: 6335
# Use TLS for communication between peers
enable_tls: false
# Configuration related to distributed consensus algorithm
consensus:
# How frequently peers should ping each other.
# Setting this parameter to lower value will allow consensus
# to detect disconnected nodes earlier, but too frequent
# tick period may create significant network and CPU overhead.
# We encourage you NOT to change this parameter unless you know what you are doing.
tick_period_ms: 100
# Compact consensus operations once we have this amount of applied
# operations. Allows peers to join quickly with a consensus snapshot without
# replaying a huge amount of operations.
# If 0 - disable compaction
compact_wal_entries: 128
# Set to true to prevent service from sending usage statistics to the developers.
# Read more: https://qdrant.tech/documentation/guides/telemetry
telemetry_disabled: true

370
AGENTS.md
View File

@@ -1,370 +0,0 @@
# PhotoPrism® Repository Guidelines
**Last Updated:** October 3, 2025
## Purpose
This file tells automated coding agents (and humans) where to find the single sources of truth for building, testing, and contributing to PhotoPrism.
Learn more: https://agents.md/
## Sources of Truth
- Makefile targets (always prefer existing targets): https://github.com/photoprism/photoprism/blob/develop/Makefile
- Developer Guide Setup: https://docs.photoprism.app/developer-guide/setup/
- Developer Guide Tests: https://docs.photoprism.app/developer-guide/tests/
- Contributing: https://github.com/photoprism/photoprism/blob/develop/CONTRIBUTING.md
- Security: https://github.com/photoprism/photoprism/blob/develop/SECURITY.md
- REST API: https://docs.photoprism.dev/ (Swagger), https://docs.photoprism.app/developer-guide/api/ (Docs)
- Code Maps: `CODEMAP.md` (Backend/Go), `frontend/CODEMAP.md` (Frontend/JS)
- Face Detection & Embeddings Notes: `internal/ai/face/README.md`
### Specifications (Versioning & Usage)
- Always use the latest spec version for a topic (highest `-vN`), as linked from `specs/README.md` and the portal cheatsheet (`specs/portal/README.md`).
- Testing Guides: `specs/dev/backend-testing.md` (Backend/Go), `specs/dev/frontend-testing.md` (Frontend/JS)
- Whenever the Change Management instructions for a document require it, publish changes as a new file with an incremented version suffix (e.g., `*-v3.md`) rather than overwriting the original file.
- Older spec versions remain in the repo for historical reference but are not linked from the main TOC. Do not base new work on superseded files (e.g., `*-v1.md` when `*-v2.md` exists).
Note on specs repository availability
- The `specs/` repository may be private and is not guaranteed to be present in every clone or environment. Do not add Makefile targets in the main project that depend on `specs/` paths. When `specs/` is available, run its tools directly (e.g., `bash specs/scripts/lint-status.sh`).
- In the main repo, `specs/` appears ignored because it is managed as a nested Git repository; change into `specs/` before staging or committing spec updates.
- When introducing new metadata sources (e.g., `SrcOllama`, `SrcOpenAI`), define them in both `internal/entity/src.go` and the frontend lookup tables (`frontend/src/common/util.js`) so UI badges and server priorities stay aligned.
## Project Structure & Languages
- Backend: Go (`internal/`, `pkg/`, `cmd/`) + MariaDB/SQLite
- Package boundaries: Code in `pkg/*` MUST NOT import from `internal/*`.
- If you need access to config/entity/DB, put new code in a package under `internal/` instead of `pkg/`.
- Frontend: Vue 3 + Vuetify 3 (`frontend/`)
- Docker/compose for dev/CI; Traefik is used for local TLS (`*.localssl.dev`)
## Agent Runtime (Host vs Container)
Agents MAY run either:
- **Inside the Development Environment container** (recommended for least privilege).
- **On the host** (outside Docker), in which case the agent MAY start/stop the Dev Environment as needed.
### Detecting the environment (agent logic)
Agents SHOULD detect the runtime and choose commands accordingly:
- **Inside container if** one of the following is true:
- File exists: `/.dockerenv`
- Project path equals (or is a direct child of): `/go/src/github.com/photoprism/photoprism`
#### Examples
Bash:
```bash
if [ -f "/.dockerenv" ] || [ -d "/go/src/github.com/photoprism/photoprism/.git" ]; then
echo "container"
else
echo "host"
fi
```
Node.js:
```js
const fs = require("fs");
const inContainer = fs.existsSync("/.dockerenv");
const inDevPath = fs.existsSync("/go/src/github.com/photoprism/photoprism/.git");
console.log(inContainer || inDevPath ? "container" : "host");
```
### Agent installation and invocation
- **Inside container**: Prefer running agents via `npm exec` (no global install), for example:
- `npm exec --yes <agent-binary> -- --help`
- Or use `npx <agent-binary> ...`
- If the agent is distributed via npm and must be global, install inside the container only:
- `npm install -g <agent-npm-package>`
- Replace `<agent-binary>` / `<agent-npm-package>` with the names from the agents official docs.
- **On host**: Use the vendors recommended install for your OS. Ensure your agent runs from the repository root so it can discover `AGENTS.md` and project files.
## Build & Run (local)
- Run `make help` to see common targets (or open the `Makefile`).
- **Host mode** (agent runs on the host; agent MAY manage Docker lifecycle):
- Build local dev image (once): `make docker-build`
- Start services: `docker compose up` (add `-d` to start in the background)
- Follow live app logs: `docker compose logs -f --tail=100 photoprism` (Ctrl+C to stop)
- All services: `docker compose logs -f --tail=100`
- Last 10 minutes only: `docker compose logs -f --since=10m photoprism`
- Plain output (easier to copy): `docker compose logs -f --no-log-prefix --no-color photoprism`
- Execute a single command in the app container: `docker compose exec photoprism <command>`
- Example: `docker compose exec photoprism ./photoprism help`
- Why `./photoprism`? It runs the locally built binary in the project directory.
- Run as non-root to avoid root-owned files on bind mounts:
`docker compose exec -u "$(id -u):$(id -g)" photoprism <command>`
- Durable alternative: set the service user or `PHOTOPRISM_UID`/`PHOTOPRISM_GID` in `compose.yaml`; if you hit issues, run `make fix-permissions`.
- Open a terminal session in the app container: `make terminal`
- Stop everything when done: `docker compose --profile=all down --remove-orphans` (`make down` does the same)
- **Container mode** (agent runs inside the app container):
- Install deps: `make dep`
- Build frontend/backend: `make build-js` and `make build-go`
- Watch frontend changes (auto-rebuild): `make watch-js`
- Or run directly: `cd frontend && npm run watch`
- Tips: refresh the browser to see changes; running the watcher outside the container can be faster on non-Linux hosts; stop with Ctrl+C
- Start the PhotoPrism server: `./photoprism start`
- Open http://localhost:2342/ (HTTP)
- Or https://app.localssl.dev/ (HTTPS via Traefik reverse proxy)
- Only if Traefik is running and the dev compose labels are active
- Labels for `*.localssl.dev` are defined in the dev compose files, e.g. https://github.com/photoprism/photoprism/blob/develop/compose.yaml
- Do not use the Docker CLI inside the container; starting/stopping services requires host Docker access.
Note: Across our public documentation, official images, and in production, the command-line interface (CLI) name is `photoprism`. Other PhotoPrism binary names are only used in development builds for side-by-side comparisons of the Community Edition (CE) with PhotoPrism Plus (`photoprism-plus`) and PhotoPrism Pro (`photoprism-pro`).
## Tests
- From within the Development Environment:
- Full unit test suite: `make test` (runs backend and frontend tests)
- Test frontend/backend: `make test-js` and `make test-go`
- Go packages: `go test` (all tests) or `go test -run <name>` (specific tests only)
- Need to inspect the MariaDB data while iterating? Connect directly inside the dev shell with `mariadb -D photoprism` and run SQL without rebuilding Go code.
- Go tests live beside sources: for `path/to/pkg/<file>.go`, add tests in `path/to/pkg/<file>_test.go` (create if missing). For the same function, group related cases as `t.Run(...)` sub-tests (table-driven where helpful) and use PascalCase subtest names (for example, `t.Run("Success", ...)`).
- Frontend unit tests are driven by Vitest; see scripts in `frontend/package.json`
- Vitest watch/coverage: `make vitest-watch` and `make vitest-coverage`
- Acceptance tests: use the `acceptance-*` targets in the `Makefile`
### FFmpeg Tests & Hardware Gating
- By default, do not run GPU/HW encoder integrations in CI. Gate with `PHOTOPRISM_FFMPEG_ENCODER` (one of: `vaapi`, `intel`, `nvidia`).
- Negative-path tests should remain fast and always run:
- Missing ffmpeg binary → immediate exec error.
- Unwritable destination → command fails without creating files.
- Prefer command-string assertions when hardware is unavailable; enable HW runs locally only when a device is configured.
### Fast, Focused Test Recipes
- Filesystem + archives (fast): `go test ./pkg/fs -run 'Copy|Move|Unzip' -count=1`
- Media helpers (fast): `go test ./pkg/media/... -count=1`
- Thumbnails (libvips, moderate): `go test ./internal/thumb/... -count=1`
- FFmpeg command builders (moderate): `go test ./internal/ffmpeg -run 'Remux|Transcode|Extract' -count=1`
### CLI Testing Gotchas (Go)
- Exit codes and `os.Exit`:
- `urfave/cli` calls `os.Exit(code)` when a command returns `cli.Exit(...)`, which will terminate `go test` abruptly (often after logs like `http 401:`).
- Use the test helper `RunWithTestContext` (in `internal/commands/commands_test.go`) which temporarily overrides `cli.OsExiter` so the process doesnt exit; you still receive the error to assert `ExitCoder`.
- If you only need to assert the exit code and dont need printed output, you can invoke `cmd.Action(ctx)` directly and check `err.(cli.ExitCoder).ExitCode()`.
- Noninteractive mode: set `PHOTOPRISM_CLI=noninteractive` and/or pass `--yes` to avoid prompts that block tests and CI.
- SQLite DSN in tests:
- `config.NewTestConfig("<pkg>")` defaults to SQLite with a persuite DSN like `.<pkg>.db`. Dont assert an empty DSN for SQLite.
- Clean up any persuite SQLite files in tests with `t.Cleanup(func(){ _ = os.Remove(dsn) })` if you capture the DSN.
## Code Style & Lint
- Go: run `make fmt-go swag-fmt` to reformat the backend code + Swagger annotations (see `Makefile` for additional targets)
- Doc comments for packages and exported identifiers must be complete sentences that begin with the name of the thing being described and end with a period.
- For short examples inside comments, indent code rather than using backticks; godoc treats indented blocks as preformatted.
- Branding: Always spell the product name as `PhotoPrism`; this proper noun is an exception to generic naming rules.
- Every Go package must contain a `<package>.go` file in its root (for example, `internal/auth/jwt/jwt.go`) with the standard license header and a short package description comment explaining its purpose.
- JS/Vue: use the lint/format scripts in `frontend/package.json` (ESLint + Prettier)
- All added code and tests **must** be formatted according to our standards.
> Remember to update the `**Last Updated:**` line at the top whenever you edit these guidelines or other files containing a timestamp.
## Safety & Data
- Never commit secrets, local configurations, or cache files. Use environment variables or a local `.env`.
- Ensure `.env` and `.local` are ignored in `.gitignore` and `.dockerignore`.
- Prefer using existing caches, workers, and batching strategies referenced in code and `Makefile`. Consider memory/CPU impact; suggest benchmarks or profiling only when justified.
- Do not run destructive commands against production data. Prefer ephemeral volumes and test fixtures when running acceptance tests.
### Filesystem Permissions & io/fs Aliasing (Go)
- Always use our shared permission variables from `pkg/fs` when creating files/directories:
- Directories: `fs.ModeDir` (0o755 with umask)
- Regular files: `fs.ModeFile` (0o644 with umask)
- Config files: `fs.ModeConfigFile` (default 0o664)
- Secrets/tokens: `fs.ModeSecretFile` (default 0o600)
- Backups: `fs.ModeBackupFile` (default 0o600)
- Do not pass stdlib `io/fs` flags (e.g., `fs.ModeDir`) to functions expecting permission bits.
- When importing the stdlib package, alias it to avoid collisions: `iofs "io/fs"` or `gofs "io/fs"`.
- Our package is `github.com/photoprism/photoprism/pkg/fs` and provides the only approved permission constants for `os.MkdirAll`, `os.WriteFile`, `os.OpenFile`, and `os.Chmod`.
- Prefer `filepath.Join` for filesystem paths; reserve `path.Join` for URL paths.
### File I/O — Overwrite Policy (force semantics)
- Default is safety-first: callers must not overwrite non-empty destination files unless they opt-in with a `force` flag.
- Replacing empty destination files is allowed without `force=true` (useful for placeholder files).
- Open destinations with `O_WRONLY|O_CREATE|O_TRUNC` to avoid trailing bytes when overwriting; use `O_EXCL` when the caller must detect collisions.
- Where this lives:
- App-level helpers: `internal/photoprism/mediafile.go` (`MediaFile.Copy/Move`).
- Reusable utils: `pkg/fs/copy.go`, `pkg/fs/move.go`.
- When to set `force=true`:
- Explicit “replace” actions or admin tools where the user confirmed overwrite.
- Not for import/index flows; Originals must not be clobbered.
### Archive Extraction — Security Checklist
- Always validate ZIP entry names with a safe join; reject:
- absolute paths (e.g., `/etc/passwd`).
- Windows drive/volume paths (e.g., `C:\\…` or `C:/…`).
- any entry that escapes the target directory after cleaning (path traversal via `..`).
- Enforce per-file and total size budgets to prevent resource exhaustion.
- Skip OS metadata directories (e.g., `__MACOSX`) and reject suspicious names.
- Where this lives: `pkg/fs/zip.go` (`Unzip`, `UnzipFile`, `safeJoin`).
- Tests to keep:
- Absolute/volume paths rejected (Windows-specific backslash path covered on Windows).
- `..` traversal skipped; `__MACOSX` skipped.
- Per-file and total size limits enforced; directory entries created; nested paths extracted safely.
- Examples assume a Linux/Unix shell. For Windows specifics, see the Developer Guide FAQ:
https://docs.photoprism.app/developer-guide/faq/#can-your-development-environment-be-used-under-windows
### HTTP Download — Security Checklist
- Use the shared safe HTTP helper instead of adhoc `net/http` code:
- Package: `pkg/service/http/safe``safe.Download(destPath, url, *safe.Options)`.
- Default policy in this repo: allow only `http/https`, enforce timeouts and max size, write to a `0600` temp file then rename.
- SSRF protection (mandatory unless explicitly needed for tests):
- Set `AllowPrivate=false` to block private/loopback/multicast/linklocal ranges.
- All redirect targets are validated; the final connected peer IP is also checked.
- Prefer an imagefocused `Accept` header for image downloads: `"image/jpeg, image/png, */*;q=0.1"`.
- Avatars and small images: use the thin wrapper in `internal/thumb/avatar.SafeDownload` which applies stricter defaults (15s timeout, 10 MiB, `AllowPrivate=false`).
- Tests using `httptest.Server` on 127.0.0.1 must pass `AllowPrivate=true` explicitly to succeed.
- Keep perresource size budgets small; rely on `io.LimitReader` + `Content-Length` prechecks.
If anything in this file conflicts with the `Makefile` or the Developer Guide, the `Makefile` and the documentation win. When unsure, **ask** for clarification before proceeding.
## Agent Quick Tips (Do This)
### Testing & Fixtures
- Go tests live next to their sources (`path/to/pkg/<file>_test.go`); group related cases as `t.Run(...)` sub-tests to keep table-driven coverage readable, and name each subtest with a PascalCase string.
- Prefer focused `go test` runs for speed (`go test ./internal/<pkg> -run <Name> -count=1`, `go test ./internal/commands -run <Name> -count=1`) and avoid `./...` unless you need the entire suite.
- Heavy packages such as `internal/entity` and `internal/photoprism` run migrations and fixtures; expect 30120s on first run and narrow with `-run` to keep iterations low.
- For CLI-driven tests, wrap commands with `RunWithTestContext(cmd, args)` so `urfave/cli` cannot exit the process, and assert CLI output with `assert.Contains`/regex because `show` reports quote strings.
- In `internal/photoprism` tests, rely on `photoprism.Config()` for runtime-accurate behavior; only build a new config if you replace it via `photoprism.SetConfig`.
- Generate identifiers with `rnd.GenerateUID(entity.ClientUID)` for OAuth client IDs and `rnd.UUIDv7()` for node UUIDs; treat `node.uuid` as required in responses.
- When adding persistent fixtures (photos, files, labels, etc.), always obtain new IDs via `rnd.GenerateUID(...)` with the matching prefix (`entity.PhotoUID`, `entity.FileUID`, `entity.LabelUID`, …) instead of inventing manual strings so the search helpers recognize them.
- For database updates, prefer the `entity.Values` type alias over raw `map[string]interface{}` so helpers stay type-safe and consistent with existing code.
- Reach for `config.NewMinimalTestConfig(t.TempDir())` when a test only needs filesystem/config scaffolding, and use `config.NewMinimalTestConfigWithDb("<name>", t.TempDir())` when you need a fresh SQLite schema without the cached fixture snapshot.
- Avoid `config.TestConfig()` in new tests unless you truly need the fully seeded fixture set: it shares a singleton instance that runs `InitializeTestData()` and wipes `storage/testdata`. Tests that write to Originals/Import (e.g. WebDAV helpers) should instead call `config.NewMinimalTestConfig(t.TempDir())` (or the DB variant) and follow up with `conf.CreateDirectories()` so they operate on an isolated sandbox.
- Shared fixtures live under `storage/testdata`; `NewTestConfig("<pkg>")` already calls `InitializeTestData()`, but call `c.InitializeTestData()` (and optionally `c.AssertTestData(t)`) when you construct custom configs so originals/import/cache/temp exist. `InitializeTestData()` clears old data, downloads fixtures if needed, then calls `CreateDirectories()`.
- `PhotoFixtures.Get()` and similar helpers return value copies; when a test needs the database-backed row (with associations preloaded), re-query by UID/ID using helpers like `entity.FindPhoto(fixture)` so updates observe persisted IDs and in-memory caches stay coherent.
- For slimmer tests that only need config objects, prefer the new helpers in `internal/config/test.go`: `NewMinimalTestConfig(t.TempDir())` when no database is needed, or `NewMinimalTestConfigWithDb("<pkg>", t.TempDir())` to spin up an isolated SQLite schema without seeding all fixtures.
- When you need illustrative credentials (join tokens, client IDs/secrets, etc.), reuse the shared `Example*` constants (see `internal/service/cluster/examples.go`) so tests, docs, and examples stay consistent.
### Roles & ACL
- Map roles via the shared tables: users through `acl.ParseRole(s)` / `acl.UserRoles[...]`, clients through `acl.ClientRoles[...]`.
- Treat `RoleAliasNone` ("none") and an empty string as `RoleNone`; no caller-specific overrides.
- Default unknown client roles to `RoleClient`; `acl.ParseRole` already handles `0/false/nil` as none for users.
- Build CLI role help from `Roles.CliUsageString()` (e.g., `acl.ClientRoles.CliUsageString()`); never hand-maintain role lists.
- When checking JWT/client scopes, use the shared helpers (`acl.ScopePermits` / `acl.ScopeAttrPermits`) instead of hand-written parsing.
### Import/Index
- ImportWorker may skip files if an identical file already exists (duplicate detection). Use unique copies or assert DB rows after ensuring a nonduplicate destination.
- Mixed roots: when testing related files, keep `ExamplesPath()/ImportPath()/OriginalsPath()` consistent so `RelatedFiles` and `AllowExt` behave as expected.
### CLI Usage & Assertions
- Wrap CLI tests in `RunWithTestContext(cmd, args)` so `urfave/cli` cannot exit the process; assert quoted `show` output with `assert.Contains`/regex for the trailing ", or <last>" rule.
- Prefer `--json` responses for automation. `photoprism show commands --json [--nested]` exposes the tree view (add `--all` for hidden entries).
- Use `internal/commands/catalog` to inspect commands/flags without running the binary; when validating large JSON docs, marshal DTOs via `catalog.BuildFlat/BuildNode` instead of parsing CLI stdout.
- Expect `show` commands to return arrays of snake_case rows, except `photoprism show config`, which yields `{ sections: [...] }`, and the `config-options`/`config-yaml` variants, which flatten to a top-level array.
### API & Config Changes
- Respect precedence: `options.yml` overrides CLI/env values, which override defaults. When adding a new option, update `internal/config/options.go` (yaml/flag tags), register it in `internal/config/flags.go`, expose a getter, surface it in `*config.Report()`, and write generated values back to `options.yml` by setting `c.options.OptionsYaml` before persisting. Use `CliTestContext` in `internal/config/test.go` to exercise new flags.
- When touching configuration in Go code, use the public accessors on `*config.Config` (e.g. `Config.JWKSUrl()`, `Config.SetJWKSUrl()`, `Config.ClusterUUID()`) instead of mutating `Config.Options()` directly; reserve raw option tweaks for test fixtures only.
- Vision worker scheduling is controlled via `VisionSchedule` / `VisionFilter` and the `Run` property set in `vision.yml`. Utilities like `vision.FilterModels` and `entity.Photo.ShouldGenerateLabels/Caption` help decide when work is required before loading media files.
- Logging: use the shared logger (`event.Log`) via the package-level `log` variable (see `internal/auth/jwt/logger.go`) instead of direct `fmt.Print*` or ad-hoc loggers.
- Cluster registry tests (`internal/service/cluster/registry`) currently rely on a full test config because they persist `entity.Client` rows. They run migrations and seed the SQLite DB, so they are intentionally slow. If you refactor them, consider sharing a single `config.TestConfig()` across subtests or building a lightweight schema harness; do not swap to the minimal config helper unless the tests stop touching the database.
- Favor explicit CLI flags: check `c.cliCtx.IsSet("<flag>")` before overriding user-supplied values, and follow the `ClusterUUID` pattern (`options.yml` → CLI/env → generated UUIDv4 persisted).
- Database helpers: reuse `conf.Db()` / `conf.Database*()`, avoid GORM `WithContext`, quote MySQL identifiers, and reject unsupported drivers early.
- Handler conventions: reuse limiter stacks (`limiter.Auth`, `limiter.Login`) and `limiter.AbortJSON` for 429s, lean on `api.ClientIP`, `header.BearerToken`, and `Abort*` helpers, compare secrets with constant time checks, set `Cache-Control: no-store` on sensitive responses, and register routes in `internal/server/routes.go`. For new list endpoints default `count=100` (max 1000) and `offset≥0`, document parameters explicitly, and set portal mode via `PHOTOPRISM_NODE_ROLE=portal` plus `PHOTOPRISM_JOIN_TOKEN` when needed.
- Swagger & docs: annotate only routed handlers in `internal/api/*.go`, use full `/api/v1/...` paths, skip helpers, and regenerate docs with `make fmt-go swag-fmt swag` or `make swag-json` (which also strips duplicate `time.Duration` enums). When iterating, target packages with `go test ./internal/api -run Cluster -count=1` or similarly scoped runs.
- Testing helpers: isolate config paths with `t.TempDir()`, reuse `NewConfig`, `CliTestContext`, and `NewApiTest()` harnesses, authenticate via `AuthenticateAdmin`, `AuthenticateUser`, or `OAuthToken`, toggle auth with `conf.SetAuthMode(config.AuthModePasswd)`, and prefer OAuth client tokens over non-admin fixtures for negative permission checks.
- Registry data and secrets: store portal/node registry files under `conf.PortalConfigPath()/nodes/` with mode `0600`, keep secrets out of logs, and only return them on creation/rotation flows.
### Formatting (Go)
- Go is formatted by `gofmt` and uses tabs. Do not hand-format indentation.
- Always run after edits: `make fmt-go` (gofmt + goimports).
### API Shape Checklist
- When renaming or adding fields:
- Update DTOs in `internal/service/cluster/response.go` and any mappers.
- Update handlers and regenerate Swagger: `make fmt-go swag-fmt swag`.
- Update tests (search/replace old field names) and examples in `specs/`.
- Quick grep: `rg -n 'oldField|newField' -S` across code, tests, and specs.
### API/CLI Tests: Known Pitfalls
- Gin routes: Register `CreateSession(router)` once per test router; reusing it twice panics on duplicate route.
- CLI commands: Some commands defer `conf.Shutdown()` or emit signals that close the DB. The harness reopens DB before each run, but avoid invoking `start` or emitting signals in unit tests.
- Signals: `internal/commands/start.go` waits on `process.Signal`; calling `process.Shutdown()/Restart()` can close DB. Prefer not to trigger signals in tests.
### Download CLI Workbench (yt-dlp, remux, importer)
- Code anchors
- CLI flags and examples: `internal/commands/download.go`
- Core implementation (testable): `internal/commands/download_impl.go`
- yt-dlp helpers and arg wiring: `internal/photoprism/dl/*` (`options.go`, `info.go`, `file.go`, `meta.go`)
- Importer entry point: `internal/photoprism/get/import.go`; options: `internal/photoprism/import_options.go`
- Quick test runs (fast feedback)
- yt-dlp package: `go test ./internal/photoprism/dl -run 'Options|Created|PostprocessorArgs' -count=1`
- CLI command: `go test ./internal/commands -run 'DownloadImpl|HelpFlags' -count=1`
- FFmpeg-less tests
- In tests: set `c.Options().FFmpegBin = "/bin/false"` and `c.Settings().Index.Convert = false` to avoid ffmpeg dependencies when not validating remux.
- Stubbing yt-dlp (no network)
- Use a tiny shell script that:
- prints minimal JSON for `--dump-single-json`
- creates a file and prints its path when `--print` is requested
- Harness env vars (supported by our tests):
- `YTDLP_ARGS_LOG` — append final args for assertion
- `YTDLP_OUTPUT_FILE` — absolute file path to create for `--print`
- `YTDLP_DUMMY_CONTENT` — file contents to avoid importer duplicate detection between tests
- Remux policy and metadata
- Pipe method: PhotoPrism remux (ffmpeg) always embeds title/description/created.
- File method: ytdlp writes files; we pass `--postprocessor-args 'ffmpeg:-metadata creation_time=<RFC3339>'` so imports get `Created` even without local remux (fallback from `upload_date`/`release_date`).
- Default remux policy: `auto`; use `always` for the most complete metadata (chapters, extended tags).
- Testing workflow: lean on the focused commands above; if importer dedupe kicks in, vary bytes with `YTDLP_DUMMY_CONTENT` or adjust `dest`, and remember `internal/photoprism` is heavy so validate downstream packages first.
### Sessions & Redaction (building sessions in tests)
- Admin session (full view): `AuthenticateAdmin(app, router)`.
- User session: Create a nonadmin test user (role=guest), set a password, then `AuthenticateUser`.
- Client session (redacted internal fields; `siteUrl` visible):
```go
s, _ := entity.AddClientSession("test-client", conf.SessionMaxAge(), "cluster", authn.GrantClientCredentials, nil)
token := s.AuthToken()
r := AuthenticatedRequest(app, http.MethodGet, "/api/v1/cluster/nodes", token)
```
Admins see `advertiseUrl` and `database`; client/user sessions dont. `siteUrl` is safe to show to all roles.
### Preflight Checklist
- `go build ./...`
- `make fmt-go swag-fmt swag`
- `go test ./internal/service/cluster/registry -count=1`
- `go test ./internal/api -run 'Cluster' -count=1`
- `go test ./internal/commands -run 'ClusterRegister|ClusterNodesRotate' -count=1`
- Tooling constraints: `make swag` may fetch modules, so confirm network access before running it.
### Cluster Operations
- Keep bootstrap code decoupled: avoid importing `internal/service/cluster/node/*` from `internal/config` or the cluster root, let nodes talk to the Portal over HTTP(S), and rely on constants from `internal/service/cluster/const.go`.
- Config init order: load `options.yml` (`c.initSettings()`), run `EarlyExt().InitEarly(c)`, connect/register the DB, then invoke `Ext().Init(c)`.
- Theme endpoint: `GET /api/v1/cluster/theme` streams a zip from `conf.ThemePath()`; only reinstall when `app.js` is missing and always use the header helpers in `pkg/service/http/header`.
- Registration flow: send `rotate=true` only for MySQL/MariaDB nodes without credentials, treat 401/403/404 as terminal, include `clientId` + `clientSecret` when renaming an existing node, and persist only newly generated secrets or DB settings.
- Registry & DTOs: use the client-backed registry (`NewClientRegistryWithConfig`)—the file-backed version is legacy—and treat migration as complete only after swapping callsites, building, and running focused API/CLI tests. Nodes are keyed by UUID v7 (`/api/v1/cluster/nodes/{uuid}`), the registry interface stays UUID-first (`Get`, `FindByNodeUUID`, `FindByClientID`, `RotateSecret`, `DeleteAllByUUID`), CLI lookups resolve `uuid → clientId → name`, and DTOs normalize `database.{name,user,driver,rotatedAt}` while exposing `clientSecret` only during creation/rotation. `nodes rm --all-ids` cleans duplicate client rows, admin responses may include `advertiseUrl`/`database`, client/user sessions stay redacted, registry files live under `conf.PortalConfigPath()/nodes/` (mode 0600), and `ClientData` no longer stores `NodeUUID`.
- Provisioner & DSN: database/user names use UUID-based HMACs (`photoprism_d<hmac11>`, `photoprism_u<hmac11>`); `BuildDSN` accepts a `driver` but falls back to MySQL format with a warning when unsupported.
- If we add Postgres provisioning support, extend `BuildDSN` and `provisioner.DatabaseDriver` handling, add validations, and return `driver=postgres` consistently in API/CLI.
- Testing: exercise Portal endpoints with `httptest`, guard extraction paths with `pkg/fs.Unzip` size caps, and expect admin-only fields to disappear when authenticated as a client/user session.

View File

@@ -1,248 +0,0 @@
PhotoPrism — Backend CODEMAP
**Last Updated:** October 2, 2025
Purpose
- Give agents and contributors a fast, reliable map of where things live and how they fit together, so you can add features, fix bugs, and write tests without spelunking.
- Sources of truth: prefer Makefile targets and the Developer Guide linked in AGENTS.md.
Quick Start
- Inside dev container (recommended):
- Install deps: `make dep`
- Build backend: `make build-go`
- Run server: `./photoprism start`
- Open: http://localhost:2342/ or https://app.localssl.dev/ (Traefik required)
- On host (manages Docker):
- Build image: `make docker-build`
- Start services: `docker compose up -d`
- Logs: `docker compose logs -f --tail=100 photoprism`
Executables & Entry Points
- CLI app (binary name across docs/images is `photoprism`):
- Main: `cmd/photoprism/photoprism.go`
- Commands registry: `internal/commands/commands.go` (array `commands.PhotoPrism`)
- Catalog helpers: `internal/commands/catalog` (DTOs and builders to enumerate commands/flags; Markdown renderer)
- Web server:
- Startup: `internal/commands/start.go``server.Start` (starts HTTP(S), workers, session cleanup)
- HTTP server: `internal/server/start.go` (compression, security, healthz, readiness, TLS/AutoTLS/unix socket)
- Routes: `internal/server/routes.go` (registers all v1 API groups + UI, WebDAV, sharing, .well-known)
- API group: `APIv1 = router.Group(conf.BaseUri("/api/v1"), Api(conf))`
High-Level Package Map (Go)
- `internal/api` — Gin handlers and Swagger annotations; only glue, no business logic
- `internal/commands/catalog` — DTOs (App, Command, Flag, Node), builders (BuildFlat/BuildNode, CommandInfo, FlagsToCatalog), and a templated Markdown renderer (RenderMarkdown) for the CLI commands catalog. Depends only on `urfave/cli/v2` and stdlib.
- `internal/server` — HTTP server, middleware, routing, static/ui/webdav
- `internal/config` — configuration, flags/env/options, client config, DB init/migrate
- `internal/entity` — GORM v1 models, queries, search helpers, migrations
- `internal/photoprism` — core domain logic (indexing, import, faces, thumbnails, cleanup)
- `internal/workers` — background schedulers (index, vision, sync, meta, backup)
- `internal/auth` — ACL, sessions, OIDC
- `internal/service` — cluster/portal, maps, hub, webdav
- `internal/event` — logging, pub/sub, audit
- `internal/ffmpeg`, `internal/thumb`, `internal/meta`, `internal/form`, `internal/mutex` — media, thumbs, metadata, forms, coordination
- `pkg/*` — reusable utilities (must never import from `internal/*`), e.g. `pkg/fs`, `pkg/log`, `pkg/service/http/header`
HTTP API
- Handlers live in `internal/api/*.go` and are registered in `internal/server/routes.go`.
- Annotate new endpoints in handler files; generate docs with: `make fmt-go swag-fmt && make swag`.
- Do not edit `internal/api/swagger.json` by hand.
- Swagger notes:
- Use full `/api/v1/...` in every `@Router` annotation (match the group prefix).
- Annotate only public handlers; skip internal helpers to avoid stray generic paths.
- `make swag-json` runs a stabilization step (`swaggerfix`) removing duplicated enums for `time.Duration`; API uses integer nanoseconds for durations.
- Common groups in `routes.go`: sessions, OAuth/OIDC, config, users, services, thumbnails, video, downloads/zip, index/import, photos/files/labels/subjects/faces, batch ops, cluster, technical (metrics, status, echo).
Configuration & Flags
- Options struct: `internal/config/options.go` with `yaml:"…"` (for `defaults.yml`/`options.yml`), `json:"…"` (clients/API), and `flag:"…"` (CLI flags/env) tags.
- For secrets/internals: `json:"-"` disables JSON processing to prevent values from being exposed through the API (see `internal/api/config_options.go`).
- If needed: `yaml:"-"` disables YAML processing; `flag:"-"` prevents `ApplyCliContext()` from assigning CLI values (flags/env variables) to a field, without affecting the flags in `internal/config/flags.go`.
- Annotations may include edition tags like `tags:"plus,pro"` to control visibility (see `internal/config/options_report.go` logic).
- Global flags/env: `internal/config/flags.go` (`EnvVars(...)`)
- Available flags/env: `internal/config/cli_flags_report.go` + `internal/config/report_sections.go` → surfaced by `photoprism show config-options --md/--json`
- YAML options mapping: `internal/config/options_report.go` + `internal/config/report_sections.go` → surfaced by `photoprism show config-yaml --md/--json`
- Report current values: `internal/config/report.go` → surfaced by `photoprism show config` (alias `photoprism config --md`).
- CLI commands catalog: `internal/commands/show_commands.go` → surfaced by `photoprism show commands` (Markdown by default; `--json` alternative; `--nested` optional tree; `--all` includes hidden commands/flags; nested `help` subcommands omitted).
- Precedence: `defaults.yml` < CLI/env < `options.yml` (global options rule). See Agent Tips in `AGENTS.md`.
- Getters are grouped by topic, e.g. DB in `internal/config/config_db.go`, server in `config_server.go`, TLS in `config_tls.go`, etc.
- Client Config (read-only)
- Endpoint: GET `/api/v1/config` (see `internal/api/api_client_config.go`).
- Assembly: Built from `internal/config/client_config.go` (not a direct serialization of Options) plus extension values registered via `config.Register` in `internal/config/extensions.go`.
- Updates: Back-end calls `UpdateClientConfig()` to publish "config.updated" over websockets after changes (see `internal/api/config_options.go` and `internal/api/config_settings.go`).
- ACL/mode aware: Values are filtered by user/session and may differ for public vs. authenticated users.
- Dont expose secrets: Treat it as client-visible; avoid sensitive data. To add fields, extend client values via `config.Register` rather than exposing Options directly.
- Refresh cadence: The web UI (nonmobile) also polls for updates every 10 minutes via `$config.update()` in `frontend/src/app.js`, complementing the websocket push.
Database & Migrations
- Driver: GORM v1 (`github.com/jinzhu/gorm`). No `WithContext`. Use `db.Raw(stmt).Scan(&nop)` for raw SQL.
- Entities and helpers: `internal/entity/*.go` and subpackages (`query`, `search`, `sortby`).
- Migrations engine: `internal/entity/migrate/*` run via `config.MigrateDb()`; CLI: `photoprism migrate` / `photoprism migrations`.
- DB init/migrate flow: `internal/config/config_db.go` chooses driver/DSN, sets `gorm:table_options`, then `entity.InitDb(migrate.Opt(...))`.
AuthN/Z & Sessions
- Session model and cache: `internal/entity/auth_session*` and `internal/auth/session/*` (cleanup worker).
- ACL: `internal/auth/acl/*` roles, grants, scopes; use constants; avoid logging secrets, compare tokens constanttime; for scope checks use `acl.ScopePermits` / `ScopeAttrPermits` instead of rolling your own parsing.
- OIDC: `internal/auth/oidc/*`.
Media Processing
- Thumbnails: `internal/thumb/*` and helpers in `internal/photoprism/mediafile_thumbs.go`.
- Metadata: `internal/meta/*`.
- FFmpeg integration: `internal/ffmpeg/*`.
Background Workers
- Scheduler and workers: `internal/workers/*.go` (index, vision, meta, sync, backup, share); started from `internal/commands/start.go`.
- Auto indexer: `internal/workers/auto/*`.
Cluster / Portal
- Node types: `internal/service/cluster/const.go` (`cluster.RoleInstance`, `cluster.RolePortal`, `cluster.RoleService`).
- Node bootstrap & registration: `internal/service/cluster/node/*` (HTTP to Portal; do not import Portal internals).
- Registry/provisioner: `internal/service/cluster/registry/*`, `internal/service/cluster/provisioner/*`.
- Theme endpoint (server): GET `/api/v1/cluster/theme`; client/CLI installs theme only if missing or no `app.js`.
- See specs cheat sheet: `specs/portal/README.md`.
Logging & Events
- Logger and event hub: `internal/event/*`; `event.Log` is the shared logger.
- HTTP headers/constants: `pkg/service/http/header/*` always prefer these in handlers and tests.
Server Startup Flow (happy path)
1) `photoprism start` (CLI) `internal/commands/start.go`
2) Config init, DB init/migrate, session cleanup worker
3) `internal/server/start.go` builds Gin engine, middleware, API group, templates
4) `internal/server/routes.go` registers UI, WebDAV, sharing, wellknown, and all `/api/v1/*` routes
5) Workers and autoindex start; health endpoints `/livez`, `/readyz` available
Common HowTos
- Add a CLI command
- Create `internal/commands/<name>.go` with a `*cli.Command`
- Add it to `PhotoPrism` in `internal/commands/commands.go`
- Tests: prefer `RunWithTestContext` from `internal/commands/commands_test.go` to avoid `os.Exit`
- Add a REST endpoint
- Create handler in `internal/api/<area>.go` with Swagger annotations
- Register it in `internal/server/routes.go`
- Use helpers: `api.ClientIP(c)`, `header.BearerToken(c)`, `Abort*` functions
- Validate pagination bounds (default `count=100`, max `1000`, `offset>=0`) for list endpoints
- Run `make fmt-go swag-fmt && make swag`; keep docs accurate
- Tests: `go test ./internal/api -run <Name>` and focused helpers (`NewApiTest()`, `PerformRequest*`)
- Add a config option
- Add field with tags to `internal/config/options.go`
- Register CLI flag/env in `internal/config/flags.go` via `EnvVars(...)`
- Expose a getter (e.g., in `config_server.go` or topic file)
- Append to `rows` in `*config.Report()` after the same option as in `options.go`
- If value must persist, write back to `options.yml` and reload into memory
- Tests: cover CLI/env/file precedence (see `internal/config/test.go` helpers)
- Touch the DB schema
- Use GORM auto-migration, or add a custom migration in `internal/entity/migrate/<dialect>/...` and run `go generate` or `make generate` (runs `go generate` for all packages)
- Bump/review version gates in `migrate.Version` usage via `config_db.go`
- Tests: run against SQLite by default; for MySQL cases, gate appropriately
Testing
- Full suite: `make test` (frontend + backend). Backend only: `make test-go`.
- Focused packages: `go test ./internal/<pkg> -run <Name>`.
- CLI tests: `PHOTOPRISM_CLI=noninteractive` or pass `--yes` to avoid prompts; use `RunWithTestContext` to prevent `os.Exit`.
- SQLite DSN in tests is persuite (not empty). Clean up files if you capture the DSN.
- Frontend unit tests via Vitest are separate; see `frontend/CODEMAP.md`.
Security & Hot Spots (Where to Look)
- Zip extraction (path traversal prevention): `pkg/fs/zip.go`
- Uses `safeJoin` to reject absolute/volume paths and `..` traversal; enforces per-file and total size limits.
- Tests: `pkg/fs/zip_extra_test.go` cover abs/volume/.. cases and limits.
- Force-aware Copy/Move and truncation-safe writes:
- App helpers: `internal/photoprism/mediafile.go` (`MediaFile.Copy/Move` with `force`).
- Utils: `pkg/fs/copy.go`, `pkg/fs/move.go` (use `O_TRUNC` to avoid trailing bytes).
- FFmpeg command builders and encoders:
- Core: `internal/ffmpeg/transcode_cmd.go`, `internal/ffmpeg/remux.go`.
- Encoders (string builders only): `internal/ffmpeg/{apple,intel,nvidia,vaapi,v4l}/avc.go`.
- Tests guard HW runs with `PHOTOPRISM_FFMPEG_ENCODER`; otherwise assert command strings and negative paths.
- libvips thumbnails:
- Pipeline: `internal/thumb/vips.go` (VipsInit, VipsRotate, export params).
- Sizes & names: `internal/thumb/sizes.go`, `internal/thumb/names.go`, `internal/thumb/filter.go`; face/marker crop helpers live in `internal/thumb/crop` (e.g., `ParseThumb`, `IsCroppedThumb`).
- Safe HTTP downloader:
- Shared utility: `pkg/service/http/safe` (`Download`, `Options`).
- Protections: scheme allowlist (http/https), preDNS + perredirect hostname/IP validation, final peer IP check, size and timeout enforcement, temp file `0600` + rename.
- Avatars: wrapper `internal/thumb/avatar.SafeDownload` applies stricter defaults (15s, 10MiB, `AllowPrivate=false`, imagefocused `Accept`).
- Tests: `go test ./pkg/service/http/safe -count=1` (includes redirect SSRF cases); avatars: `go test ./internal/thumb/avatar -count=1`.
Performance & Limits
- Prefer existing caches/workers/batching as per Makefile and code.
- When adding list endpoints, default `count=100` (max `1000`); set `Cache-Control: no-store` for secrets.
Conventions & Rules of Thumb
- Respect package boundaries: code in `pkg/*` must not import `internal/*`.
- Prefer constants/helpers from `pkg/service/http/header` over string literals.
- Never log secrets; compare tokens constanttime.
- Dont import Portal internals from cluster instance/service bootstraps; use HTTP.
- Prefer small, hermetic unit tests; isolate filesystem paths with `t.TempDir()` and env like `PHOTOPRISM_STORAGE_PATH`.
- Cluster nodes: identify by UUID v7 (internally stored as `NodeUUID`; exposed as `uuid` in API/CLI). The OAuth client ID (`NodeClientID`, exposed as `clientId`) is for OAuth only. Registry lookups and CLI commands accept uuid, clientId, or DNSlabel name (priority in that order).
Filesystem Permissions & io/fs Aliasing
- Use `github.com/photoprism/photoprism/pkg/fs` permission variables when creating files/dirs:
- `fs.ModeDir` (0o755 with umask), `fs.ModeFile` (0o644 with umask), `fs.ModeConfigFile` (0o664), `fs.ModeSecretFile` (0o600), `fs.ModeBackupFile` (0o600).
- Do not use stdlib `io/fs` mode bits as permission arguments. When importing stdlib `io/fs`, alias it (`iofs`/`gofs`) to avoid `fs.*` collisions with our package.
- Prefer `filepath.Join` for filesystem paths across platforms; use `path.Join` for URLs only.
Cluster Registry & Provisioner Cheatsheet
- UUIDfirst everywhere: API paths `{uuid}`, Registry `Get/Delete/RotateSecret` by UUID; explicit `FindByClientID` exists for OAuth.
- Node/DTO fields: `uuid` required; `clientId` optional; database metadata includes `driver`.
- Provisioner naming (no slugs):
- database: `photoprism_d<hmac11>`
- username: `photoprism_u<hmac11>`
HMAC is base32 of ClusterUUID+NodeUUID; drivers currently `mysql|mariadb`.
- DSN builder: `BuildDSN(driver, host, port, user, pass, name)`; warns and falls back to MySQL format for unsupported drivers.
- Go tests live beside sources: for `path/to/pkg/<file>.go`, add tests in `path/to/pkg/<file>_test.go` (create if missing). For the same function, group related cases as `t.Run(...)` sub-tests (table-driven where helpful) and name each subtest string in PascalCase.
- Public API and internal registry DTOs use normalized field names:
- `database` (not `db`) with `name`, `user`, `driver`, `rotatedAt`.
- Node-level rotation timestamps use `rotatedAt`.
- Registration returns `secrets.clientSecret`; the CLI persists it under config `NodeClientSecret`.
- Admin responses may include `advertiseUrl` and `database`; non-admin responses are redacted by default.
Frequently Touched Files (by topic)
- CLI wiring: `cmd/photoprism/photoprism.go`, `internal/commands/commands.go`
- Server: `internal/server/start.go`, `internal/server/routes.go`, middleware in `internal/server/*.go`
- API handlers: `internal/api/*.go` (plus `docs.go` for package docs)
- Config: `internal/config/*` (`flags.go`, `config_db.go`, `config_server.go`, `options.go`)
- Entities & queries: `internal/entity/*.go`, `internal/entity/query/*`
- Migrations: `internal/entity/migrate/*`
- Workers: `internal/workers/*`
- Cluster: `internal/service/cluster/*`
- Headers: `pkg/service/http/header/*`
Downloads (CLI) & yt-dlp helpers
- CLI command & core:
- `internal/commands/download.go` (flags, defaults, examples)
- `internal/commands/download_impl.go` (testable implementation used by CLI)
- yt-dlp wrappers:
- `internal/photoprism/dl/options.go` (arg wiring; `FFmpegPostArgs` hook for `--postprocessor-args`)
- `internal/photoprism/dl/info.go` (metadata discovery)
- `internal/photoprism/dl/file.go` (file method with `--output`/`--print`)
- `internal/photoprism/dl/meta.go` (`CreatedFromInfo` fallback; `RemuxOptionsFromInfo`)
- Importer:
- `internal/photoprism/get/import.go` (work pool)
- `internal/photoprism/import_options.go` (`ImportOptionsMove/Copy`)
- Testing hints:
- Fast loops: `go test ./internal/photoprism/dl -run 'Options|Created|PostprocessorArgs' -count=1`
- CLI only: `go test ./internal/commands -run 'DownloadImpl|HelpFlags' -count=1`
- Disable ffmpeg when not needed: set `FFmpegBin = "/bin/false"`, `Settings.Index.Convert=false` in tests.
- Stub yt-dlp: shell script that prints JSON for `--dump-single-json`, creates a file and prints path for `--print`.
- Avoid importer dedup: vary file bytes (e.g., `YTDLP_DUMMY_CONTENT`) or dest.
Useful Make Targets (selection)
- `make help` list targets
- `make dep` install Go/JS deps in container
- `make build-go` build backend
- `make test-go` backend tests (SQLite)
- `make swag` generate Swagger JSON in `internal/api/swagger.json`
- `make fmt-go swag-fmt` format Go code and Swagger annotations
See Also
- AGENTS.md (repository rules and tips for agents)
- Developer Guide (Setup/Tests/API) links in AGENTS.md Sources of Truth
- Specs: `specs/dev/backend-testing.md`, `specs/dev/api-docs-swagger.md`, `specs/portal/README.md`
Fast Test Recipes
- Filesystem + archives (fast): `go test ./pkg/fs -run 'Copy|Move|Unzip' -count=1`
- Media helpers (fast): `go test ./pkg/media/... -count=1`
- Thumbnails (libvips, moderate): `go test ./internal/thumb/... -count=1`
- FFmpeg command builders (moderate): `go test ./internal/ffmpeg -run 'Remux|Transcode|Extract' -count=1`

View File

@@ -1,12 +1,8 @@
# Ubuntu 25.04 (Plucky Puffin)
FROM photoprism/develop:250930-plucky
# Harden npm usage by default (applies to npm ci / install in dev container)
ENV NPM_CONFIG_IGNORE_SCRIPTS=true
# Ubuntu 24.10 (Oracular Oriole)
FROM photoprism/develop:250317-oracular
## Alternative Environments:
# FROM photoprism/develop:armv7 # ARMv7 (32bit)
# FROM photoprism/develop:plucky # Ubuntu 25.04 (Plucky Puffin)
# FROM photoprism/develop:oracular # Ubuntu 24.10 (Oracular Oriole)
# FROM photoprism/develop:noble # Ubuntu 24.04 LTS (Noble Numbat)
# FROM photoprism/develop:mantic # Ubuntu 23.10 (Mantic Minotaur)
@@ -23,5 +19,3 @@ WORKDIR "/go/src/github.com/photoprism/photoprism"
# Copy source to image.
COPY . .
COPY --chown=root:root /scripts/dist/ /scripts/
RUN sudo /scripts/install-yt-dlp.sh

252
Makefile
View File

@@ -4,23 +4,20 @@
# more about our team, products and services: https://www.photoprism.app/
export GO111MODULE=on
export NPM_CONFIG_IGNORE_SCRIPTS ?= true
-include .semver
-include .env
export
# Binary file names.
BINARY_NAME=photoprism
GOIMPORTS=goimports
# Build version string.
SEMVER_MAJOR ?= 1
export SEMVER_MAJOR
# Build version.
SEMVER_MAJOR ?= 0
SEMVER_MINOR ?= $(shell date -u +%y%m)
export SEMVER_MINOR
SEMVER_PATCH ?= $(shell date -u +%d)
SEMVER_VERSION ?= $(SEMVER_MAJOR).$(SEMVER_MINOR).$(SEMVER_PATCH)
export SEMVER_VERSION
# Build parameters.
BUILD_PATH ?= $(shell realpath "./build")
@@ -28,12 +25,9 @@ BUILD_DATE ?= $(shell date -u +%y%m%d)
REPORT_DATE ?= $(shell date -u +%Y-%m-%d)
BUILD_VERSION ?= $(shell git describe --always)
BUILD_TAG ?= $(BUILD_DATE)-$(BUILD_VERSION)
export BUILD_TAG
BUILD_OS ?= $(shell uname -s)
BUILD_ARCH ?= $(shell ./scripts/dist/arch.sh)
export BUILD_ARCH
BUILD_ARCH ?= $(shell scripts/dist/arch.sh)
JS_BUILD_PATH ?= $(shell realpath "./assets/static/build")
TF_VERSION ?= 2.18.0
# Install parameters.
INSTALL_PATH ?= $(BUILD_PATH)/photoprism-ce_$(BUILD_TAG)-$(shell echo $(BUILD_OS) | tr '[:upper:]' '[:lower:]')-$(BUILD_ARCH)
@@ -72,23 +66,18 @@ watch: watch-js
build-all: build-go build-js
pull: docker-pull
test: test-js test-go
test-go: run-test-go
test-pkg: run-test-pkg
test-ai: run-test-ai
test-api: run-test-api
test-video: run-test-video
test-entity: run-test-entity
test-commands: run-test-commands
test-photoprism: run-test-photoprism
test-short: run-test-short
test-go: reset-sqlite run-test-go
test-pkg: reset-sqlite run-test-pkg
test-api: reset-sqlite run-test-api
test-entity: reset-sqlite run-test-entity
test-commands: reset-sqlite run-test-commands
test-photoprism: reset-sqlite run-test-photoprism
test-short: reset-sqlite run-test-short
test-mariadb: reset-acceptance run-test-mariadb
acceptance-run-chromium: storage/acceptance acceptance-auth-sqlite-restart wait acceptance-auth acceptance-auth-sqlite-stop acceptance-sqlite-restart wait-2 acceptance acceptance-sqlite-stop
acceptance-run-chromium-short: storage/acceptance acceptance-auth-sqlite-restart wait acceptance-auth-short acceptance-auth-sqlite-stop acceptance-sqlite-restart wait-2 acceptance-short acceptance-sqlite-stop
acceptance-auth-run-chromium: storage/acceptance acceptance-auth-sqlite-restart wait acceptance-auth acceptance-auth-sqlite-stop
acceptance-public-run-chromium: storage/acceptance acceptance-sqlite-restart wait acceptance acceptance-sqlite-stop
help: list
list:
@awk '/^[[:alnum:]]+[^[:space:]]+:/ {printf "%s",substr($$1,1,length($$1)-1); if (match($$0,/#/)) {desc=substr($$0,RSTART+1); sub(/^[[:space:]]+/,"",desc); printf " - %s\n",desc} else printf "\n" }' "$(firstword $(MAKEFILE_LIST))"
wait:
sleep 20
wait-2:
@@ -105,15 +94,13 @@ devtools: install-go dep-npm
.SILENT: help;
logs:
$(DOCKER_COMPOSE) logs -f
down:
$(DOCKER_COMPOSE) --profile=all down --remove-orphans
help:
@echo "For build instructions, visit <https://docs.photoprism.app/developer-guide/>."
docs: swag
swag: swag-json
swag-json:
@echo "Generating ./internal/api/swagger.json..."
swag init --ot json --parseDependency --parseDepth 1 --dir internal/api -g api.go -o ./internal/api
@echo "Fixing unstable time.Duration enums in swagger.json..."
@GO111MODULE=on go run scripts/tools/swaggerfix/main.go internal/api/swagger.json || { echo "swaggerfix failed"; exit 1; }
swag-yaml:
@echo "Generating ./internal/api/swagger.yaml..."
swag init --ot yaml --parseDependency --parseDepth 1 --dir internal/api -g api.go -o ./internal/api
@@ -159,12 +146,10 @@ clean:
[ ! -d "frontend/node_modules" ] || rm -rf frontend/node_modules
[ ! -d "$(BUILD_PATH)" ] || rm -rf --preserve-root $(BUILD_PATH)
[ ! -d "$(JS_BUILD_PATH)" ] || rm -rf --preserve-root $(JS_BUILD_PATH)
clean-build:
[ ! -d "$(BUILD_PATH)" ] || rm -rf --preserve-root $(BUILD_PATH)
tar.gz:
$(info Creating tar.gz archives from the directories in "$(BUILD_PATH)"...)
find "$(BUILD_PATH)" -maxdepth 1 -mindepth 1 -type d -name "photoprism*" -exec tar --exclude='.[^/]*' -C {} -czf {}.tar.gz . \;
pkg: pkg-amd64 pkg-arm64
pkg: pkg-amd64 pkg-arm64 pkg-armv7
pkg-amd64:
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" photoprism/develop:jammy make all install tar.gz
pkg-arm64:
@@ -203,15 +188,15 @@ acceptance-sqlite-restart:
rm -rf storage/acceptance/originals/2011
rm -rf storage/acceptance/originals/2013
rm -rf storage/acceptance/originals/2017
./photoprism --auth-mode="public" -c "./storage/acceptance/config-sqlite" start -d
./photoprism --auth-mode="public" -c "./storage/acceptance/config-sqlite" --test start -d
acceptance-sqlite-stop:
./photoprism --auth-mode="public" -c "./storage/acceptance/config-sqlite" stop
./photoprism --auth-mode="public" -c "./storage/acceptance/config-sqlite" --test stop
acceptance-auth-sqlite-restart:
cp -f storage/acceptance/backup.db storage/acceptance/index.db
cp -f storage/acceptance/config-sqlite/settingsBackup.yml storage/acceptance/config-sqlite/settings.yml
./photoprism --auth-mode="password" -c "./storage/acceptance/config-sqlite" start -d
./photoprism --auth-mode="password" -c "./storage/acceptance/config-sqlite" --test start -d
acceptance-auth-sqlite-stop:
./photoprism --auth-mode="password" -c "./storage/acceptance/config-sqlite" stop
./photoprism --auth-mode="password" -c "./storage/acceptance/config-sqlite" --test stop
start:
./photoprism start -d
stop:
@@ -243,38 +228,16 @@ clean-local-config:
rm -f $(BUILD_PATH)/config/*
dep-list:
go list -u -m -json all | go-mod-outdated -direct
npm: dep-npm npm-version
npm-version:
@echo "📦 Installed npm $$(npm --version)."
dep-npm:
@echo "Installing NPM package manager..."
@if command -v sudo >/dev/null 2>&1; then \
sudo npm install -g --location=global --no-fund --no-audit "npm@latest"; \
else \
npm install -g --location=global --no-fund --no-audit "npm@latest"; \
fi
sudo npm install -g npm
dep-js:
# TODO: If in the future we want to test in a real browser environment, add this (Playwright)
# (cd frontend && npx playwright install chromium)
(cd frontend && npm ci --ignore-scripts --no-update-notifier --no-audit)
codex: dep-codex codex-version
codex-version:
@echo "🤖 Installed $$(codex --version)."
dep-codex:
@echo "Installing Codex CLI..."
@[ -n "$(CODEX_HOME)" ] && [ "$(CODEX_HOME)" != "/" ] && install -d -m 700 -- "$(CODEX_HOME)" || true
@if command -v sudo >/dev/null 2>&1; then \
sudo npm install -g --location=global --no-fund --no-audit "@openai/codex@latest"; \
else \
npm install -g --location=global --no-fund --no-audit "@openai/codex@latest"; \
fi
(cd frontend && npm ci --no-update-notifier --no-audit)
dep-go:
go build -v ./...
dep-upgrade:
go get -u -t ./...
frontend-update:
make -C frontend update
dep-upgrade-js: frontend-update
dep-upgrade-js:
(cd frontend && npm update --legacy-peer-deps)
dep-tensorflow:
scripts/download-facenet.sh
scripts/download-nasnet.sh
@@ -308,77 +271,59 @@ build-static:
scripts/build.sh static $(BINARY_NAME)
build-libheif: build-libheif-amd64 build-libheif-arm64 build-libheif-armv7
build-libheif-amd64:
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:plucky ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:oracular ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:noble ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:jammy ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:bookworm ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:oracular ./scripts/dist/build-libheif.sh v1.19.5
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:noble ./scripts/dist/build-libheif.sh v1.19.5
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:jammy ./scripts/dist/build-libheif.sh v1.19.5
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:bookworm ./scripts/dist/build-libheif.sh v1.19.5
build-libheif-arm64:
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:plucky ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:oracular ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:noble ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:jammy ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:bookworm ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:oracular ./scripts/dist/build-libheif.sh v1.19.5
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:noble ./scripts/dist/build-libheif.sh v1.19.5
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:jammy ./scripts/dist/build-libheif.sh v1.19.5
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:bookworm ./scripts/dist/build-libheif.sh v1.19.5
build-libheif-armv7:
docker run --rm -u $(UID) --platform=arm --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm -e SYSTEM_ARCH=arm photoprism/develop:armv7 ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=arm --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm -e SYSTEM_ARCH=arm photoprism/develop:jammy ./scripts/dist/build-libheif.sh v1.19.7
docker run --rm -u $(UID) --platform=arm --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm -e SYSTEM_ARCH=arm photoprism/develop:armv7 ./scripts/dist/build-libheif.sh v1.19.5
docker run --rm -u $(UID) --platform=arm --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm -e SYSTEM_ARCH=arm photoprism/develop:jammy ./scripts/dist/build-libheif.sh v1.19.5
build-libheif-latest: build-libheif-amd64-latest build-libheif-arm64-latest build-libheif-armv7-latest
build-libheif-amd64-latest:
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:plucky ./scripts/dist/build-libheif.sh
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:oracular ./scripts/dist/build-libheif.sh
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:noble ./scripts/dist/build-libheif.sh
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/develop:jammy ./scripts/dist/build-libheif.sh
build-libheif-arm64-latest:
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:plucky ./scripts/dist/build-libheif.sh
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:oracular ./scripts/dist/build-libheif.sh
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:noble ./scripts/dist/build-libheif.sh
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/develop:jammy ./scripts/dist/build-libheif.sh
build-libheif-armv7-latest:
docker run --rm -u $(UID) --platform=arm --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm -e SYSTEM_ARCH=arm photoprism/develop:armv7 ./scripts/dist/build-libheif.sh
docker run --rm -u $(UID) --platform=arm --pull=always -v ".:/go/src/github.com/photoprism/photoprism" -e BUILD_ARCH=arm -e SYSTEM_ARCH=arm photoprism/develop:jammy ./scripts/dist/build-libheif.sh
build-tensorflow: docker-tensorflow-amd64
docker-tensorflow: docker-tensorflow-amd64
docker-tensorflow-amd64:
docker build --pull --no-cache -t photoprism/tensorflow:latest -t photoprism/tensorflow:amd64 -t photoprism/tensorflow:$(TF_VERSION)-amd64 --build-arg TF_VERSION=$(TF_VERSION) docker/tensorflow
terminal-tensorflow: terminal-tensorflow-amd64
terminal-tensorflow-amd64:
mkdir -p ./build
docker run --rm --pull missing -ti --platform=amd64 -v "./build:/build" -e BUILD_ARCH=amd64 -e SYSTEM_ARCH=amd64 photoprism/tensorflow:amd64 bash
build-tensorflow-arm64: docker-tensorflow-arm64
docker-tensorflow-arm64:
docker build --pull --no-cache -t photoprism/tensorflow:arm64 -t photoprism/tensorflow:$(TF_VERSION)-arm64 --build-arg TF_VERSION=$(TF_VERSION) docker/tensorflow/arm64
terminal-tensorflow-arm64:
mkdir -p ./build
docker run --rm --pull missing -ti --platform=arm64 -v "./build:/build" -e BUILD_ARCH=arm64 -e SYSTEM_ARCH=arm64 photoprism/tensorflow:arm64 bash
build-setup: build-setup-nas-raspberry-pi
build-setup-nas-raspberry-pi:
./scripts/setup/nas/raspberry-pi/build.sh
build-tensorflow:
docker build -t photoprism/tensorflow:build docker/tensorflow
docker run -ti photoprism/tensorflow:build bash
build-tensorflow-arm64:
docker build -t photoprism/tensorflow:arm64 docker/tensorflow/arm64
docker run -ti photoprism/tensorflow:arm64 bash
watch-js:
(cd frontend && env BUILD_ENV=development NODE_ENV=production npm run watch)
test-js:
$(info Running JS unit tests...)
(cd frontend && npm run test)
(cd frontend && env TZ=UTC BUILD_ENV=development NODE_ENV=development BABEL_ENV=test npm run test)
acceptance:
$(info Running public-mode tests in Chrome...)
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Multi-Window)\:*" --test-meta mode=public --config-file ./testcaferc.json --experimental-multiple-windows "tests/acceptance" && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=public --config-file ./testcaferc.json "tests/acceptance")
acceptance-short:
$(info Running JS acceptance tests in Chrome...)
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Multi-Window)\:*" --test-meta mode=public --config-file ./testcaferc.json --experimental-multiple-windows "tests/acceptance" && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=public,type=short --config-file ./testcaferc.json "tests/acceptance")
acceptance-firefox:
$(info Running JS acceptance tests in Firefox...)
(cd frontend && npm run testcafe -- firefox:headless --test-grep "^(Common|Core)\:*" --test-meta mode=public --config-file ./testcaferc.json --disable-native-automation "tests/acceptance")
acceptance-auth:
$(info Running JS acceptance-auth tests in Chrome...)
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Multi-Window)\:*" --test-meta mode=auth --config-file ./testcaferc.json --experimental-multiple-windows "tests/acceptance" && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=auth --config-file ./testcaferc.json "tests/acceptance")
acceptance-auth-short:
$(info Running JS acceptance-auth tests in Chrome...)
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Multi-Window)\:*" --test-meta mode=auth --config-file ./testcaferc.json --experimental-multiple-windows "tests/acceptance" && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=auth,type=short --config-file ./testcaferc.json "tests/acceptance")
vitest-watch:
$(info Running Vitest unit tests in watch mode...)
(cd frontend && npm run test-watch)
vitest-coverage:
$(info Running Vitest unit tests with coverage...)
(cd frontend && npm run test-coverage)
vitest-component:
$(info Running Vitest component tests...)
(cd frontend && npm run test-component)
acceptance-auth-firefox:
$(info Running JS acceptance-auth tests in Firefox...)
(cd frontend && npm run testcafe -- firefox:headless --test-grep "^(Common|Core)\:*" --test-meta mode=auth --config-file ./testcaferc.json --disable-native-automation "tests/acceptance")
reset-mariadb:
$(info Resetting photoprism database...)
mysql < scripts/sql/reset-photoprism.sql
@@ -391,12 +336,12 @@ reset-mariadb-local:
reset-mariadb-acceptance:
$(info Resetting acceptance database...)
mysql < scripts/sql/reset-acceptance.sql
reset-mariadb-all: reset-mariadb-testdb reset-mariadb-local reset-mariadb-acceptance
reset-mariadb-all: reset-mariadb-testdb reset-mariadb-local reset-mariadb-acceptance reset-mariadb-photoprism
reset-testdb: reset-sqlite reset-mariadb-testdb
reset-acceptance: reset-mariadb-acceptance
reset-sqlite:
$(info Removing test database files...)
find ./internal -type f \( -iname '.*.db' -o -iname '.*.db-journal' -o -iname '.test.*' \) -delete
find ./internal -type f -name ".test.*" -delete
run-test-short:
$(info Running short Go tests in parallel mode...)
$(GOTEST) -parallel 2 -count 1 -cpu 2 -short -timeout 5m ./pkg/... ./internal/...
@@ -409,15 +354,9 @@ run-test-mariadb:
run-test-pkg:
$(info Running all Go tests in "/pkg"...)
$(GOTEST) -parallel 2 -count 1 -cpu 2 -tags="slow,develop" -timeout 20m ./pkg/...
run-test-ai:
$(info Running all AI tests...)
$(GOTEST) -parallel 2 -count 1 -cpu 2 -tags="slow,develop" -timeout 20m ./internal/ai/...
run-test-api:
$(info Running all API tests...)
$(GOTEST) -parallel 2 -count 1 -cpu 2 -tags="slow,develop" -timeout 20m ./internal/api/...
run-test-video:
$(info Running all video tests...)
$(GOTEST) -parallel 2 -count 1 -cpu 2 -tags="slow,develop" -timeout 20m ./internal/ffmpeg/... ./internal/photoprism/dl/... ./pkg/media/...
run-test-entity:
$(info Running all Entity tests...)
$(GOTEST) -parallel 2 -count 1 -cpu 2 -tags="slow,develop" -timeout 20m ./internal/entity/...
@@ -441,58 +380,31 @@ test-coverage:
go test -parallel 1 -count 1 -cpu 1 -failfast -tags="slow,develop" -timeout 30m -coverprofile coverage.txt -covermode atomic ./pkg/... ./internal/...
go tool cover -html=coverage.txt -o coverage.html
go tool cover -func coverage.txt | grep total:
git-pull:
@echo "Pulling changes from remote repositories..."; \
if [ -d .git ]; then \
echo "Updating photoprism"; \
git pull --ff-only || echo "Warning: git pull failed in root"; \
else \
echo "Skipping: current directory is not a Git repo"; \
fi; \
for d in */ ; do \
[ -d "$$d" ] || continue; \
[ -d "$$d/.git" ] || continue; \
echo "Updating photoprism/$$d"; \
git -C "$$d" pull --ff-only || echo "Warning: git pull failed in $$d"; \
done;
docker-pull:
$(DOCKER_COMPOSE) --profile=all pull --ignore-pull-failures
$(DOCKER_COMPOSE) -f compose.latest.yaml pull --ignore-pull-failures
build-docker: docker-build
docker-build:
$(DOCKER_COMPOSE) --profile=all pull --ignore-pull-failures
$(DOCKER_COMPOSE) down --remove-orphans
$(DOCKER_COMPOSE) build --pull
nvidia: nvidia-up
nvidia-build:
$(DOCKER_COMPOSE) --profile=qdrant -f compose.nvidia.yaml pull --ignore-pull-failures
$(DOCKER_COMPOSE) --profile=qdrant -f compose.nvidia.yaml build
nvidia-up:
$(DOCKER_COMPOSE) --profile=qdrant -f compose.nvidia.yaml pull --ignore-pull-failures
$(DOCKER_COMPOSE) --profile=qdrant -f compose.nvidia.yaml up --remove-orphans
nvidia-down:
$(DOCKER_COMPOSE) --profile=qdrant -f compose.nvidia.yaml down --remove-orphans
intel: intel-up
intel-build:
$(DOCKER_COMPOSE) -f compose.intel.yaml pull --ignore-pull-failures
$(DOCKER_COMPOSE) -f compose.intel.yaml build
intel-up:
$(DOCKER_COMPOSE) -f compose.intel.yaml pull --ignore-pull-failures
$(DOCKER_COMPOSE) -f compose.intel.yaml up --remove-orphans
intel-down:
$(DOCKER_COMPOSE) -f compose.intel.yaml down --remove-orphans
docker-nvidia: docker-nvidia-up
docker-nvidia-up:
docker compose -f compose.nvidia.yaml up
docker-local-up:
$(DOCKER_COMPOSE) -f compose.local.yaml up --force-recreate
docker-local-down:
$(DOCKER_COMPOSE) -f compose.local.yaml down -V
develop: docker-develop
docker-develop: docker-develop-latest
docker-develop-all: docker-develop-latest docker-develop-other
docker-develop-latest: docker-develop-ubuntu
docker-develop-debian: docker-develop-bookworm docker-develop-bookworm-slim
docker-develop-ubuntu: docker-develop-plucky docker-develop-plucky-slim
docker-develop-ubuntu: docker-develop-oracular docker-develop-oracular-slim
docker-develop-other: docker-develop-debian docker-develop-bullseye docker-develop-bullseye-slim docker-develop-buster
docker-develop-bookworm:
docker pull --platform=amd64 debian:bookworm-slim
docker pull --platform=arm64 debian:bookworm-slim
docker pull --platform=arm debian:bookworm-slim
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 bookworm /bookworm "-t photoprism/develop:debian"
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64,linux/arm bookworm /bookworm "-t photoprism/develop:debian"
docker-develop-bookworm-slim:
docker pull --platform=amd64 debian:bookworm-slim
docker pull --platform=arm64 debian:bookworm-slim
@@ -501,7 +413,7 @@ docker-develop-bullseye:
docker pull --platform=amd64 golang:1-bullseye
docker pull --platform=arm64 golang:1-bullseye
docker pull --platform=arm golang:1-bullseye
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 bullseye /bullseye
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64,linux/arm bullseye /bullseye
docker-develop-bullseye-slim:
docker pull --platform=amd64 debian:bullseye-slim
docker pull --platform=arm64 debian:bullseye-slim
@@ -521,7 +433,8 @@ docker-develop-impish:
docker-develop-jammy:
docker pull --platform=amd64 ubuntu:jammy
docker pull --platform=arm64 ubuntu:jammy
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 jammy /jammy
docker pull --platform=arm ubuntu:jammy
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64,linux/arm jammy /jammy
docker-develop-jammy-slim:
docker pull --platform=amd64 ubuntu:jammy
docker pull --platform=arm64 ubuntu:jammy
@@ -553,19 +466,11 @@ docker-develop-noble-slim:
docker-develop-oracular:
docker pull --platform=amd64 ubuntu:oracular
docker pull --platform=arm64 ubuntu:oracular
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 oracular /oracular
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 oracular /oracular "-t photoprism/develop:latest -t photoprism/develop:ubuntu"
docker-develop-oracular-slim:
docker pull --platform=amd64 ubuntu:oracular
docker pull --platform=arm64 ubuntu:oracular
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 oracular-slim /oracular-slim
docker-develop-plucky:
docker pull --platform=amd64 ubuntu:plucky
docker pull --platform=arm64 ubuntu:plucky
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 plucky /plucky "-t photoprism/develop:latest -t photoprism/develop:ubuntu"
docker-develop-plucky-slim:
docker pull --platform=amd64 ubuntu:plucky
docker pull --platform=arm64 ubuntu:plucky
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 plucky-slim /plucky-slim
unstable: docker-unstable
docker-unstable: docker-unstable-mantic
docker-unstable-jammy:
@@ -583,10 +488,10 @@ docker-unstable-mantic:
preview: docker-preview-ce
docker-preview: docker-preview-ce
docker-preview-all: docker-preview-latest docker-preview-other
docker-preview-ce: docker-preview-plucky
docker-preview-ce: docker-preview-oracular
docker-preview-latest: docker-preview-ubuntu
docker-preview-debian: docker-preview-bookworm
docker-preview-ubuntu: docker-preview-plucky
docker-preview-ubuntu: docker-preview-oracular
docker-preview-other: docker-preview-debian docker-preview-bullseye
docker-preview-arm: docker-preview-arm64 docker-preview-armv7
docker-preview-bookworm:
@@ -651,18 +556,12 @@ docker-preview-oracular:
docker pull --platform=arm64 photoprism/develop:oracular
docker pull --platform=arm64 photoprism/develop:oracular-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 preview-ce /oracular
docker-preview-plucky:
docker pull --platform=amd64 photoprism/develop:plucky
docker pull --platform=amd64 photoprism/develop:plucky-slim
docker pull --platform=arm64 photoprism/develop:plucky
docker pull --platform=arm64 photoprism/develop:plucky-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 preview-ce /plucky
release: docker-release
docker-release: docker-release-latest
docker-release-all: docker-release-latest docker-release-other
docker-release-latest: docker-release-ubuntu
docker-release-debian: docker-release-bookworm
docker-release-ubuntu: docker-release-plucky
docker-release-ubuntu: docker-release-oracular
docker-release-other: docker-release-debian docker-release-bullseye
docker-release-arm: docker-release-arm64 docker-release-armv7
docker-release-bookworm:
@@ -727,16 +626,6 @@ docker-release-oracular:
docker pull --platform=arm64 photoprism/develop:oracular
docker pull --platform=arm64 photoprism/develop:oracular-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 ce /oracular
docker-release-plucky:
docker pull --platform=amd64 photoprism/develop:plucky
docker pull --platform=amd64 photoprism/develop:plucky-slim
docker pull --platform=arm64 photoprism/develop:plucky
docker pull --platform=arm64 photoprism/develop:plucky-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 ce /plucky
start-traefik:
$(DOCKER_COMPOSE) up -d --wait traefik
stop-traefik:
$(DOCKER_COMPOSE) down traefik
start-local:
$(DOCKER_COMPOSE) -f compose.local.yaml up -d --wait
stop-local:
@@ -779,12 +668,8 @@ terminal-preview:
$(DOCKER_COMPOSE) -f compose.preview.yaml exec photoprism-preview bash
logs-preview:
$(DOCKER_COMPOSE) -f compose.preview.yaml logs -f photoprism-preview
docker-local: docker-local-plucky
docker-local-up:
$(DOCKER_COMPOSE) -f compose.local.yaml up --force-recreate
docker-local-down:
$(DOCKER_COMPOSE) -f compose.local.yaml down --remove-orphans
docker-local-all: docker-local-plucky docker-local-oracular docker-local-noble docker-local-mantic docker-local-lunar docker-local-jammy docker-local-bookworm docker-local-bullseye docker-local-buster
docker-local: docker-local-oracular
docker-local-all: docker-local-oracular docker-local-noble docker-local-mantic docker-local-lunar docker-local-jammy docker-local-bookworm docker-local-bullseye docker-local-buster
docker-local-bookworm:
docker pull photoprism/develop:bookworm
docker pull photoprism/develop:bookworm-slim
@@ -821,10 +706,6 @@ docker-local-oracular:
docker pull photoprism/develop:oracular
docker pull ubuntu:oracular
scripts/docker/build.sh photoprism ce-oracular /oracular "-t photoprism/photoprism:local"
docker-local-plucky:
docker pull photoprism/develop:plucky
docker pull ubuntu:plucky
scripts/docker/build.sh photoprism ce-plucky /plucky "-t photoprism/photoprism:local"
local-develop: docker-local-develop
docker-local-develop: docker-local-develop-oracular
docker-local-develop-all: docker-local-develop-oracular docker-local-develop-noble docker-local-develop-mantic docker-local-develop-lunar docker-local-develop-jammy docker-local-develop-bookworm docker-local-develop-bullseye docker-local-develop-buster docker-local-develop-impish
@@ -855,9 +736,6 @@ docker-local-develop-noble:
docker-local-develop-oracular:
docker pull ubuntu:oracular
scripts/docker/build.sh develop oracular /oracular
docker-local-develop-plucky:
docker pull ubuntu:plucky
scripts/docker/build.sh develop plucky /plucky
docker-ddns:
docker pull golang:alpine
scripts/docker/buildx-multi.sh ddns linux/amd64,linux/arm64 $(BUILD_DATE)

1403
NOTICE

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@ PhotoPrism® is an AI-Powered Photos App for the [Decentralized Web](https://en.
It makes use of the latest technologies to tag and find pictures automatically without getting in your way.
You can run it at home, on a private server, or in the cloud.
![](https://dl.photoprism.app/img/ui/2025/desktop-search.jpg)
![](https://dl.photoprism.app/img/ui/search-cards-view.jpg)
To get a first impression, you are welcome to play with our [public demo](https://try.photoprism.app/). Please be careful not to upload any private, unlawful or offensive pictures.
@@ -20,21 +20,23 @@ To get a first impression, you are welcome to play with our [public demo](https:
**Our mission is to provide the most user- and privacy-friendly solution to keep your pictures organized and accessible.** That's why PhotoPrism was built from the ground up to run wherever you need it, without compromising freedom, privacy, or functionality:
<img align="right" height="270" src="https://dl.photoprism.app/img/ui/2025/iphone-crocus-540px.png">
* Browse [all your pictures](https://docs.photoprism.app/user-guide/organize/browse/) without worrying about [RAW images](https://www.photoprism.app/kb/file-formats) or [video formats](https://docs.photoprism.app/user-guide/organize/video/)
* Whether you're using a phone, tablet, or desktop computer, our [intuitive PWA](https://try.photoprism.app/) provides a native app-like experience and can be [easily installed](https://docs.photoprism.app/user-guide/pwa/) on your home screen
* Quickly find specific photos and videos with [powerful search filters](https://docs.photoprism.app/user-guide/search/filters/) that can be combined and are available for [many different properties](https://docs.photoprism.app/user-guide/search/filters/#filter-reference), including [labels](https://try.photoprism.app/library/labels), [location](https://try.photoprism.app/library/places?q=s2:47a85a63f764), [resolution](https://try.photoprism.app/library/browse?view=cards&q=mp:4), [color](https://try.photoprism.app/library/browse?view=cards&q=color:red), [chroma](https://try.photoprism.app/library/browse?view=cards&q=mono%3Atrue), and [quality](https://try.photoprism.app/library/review)
* [Automatically labels your pictures](https://try.photoprism.app/library/labels) based on content and location, and recognizes the faces of [your family and friends](https://try.photoprism.app/library/people/new)
* [Live Photos](https://try.photoprism.app/library/live) start playing when you [hover over them](https://try.photoprism.app/library/browse?view=cards&q=type%3Alive) and when viewing a slideshow
* Six high-resolution [World Maps](https://try.photoprism.app/library/places) and our [privacy-preserving geocoding service](https://docs.photoprism.app/getting-started/#maps-places) help bring back memories of your favorite trips and let you explore the world
* Metadata can be extracted and merged from Exif, XMP, and other sources like Google Photos
* [Use compatible apps](https://docs.photoprism.app/user-guide/native-apps/) like [PhotoSync](https://link.photoprism.app/photosync) to back up iOS and Android phones in the background
* WebDAV clients such as [Microsoft's Windows Explorer](https://docs.photoprism.app/user-guide/sync/webdav/#__tabbed_1_2) and [Apple's Finder](https://docs.photoprism.app/user-guide/sync/webdav/#connect-to-a-webdav-server) can [connect directly to PhotoPrism](https://docs.photoprism.app/user-guide/sync/webdav/), allowing you to open, edit, and delete files from your computer as if they were local
* Browse [all your photos](https://docs.photoprism.app/user-guide/organize/browse/) and [videos](https://try.photoprism.app/library/videos) without worrying about [RAW conversion, duplicates or video formats](https://docs.photoprism.app/user-guide/settings/library/)
* Easily find specific pictures using [powerful search filters](https://try.photoprism.app/library/browse?view=cards&q=flower%20color%3Ared)
* Recognizes [the faces of your family and friends](https://try.photoprism.app/library/people)
* [Automatic classification](https://try.photoprism.app/library/labels) of pictures based on their content and location
* [Play Live Photos](https://try.photoprism.app/library/live) by hovering over them in [albums](https://try.photoprism.app/library/albums) and [search results](https://try.photoprism.app/library/browse?view=cards&q=type%3Alive)
* Since the [User Interface](https://try.photoprism.app/) is a [Progressive Web App](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps),
it provides a native app-like experience, and you can conveniently install it on the home screen of all major operating systems and mobile devices
* Includes four high-resolution [World Maps](https://try.photoprism.app/library/places) to bring back the memories of your favorite trips
* Metadata is extracted and merged from Exif, XMP, and other sources such as Google Photos
* Many more image properties like [Colors](https://try.photoprism.app/library/browse?view=cards&q=color:red), [Chroma](https://try.photoprism.app/library/browse?view=cards&q=mono%3Atrue), and [Quality](https://try.photoprism.app/library/review) can be searched as well
* Use [PhotoSync](https://link.photoprism.app/photosync) to securely backup iOS and Android phones in the background
* WebDAV clients such as Microsoft's Windows Explorer and Apple's Finder [can connect directly](https://docs.photoprism.app/user-guide/sync/webdav/) to PhotoPrism, allowing you to open, edit, and delete files from your computer as if they were local
Being completely [**self-funded and independent**](https://link.photoprism.app/membership), we can promise you that we will [never sell your data](https://www.photoprism.app/privacy) and that we will [always be transparent](https://www.photoprism.app/terms) about our software and services. Your data will never be shared with Google, Amazon, Microsoft or Apple unless you intentionally upload files to one of their services. 🔒
## Getting Started ##
<img align="right" width="25%" src="https://www.photoprism.app/user/pages/01.home/03._screenshots/iphone-maps-hybrid-540px.png">
Step-by-step [installation instructions](https://docs.photoprism.app/getting-started/) for our self-hosted [community edition](https://link.photoprism.app/personal-editions) can be found on [docs.photoprism.app](https://docs.photoprism.app/getting-started/) - all you need is a Web browser and [Docker](https://docs.docker.com/get-docker/) to run the server. It is available for Mac, Linux, and Windows.
@@ -77,15 +79,11 @@ Common problems can be quickly diagnosed and solved using our [Troubleshooting C
## Upcoming Features and Enhancements ##
<a href="https://github.com/orgs/photoprism/projects/5"><img align="right" height="240" src="https://dl.photoprism.app/img/ui/2025/upcoming-features-240px.png"></a>
Our [Project Roadmap](https://link.photoprism.app/roadmap) shows what tasks are in progress and what features will be implemented next. You are invited to give ideas you like a thumbs-up, so we know what's most popular.
Be aware that we have a zero-bug policy and do our best to help users when they need support or have other questions. This comes at a price though, as we can't give exact release dates for new features. Our team receives many more requests than can be implemented, so we want to emphasize that we are in no way obligated to implement the features, enhancements, or other changes you request. We do, however, appreciate your feedback and carefully consider all requests.
**Because sustained funding is key to quickly releasing new features, we encourage you to support our mission by [signing up for a personal membership](https://link.photoprism.app/membership) or [purchasing a commercial license](https://www.photoprism.app/teams#compare).**
[Become a Member ](https://link.photoprism.app/membership)
**Because sustained funding is key to quickly releasing new features, we encourage you to support our mission by [signing up as a sponsor](https://link.photoprism.app/sponsor) or purchasing a [commercial license](https://www.photoprism.app/teams). Ultimately, that's what's best for the product and the community.**
## GitHub Issues ⚠️ ##
@@ -98,8 +96,6 @@ We kindly ask you not to report bugs via GitHub Issues **unless you are certain
## Connect with the Community ##
<a href="https://link.photoprism.app/chat"><img align="right" width="144" height="144" src="https://dl.photoprism.app/img/brands/element-logo.svg"></a>
Follow us on [Mastodon](https://floss.social/@photoprism), [Bluesky](https://bsky.app/profile/photoprism.app), or join the [Community Chat](https://link.photoprism.app/chat) to get regular updates, connect with other users, and discuss your ideas. Our [Code of Conduct](https://www.photoprism.app/code-of-conduct) explains the "dos and donts" when interacting with other community members.
As a [contributor](CONTRIBUTING.md), you are also welcome to [contact us directly](https://www.photoprism.app/contact) if you have something on your mind that you don't want to discuss publicly. Please note, however, that due to the high volume of emails we receive, our team may be unable to get back to you immediately. We do our best to respond within five business days or less.

View File

@@ -30,8 +30,6 @@ You are [welcome to contact us](https://www.photoprism.app/contact) for change r
**[Andreas Krizek](https://github.com/Cosmic314)** (January 2025)
**[Jason Grim](https://github.com/jgrim)** (June 2025)
## Gold Sponsors ##
[**Simen Eriksen**](https://github.com/dennorske) (GitHub Sponsors, December 2019)

View File

@@ -1,5 +1,4 @@
examples
README.md
docs
.*
_*
.*

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

View File

@@ -2,9 +2,10 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-15 12:54+0000\n"
"PO-Revision-Date: 2025-07-21 11:32+0000\n"
"Last-Translator: NoufAM <Nouf.almashghouni@deg.shj.ae>\n"
"POT-Creation-Date: 2025-03-11 19:22+0000\n"
"PO-Revision-Date: 2025-03-11 19:36+0000\n"
"Last-Translator: Google Cloud Translation Basic <noreply-mt-google-"
"translate@weblate.org>\n"
"Language-Team: none\n"
"Language: ar\n"
"MIME-Version: 1.0\n"
@@ -12,11 +13,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Weblate 5.12.2\n"
"X-Generator: Weblate 5.9.2\n"
#: messages.go:103
msgid "Something went wrong, try again"
msgstr "حدث خطأ ما، حاول مرة أخرى"
msgstr "حدث خطأ ما حاول مرة أخرى"
#: messages.go:104
msgid "Unable to do that"
@@ -37,7 +38,7 @@ msgstr "%s موجود بالفعل"
#: messages.go:108
msgid "Not found"
msgstr "لم يتم العثور"
msgstr "لم يتم العثور على"
#: messages.go:109
msgid "File not found"
@@ -117,23 +118,23 @@ msgstr "طلب الاذن مرفوض"
#: messages.go:128
msgid "Upload might be offensive"
msgstr "المحتوى المرفوع قد يكون مسيئا"
msgstr "تحميل قد يكون مسيئا"
#: messages.go:129
msgid "Upload failed"
msgstr "فشل التحميل"
msgstr "التحميل فشل"
#: messages.go:130
msgid "No items selected"
msgstr "لم يتم اختيار المحتوى"
msgstr "لم يتم تحديد الاختيار"
#: messages.go:131
msgid "Failed creating file, please check permissions"
msgstr "فشل إنشاء الملف ، يرجى التحقق من الصلاحيات"
msgstr "فشل إنشاء الملف ، يرجى التحقق من الأذونات"
#: messages.go:132
msgid "Failed creating folder, please check permissions"
msgstr "لم يتم إنشاء المجلد ، يرجى التحقق من الصلاحيات"
msgstr "فشل إنشاء المجلد ، يرجى التحقق من الأذونات"
#: messages.go:133
msgid "Could not connect, please try again"
@@ -149,15 +150,15 @@ msgstr "رمز التحقق غير صالح، يرجى المحاولة مرة
#: messages.go:136
msgid "Invalid password, please try again"
msgstr "كلمة السر غير مطابقة، يرجى المحاولة مرة أخرى"
msgstr "كلمة السر غير مطابقة، برجاء حاول مرة أخرى"
#: messages.go:137
msgid "Feature disabled"
msgstr "الخاصية غير مفعلة"
msgstr "الخاصية معطلة"
#: messages.go:138
msgid "No labels selected"
msgstr "لم يتم تحديد المعرفات"
msgstr "لم يتم تحديد تسميات"
#: messages.go:139
msgid "No albums selected"
@@ -169,7 +170,7 @@ msgstr "لا توجد ملفات متاحة للتنزيل"
#: messages.go:141
msgid "Failed to create zip file"
msgstr "فشل في إنشاء ملف مضغوط"
msgstr "فشل إنشاء ملف مضغوط"
#: messages.go:142
msgid "Invalid credentials"
@@ -177,7 +178,7 @@ msgstr "بيانات الاعتماد غير صالحة"
#: messages.go:143
msgid "Invalid link"
msgstr "رابط غير صالح"
msgstr "ارتباط غير صالح"
#: messages.go:144
msgid "Invalid name"
@@ -185,7 +186,7 @@ msgstr "اسم غير صحيح"
#: messages.go:145
msgid "Busy, please try again later"
msgstr "مشغول، يرجى المحاولة مرة أخرى في وقت لاحق"
msgstr "مشغول ، يرجى المحاولة مرة أخرى في وقت لاحق"
#: messages.go:146
#, c-format
@@ -206,7 +207,7 @@ msgstr "مساحة تخزين غير كافية"
#: messages.go:150
msgid "Quota exceeded"
msgstr "تم تجاوز المساحة المخصصة"
msgstr "تم تجاوز الحصة"
#: messages.go:153
msgid "Changes successfully saved"
@@ -375,7 +376,7 @@ msgstr "تمت أرشفة الاختيار"
#: messages.go:191
msgid "Selection restored"
msgstr "تم استعادة المحتوى المحدد"
msgstr "تمت استعادة التحديد"
#: messages.go:192
msgid "Selection marked as private"
@@ -388,7 +389,7 @@ msgstr "تم حذف الألبومات"
#: messages.go:194
#, c-format
msgid "Zip created in %d s"
msgstr "تم إنشاء الملف المضغوط خلال %d ثوانٍ"
msgstr "إنشاء الملف المضغوط خلال %d ثوانٍ"
#: messages.go:195
msgid "Permanently deleted"
@@ -405,7 +406,7 @@ msgstr "تم التحقق بنجاح"
#: messages.go:198
msgid "Successfully activated"
msgstr "تم التفعيل بنجاح"
msgstr "تم التنشيط بنجاح"
#~ msgid "Storage is full"
#~ msgstr "التخزين ممتلئ"

View File

@@ -294,7 +294,7 @@ msgstr "Importación cancelada"
#: messages.go:172
#, c-format
msgid "Indexing completed in %d s"
msgstr "Indexación completada en %d"
msgstr "Indexación completada em %d"
#: messages.go:173
msgid "Indexing originals..."

View File

@@ -2,25 +2,25 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-15 12:54+0000\n"
"PO-Revision-Date: 2025-05-12 23:50+0000\n"
"Last-Translator: Admin <hello@photoprism.app>\n"
"POT-Creation-Date: 2025-03-11 19:22+0000\n"
"PO-Revision-Date: 2025-03-11 19:36+0000\n"
"Last-Translator: DeepL <noreply-mt-deepl@weblate.org>\n"
"Language-Team: Japanese <https://translate.photoprism.app/projects/"
"photoprism/backend/ja/>\n"
"Language: ja\n"
"Language: ja_JP\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.11.1\n"
"X-Generator: Weblate 5.9.2\n"
#: messages.go:103
msgid "Something went wrong, try again"
msgstr "何かが間違っています。もう一度やり直してください"
msgstr "問題が発生しました。もう一度やり直してください"
#: messages.go:104
msgid "Unable to do that"
msgstr "その操作はできません"
msgstr "実行できませんでした"
#: messages.go:105
msgid "Changes could not be saved"
@@ -45,7 +45,7 @@ msgstr "ファイルが見つかりませんでした"
#: messages.go:110
msgid "File too large"
msgstr "ファイルが大きすぎます"
msgstr "ファイルサイズが大きすぎます"
#: messages.go:111
msgid "Unsupported"
@@ -53,7 +53,7 @@ msgstr "サポートされていません"
#: messages.go:112
msgid "Unsupported type"
msgstr "サポートされていない形式です"
msgstr "サポートされていないタイプ"
#: messages.go:113
msgid "Unsupported format"
@@ -61,7 +61,7 @@ msgstr "非対応のフォーマットです"
#: messages.go:114
msgid "Originals folder is empty"
msgstr "Originals フォルダー空です"
msgstr "Originalsフォルダー空です"
#: messages.go:115
msgid "Selection not found"
@@ -73,19 +73,19 @@ msgstr "エンティティが見つかりません"
#: messages.go:117
msgid "Account not found"
msgstr "アカウント存在しません"
msgstr "アカウント存在しません"
#: messages.go:118
msgid "User not found"
msgstr "ユーザーが存在しません"
msgstr "ユーザ存在しません"
#: messages.go:119
msgid "Label not found"
msgstr "ラベル存在しません"
msgstr "ラベル存在しません"
#: messages.go:120
msgid "Album not found"
msgstr "アルバム存在しません"
msgstr "アルバム存在しません"
#: messages.go:121
msgid "Subject not found"
@@ -113,7 +113,7 @@ msgstr "アカウントにログインしてください"
#: messages.go:127
msgid "Permission denied"
msgstr "アクセス拒否されました"
msgstr "アクセス拒否"
#: messages.go:128
msgid "Upload might be offensive"
@@ -121,7 +121,7 @@ msgstr "アップロードされた項目に過激なものが含まれている
#: messages.go:129
msgid "Upload failed"
msgstr "アップロード失敗しました"
msgstr "アップロード失敗"
#: messages.go:130
msgid "No items selected"
@@ -129,15 +129,15 @@ msgstr "項目が選択されていません"
#: messages.go:131
msgid "Failed creating file, please check permissions"
msgstr "ファイルの作成に失敗しました権限を確認してください"
msgstr "ファイルの作成に失敗しました権限を確認してください"
#: messages.go:132
msgid "Failed creating folder, please check permissions"
msgstr "フォルダの作成に失敗しました権限を確認してください"
msgstr "フォルダの作成に失敗しました権限を確認してください"
#: messages.go:133
msgid "Could not connect, please try again"
msgstr "接続できませんでした。もう一度お試しください"
msgstr "接続できませんでした、再度試してみてください"
#: messages.go:134
msgid "Enter verification code"
@@ -149,7 +149,7 @@ msgstr "認証コードが無効です。もう一度お試しください"
#: messages.go:136
msgid "Invalid password, please try again"
msgstr "不正なパスワードです。もう一度お試しください"
msgstr "不正なパスワードです、再度試してみてください"
#: messages.go:137
msgid "Feature disabled"
@@ -173,28 +173,28 @@ msgstr "zip ファイルの作成に失敗しました"
#: messages.go:142
msgid "Invalid credentials"
msgstr "正しくない認証情報です"
msgstr "不正な認証情報"
#: messages.go:143
msgid "Invalid link"
msgstr "正しくないリンクです"
msgstr "不正なリンク"
#: messages.go:144
msgid "Invalid name"
msgstr "無効な名前です"
msgstr "無効なバケット名"
#: messages.go:145
msgid "Busy, please try again later"
msgstr "他の処理中です。後で再試行してください"
msgstr "混雑しています、後で再試行してください"
#: messages.go:146
#, c-format
msgid "The wakeup interval is %s, but must be 1h or less"
msgstr "ウェイクアップ間隔は %s ですが、1時間以内で指定する必要があります"
msgstr "ウェイクアップ間隔は%sであるが、1h以下でなければならない"
#: messages.go:147
msgid "Your account could not be connected"
msgstr "アカウントに接続できませんでした"
msgstr "お客様のアカウントに接続できませんでした"
#: messages.go:148
msgid "Too many requests"
@@ -202,7 +202,7 @@ msgstr "リクエストが多すぎます"
#: messages.go:149
msgid "Insufficient storage"
msgstr "ストレージ不足しています"
msgstr "ストレージ不足"
#: messages.go:150
msgid "Quota exceeded"
@@ -214,16 +214,16 @@ msgstr "変更が正常に保存されました"
#: messages.go:154
msgid "Album created"
msgstr "アルバム作成されました"
msgstr "アルバム作成ました"
#: messages.go:155
msgid "Album saved"
msgstr "アルバム保存されました"
msgstr "アルバム保存ました"
#: messages.go:156
#, c-format
msgid "Album %s deleted"
msgstr "アルバム %s 削除されました"
msgstr "アルバム %s 削除ました"
#: messages.go:157
msgid "Album contents cloned"
@@ -298,7 +298,7 @@ msgstr "インデックスが %d 秒で完了しました"
#: messages.go:173
msgid "Indexing originals..."
msgstr "originals をインデックスしています..."
msgstr "オリジナルの項目をインデックスしています..."
#: messages.go:174
#, c-format
@@ -338,7 +338,7 @@ msgstr "保存対象"
#: messages.go:182
msgid "Subject deleted"
msgstr "件名削除しました"
msgstr "件名 削除"
#: messages.go:183
msgid "Person saved"
@@ -346,11 +346,11 @@ msgstr "保存された人"
#: messages.go:184
msgid "Person deleted"
msgstr "人を削除しました"
msgstr "削除された人"
#: messages.go:185
msgid "File uploaded"
msgstr "ファイルがアップロードされました"
msgstr "アップロードされたファイル"
#: messages.go:186
#, c-format
@@ -363,7 +363,7 @@ msgstr "アップロードの処理..."
#: messages.go:188
msgid "Upload has been processed"
msgstr "アップロードが完了しました"
msgstr "アップロードが処理されました"
#: messages.go:189
msgid "Selection approved"
@@ -379,7 +379,7 @@ msgstr "選択した項目が復元されました"
#: messages.go:192
msgid "Selection marked as private"
msgstr "選択した項目プライベートに設定されました"
msgstr "選択した項目プライベートにました"
#: messages.go:193
msgid "Albums deleted"
@@ -392,7 +392,7 @@ msgstr "%d 秒で zip ファイルを作成しました"
#: messages.go:195
msgid "Permanently deleted"
msgstr "完全に削除されました"
msgstr "永久に削除"
#: messages.go:196
#, c-format

View File

@@ -1,2 +0,0 @@
*
!.gitignore

View File

@@ -28,8 +28,8 @@
<div class="splash-logo">
{{template "logo.gohtml" .}}
</div>
<progress id="progress" class="html-progress" role="progressbar" max="100"></progress>
<progress id="progress" class="html-progress" max="100"></progress>
</div>
</div>
</div>
<div id="busy-overlay"><div class="splash-center"><progress id="busy-progress" class="html-progress" role="progressbar" max="100"></progress></div></div>
<div id="busy-overlay"><div class="splash-center"><progress id="busy-progress" class="html-progress" max="100"></progress></div></div>

View File

@@ -55,8 +55,6 @@ services:
PHOTOPRISM_DISABLE_TENSORFLOW: "false" # Don't use TensorFlow for image classification
PHOTOPRISM_DETECT_NSFW: "false" # Flag photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW: "false" # Allows uploads that may be offensive
PHOTOPRISM_UPLOAD_ALLOW: "" # restricts uploads to these file types (comma-separated list of EXTENSIONS; leave blank to allow all)
PHOTOPRISM_UPLOAD_ARCHIVES: "true" # allows upload of zip archives (will be extracted before import)
PHOTOPRISM_DARKTABLE_PRESETS: "false" # Enables Darktable presets and disables concurrent RAW conversion
PHOTOPRISM_THUMB_FILTER: "lanczos" # Resample filter, best to worst: blackman, lanczos, cubic, linear
PHOTOPRISM_THUMB_UNCACHED: "true" # Enables on-demand thumbnail rendering (high memory and cpu usage)
@@ -64,12 +62,12 @@ services:
# PHOTOPRISM_THUMB_SIZE: 4096 # Retina 4K, DCI 4K (requires more storage); 7680 for 8K Ultra HD
PHOTOPRISM_THUMB_SIZE_UNCACHED: 7680 # On-demand rendering size limit (default 7680, min 720, max 7680)
PHOTOPRISM_JPEG_SIZE: 7680 # Size limit for converted image files in pixels (720-30000)
TF_CPP_MIN_LOG_LEVEL: 1 # Show TensorFlow log messages for development
TF_CPP_MIN_LOG_LEVEL: 0 # Show TensorFlow log messages for development
## Enable TensorFlow AVX2 support for modern Intel CPUs (requires starting the container as root):
# PHOTOPRISM_INIT: "tensorflow-amd64-avx2"
## Hardware video transcoding config (optional):
# PHOTOPRISM_FFMPEG_BUFFERS: "64" # FFmpeg capture buffers (default: 32)
# PHOTOPRISM_FFMPEG_BITRATE: "32" # FFmpeg encoding bitrate limit in Mbps (default: 60)
# PHOTOPRISM_FFMPEG_BITRATE: "32" # FFmpeg encoding bitrate limit in Mbit/s (default: 50)
# PHOTOPRISM_FFMPEG_ENCODER: "h264_v4l2m2m" # Use Video4Linux for AVC transcoding (default: libx264)
# PHOTOPRISM_FFMPEG_ENCODER: "h264_qsv" # Use Intel Quick Sync Video for AVC transcoding (default: libx264)
# PHOTOPRISM_INIT: "intel-graphics tensorflow-amd64-avx2" # Enable TensorFlow AVX2 & Intel Graphics support
@@ -122,8 +120,8 @@ volumes:
go-mod:
driver: local
## Create shared "photoprism" network for connecting with services in other compose.yaml files
## Create shared "photoprism-develop" network for connecting with services in other compose.yaml files
networks:
default:
name: photoprism
name: shared
driver: bridge

View File

@@ -1,190 +0,0 @@
services:
## PhotoPrism (Development Environment for Intel QSV hardware transcoding)
photoprism:
build: .
image: photoprism/photoprism:develop
depends_on:
- mariadb
- dummy-webdav
- dummy-oidc
stop_grace_period: 15s
privileged: true
security_opt:
- seccomp:unconfined
- apparmor:unconfined
## Expose HTTP and debug ports
ports:
- "2342:2342" # Default HTTP port (host:container)
- "2443:2443" # Default TLS port (host:container)
- "2343:2343" # Acceptance Test HTTP port (host:container)
- "40000:40000" # Go Debugger (host:container)
shm_size: "2gb"
## Set links and labels for use with Traefik reverse proxy
links:
- "traefik:localssl.dev"
- "traefik:app.localssl.dev"
- "traefik:keycloak.localssl.dev"
- "traefik:dummy-oidc.localssl.dev"
- "traefik:dummy-webdav.localssl.dev"
labels:
- "traefik.enable=true"
- "traefik.docker.network=photoprism"
- "traefik.http.services.photoprism.loadbalancer.server.port=2342"
- "traefik.http.services.photoprism.loadbalancer.server.scheme=http"
- "traefik.http.routers.photoprism.entrypoints=websecure"
- "traefik.http.routers.photoprism.rule=Host(`localssl.dev`) || HostRegexp(`^.+\\.localssl\\.dev`)"
- "traefik.http.routers.photoprism.priority=2"
- "traefik.http.routers.photoprism.tls.domains[0].main=localssl.dev"
- "traefik.http.routers.photoprism.tls.domains[0].sans=*.localssl.dev"
- "traefik.http.routers.photoprism.tls=true"
## Configure development environment
environment:
## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200):
PHOTOPRISM_UID: ${UID:-1000} # user id, should match your host user id
PHOTOPRISM_GID: ${GID:-1000} # group id
## Access Management:
PHOTOPRISM_ADMIN_USER: "admin" # admin login username
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial admin password (8-72 characters)
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
PHOTOPRISM_REGISTER_URI: "https://keycloak.localssl.dev/admin/"
PHOTOPRISM_PASSWORD_RESET_URI: "https://keycloak.localssl.dev/realms/master/login-actions/reset-credentials"
PHOTOPRISM_USAGE_INFO: "true"
PHOTOPRISM_FILES_QUOTA: "100"
## Customization:
PHOTOPRISM_DEFAULT_LOCALE: "en" # default user interface language, e.g. "en" or "de"
PHOTOPRISM_PLACES_LOCALE: "local" # location details language, e.g. "local", "en", or
## OpenID Connect (pre-configured for local tests):
## see https://keycloak.localssl.dev/realms/master/.well-known/openid-configuration
PHOTOPRISM_OIDC_URI: "https://keycloak.localssl.dev/realms/master"
PHOTOPRISM_OIDC_CLIENT: "photoprism-develop"
PHOTOPRISM_OIDC_SECRET: "9d8351a0-ca01-4556-9c37-85eb634869b9"
PHOTOPRISM_OIDC_PROVIDER: "Keycloak"
PHOTOPRISM_OIDC_REGISTER: "true"
PHOTOPRISM_OIDC_WEBDAV: "true"
PHOTOPRISM_DISABLE_OIDC: "false"
## LDAP Authentication (pre-configured for local tests):
PHOTOPRISM_LDAP_URI: "ldap://dummy-ldap:389"
PHOTOPRISM_LDAP_INSECURE: "true"
PHOTOPRISM_LDAP_SYNC: "true"
PHOTOPRISM_LDAP_BIND: "simple"
PHOTOPRISM_LDAP_BIND_DN: "cn"
PHOTOPRISM_LDAP_BASE_DN: "dc=localssl,dc=dev"
PHOTOPRISM_LDAP_ROLE: ""
PHOTOPRISM_LDAP_ROLE_DN: "ou=photoprism-*,ou=groups,dc=localssl,dc=dev"
PHOTOPRISM_LDAP_WEBDAV_DN: "ou=photoprism-webdav,ou=groups,dc=localssl,dc=dev"
## HTTPS/TLS Options:
## see https://docs.photoprism.app/getting-started/using-https/
PHOTOPRISM_DISABLE_TLS: "true"
PHOTOPRISM_DEFAULT_TLS: "true"
## Site Information:
PHOTOPRISM_SITE_URL: "https://app.localssl.dev/" # server URL in the format "http(s)://domain.name(:port)/(path)"
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
PHOTOPRISM_SITE_DESCRIPTION: "Tags and finds pictures without getting in your way!"
PHOTOPRISM_SITE_AUTHOR: "@photoprism_app"
PHOTOPRISM_DEBUG: "true"
PHOTOPRISM_READONLY: "false"
PHOTOPRISM_EXPERIMENTAL: "true"
PHOTOPRISM_HTTP_MODE: "debug"
PHOTOPRISM_HTTP_HOST: "0.0.0.0"
PHOTOPRISM_HTTP_PORT: 2342
PHOTOPRISM_HTTP_COMPRESSION: "gzip" # improves transfer speed and bandwidth utilization (none or gzip)
PHOTOPRISM_DATABASE_DRIVER: "mysql"
PHOTOPRISM_DATABASE_SERVER: "mariadb:4001"
PHOTOPRISM_DATABASE_NAME: "photoprism"
PHOTOPRISM_DATABASE_USER: "root"
PHOTOPRISM_DATABASE_PASSWORD: "photoprism"
PHOTOPRISM_TEST_DRIVER: "sqlite"
# PHOTOPRISM_TEST_DSN_MYSQL8: "root:photoprism@tcp(mysql:4001)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s"
PHOTOPRISM_ASSETS_PATH: "/go/src/github.com/photoprism/photoprism/assets"
PHOTOPRISM_STORAGE_PATH: "/go/src/github.com/photoprism/photoprism/storage"
PHOTOPRISM_ORIGINALS_PATH: "/go/src/github.com/photoprism/photoprism/storage/originals"
PHOTOPRISM_ORIGINALS_LIMIT: 128000 # sets originals file size limit to 128 GB
PHOTOPRISM_IMPORT_PATH: "/go/src/github.com/photoprism/photoprism/storage/import"
PHOTOPRISM_DISABLE_CHOWN: "false" # disables updating storage permissions via chmod and chown on startup
PHOTOPRISM_DISABLE_BACKUPS: "false" # disables backing up albums and photo metadata to YAML files
PHOTOPRISM_DISABLE_WEBDAV: "false" # disables built-in WebDAV server
PHOTOPRISM_DISABLE_SETTINGS: "false" # disables settings UI and API
PHOTOPRISM_DISABLE_PLACES: "false" # disables reverse geocoding and maps
PHOTOPRISM_DISABLE_EXIFTOOL: "false" # disables creating JSON metadata sidecar files with ExifTool
PHOTOPRISM_DISABLE_TENSORFLOW: "false" # disables all features depending on TensorFlow
PHOTOPRISM_DISABLE_RAW: "false" # disables indexing and conversion of RAW images
PHOTOPRISM_RAW_PRESETS: "false" # enables applying user presets when converting RAW images (reduces performance)
PHOTOPRISM_DETECT_NSFW: "false" # automatically flags photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW: "false" # allows uploads that MAY be offensive (no effect without TensorFlow)
PHOTOPRISM_UPLOAD_ALLOW: "" # restricts uploads to these file types (comma-separated list of EXTENSIONS; leave blank to allow all)
PHOTOPRISM_UPLOAD_ARCHIVES: "true" # allows upload of zip archives (will be extracted before import)
PHOTOPRISM_THUMB_LIBRARY: "auto" # image processing library to be used for generating thumbnails (auto, imaging, vips)
PHOTOPRISM_THUMB_FILTER: "auto" # downscaling filter (imaging best to worst: blackman, lanczos, cubic, linear, nearest)
PHOTOPRISM_THUMB_UNCACHED: "true" # enables on-demand thumbnail rendering (high memory and cpu usage)
## Intel Quick Sync Video (QSV) (https://docs.photoprism.app/getting-started/advanced/transcoding/#intel-quick-sync):
PHOTOPRISM_FFMPEG_ENCODER: "intel" # H.264/AVC encoder (software, intel, nvidia, apple, raspberry, or vaapi)
PHOTOPRISM_FFMPEG_SIZE: "1920" # video size limit in pixels (720-7680) (default: 3840)
# PHOTOPRISM_FFMPEG_BITRATE: "64" # video bitrate limit in Mbps (default: 60)
## Run/install on first startup (options: update tensorflow https intel gpu davfs yt-dlp):
PHOTOPRISM_INIT: "https intel tensorflow"
## External dependencies and tools:
TF_CPP_MIN_LOG_LEVEL: 1
GOCACHE: "/go/src/github.com/photoprism/photoprism/.local/gocache"
CODEX_HOME: "/go/src/github.com/photoprism/photoprism/.local/codex"
## Share hardware devices with FFmpeg for hardware video transcoding:
devices:
- "/dev/dri:/dev/dri"
working_dir: "/go/src/github.com/photoprism/photoprism"
volumes:
- ".:/go/src/github.com/photoprism/photoprism"
- "./storage:/photoprism"
- "go-mod:/go/pkg/mod"
mariadb:
extends:
file: ./compose.yaml
service: mariadb
qdrant:
extends:
file: ./compose.yaml
service: qdrant
photoprism-vision:
extends:
file: ./compose.yaml
service: photoprism-vision
ollama:
extends:
file: ./compose.yaml
service: ollama
traefik:
extends:
file: ./compose.yaml
service: traefik
dummy-webdav:
extends:
file: ./compose.yaml
service: dummy-webdav
dummy-oidc:
extends:
file: ./compose.yaml
service: dummy-oidc
dummy-ldap:
extends:
file: ./compose.yaml
service: dummy-ldap
keycloak:
extends:
file: ./compose.yaml
service: keycloak
prometheus:
extends:
file: ./compose.yaml
service: prometheus
## Create named volume for Go module cache
volumes:
go-mod:
driver: local
mariadb:
driver: local
## Create shared "photoprism" network for connecting with services in other compose.yaml files
networks:
default:
name: photoprism
driver: bridge

View File

@@ -12,7 +12,6 @@ services:
- "2344:2342" # HTTP port (host:container)
labels:
- "traefik.enable=true"
- "traefik.docker.network=photoprism"
- "traefik.http.services.latest.loadbalancer.server.port=2342"
- "traefik.http.routers.latest.entrypoints=websecure"
- "traefik.http.routers.latest.rule=Host(`latest.localssl.dev`)"
@@ -51,8 +50,6 @@ services:
PHOTOPRISM_DISABLE_TENSORFLOW: "false" # disables all features depending on TensorFlow
PHOTOPRISM_DETECT_NSFW: "false" # automatically flags photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW: "false" # allows uploads that MAY be offensive (no effect without TensorFlow)
PHOTOPRISM_UPLOAD_ALLOW: "" # restricts uploads to these file types (comma-separated list of EXTENSIONS; leave blank to allow all)
PHOTOPRISM_UPLOAD_ARCHIVES: "true" # allows upload of zip archives (will be extracted before import)
PHOTOPRISM_RAW_PRESETS: "false" # enables applying user presets when converting RAW images (reduces performance)
PHOTOPRISM_THUMB_FILTER: "lanczos" # resample filter, best to worst: blackman, lanczos, cubic, linear
PHOTOPRISM_THUMB_UNCACHED: "true" # enables on-demand thumbnail rendering (high memory and cpu usage)
@@ -60,13 +57,13 @@ services:
# PHOTOPRISM_THUMB_SIZE: 4096 # Retina 4K, DCI 4K (requires more storage); 7680 for 8K Ultra HD
PHOTOPRISM_THUMB_SIZE_UNCACHED: 7680 # on-demand rendering size limit (default 7680, min 720, max 7680)
PHOTOPRISM_JPEG_SIZE: 7680 # size limit for converted image files in pixels (720-30000)
TF_CPP_MIN_LOG_LEVEL: 1 # show TensorFlow log messages for development
TF_CPP_MIN_LOG_LEVEL: 0 # show TensorFlow log messages for development
working_dir: "/photoprism"
volumes:
- "./storage:/photoprism/storage"
- "./storage/originals:/photoprism/originals"
## Join shared "photoprism" network
## Join shared "photoprism-develop" network
networks:
default:
name: photoprism

View File

@@ -6,7 +6,7 @@ services:
## Docs: https://docs.photoprism.org/
photoprism-local:
image: photoprism/photoprism:local
stop_grace_period: 15s
stop_grace_period: 10s
security_opt:
- seccomp:unconfined
- apparmor:unconfined
@@ -14,7 +14,6 @@ services:
- "2345:2342" # HTTP port (host:container)
labels:
- "traefik.enable=true"
- "traefik.docker.network=photoprism"
- "traefik.http.services.latest.loadbalancer.server.port=2342"
- "traefik.http.routers.latest.entrypoints=websecure"
- "traefik.http.routers.latest.rule=Host(`local.localssl.dev`)"
@@ -52,8 +51,6 @@ services:
PHOTOPRISM_DISABLE_TENSORFLOW: "false" # disables all features depending on TensorFlow
PHOTOPRISM_DETECT_NSFW: "false" # automatically flags photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW: "false" # allows uploads that MAY be offensive (no effect without TensorFlow)
PHOTOPRISM_UPLOAD_ALLOW: "" # restricts uploads to these file types (comma-separated list of EXTENSIONS; leave blank to allow all)
PHOTOPRISM_UPLOAD_ARCHIVES: "true" # allows upload of zip archives (will be extracted before import)
PHOTOPRISM_RAW_PRESETS: "false" # enables applying user presets when converting RAW images (reduces performance)
PHOTOPRISM_THUMB_FILTER: "lanczos" # resample filter, best to worst: blackman, lanczos, cubic, linear
PHOTOPRISM_THUMB_UNCACHED: "true" # enables on-demand thumbnail rendering (high memory and cpu usage)
@@ -61,8 +58,8 @@ services:
# PHOTOPRISM_THUMB_SIZE: 4096 # Retina 4K, DCI 4K (requires more storage); 7680 for 8K Ultra HD
PHOTOPRISM_THUMB_SIZE_UNCACHED: 7680 # on-demand rendering size limit (default 7680, min 720, max 7680)
PHOTOPRISM_JPEG_SIZE: 7680 # size limit for converted image files in pixels (720-30000)
TF_CPP_MIN_LOG_LEVEL: 1 # show TensorFlow log messages for development
# PHOTOPRISM_INIT: "http gpu tensorflow yt-dlp" # Options: "update https gpu tensorflow davfs clitools clean"
TF_CPP_MIN_LOG_LEVEL: 0 # show TensorFlow log messages for development
# PHOTOPRISM_INIT: "http gpu tensorflow" # Options: "update https gpu tensorflow davfs clitools clean"
PHOTOPRISM_FFMPEG_ENCODER: "nvidia" # Options: "software", "intel", "nvidia", "apple", "raspberry"
PHOTOPRISM_STORAGE_PATH: "/photoprism/storage"
PHOTOPRISM_ORIGINALS_PATH: "/photoprism/storage/originals"
@@ -73,7 +70,7 @@ services:
volumes:
- "./storage:/photoprism/storage"
## Join shared "photoprism" network
## Join shared "photoprism-develop" network
networks:
default:
name: photoprism

View File

@@ -145,7 +145,7 @@ services:
MYSQL_PASSWORD: "photoprism"
MYSQL_ROOT_PASSWORD: "photoprism"
## Join shared "photoprism" network
## Join shared "photoprism-develop" network
networks:
default:
name: photoprism

View File

@@ -15,7 +15,7 @@ services:
MYSQL_PASSWORD: "photoprism"
MYSQL_ROOT_PASSWORD: "photoprism"
## Join shared "photoprism" network
## Join shared "photoprism-develop" network
networks:
default:
name: photoprism

View File

@@ -3,13 +3,11 @@ services:
photoprism:
build: .
image: photoprism/photoprism:develop
runtime: nvidia
depends_on:
- mariadb
- dummy-webdav
- dummy-oidc
stop_grace_period: 15s
privileged: true
stop_grace_period: 10s
security_opt:
- seccomp:unconfined
- apparmor:unconfined
@@ -24,14 +22,11 @@ services:
links:
- "traefik:localssl.dev"
- "traefik:app.localssl.dev"
- "traefik:vision.localssl.dev"
- "traefik:qdrant.localssl.dev"
- "traefik:keycloak.localssl.dev"
- "traefik:dummy-oidc.localssl.dev"
- "traefik:dummy-webdav.localssl.dev"
labels:
- "traefik.enable=true"
- "traefik.docker.network=photoprism"
- "traefik.http.services.photoprism.loadbalancer.server.port=2342"
- "traefik.http.services.photoprism.loadbalancer.server.scheme=http"
- "traefik.http.routers.photoprism.entrypoints=websecure"
@@ -53,9 +48,6 @@ services:
PHOTOPRISM_PASSWORD_RESET_URI: "https://keycloak.localssl.dev/realms/master/login-actions/reset-credentials"
PHOTOPRISM_USAGE_INFO: "true"
PHOTOPRISM_FILES_QUOTA: "100"
## Customization:
PHOTOPRISM_DEFAULT_LOCALE: "en" # default user interface language, e.g. "en" or "de"
PHOTOPRISM_PLACES_LOCALE: "local" # location details language, e.g. "local", "en", or
## OpenID Connect (pre-configured for local tests):
## see https://keycloak.localssl.dev/realms/master/.well-known/openid-configuration
PHOTOPRISM_OIDC_URI: "https://keycloak.localssl.dev/realms/master"
@@ -114,116 +106,44 @@ services:
PHOTOPRISM_RAW_PRESETS: "false" # enables applying user presets when converting RAW images (reduces performance)
PHOTOPRISM_DETECT_NSFW: "false" # automatically flags photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW: "false" # allows uploads that MAY be offensive (no effect without TensorFlow)
PHOTOPRISM_UPLOAD_ALLOW: "" # restricts uploads to these file types (comma-separated list of EXTENSIONS; leave blank to allow all)
PHOTOPRISM_UPLOAD_ARCHIVES: "true" # allows upload of zip archives (will be extracted before import)
PHOTOPRISM_THUMB_LIBRARY: "auto" # image processing library to be used for generating thumbnails (auto, imaging, vips)
PHOTOPRISM_THUMB_FILTER: "auto" # downscaling filter (imaging best to worst: blackman, lanczos, cubic, linear, nearest)
PHOTOPRISM_THUMB_UNCACHED: "true" # enables on-demand thumbnail rendering (high memory and cpu usage)
## Run/install on first startup (options: update tensorflow https intel gpu davfs yt-dlp):
PHOTOPRISM_INIT: "https tensorflow-gpu"
## Computer Vision API (https://docs.photoprism.app/getting-started/config-options/#computer-vision):
PHOTOPRISM_VISION_API: "true" # server: enables service API endpoints under /api/v1/vision (requires access token)
PHOTOPRISM_VISION_URI: "" # client: service URI, e.g. http://hostname/api/v1/vision (leave blank to disable)
PHOTOPRISM_VISION_KEY: "" # client: service access token (for authentication)
## NVIDIA GPU Hardware Acceleration (see https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html):
TF_CPP_MIN_LOG_LEVEL: 0 # show TensorFlow log messages for development
## Nvidia Video Transcoding (https://docs.photoprism.app/getting-started/advanced/transcoding/#nvidia-container-toolkit):
NVIDIA_VISIBLE_DEVICES: "all"
NVIDIA_DRIVER_CAPABILITIES: "all"
NVIDIA_DRIVER_CAPABILITIES: "compute,video,utility"
PHOTOPRISM_FFMPEG_ENCODER: "nvidia" # H.264/AVC encoder (software, intel, nvidia, apple, raspberry, or vaapi)
PHOTOPRISM_FFMPEG_SIZE: "1920" # video size limit in pixels (720-7680) (default: 3840)
PHOTOPRISM_FFMPEG_BITRATE: "64" # video bitrate limit in Mbps (default: 60)
## External dependencies and tools:
TF_CPP_MIN_LOG_LEVEL: 1
GOCACHE: "/go/src/github.com/photoprism/photoprism/.local/gocache"
CODEX_HOME: "/go/src/github.com/photoprism/photoprism/.local/codex"
## Shared devices for video hardware transcoding (optional):
PHOTOPRISM_FFMPEG_BITRATE: "50" # video bitrate limit in Mbit/s (default: 50)
## Run/install on first startup (options: update https gpu ffmpeg tensorflow davfs clitools clean):
PHOTOPRISM_INIT: "https tensorflow"
## Share hardware devices with FFmpeg and TensorFlow (optional):
# devices:
# - "/dev/dri:/dev/dri" # Required Intel QSV or VAAPI hardware transcoding
# - "/dev/dri:/dev/dri" # Intel QSV (Broadwell and later) or VAAPI (Haswell and earlier)
# - "/dev/nvidia0:/dev/nvidia0" # Nvidia CUDA
# - "/dev/nvidiactl:/dev/nvidiactl"
# - "/dev/nvidia-modeset:/dev/nvidia-modeset"
# - "/dev/nvidia-nvswitchctl:/dev/nvidia-nvswitchctl"
# - "/dev/nvidia-uvm:/dev/nvidia-uvm"
# - "/dev/nvidia-uvm-tools:/dev/nvidia-uvm-tools"
# - "/dev/video11:/dev/video11" # Video4Linux Video Encode Device (h264_v4l2m2m)
working_dir: "/go/src/github.com/photoprism/photoprism"
volumes:
- ".:/go/src/github.com/photoprism/photoprism"
- "./storage:/photoprism"
- "go-mod:/go/pkg/mod"
## NVIDIA GPU Hardware Acceleration (see https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html):
deploy:
resources:
reservations:
devices:
- driver: "nvidia"
capabilities: [gpu]
count: "all"
## Ollama Large-Language Model Runner
## run "ollama pull [name]:[version]" to download a vision model
## listed at <https://ollama.com/search?c=vision>, for example:
## docker compose exec ollama ollama pull gemma3:latest
ollama:
image: ollama/ollama:latest
restart: unless-stopped
stop_grace_period: 15s
## Only starts this service if the "all", "ollama", or "vision" profile is specified::
## docker compose --profile ollama up -d
profiles: ["all", "ollama", "vision"]
## Insecurely exposes the Ollama service on port 11434
## without authentication (for private networks only):
# ports:
# - "11434:11434"
labels:
- "traefik.enable=true"
- "traefik.docker.network=photoprism"
- "traefik.http.services.ollama.loadbalancer.server.port=11434"
- "traefik.http.routers.ollama.rule=Host(`ollama.localssl.dev`)"
- "traefik.http.routers.ollama.entrypoints=websecure"
- "traefik.http.routers.ollama.tls=true"
environment:
## Ollama Configuration Options:
OLLAMA_HOST: "0.0.0.0:11434"
OLLAMA_MODELS: "/root/.ollama" # model storage path (see volumes section below)
OLLAMA_MAX_QUEUE: "100" # maximum number of queued requests
OLLAMA_NUM_PARALLEL: "1" # maximum number of parallel requests
OLLAMA_MAX_LOADED_MODELS: "1" # maximum number of loaded models per GPU
OLLAMA_LOAD_TIMEOUT: "5m" # maximum time for loading models (default "5m")
OLLAMA_KEEP_ALIVE: "5m" # duration that models stay loaded in memory (default "5m")
OLLAMA_CONTEXT_LENGTH: "4096" # maximum input context length
OLLAMA_MULTIUSER_CACHE: "false" # optimize prompt caching for multi-user scenarios
OLLAMA_NOPRUNE: "false" # disables pruning of model blobs at startup
OLLAMA_NOHISTORY: "true" # disables readline history
OLLAMA_FLASH_ATTENTION: "false" # enables the experimental flash attention feature
OLLAMA_KV_CACHE_TYPE: "f16" # cache quantization (f16, q8_0, or q4_0)
OLLAMA_SCHED_SPREAD: "false" # allows scheduling models across all GPUs.
OLLAMA_NEW_ENGINE: "true" # enables the new Ollama engine
# OLLAMA_DEBUG: "true" # shows additional debug information
# OLLAMA_INTEL_GPU: "true" # enables experimental Intel GPU detection
## NVIDIA GPU Hardware Acceleration (see https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html):
NVIDIA_VISIBLE_DEVICES: "all"
NVIDIA_DRIVER_CAPABILITIES: "compute,utility"
volumes:
- "./storage/services/ollama:/root/.ollama"
## NVIDIA GPU Hardware Acceleration (see https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html):
deploy:
resources:
reservations:
devices:
- driver: "nvidia"
count: 1
capabilities: [ gpu ]
count: "all"
mariadb:
extends:
file: ./compose.yaml
service: mariadb
qdrant:
extends:
file: ./compose.yaml
service: qdrant
open-webui:
extends:
file: ./compose.yaml
service: open-webui
photoprism-vision:
extends:
file: ./compose.yaml
service: photoprism-vision
traefik:
extends:
file: ./compose.yaml
@@ -256,7 +176,7 @@ volumes:
mariadb:
driver: local
## Create shared "photoprism" network for connecting with services in other compose.yaml files
## Create shared "photoprism-develop" network for connecting with services in other compose.yaml files
networks:
default:
name: photoprism

View File

@@ -24,6 +24,7 @@ services:
- "go-mod:/go/pkg/mod"
shm_size: "2gb"
environment:
PHOTOPRISM_INIT: "https"
PHOTOPRISM_ADMIN_USER: "admin" # admin login username
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial admin password (8-72 characters)
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
@@ -57,8 +58,6 @@ services:
PHOTOPRISM_DISABLE_TENSORFLOW: "false" # disables all features depending on TensorFlow
PHOTOPRISM_DETECT_NSFW: "false" # automatically flags photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW: "false" # allows uploads that MAY be offensive (no effect without TensorFlow)
PHOTOPRISM_UPLOAD_ALLOW: "" # restricts uploads to these file types (comma-separated list of EXTENSIONS; leave blank to allow all)
PHOTOPRISM_UPLOAD_ARCHIVES: "true" # allows upload of zip archives (will be extracted before import)
PHOTOPRISM_RAW_PRESETS: "false" # enables applying user presets when converting RAW images (reduces performance)
PHOTOPRISM_THUMB_FILTER: "lanczos" # resample filter, best to worst: blackman, lanczos, cubic, linear
PHOTOPRISM_THUMB_UNCACHED: "true" # enables on-demand thumbnail rendering (high memory and cpu usage)
@@ -66,12 +65,8 @@ services:
# PHOTOPRISM_THUMB_SIZE: 4096 # Retina 4K, DCI 4K (requires more storage); 7680 for 8K Ultra HD
PHOTOPRISM_THUMB_SIZE_UNCACHED: 7680 # on-demand rendering size limit (default 7680, min 720, max 7680)
PHOTOPRISM_JPEG_SIZE: 7680 # size limit for converted image files in pixels (720-30000)
## Run/install on first startup (options: update tensorflow https intel gpu davfs yt-dlp):
PHOTOPRISM_INIT: "https"
## External dependencies and tools:
TF_CPP_MIN_LOG_LEVEL: 1
GOCACHE: "/go/src/github.com/photoprism/photoprism/.local/gocache"
CODEX_HOME: "/go/src/github.com/photoprism/photoprism/.local/codex"
TF_CPP_MIN_LOG_LEVEL: 0 # show TensorFlow log messages for development
## PostgreSQL Database Server
## Docs: https://www.postgresql.org/docs/
postgres:

View File

@@ -12,7 +12,6 @@ services:
- "2344:2342" # HTTP port (host:container)
labels:
- "traefik.enable=true"
- "traefik.docker.network=photoprism"
- "traefik.http.services.preview.loadbalancer.server.port=2342"
- "traefik.http.routers.preview.entrypoints=websecure"
- "traefik.http.routers.preview.rule=Host(`preview.localssl.dev`)"
@@ -51,8 +50,6 @@ services:
PHOTOPRISM_DISABLE_TENSORFLOW: "false" # disables all features depending on TensorFlow
PHOTOPRISM_DETECT_NSFW: "false" # automatically flags photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW: "false" # allows uploads that MAY be offensive (no effect without TensorFlow)
PHOTOPRISM_UPLOAD_ALLOW: "" # restricts uploads to these file types (comma-separated list of EXTENSIONS; leave blank to allow all)
PHOTOPRISM_UPLOAD_ARCHIVES: "true" # allows upload of zip archives (will be extracted before import)
PHOTOPRISM_RAW_PRESETS: "false" # enables applying user presets when converting RAW images (reduces performance)
PHOTOPRISM_THUMB_FILTER: "lanczos" # resample filter, best to worst: blackman, lanczos, cubic, linear
PHOTOPRISM_THUMB_UNCACHED: "true" # enables on-demand thumbnail rendering (high memory and cpu usage)
@@ -60,13 +57,13 @@ services:
# PHOTOPRISM_THUMB_SIZE: 4096 # Retina 4K, DCI 4K (requires more storage); 7680 for 8K Ultra HD
PHOTOPRISM_THUMB_SIZE_UNCACHED: 7680 # on-demand rendering size limit (default 7680, min 720, max 7680)
PHOTOPRISM_JPEG_SIZE: 7680 # size limit for converted image files in pixels (720-30000)
TF_CPP_MIN_LOG_LEVEL: 1 # show TensorFlow log messages for development
TF_CPP_MIN_LOG_LEVEL: 0 # show TensorFlow log messages for development
working_dir: "/photoprism"
volumes:
- "./storage:/photoprism/storage"
- "./storage/originals:/photoprism/originals"
## Join shared "photoprism" network
## Join shared "photoprism-develop" network
networks:
default:
name: photoprism

View File

@@ -10,7 +10,7 @@ services:
- mariadb
- dummy-webdav
- dummy-oidc
stop_grace_period: 15s
stop_grace_period: 10s
security_opt:
- seccomp:unconfined
- apparmor:unconfined
@@ -25,14 +25,11 @@ services:
links:
- "traefik:localssl.dev"
- "traefik:app.localssl.dev"
- "traefik:vision.localssl.dev"
- "traefik:qdrant.localssl.dev"
- "traefik:keycloak.localssl.dev"
- "traefik:dummy-oidc.localssl.dev"
- "traefik:dummy-webdav.localssl.dev"
labels:
- "traefik.enable=true"
- "traefik.docker.network=photoprism"
- "traefik.http.services.photoprism.loadbalancer.server.port=2342"
- "traefik.http.services.photoprism.loadbalancer.server.scheme=http"
- "traefik.http.routers.photoprism.entrypoints=websecure"
@@ -58,9 +55,6 @@ services:
PHOTOPRISM_PASSWORD_RESET_URI: "https://keycloak.localssl.dev/realms/master/login-actions/reset-credentials"
PHOTOPRISM_USAGE_INFO: "true"
PHOTOPRISM_FILES_QUOTA: "100"
## Customization:
PHOTOPRISM_DEFAULT_LOCALE: "en" # default user interface language, e.g. "en" or "de"
PHOTOPRISM_PLACES_LOCALE: "local" # location details language, e.g. "local", "en", or "de"
## OpenID Connect (pre-configured for local tests):
## see https://keycloak.localssl.dev/realms/master/.well-known/openid-configuration
PHOTOPRISM_OIDC_URI: "https://keycloak.localssl.dev/realms/master"
@@ -119,29 +113,26 @@ services:
PHOTOPRISM_RAW_PRESETS: "false" # enables applying user presets when converting RAW images (reduces performance)
PHOTOPRISM_DETECT_NSFW: "false" # automatically flags photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW: "false" # allows uploads that MAY be offensive (no effect without TensorFlow)
PHOTOPRISM_UPLOAD_ALLOW: "" # restricts uploads to these file types (comma-separated list of EXTENSIONS; leave blank to allow all)
PHOTOPRISM_UPLOAD_ARCHIVES: "true" # allows upload of zip archives (will be extracted before import)
PHOTOPRISM_THUMB_LIBRARY: "auto" # image processing library to be used for generating thumbnails (auto, imaging, vips)
PHOTOPRISM_THUMB_FILTER: "auto" # downscaling filter (imaging best to worst: blackman, lanczos, cubic, linear, nearest)
PHOTOPRISM_THUMB_UNCACHED: "true" # enables on-demand thumbnail rendering (high memory and cpu usage)
TF_CPP_MIN_LOG_LEVEL: 0 # show TensorFlow log messages for development
## Video Transcoding (https://docs.photoprism.app/getting-started/advanced/transcoding/):
# PHOTOPRISM_FFMPEG_ENCODER: "software" # H.264/AVC encoder (software, intel, nvidia, apple, raspberry, or vaapi)
# PHOTOPRISM_FFMPEG_SIZE: "1920" # video size limit in pixels (720-7680) (default: 3840)
# PHOTOPRISM_FFMPEG_BITRATE: "32" # video bitrate limit in Mbit/s (default: 50)
# LIBVA_DRIVER_NAME: "i965" # For Intel architectures Haswell and older which do not support QSV yet but use VAAPI instead
PHOTOPRISM_FFMPEG_SIZE: "1920" # video size limit in pixels (720-7680) (default: 3840)
# PHOTOPRISM_FFMPEG_BITRATE: "64" # video bitrate limit in Mbps (default: 60)
## Run/install on first startup (options: update tensorflow https intel gpu davfs yt-dlp):
PHOTOPRISM_INIT: "https"
## Computer Vision API (https://docs.photoprism.app/getting-started/config-options/#computer-vision):
PHOTOPRISM_VISION_API: "true" # server: enables service API endpoints under /api/v1/vision (requires access token)
PHOTOPRISM_VISION_URI: "" # client: service URI, e.g. http://hostname/api/v1/vision (leave blank to disable)
PHOTOPRISM_VISION_KEY: "" # client: service access token (for authentication)
## External dependencies and tools:
TF_CPP_MIN_LOG_LEVEL: 1
GOCACHE: "/go/src/github.com/photoprism/photoprism/.local/gocache"
CODEX_HOME: "/go/src/github.com/photoprism/photoprism/.local/codex"
## Shared devices for video hardware transcoding (optional):
## Run/install on first startup (options: update https gpu ffmpeg tensorflow davfs clitools clean):
PHOTOPRISM_INIT: "https tensorflow"
## Share hardware devices with FFmpeg and TensorFlow (optional):
# devices:
# - "/dev/dri:/dev/dri" # Required Intel QSV or VAAPI hardware transcoding
# - "/dev/dri:/dev/dri" # Intel QSV (Broadwell and later) or VAAPI (Haswell and earlier)
# - "/dev/nvidia0:/dev/nvidia0" # Nvidia CUDA
# - "/dev/nvidiactl:/dev/nvidiactl"
# - "/dev/nvidia-modeset:/dev/nvidia-modeset"
# - "/dev/nvidia-nvswitchctl:/dev/nvidia-nvswitchctl"
# - "/dev/nvidia-uvm:/dev/nvidia-uvm"
# - "/dev/nvidia-uvm-tools:/dev/nvidia-uvm-tools"
# - "/dev/video11:/dev/video11" # Video4Linux Video Encode Device (h264_v4l2m2m)
working_dir: "/go/src/github.com/photoprism/photoprism"
volumes:
@@ -173,150 +164,6 @@ services:
MARIADB_PASSWORD: "photoprism"
MARIADB_ROOT_PASSWORD: "photoprism"
## Qdrant (Vector Database)
## Docs: https://qdrant.tech/documentation/guides/installation/#docker-compose
## Release Notes: https://github.com/qdrant/qdrant/releases
## Web UI: https://qdrant.localssl.dev/dashboard
qdrant:
image: qdrant/qdrant:latest
profiles: [ "all", "qdrant" ]
links:
- "traefik:localssl.dev"
- "traefik:app.localssl.dev"
- "traefik:vision.localssl.dev"
labels:
- "traefik.enable=true"
- "traefik.http.services.qdrant.loadbalancer.server.port=6333"
- "traefik.http.services.qdrant.loadbalancer.server.scheme=http"
- "traefik.http.routers.qdrant.entrypoints=websecure"
- "traefik.http.routers.qdrant.rule=Host(`qdrant.localssl.dev`)"
- "traefik.http.routers.qdrant.priority=3"
- "traefik.http.routers.qdrant.tls.domains[0].main=localssl.dev"
- "traefik.http.routers.qdrant.tls.domains[0].sans=*.localssl.dev"
- "traefik.http.routers.qdrant.tls=true"
expose:
- 6333
- 6334
- 6335
volumes:
- "./.qdrant.yaml:/qdrant/config/production.yaml"
- "./storage/services/qdrant:/qdrant/storage"
## Ollama Large-Language Model Runner
## run "ollama pull [name]:[version]" to download a vision model
## listed at <https://ollama.com/search?c=vision>, for example:
## docker compose exec ollama ollama pull gemma3:latest
ollama:
image: ollama/ollama:latest
restart: unless-stopped
stop_grace_period: 10s
## Only starts this service if the "all", "ollama", or "vision" profile is specified::
## docker compose --profile ollama up -d
profiles: [ "all", "ollama", "vision" ]
## Insecurely exposes the Ollama service on port 11434
## without authentication (for private networks only):
# ports:
# - "11434:11434"
labels:
- "traefik.enable=true"
- "traefik.docker.network=photoprism"
- "traefik.http.services.ollama.loadbalancer.server.port=11434"
- "traefik.http.routers.ollama.rule=Host(`ollama.localssl.dev`)"
- "traefik.http.routers.ollama.entrypoints=websecure"
- "traefik.http.routers.ollama.tls=true"
environment:
## Ollama Configuration Options:
OLLAMA_HOST: "0.0.0.0:11434"
OLLAMA_MODELS: "/root/.ollama" # model storage path (see volumes section below)
OLLAMA_MAX_QUEUE: "100" # maximum number of queued requests
OLLAMA_NUM_PARALLEL: "1" # maximum number of parallel requests
OLLAMA_MAX_LOADED_MODELS: "1" # maximum number of loaded models per GPU
OLLAMA_LOAD_TIMEOUT: "5m" # maximum time for loading models (default "5m")
OLLAMA_KEEP_ALIVE: "5m" # duration that models stay loaded in memory (default "5m")
OLLAMA_CONTEXT_LENGTH: "4096" # maximum input context length
OLLAMA_MULTIUSER_CACHE: "false" # optimize prompt caching for multi-user scenarios
OLLAMA_NOPRUNE: "false" # disables pruning of model blobs at startup
OLLAMA_NOHISTORY: "true" # disables readline history
OLLAMA_FLASH_ATTENTION: "false" # enables the experimental flash attention feature
OLLAMA_KV_CACHE_TYPE: "f16" # cache quantization (f16, q8_0, or q4_0)
OLLAMA_SCHED_SPREAD: "false" # allows scheduling models across all GPUs.
OLLAMA_NEW_ENGINE: "true" # enables the new Ollama engine
# OLLAMA_DEBUG: "true" # shows additional debug information
# OLLAMA_INTEL_GPU: "true" # enables experimental Intel GPU detection
## NVIDIA GPU Hardware Acceleration (optional):
# NVIDIA_VISIBLE_DEVICES: "all"
# NVIDIA_DRIVER_CAPABILITIES: "compute,utility"
volumes:
- "./storage/services/ollama:/root/.ollama"
## NVIDIA GPU Hardware Acceleration (optional):
# deploy:
# resources:
# reservations:
# devices:
# - driver: "nvidia"
# capabilities: [ gpu ]
# count: "all"
## Open WebUI, a Web Interface for Ollama
## see https://github.com/open-webui/open-webui
open-webui:
image: ghcr.io/open-webui/open-webui:main
restart: unless-stopped
stop_grace_period: 5s
## Only starts this service if the "all", "ollama", "open-webui", or "vision" profile is specified::
## docker compose --profile ollama up -d
profiles: [ "all", "ollama", "open-webui", "vision" ]
## Exposes Open WebUI at http://localhost:8080 (use https://chat.localssl.dev/ to access it through Traefik):
ports:
- "127.0.0.1:8080:8080"
labels:
- "traefik.enable=true"
- "traefik.docker.network=photoprism"
- "traefik.http.services.open-webui.loadbalancer.server.port=8080"
- "traefik.http.routers.open-webui.rule=Host(`chat.localssl.dev`) || Host(`open-webui.localssl.dev`) || Host(`ollama-ui.localssl.dev`)"
- "traefik.http.routers.open-webui.entrypoints=websecure"
- "traefik.http.routers.open-webui.tls=true"
environment:
WEBUI_URL: "https://chat.localssl.dev"
# WEBUI_SECRET_KEY: ""
OLLAMA_BASE_URL: "http://ollama:11434"
ANONYMIZED_TELEMETRY: "false" # disable Chroma telemetry
HF_HUB_DISABLE_TELEMETRY: "1" # disable Hugging Face telemetry
# HUGGING_FACE_HUB_TOKEN: "" # see https://huggingface.co/docs/hub/en/security-tokens
volumes:
- "./storage/services/open-webui:/app/backend/data"
## PhotoPrism® Computer Vision API
## see https://github.com/photoprism/photoprism-vision
photoprism-vision:
image: photoprism/vision:latest
## Only starts this service if the "all" or "vision" profile is specified::
## docker compose --profile vision up -d
profiles: [ "all", "vision" ]
stop_grace_period: 15s
working_dir: "/app"
links:
- "traefik:localssl.dev"
- "traefik:app.localssl.dev"
- "traefik:qdrant.localssl.dev"
labels:
- "traefik.enable=true"
- "traefik.http.services.qdrant.loadbalancer.server.port=5000"
- "traefik.http.services.qdrant.loadbalancer.server.scheme=http"
- "traefik.http.routers.qdrant.entrypoints=websecure"
- "traefik.http.routers.qdrant.rule=Host(`vision.localssl.dev`)"
- "traefik.http.routers.qdrant.priority=3"
- "traefik.http.routers.qdrant.tls.domains[0].main=localssl.dev"
- "traefik.http.routers.qdrant.tls.domains[0].sans=*.localssl.dev"
- "traefik.http.routers.qdrant.tls=true"
expose:
- 5000
environment:
TF_CPP_MIN_LOG_LEVEL: 2
## Ollama client configuration (for the service, see below):
OLLAMA_ENABLED: "true"
OLLAMA_HOST: "http://ollama:11434"
## Traefik v3 (Reverse Proxy)
## includes "*.localssl.dev" SSL certificate for test environments
## Docs: https://doc.traefik.io/traefik/
@@ -415,7 +262,7 @@ services:
## ./photoprism client add --id=cs5cpu17n6gj2qo5 --secret=xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e -s metrics -n Prometheus -e 60 -t 1
prometheus:
image: prom/prometheus:latest
profiles: [ "all", "auth", "prometheus" ]
profiles: ["all", "auth", "prometheus"]
labels:
- "traefik.enable=true"
- "traefik.http.services.prometheus.loadbalancer.server.port=9090"
@@ -435,7 +282,7 @@ volumes:
mariadb:
driver: local
## Create shared "photoprism" network for connecting with services in other compose.yaml files
## Create shared "photoprism-develop" network for connecting with services in other compose.yaml files
networks:
default:
name: photoprism

View File

@@ -1,9 +1,8 @@
FROM photoprism/photoprism:preview-ce AS build
# Set environment variables
ENV TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
ENV TF_CPP_MIN_LOG_LEVEL=2 \
MALLOC_ARENA_MAX="4" \
PHOTOPRISM_STORAGE_PATH="/photoprism/storage" \
PHOTOPRISM_DEBUG="false" \
PHOTOPRISM_READONLY="false" \

View File

@@ -1,6 +1,7 @@
services:
demo:
restart: always
command: photoprism --public start
image: photoprism/demo:latest
container_name: demo
depends_on:
@@ -13,8 +14,6 @@ services:
volumes:
- "./config:/photoprism/storage/config"
environment:
PHOTOPRISM_DEMO: "true"
PHOTOPRISM_PUBLIC: "true"
PHOTOPRISM_SITE_URL: "https://demo.yourdomain.com/"
# PHOTOPRISM_CDN_URL: "https://demo-cdn.yourdomain.com/"
PHOTOPRISM_SITE_TITLE: "PhotoPrism"
@@ -27,7 +26,7 @@ services:
traefik:
restart: always
image: traefik:v3.5
image: traefik:v3.1
container_name: traefik
ports:
- "80:80"

View File

@@ -1,9 +1,8 @@
FROM photoprism/photoprism:preview-ce-debian AS build
# Set environment variables
ENV TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
ENV TF_CPP_MIN_LOG_LEVEL=2 \
MALLOC_ARENA_MAX="4" \
PHOTOPRISM_STORAGE_PATH="/photoprism/storage" \
PHOTOPRISM_DEBUG="false" \
PHOTOPRISM_READONLY="false" \

View File

@@ -6,7 +6,7 @@ serversTransport:
# Required to proxy services with self-signed HTTPS certificates:
insecureSkipVerify: true
# Open ports and protocols (HTTP will be redirected to HTTPS):
# Open ports ond protocols (HTTP will be redirected to HTTPS):
entryPoints:
web:
address: ":80"

View File

@@ -1,9 +1,8 @@
FROM photoprism/photoprism:preview-ce-ubuntu AS build
# Set environment variables
ENV TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
ENV TF_CPP_MIN_LOG_LEVEL=2 \
MALLOC_ARENA_MAX="4" \
PHOTOPRISM_STORAGE_PATH="/photoprism/storage" \
PHOTOPRISM_DEBUG="false" \
PHOTOPRISM_READONLY="false" \

View File

@@ -1,9 +1,8 @@
FROM photoprism/photoprism:unstable-ce AS build
# Set environment variables
ENV TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
ENV TF_CPP_MIN_LOG_LEVEL=2 \
MALLOC_ARENA_MAX="4" \
PHOTOPRISM_STORAGE_PATH="/photoprism/storage" \
PHOTOPRISM_DEBUG="false" \
PHOTOPRISM_READONLY="false" \

View File

@@ -30,16 +30,14 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
NODE_ENV="production" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_VERSION=1.15.2 \
TF_CPP_MIN_LOG_LEVEL=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="0" \
MALLOC_ARENA_MAX="4" \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \
CGO_CFLAGS="-g -O2 -Wno-return-local-addr" \
PROG="photoprism" \
S6_KEEP_ENV=1 \
S6_VERBOSITY=0 \
S6_LOGGING=0
# Copy scripts and package sources config.
@@ -64,7 +62,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
iputils-ping dnsutils \
&& \
/scripts/install-nodejs.sh && \
/scripts/install-yt-dlp.sh && \
/scripts/install-libheif.sh && \
/scripts/install-tensorflow.sh && \
echo "ALL ALL=(ALL) NOPASSWD:SETENV: ALL" >> /etc/sudoers.d/all && \

View File

@@ -29,9 +29,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism"
# Copy scripts and package sources config.

View File

@@ -30,9 +30,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_CPP_MIN_LOG_LEVEL=1 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="0" \
MALLOC_ARENA_MAX="4" \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \

View File

@@ -29,9 +29,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism"
# copy scripts and debian backports sources list

View File

@@ -30,9 +30,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_CPP_MIN_LOG_LEVEL=1 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="0" \
MALLOC_ARENA_MAX="4" \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \
@@ -54,7 +53,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get -qq install \
apt-utils \
ripgrep \
gpg \
pkg-config \
software-properties-common \

View File

@@ -30,9 +30,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
NODE_ENV="production" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_CPP_MIN_LOG_LEVEL=1 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="0" \
MALLOC_ARENA_MAX="4" \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \
@@ -53,7 +52,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && apt-get -qq install --no-install-recommends \
apt-utils \
ripgrep \
gpg \
pkg-config \
software-properties-common \

View File

@@ -30,9 +30,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
NODE_ENV="production" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_CPP_MIN_LOG_LEVEL=1 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="0" \
MALLOC_ARENA_MAX="4" \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \
@@ -52,7 +51,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && apt-get -qq install --no-install-recommends \
apt-utils \
ripgrep \
gpg \
gpg-agent \
pkg-config \

View File

@@ -29,12 +29,10 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
S6_KEEP_ENV=0 \
S6_VERBOSITY=0 \
S6_KEEP_ENV=1 \
S6_LOGGING=0
# Copy scripts and package sources config.
@@ -50,14 +48,13 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get -qq install \
libc6 ca-certificates bash sudo nano avahi-utils jq lsof lshw \
xz-utils exiftool sqlite3 postgresql-client tzdata gpg make zip unzip wget curl rsync \
xz-utils exiftool sqlite3 tzdata gpg make zip unzip wget curl rsync \
imagemagick libvips-dev rawtherapee ffmpeg libavcodec-extra x264 x265 libde265-dev \
libaom-dev libvpx-dev libwebm-dev libjpeg-dev libmatroska-dev libdvdread-dev libebml5 libgav1-bin libatomic1 \
va-driver-all libva2 iputils-ping dnsutils libmagic-mgc \
iputils-ping dnsutils \
&& \
/scripts/install-mariadb.sh mariadb-client && \
/scripts/install-darktable.sh && \
/scripts/install-yt-dlp.sh && \
/scripts/install-libheif.sh && \
echo 'alias ll="ls -alh"' >> /etc/skel/.bashrc && \
echo 'export PS1="\u@$DOCKER_TAG:\w\$ "' >> /etc/skel/.bashrc && \
@@ -75,8 +72,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
/photoprism/storage/config \
/photoprism/storage/cache && \
/scripts/install-s6.sh && \
ln -sf /scripts/services/photoprism /etc/s6-overlay/s6-rc.d/photoprism && \
touch /etc/s6-overlay/s6-rc.d/user/contents.d/photoprism && \
/scripts/cleanup.sh
# Set default working directory.
@@ -87,3 +82,4 @@ EXPOSE 2342 2442 2443
# Set default entrypoint and command.
ENTRYPOINT ["/init"]
CMD ["/scripts/cmd.sh", "tail", "-f", "/dev/null"]

View File

@@ -30,16 +30,14 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_CPP_MIN_LOG_LEVEL=1 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="0" \
MALLOC_ARENA_MAX="4" \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \
CGO_CFLAGS="-g -O2 -Wno-return-local-addr" \
PROG="photoprism" \
S6_KEEP_ENV=1 \
S6_VERBOSITY=0 \
S6_LOGGING=0
# Copy scripts and package sources config.
@@ -56,28 +54,24 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get -qq install \
libc6 ca-certificates bash sudo nano avahi-utils jq lsof lshw \
xz-utils exiftool sqlite3 postgresql-client tzdata gpg make zip unzip wget curl rsync \
xz-utils exiftool sqlite3 tzdata gpg make zip unzip wget curl rsync \
imagemagick libvips-dev rawtherapee ffmpeg libavcodec-extra x264 x265 libde265-dev \
libaom-dev libvpx-dev libwebm-dev libjpeg-dev libmatroska-dev libdvdread-dev libebml5 libgav1-bin libatomic1 \
va-driver-all libva2 iputils-ping dnsutils libmagic-mgc \
iputils-ping dnsutils \
&& \
apt-get -qq install \
build-essential software-properties-common pkg-config apt-utils \
gcc g++ git autoconf automake cmake gettext apache2-utils davfs2 \
chrpath tree ripgrep fd-find bat shellcheck shfmt fzf \
libtool libjpeg-dev libpng-dev libwebp-dev libavcodec-dev \
apt-utils pkg-config software-properties-common \
build-essential gcc g++ git gettext davfs2 chrpath apache2-utils \
autoconf automake cmake libtool libjpeg-dev libpng-dev libwebp-dev libavcodec-dev \
libx264-dev libx265-dev libaom-dev libvpx-dev libwebm-dev libxft-dev \
libc6-dev libhdf5-serial-dev libzmq3-dev libssl-dev libnss3 \
libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev fonts-roboto \
librsvg2-bin ghostscript gsfonts pdf2svg ps2eps \
&& \
ln -sf /usr/bin/fdfind /usr/local/bin/fd && \
ln -sf /usr/bin/batcat /usr/local/bin/bat && \
/scripts/install-nodejs.sh && \
/scripts/install-mariadb.sh mariadb-client && \
/scripts/install-tensorflow.sh && \
/scripts/install-darktable.sh && \
/scripts/install-yt-dlp.sh && \
/scripts/install-libheif.sh && \
/scripts/install-chrome.sh && \
echo "ALL ALL=(ALL) NOPASSWD:SETENV: ALL" >> /etc/sudoers.d/all && \

View File

@@ -29,9 +29,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism"
# Copy scripts and package sources config.

View File

@@ -30,9 +30,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_CPP_MIN_LOG_LEVEL=1 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="0" \
MALLOC_ARENA_MAX="4" \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \
@@ -58,7 +57,7 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
x264 x265 libde265-dev libaom-dev libvpx-dev libwebm-dev libjpeg-dev libmatroska-dev libdvdread-dev \
&& \
apt-get -qq install \
software-properties-common pkg-config apt-utils ripgrep fd-find bat eza \
apt-utils pkg-config software-properties-common \
build-essential gcc g++ git gettext davfs2 chrpath apache2-utils \
autoconf automake cmake libtool libjpeg-dev libpng-dev libwebp-dev \
libx264-dev libx265-dev libaom-dev libvpx-dev libwebm-dev libxft-dev \
@@ -66,8 +65,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev fonts-roboto \
librsvg2-bin ghostscript gsfonts pdf2svg ps2eps \
&& \
ln -sf /usr/bin/fdfind /usr/local/bin/fd && \
ln -sf /usr/bin/batcat /usr/local/bin/bat && \
/scripts/install-nodejs.sh && \
/scripts/install-mariadb.sh mariadb-client && \
/scripts/install-tensorflow.sh && \

View File

@@ -29,9 +29,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism"
# Copy scripts and package sources config.

View File

@@ -30,9 +30,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_CPP_MIN_LOG_LEVEL=1 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="0" \
MALLOC_ARENA_MAX="4" \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \
@@ -58,7 +57,7 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
x264 x265 libde265-dev libaom-dev libvpx-dev libwebm-dev libjpeg-dev libmatroska-dev libdvdread-dev \
&& \
apt-get -qq install \
software-properties-common pkg-config apt-utils ripgrep fd-find bat eza \
apt-utils pkg-config software-properties-common \
build-essential gcc g++ git gettext davfs2 chrpath apache2-utils \
autoconf automake cmake libtool libjpeg-dev libpng-dev libwebp-dev \
libx264-dev libx265-dev libaom-dev libvpx-dev libwebm-dev libxft-dev \
@@ -66,8 +65,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev fonts-roboto \
librsvg2-bin ghostscript gsfonts pdf2svg ps2eps \
&& \
ln -sf /usr/bin/fdfind /usr/local/bin/fd && \
ln -sf /usr/bin/batcat /usr/local/bin/bat && \
/scripts/install-nodejs.sh && \
/scripts/install-mariadb.sh mariadb-client && \
/scripts/install-tensorflow.sh && \

View File

@@ -29,9 +29,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism"
# Copy scripts and package sources config.
@@ -47,7 +46,7 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get -qq install \
libc6 ca-certificates bash sudo nano tzdata gpg make zip unzip wget curl rsync \
xz-utils avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 exiftool sqlite3 postgresql-client \
xz-utils avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 exiftool sqlite3 \
ffmpeg imagemagick libvips-dev rawtherapee libjxl-dev libjxl-tools libffmpeg-nvenc-dev librav1e-dev \
libswscale-dev libavfilter-extra libavformat-extra libavcodec-extra x264 x265 libde265-dev libaom-dev \
libvpx-dev libwebm-dev libjpeg-dev libmatroska-dev libdvdread-dev libdav1d-dev libsharpyuv0 \

View File

@@ -30,9 +30,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_CPP_MIN_LOG_LEVEL=1 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="0" \
MALLOC_ARENA_MAX="4" \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \
@@ -53,14 +52,14 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get -qq install \
libc6 libbsd-dev ca-certificates bash sudo nano tzdata gpg make zip unzip wget curl rsync \
xz-utils avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 exiftool sqlite3 postgresql-client \
xz-utils avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 exiftool sqlite3 \
ffmpeg imagemagick libvips-dev rawtherapee libjxl-dev libjxl-tools libffmpeg-nvenc-dev librav1e-dev \
libswscale-dev libavfilter-extra libavformat-extra libavcodec-extra x264 x265 libde265-dev libaom-dev \
libvpx-dev libwebm-dev libjpeg-dev libmatroska-dev libdvdread-dev libdav1d-dev libsharpyuv0 \
iputils-ping dnsutils \
&& \
apt-get -qq install \
software-properties-common pkg-config apt-utils ripgrep fd-find bat eza \
software-properties-common pkg-config apt-utils \
build-essential gcc g++ git gettext davfs2 chrpath apache2-utils \
autoconf automake cmake libtool libjpeg-dev libpng-dev libwebp-dev libavcodec-dev \
libx264-dev libx265-dev libaom-dev libvpx-dev libwebm-dev libxft-dev \
@@ -68,8 +67,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev fonts-roboto \
librsvg2-bin ghostscript gsfonts pdf2svg ps2eps libsharpyuv-dev \
&& \
ln -sf /usr/bin/fdfind /usr/local/bin/fd && \
ln -sf /usr/bin/batcat /usr/local/bin/bat && \
/scripts/install-nodejs.sh && \
/scripts/install-mariadb.sh mariadb-client && \
/scripts/install-tensorflow.sh && \

View File

@@ -29,12 +29,10 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
S6_KEEP_ENV=0 \
S6_VERBOSITY=0 \
S6_KEEP_ENV=1 \
S6_LOGGING=0
# Copy scripts and package sources config.
@@ -50,7 +48,7 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get -qq install \
libc6 ca-certificates bash sudo nano tzdata gpg make zip unzip wget curl rsync \
xz-utils avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 exiftool sqlite3 postgresql-client \
xz-utils avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 exiftool sqlite3 \
ffmpeg imagemagick libvips-dev rawtherapee libjxl-dev libjxl-tools libffmpeg-nvenc-dev librav1e-dev \
libswscale-dev libavfilter-extra libavformat-extra libavcodec-extra x264 x265 libde265-dev libaom-dev \
libvpx-dev libwebm-dev libjpeg-dev libmatroska-dev libdvdread-dev libdav1d-dev libsharpyuv0 \
@@ -75,8 +73,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
/photoprism/storage/config \
/photoprism/storage/cache && \
/scripts/install-s6.sh && \
ln -sf /scripts/services/photoprism /etc/s6-overlay/s6-rc.d/photoprism && \
touch /etc/s6-overlay/s6-rc.d/user/contents.d/photoprism && \
/scripts/cleanup.sh
# Set default working directory.
@@ -87,3 +83,4 @@ EXPOSE 2342 2442 2443
# Set default entrypoint and command.
ENTRYPOINT ["/init"]
CMD ["/scripts/cmd.sh", "tail", "-f", "/dev/null"]

View File

@@ -30,16 +30,14 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_CPP_MIN_LOG_LEVEL=1 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="0" \
MALLOC_ARENA_MAX="4" \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \
CGO_CFLAGS="-g -O2 -Wno-return-local-addr" \
PROG="photoprism" \
S6_KEEP_ENV=1 \
S6_VERBOSITY=0 \
S6_LOGGING=0
# Copy scripts and package sources config.
@@ -56,14 +54,14 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get -qq install \
libc6 libbsd-dev ca-certificates bash sudo nano tzdata gpg make zip unzip wget curl rsync \
xz-utils avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 exiftool sqlite3 postgresql-client \
xz-utils avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 exiftool sqlite3 \
ffmpeg imagemagick libvips-dev rawtherapee libjxl-dev libjxl-tools libffmpeg-nvenc-dev librav1e-dev \
libswscale-dev libavfilter-extra libavformat-extra libavcodec-extra x264 x265 libde265-dev libaom-dev \
libvpx-dev libwebm-dev libjpeg-dev libmatroska-dev libdvdread-dev libdav1d-dev libsharpyuv0 \
iputils-ping dnsutils \
&& \
apt-get -qq install \
software-properties-common pkg-config apt-utils ripgrep fd-find bat eza \
software-properties-common pkg-config apt-utils \
build-essential gcc g++ git gettext davfs2 chrpath apache2-utils \
autoconf automake cmake libtool libjpeg-dev libpng-dev libwebp-dev libavcodec-dev \
libx264-dev libx265-dev libaom-dev libvpx-dev libwebm-dev libxft-dev \
@@ -71,8 +69,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev fonts-roboto \
librsvg2-bin ghostscript gsfonts pdf2svg ps2eps libsharpyuv-dev \
&& \
ln -sf /usr/bin/fdfind /usr/local/bin/fd && \
ln -sf /usr/bin/batcat /usr/local/bin/bat && \
/scripts/install-nodejs.sh && \
/scripts/install-mariadb.sh mariadb-client && \
/scripts/install-tensorflow.sh && \

View File

@@ -1,91 +0,0 @@
#### Base Image: Ubuntu 25.04 (Plucky Puffin)
FROM ubuntu:plucky
# Copyright © 2018 - 2025 PhotoPrism UG. All rights reserved.
#
# Questions? Email us at hello@photoprism.app or visit our website to learn
# more about our team, products and services: https://www.photoprism.app/
# Add Open Container Initiative (OCI) annotations.
# See: https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="PhotoPrism® Base Image (Ubuntu 25.04)"
LABEL org.opencontainers.image.description="Ubuntu 25.04 (Plucky Puffin)"
LABEL org.opencontainers.image.url="https://hub.docker.com/repository/docker/photoprism/develop"
LABEL org.opencontainers.image.source="https://github.com/photoprism/photoprism"
LABEL org.opencontainers.image.documentation="https://docs.photoprism.app/developer-guide/setup/"
LABEL org.opencontainers.image.authors="PhotoPrism UG <hello@photoprism.app>"
LABEL org.opencontainers.image.vendor="PhotoPrism UG"
# Declare build parameters.
ARG TARGETARCH
ARG BUILD_TAG
# Set environment variables, see https://docs.photoprism.app/getting-started/config-options/.
ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_TAG=$BUILD_TAG \
DOCKER_ENV="prod" \
PS1="\u@$BUILD_TAG:\w\$ " \
PATH="/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin:/scripts:/opt/photoprism/bin" \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
PROG="photoprism" \
S6_KEEP_ENV=0 \
S6_VERBOSITY=0 \
S6_LOGGING=0
# Copy scripts and package sources config.
COPY --chown=root:root --chmod=755 /scripts/dist/ /scripts/
# Update base image and add dependencies.
RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Install-Recommends "false";' > /etc/apt/apt.conf.d/80recommends && \
echo 'APT::Install-Suggests "false";' > /etc/apt/apt.conf.d/80suggests && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get -qq install \
libc6 ca-certificates bash sudo nano tzdata gpg make zip unzip wget curl rsync \
xz-utils avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 exiftool sqlite3 postgresql-client \
ffmpeg imagemagick libvips-dev rawtherapee libjxl-dev libjxl-tools libffmpeg-nvenc-dev librav1e-dev \
libswscale-dev libavfilter-extra libavformat-extra libavcodec-extra x264 x265 libde265-dev libaom-dev \
libvpx-dev libwebm-dev libjpeg-dev libmatroska-dev libdvdread-dev libdav1d-dev libsharpyuv0 \
va-driver-all libva2 iputils-ping dnsutils libmagic-mgc \
&& \
/scripts/install-mariadb.sh mariadb-client && \
/scripts/install-darktable.sh && \
/scripts/install-yt-dlp.sh && \
/scripts/install-libheif.sh && \
echo 'alias ll="ls -alh"' >> /etc/skel/.bashrc && \
echo 'export PS1="\u@$DOCKER_TAG:\w\$ "' >> /etc/skel/.bashrc && \
echo "ALL ALL=(ALL) NOPASSWD:SETENV: /scripts/entrypoint-init.sh" >> /etc/sudoers.d/init && \
/scripts/install-dircolors.sh && \
cp /etc/skel/.bashrc /root/.bashrc && \
/scripts/create-users.sh && \
install -d -m 0777 -o 1000 -g 1000 \
/photoprism/originals \
/photoprism/import \
/photoprism/storage \
/photoprism/storage/sidecar \
/photoprism/storage/albums \
/photoprism/storage/backups \
/photoprism/storage/config \
/photoprism/storage/cache && \
/scripts/install-s6.sh && \
ln -sf /scripts/services/photoprism /etc/s6-overlay/s6-rc.d/photoprism && \
touch /etc/s6-overlay/s6-rc.d/user/contents.d/photoprism && \
apt modernize-sources && \
/scripts/cleanup.sh
# Set default working directory.
WORKDIR /photoprism
# Expose default HTTP and HTTPS ports.
EXPOSE 2342 2442 2443
# Set default entrypoint and command.
ENTRYPOINT ["/init"]

View File

@@ -1,131 +0,0 @@
#### Base Image: Ubuntu 25.04 (Plucky Puffin)
FROM ubuntu:plucky
# Copyright © 2018 - 2025 PhotoPrism UG. All rights reserved.
#
# Questions? Email us at hello@photoprism.app or visit our website to learn
# more about our team, products and services: https://www.photoprism.app/
# Add Open Container Initiative (OCI) annotations.
# See: https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="PhotoPrism® Build Image (Ubuntu 25.04)"
LABEL org.opencontainers.image.description="Ubuntu 25.04 (Plucky Puffin)"
LABEL org.opencontainers.image.url="https://hub.docker.com/repository/docker/photoprism/develop"
LABEL org.opencontainers.image.source="https://github.com/photoprism/photoprism"
LABEL org.opencontainers.image.documentation="https://docs.photoprism.app/developer-guide/setup/"
LABEL org.opencontainers.image.authors="PhotoPrism UG <hello@photoprism.app>"
LABEL org.opencontainers.image.vendor="PhotoPrism UG"
# Declare build parameters.
ARG TARGETARCH
ARG BUILD_TAG
# Set environment variables, see https://docs.photoprism.app/getting-started/config-options/.
ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_TAG=$BUILD_TAG \
DOCKER_ENV="develop" \
NODE_ENV="production" \
PS1="\u@$BUILD_TAG:\w\$ " \
PATH="/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin:/scripts:/usr/local/go/bin:/go/bin:/opt/photoprism/bin" \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_CPP_MIN_LOG_LEVEL=1 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \
CGO_CFLAGS="-g -O2 -Wno-return-local-addr" \
PROG="photoprism" \
S6_KEEP_ENV=1 \
S6_VERBOSITY=0 \
S6_LOGGING=0
# Copy scripts and package sources config.
COPY --chown=root:root --chmod=755 /scripts/dist/ /scripts/
COPY --chown=root:root --chmod=644 /.my.cnf /etc/my.cnf
# Update base image and add dependencies.
RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Install-Recommends "false";' > /etc/apt/apt.conf.d/80recommends && \
echo 'APT::Install-Suggests "false";' > /etc/apt/apt.conf.d/80suggests && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get -qq install \
libc6 libbsd-dev ca-certificates bash sudo nano tzdata gpg make zip unzip wget curl rsync \
xz-utils avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 exiftool sqlite3 postgresql-client \
ffmpeg imagemagick libvips-dev rawtherapee libjxl-dev libjxl-tools libffmpeg-nvenc-dev librav1e-dev \
libswscale-dev libavfilter-extra libavformat-extra libavcodec-extra x264 x265 libde265-dev libaom-dev \
libvpx-dev libwebm-dev libjpeg-dev libmatroska-dev libdvdread-dev libdav1d-dev libsharpyuv0 \
va-driver-all libva2 iputils-ping dnsutils libmagic-mgc \
&& \
apt-get -qq install \
build-essential software-properties-common pkg-config apt-utils \
gcc g++ git autoconf automake cmake gettext apache2-utils davfs2 \
chrpath tree ripgrep fd-find bat eza git-delta shellcheck shfmt fzf xxd \
libtool libjpeg-dev libpng-dev libwebp-dev libavcodec-dev \
libx264-dev libx265-dev libaom-dev libvpx-dev libwebm-dev libxft-dev \
libc6-dev libhdf5-serial-dev libzmq3-dev libssl-dev libnss3 \
libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev fonts-roboto \
librsvg2-bin ghostscript gsfonts pdf2svg ps2eps libsharpyuv-dev \
&& \
ln -sf /usr/bin/fdfind /usr/local/bin/fd && \
ln -sf /usr/bin/batcat /usr/local/bin/bat && \
ln -sf /usr/bin/python3 /usr/local/bin/python && \
/scripts/install-nodejs.sh && \
/scripts/install-mariadb.sh mariadb-client && \
/scripts/install-tensorflow.sh && \
/scripts/install-darktable.sh && \
/scripts/install-yt-dlp.sh && \
/scripts/install-libheif.sh && \
/scripts/install-chrome.sh && \
echo "ALL ALL=(ALL) NOPASSWD:SETENV: ALL" >> /etc/sudoers.d/all && \
mkdir -p /etc/skel/.config/go/telemetry && \
echo 'off 2025-01-03' > '/etc/skel/.config/go/telemetry/mode' && \
cp -r /etc/skel/.config /root/.config && \
/scripts/install-go.sh && \
/scripts/install-go-tools.sh && \
echo 'alias go=richgo ll="ls -alh"' >> /etc/skel/.bashrc && \
echo 'export PS1="\u@$DOCKER_TAG:\w\$ "' >> /etc/skel/.bashrc && \
/scripts/install-dircolors.sh && \
cp /etc/skel/.bashrc /root/.bashrc && \
cp /scripts/convert/policy.xml /etc/ImageMagick-7/policy.xml && \
/scripts/create-users.sh && \
install -d -m 0777 -o 1000 -g 1000 \
/photoprism/originals \
/photoprism/import \
/photoprism/storage \
/photoprism/storage/sidecar \
/photoprism/storage/albums \
/photoprism/storage/backups \
/photoprism/storage/config \
/photoprism/storage/cache && \
/scripts/install-s6.sh && \
apt modernize-sources && \
/scripts/cleanup.sh
# Download machine learning models and test data.
RUN mkdir /tmp/photoprism && \
wget "https://dl.photoprism.app/tensorflow/nsfw.zip?${BUILD_TAG}" -O /tmp/photoprism/nsfw.zip && \
wget "https://dl.photoprism.app/tensorflow/nasnet.zip?${BUILD_TAG}" -O /tmp/photoprism/nasnet.zip && \
wget "https://dl.photoprism.app/tensorflow/facenet.zip?${BUILD_TAG}" -O /tmp/photoprism/facenet.zip && \
wget "https://dl.photoprism.app/qa/testdata.zip?${BUILD_TAG}" -O /tmp/photoprism/testdata.zip
# Set default working directory.
WORKDIR "/go/src/github.com/photoprism/photoprism"
# Expose the following container ports:
# - 2342 (HTTP)
# - 2343 (Acceptance Tests)
# - 2442 (HTTP)
# - 2443 (HTTPS)
# - 9515 (Chromedriver)
# - 40000 (Go Debugger)
EXPOSE 2342 2343 2442 2443 9515 40000
# Set default entrypoint and command.
ENTRYPOINT ["/init"]
CMD ["/scripts/cmd.sh", "tail", "-f", "/dev/null"]

View File

@@ -46,9 +46,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_VERSION=1.15.2 \
TF_CPP_MIN_LOG_LEVEL=4 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \
@@ -92,8 +91,7 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
PHOTOPRISM_AUTO_INDEX="300" \
PHOTOPRISM_AUTO_IMPORT="-1" \
PHOTOPRISM_INIT="https" \
S6_KEEP_ENV=0 \
S6_VERBOSITY=0 \
S6_KEEP_ENV=1 \
S6_LOGGING=0
# Copy dist files, scripts, and debian backports sources list.
@@ -116,7 +114,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
libvpx-dev libwebm-dev libjpeg-dev libmatroska-dev libdvdread-dev libdav1d-dev libsharpyuv0 \
iputils-ping dnsutils \
&& \
/scripts/install-yt-dlp.sh && \
/scripts/install-libheif.sh && \
echo 'alias ll="ls -alh"' >> /etc/skel/.bashrc && \
echo 'export PS1="\u@$DOCKER_TAG:\w\$ "' >> /etc/skel/.bashrc && \
@@ -134,8 +131,6 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
/photoprism/storage/config \
/photoprism/storage/cache && \
/scripts/install-s6.sh && \
ln -sf /scripts/services/photoprism /etc/s6-overlay/s6-rc.d/photoprism && \
touch /etc/s6-overlay/s6-rc.d/user/contents.d/photoprism && \
/scripts/cleanup.sh
# Set default working directory.
@@ -146,3 +141,4 @@ EXPOSE 2342 2443
# Set default entrypoint and command.
ENTRYPOINT ["/init"]
CMD ["/scripts/cmd.sh", "/opt/photoprism/bin/photoprism", "start"]

View File

@@ -41,9 +41,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_ENV="prod" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \

View File

@@ -41,9 +41,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_ENV="prod" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \

View File

@@ -44,9 +44,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \

View File

@@ -44,9 +44,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \

View File

@@ -42,9 +42,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_ENV="prod" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \
@@ -91,7 +90,6 @@ COPY --chown=root:root --chmod=755 /scripts/dist/ /scripts/
# Update pre-installed packages.
RUN apt-get update && \
apt-get -qq dist-upgrade && \
yt-dlp -U && \
/scripts/cleanup.sh
# Set default working directory.
@@ -102,3 +100,6 @@ EXPOSE 2342 2443
# Copy app files.
COPY --from=build --chown=root:root --chmod=755 /opt/photoprism/ /opt/photoprism
# Start app.
CMD ["/scripts/cmd.sh", "/opt/photoprism/bin/photoprism", "start"]

View File

@@ -42,9 +42,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_ENV="prod" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \

View File

@@ -42,9 +42,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_ENV="prod" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \

View File

@@ -42,9 +42,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_ENV="prod" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \

View File

@@ -42,9 +42,8 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_ENV="prod" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
TF_CPP_MIN_LOG_LEVEL="2" \
MALLOC_ARENA_MAX="4" \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \
@@ -101,3 +100,6 @@ EXPOSE 2342 2443
# Copy app files.
COPY --from=build --chown=root:root --chmod=755 /opt/photoprism/ /opt/photoprism
# Start app.
CMD ["/scripts/cmd.sh", "/opt/photoprism/bin/photoprism", "start"]

View File

@@ -1,104 +0,0 @@
##################################################### BUILD STAGE ######################################################
FROM photoprism/develop:plucky AS build
# Copyright © 2018 - 2025 PhotoPrism UG. All rights reserved.
#
# Questions? Email us at hello@photoprism.app or visit our website to learn
# more about our team, products and services: https://www.photoprism.app/
# Declare build parameters.
ARG TARGETARCH
ARG TARGETPLATFORM
ARG BUILD_TAG
# Copy source to image.
WORKDIR "/go/src/github.com/photoprism/photoprism"
COPY . .
# Build app.
RUN make all install DESTDIR=/opt/photoprism
################################################## PRODUCTION STAGE ####################################################
#### Base Image: Ubuntu 25.04 (Plucky Puffin)
FROM photoprism/develop:plucky-slim
# Add Open Container Initiative (OCI) annotations.
# See: https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="PhotoPrism® (Ubuntu 25.04)"
LABEL org.opencontainers.image.description="Ubuntu 25.04 (Plucky Puffin)"
LABEL org.opencontainers.image.url="https://hub.docker.com/r/photoprism/photoprism"
LABEL org.opencontainers.image.source="https://github.com/photoprism/photoprism"
LABEL org.opencontainers.image.documentation="https://docs.photoprism.app/getting-started/"
LABEL org.opencontainers.image.authors="PhotoPrism UG <hello@photoprism.app>"
LABEL org.opencontainers.image.vendor="PhotoPrism UG"
# Declare build parameters.
ARG TARGETARCH
ARG BUILD_TAG
# Set environment variables, see https://docs.photoprism.app/getting-started/config-options/.
ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_TAG=$BUILD_TAG \
DOCKER_ENV="prod" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL=4 \
TF_ENABLE_ONEDNN_OPTS=1 \
MALLOC_ARENA_MAX=2 \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \
PHOTOPRISM_ORIGINALS_PATH="/photoprism/originals" \
PHOTOPRISM_STORAGE_PATH="/photoprism/storage" \
PHOTOPRISM_BACKUP_PATH="/photoprism/storage/backups" \
PHOTOPRISM_LOG_FILENAME="/photoprism/storage/photoprism.log" \
PHOTOPRISM_PID_FILENAME="/photoprism/storage/photoprism.pid" \
PHOTOPRISM_DEBUG="false" \
PHOTOPRISM_PUBLIC="false" \
PHOTOPRISM_READONLY="false" \
PHOTOPRISM_UPLOAD_NSFW="true" \
PHOTOPRISM_DETECT_NSFW="false" \
PHOTOPRISM_EXPERIMENTAL="false" \
PHOTOPRISM_SITE_URL="http://localhost:2342/" \
PHOTOPRISM_SITE_CAPTION="AI-Powered Photos App" \
PHOTOPRISM_SITE_DESCRIPTION="" \
PHOTOPRISM_SITE_AUTHOR="" \
PHOTOPRISM_HTTP_HOST="0.0.0.0" \
PHOTOPRISM_HTTP_PORT=2342 \
PHOTOPRISM_DATABASE_DRIVER="sqlite" \
PHOTOPRISM_DATABASE_SERVER="" \
PHOTOPRISM_DATABASE_NAME="photoprism" \
PHOTOPRISM_DATABASE_USER="photoprism" \
PHOTOPRISM_DATABASE_PASSWORD="" \
PHOTOPRISM_DISABLE_CHOWN="false" \
PHOTOPRISM_DISABLE_WEBDAV="false" \
PHOTOPRISM_DISABLE_SETTINGS="false" \
PHOTOPRISM_DISABLE_BACKUPS="false" \
PHOTOPRISM_DISABLE_EXIFTOOL="false" \
PHOTOPRISM_DISABLE_PLACES="false" \
PHOTOPRISM_DISABLE_TENSORFLOW="false" \
PHOTOPRISM_DISABLE_FACES="false" \
PHOTOPRISM_DISABLE_CLASSIFICATION="false" \
PHOTOPRISM_RAW_PRESETS="false" \
PHOTOPRISM_THUMB_UNCACHED="false" \
PHOTOPRISM_AUTO_INDEX="300" \
PHOTOPRISM_AUTO_IMPORT="-1" \
PHOTOPRISM_INIT="https"
# Copy scripts.
COPY --chown=root:root --chmod=755 /scripts/dist/ /scripts/
# Update pre-installed packages.
RUN apt-get update && \
apt-get -qq dist-upgrade && \
yt-dlp -U && \
/scripts/cleanup.sh
# Set default working directory.
WORKDIR /photoprism
# Expose HTTP(S) ports.
EXPOSE 2342 2443
# Copy app files.
COPY --from=build --chown=root:root --chmod=755 /opt/photoprism/ /opt/photoprism

View File

@@ -0,0 +1,27 @@
build --action_env PYTHON_BIN_PATH="/usr/bin/python3"
build --action_env PYTHON_LIB_PATH="/usr/lib/python3/dist-packages"
build --python_path="/usr/bin/python3"
build --action_env TF_NEED_OPENCL_SYCL="0"
build --action_env TF_NEED_ROCM="0"
build --action_env TF_NEED_CUDA="0"
build --action_env TF_NEED_TENSORRT="0"
build --action_env CUDA_TOOLKIT_PATH="/usr/local/cuda"
build --action_env TF_CUDA_COMPUTE_CAPABILITIES="5.3"
build --action_env TF_CUDA_CLANG="0"
build --action_env TF_CUDA_VERSION="10"
build --action_env GCC_HOST_COMPILER_PATH="/usr/bin/gcc-4.8"
build --verbose_failures
build:opt --copt=-Wno-sign-compare
build:opt --conlyopt=-std=c11
build:opt --cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0"
build:opt --host_conlyopt=-std=c11
build:opt --host_cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0"
build:opt --host_copt=-Wno-sign-compare
build:opt --define with_default_optimizations=true
build:v2 --define=tf_api_version=2
build:xla --define with_xla_support=true
test --flaky_test_attempts=3
test --test_size_filters=small,medium
test --test_tag_filters=-benchmark-test,-no_oss,-oss_serial
test --build_tag_filters=-benchmark-test,-no_oss
build --action_env TF_CONFIGURE_IOS="0"

View File

@@ -1,13 +1,10 @@
FROM ubuntu:22.04
FROM ubuntu:18.04
LABEL maintainer="PhotoPrism UG <hello@photoprism.app>"
ENV DEBIAN_FRONTEND=noninteractive
ENV TMP=/tmp
# see https://docs.docker.com/build/building/variables/#env-usage-example
ARG TF_VERSION=2.18.0
ENV TF_VERSION=$TF_VERSION
ENV DEBIAN_FRONTEND noninteractive
ENV TMP /tmp
ENV EXTRA_BAZEL_ARGS "--host_javabase=@local_jdk//:jdk"
# apt default settings
RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
@@ -20,32 +17,47 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
# Install dev / build dependencies
RUN apt-get update && apt-get upgrade && \
apt-get install \
build-essential \
python3 \
ca-certificates \
build-essential \
autoconf \
automake \
libtool \
g++-4.8 \
gcc-4.8 \
libc6-dev \
zlib1g-dev \
libssl-dev \
curl \
chrpath \
pkg-config \
unzip \
zip \
make \
nano \
wget \
git \
lsb-release \
software-properties-common \
gnupg \
jq \
nano
libtool \
python3 \
python3-git \
openjdk-8-jdk
# Install bazelisk and llvm
RUN wget https://apt.llvm.org/llvm.sh && chmod u+x llvm.sh && \
./llvm.sh 17 && rm llvm.sh && \
ln -s /usr/bin/python3 /usr/bin/python && \
ln -s /usr/bin/clang-17 /usr/bin/clang && \
ln -s /usr/bin/clang++-17 /usr/bin/clang++ && \
ln -s /usr/bin/clang-cpp /usr/bin/clang-cpp && \
export BAZELISK_VERSION=$(curl -L https://api.github.com/repos/bazelbuild/bazelisk/releases/latest | jq -r .tag_name) && \
curl -L https://github.com/bazelbuild/bazelisk/releases/download/${BAZELISK_VERSION}/bazelisk-linux-amd64 -o /usr/bin/bazel && \
chmod +x /usr/bin/bazel && \
mkdir -p /home/tensorflow
# Use GCC 4.8 and Python 3 as default
# See https://www.tensorflow.org/install/source#tested_build_configurations
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 10 && \
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 10 && \
update-alternatives --install /usr/bin/python python /usr/bin/python3.6 10
# Download Bazel & TensorFlow
WORKDIR "/home/tensorflow"
RUN wget https://github.com/tensorflow/tensorflow/archive/v1.15.2.tar.gz
RUN tar -xzf v1.15.2.tar.gz
# Install Bazel
RUN wget https://github.com/bazelbuild/bazel/releases/download/0.24.1/bazel-0.24.1-linux-x86_64
RUN mv bazel-0.24.1-linux-x86_64 /usr/local/bin/bazel && chmod 755 /usr/local/bin/bazel
# Configure TensorFlow
WORKDIR "/home/tensorflow/tensorflow-1.15.2"
COPY ./*.sh ./
COPY ./.tf_configure.bazelrc .tf_configure.bazelrc
COPY ./Makefile Makefile

View File

@@ -1,26 +1,20 @@
all: libtensorflow libtensorflow-ssse3 libtensorflow-avx libtensorflow-avx2 libtensorflow-vnni libtensorflow-avx512
# Downloads the TensorFlow source code from GitHub and extracts it to a subdirectory:
TF_VERSION=1.15.2
# -march see https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/i386-and-x86-64-Options.html
all: libtensorflow libtensorflow-avx libtensorflow-avx2
patch:
git apply tensorflow-$(TF_VERSION).diff
download:
wget https://github.com/tensorflow/tensorflow/archive/v$(TF_VERSION).tar.gz
tar -xzf v$(TF_VERSION).tar.gz
cp Makefile *.sh tensorflow-$(TF_VERSION)
# Clang command line argument reference:
# https://clang.llvm.org/docs/ClangCommandLineReference.html#x86
cp .tf_configure.bazelrc Makefile *.sh tensorflow-$(TF_VERSION)
libtensorflow:
bazel build -c opt //tensorflow:libtensorflow.so --copt=-O2 --copt=-mno-avx --copt=-mno-avx2 --copt=-mno-fma --copt=-march=x86-64
./create_archive.sh amd64 $(TF_VERSION)
libtensorflow-ssse3:
bazel build -c opt //tensorflow:libtensorflow.so --copt=-O2 --copt=-mno-avx --copt=-mno-avx2 --copt=-mno-fma --copt=-march=core2
./create_archive.sh amd64-ssse3 $(TF_VERSION)
bazel build --jobs 2 --config=opt //tensorflow:libtensorflow.so
./create_archive.sh linux-cpu $(TF_VERSION)
libtensorflow-avx:
bazel build -c opt //tensorflow:libtensorflow.so --copt=-O2 --copt=-mavx
./create_archive.sh amd64-avx $(TF_VERSION)
bazel build --jobs 2 --config=opt //tensorflow:libtensorflow.so --copt=-march=core-avx-i --host_copt=-march=core-avx-i
./create_archive.sh linux-avx $(TF_VERSION)
libtensorflow-avx2:
bazel build -c opt //tensorflow:libtensorflow.so --copt=-O2 --copt=-mavx2 --copt=-mfma --copt=-mfpmath=both --copt=-msse4.2
./create_archive.sh amd64-avx2 $(TF_VERSION)
libtensorflow-vnni:
bazel build -c opt //tensorflow:libtensorflow.so --copt=-O2 --copt=-mavx2 --copt=-mavxvnni --copt=-mfma --copt=-mfpmath=both --copt=-msse4.2
./create_archive.sh amd64-vnni $(TF_VERSION)
libtensorflow-avx512:
bazel build -c opt //tensorflow:libtensorflow.so --copt=-O2 --copt=-mavx2 --copt=-mavx512f --copt=-mavx512vnni --copt=-mfma --copt=-mfpmath=both --copt=-msse4.2
./create_archive.sh amd64-avx512 $(TF_VERSION)
bazel build --jobs 2 --config=opt //tensorflow:libtensorflow.so --copt=-march=core-avx2 --host_copt=-march=core-avx2
./create_archive.sh linux-avx2 $(TF_VERSION)

View File

@@ -0,0 +1,31 @@
build --action_env PYTHON_BIN_PATH="/usr/bin/python3"
build --action_env PYTHON_LIB_PATH="/usr/lib/python3/dist-packages"
build --python_path="/usr/bin/python3"
build --action_env TF_NEED_OPENCL_SYCL="0"
build --action_env TF_NEED_ROCM="0"
build --action_env TF_NEED_CUDA="0"
build --action_env TF_NEED_TENSORRT="0"
build --action_env CUDA_TOOLKIT_PATH="/usr/local/cuda"
build --action_env TF_CUDA_COMPUTE_CAPABILITIES="5.3"
build --action_env TF_CUDA_CLANG="0"
build --action_env TF_CUDA_VERSION="10"
build --action_env GCC_HOST_COMPILER_PATH="/usr/bin/gcc-4.8"
build --verbose_failures
build:opt --copt=-march=armv8-a
build:opt --copt=-Wno-sign-compare
build:opt --conlyopt=-std=c11
build:opt --conlyopt=-D_XOPEN_SOURCE=600
build:opt --cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0"
build:opt --host_copt=-march=armv8-a
build:opt --host_conlyopt=-std=c11
build:opt --host_conlyopt=-D_XOPEN_SOURCE=600
build:opt --host_cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0"
build:opt --host_copt=-Wno-sign-compare
build:opt --define with_default_optimizations=true
build:v2 --define=tf_api_version=2
build:xla --define with_xla_support=true
test --flaky_test_attempts=3
test --test_size_filters=small,medium
test --test_tag_filters=-benchmark-test,-no_oss,-oss_serial
test --build_tag_filters=-benchmark-test,-no_oss
build --action_env TF_CONFIGURE_IOS="0"

View File

@@ -1,13 +1,10 @@
FROM ubuntu:22.04
FROM ubuntu:18.04
LABEL maintainer="PhotoPrism UG <hello@photoprism.app>"
ENV DEBIAN_FRONTEND=noninteractive
ENV TMP=/tmp
# see https://docs.docker.com/build/building/variables/#env-usage-example
ARG TF_VERSION=2.18.0
ENV TF_VERSION=$TF_VERSION
ENV DEBIAN_FRONTEND noninteractive
ENV TMP /tmp
ENV EXTRA_BAZEL_ARGS "--host_javabase=@local_jdk//:jdk"
# apt default settings
RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
@@ -20,32 +17,48 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
# Install dev / build dependencies
RUN apt-get update && apt-get upgrade && \
apt-get install \
build-essential \
python3 \
ca-certificates \
build-essential \
autoconf \
automake \
libtool \
g++-4.8 \
gcc-4.8 \
libc6-dev \
zlib1g-dev \
libssl-dev \
curl \
chrpath \
pkg-config \
unzip \
zip \
make \
nano \
wget \
git \
lsb-release \
software-properties-common \
gnupg \
jq \
nano
libtool \
python3 \
python3-git \
openjdk-8-jdk
# Install bazelisk and llvm
RUN wget https://apt.llvm.org/llvm.sh && chmod u+x llvm.sh && \
./llvm.sh 17 && rm llvm.sh && \
ln -s /usr/bin/python3 /usr/bin/python && \
ln -s /usr/bin/clang-17 /usr/bin/clang && \
ln -s /usr/bin/clang++-17 /usr/bin/clang++ && \
ln -s /usr/bin/clang-cpp /usr/bin/clang-cpp && \
export BAZELISK_VERSION=$(curl -L https://api.github.com/repos/bazelbuild/bazelisk/releases/latest | jq -r .tag_name) && \
curl -L https://github.com/bazelbuild/bazelisk/releases/download/${BAZELISK_VERSION}/bazelisk-linux-arm64 -o /usr/bin/bazel && \
chmod +x /usr/bin/bazel && \
mkdir -p /home/tensorflow
# Use GCC 4.8 and Python 3 as default
# See https://www.tensorflow.org/install/source#tested_build_configurations
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 10 && \
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 10 && \
update-alternatives --install /usr/bin/python python /usr/bin/python3.6 10
# Download Bazel & TensorFlow
WORKDIR "/home/tensorflow"
RUN wget https://github.com/tensorflow/tensorflow/archive/v1.15.2.tar.gz
RUN tar -xzf v1.15.2.tar.gz
# Install Bazel
# RUN wget https://github.com/bazelbuild/bazel/releases/download/0.24.1/bazel-0.24.1-linux-x86_64
RUN wget https://github.com/guysoft/bazel-bin/raw/master/bazel-0.24.1-aarch64
RUN mv bazel-0.24.1-aarch64 /usr/local/bin/bazel && chmod 755 /usr/local/bin/bazel
# Configure TensorFlow
WORKDIR "/home/tensorflow/tensorflow-1.15.2"
COPY ./*.sh ./
COPY ./Makefile Makefile
COPY ./.tf_configure.bazelrc .tf_configure.bazelrc
COPY ./Makefile Makefile

View File

@@ -1,11 +1,10 @@
TF_VERSION=1.15.2
# -march see https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/i386-and-x86-64-Options.html
all: libtensorflow
# Downloads the TensorFlow source code from GitHub and extracts it to a subdirectory:
download:
wget https://github.com/tensorflow/tensorflow/archive/v$(TF_VERSION).tar.gz
tar -xzf v$(TF_VERSION).tar.gz
cp Makefile *.sh tensorflow-$(TF_VERSION)
# Clang command line argument reference:
# https://clang.llvm.org/docs/ClangCommandLineReference.html#aarch64
patch:
git apply tensorflow-$(TF_VERSION).diff
libtensorflow:
bazel build -c opt //tensorflow:libtensorflow.so --copt=-O2 --copt=-march=armv8-a
bazel build --jobs 2 --config=opt //tensorflow:libtensorflow.so --copt=-march=armv8-a --host_copt=-march=armv8-a
./create_archive.sh arm64 $(TF_VERSION)

View File

@@ -10,17 +10,11 @@ echo "Creating 'libtensorflow-$1-$2.tar.gz'...";
rm -rf tmp
mkdir -p tmp/lib/
mkdir -p tmp/include/tensorflow/c/eager/
mkdir -p tmp/include/tensorflow/core/platform
mkdir -p tmp/include/tsl/platform
mkdir -p tmp/include/xla/tsl/c
cp bazel-bin/tensorflow/libtensorflow.so.$2 tmp/lib/libtensorflow.so
cp bazel-bin/tensorflow/libtensorflow_framework.so.$2 tmp/lib/libtensorflow_framework.so
cp tensorflow/c/eager/c_api.h tmp/include/tensorflow/c/eager/
cp tensorflow/c/c_api.h tensorflow/c/c_api_experimental.h LICENSE tmp/include/tensorflow/c/
(cd tmp && tar -czf ../libtensorflow-$1-$2.tar.gz .)
du -h libtensorflow-$1-$2.tar.gz
cp -av bazel-bin/tensorflow/libtensorflow* tmp/lib/
cp tensorflow/c/eager/*.h tmp/include/tensorflow/c/eager/
cp tensorflow/c/*.h LICENSE tmp/include/tensorflow/c/
cp tensorflow/core/platform/*.h tmp/include/tensorflow/core/platform
cp third_party/xla/third_party/tsl/tsl/platform/*.h tmp/include/tsl/platform
cp third_party/xla/xla/tsl/c/*.h tmp/include/xla/tsl/c
(cd tmp && tar --exclude=*.params -czf /build/libtensorflow-$1-$2.tar.gz .)
du -h /build/libtensorflow-$1-$2.tar.gz
echo "Done."
echo "Done"

View File

@@ -10,17 +10,11 @@ echo "Creating 'libtensorflow-$1-$2.tar.gz'...";
rm -rf tmp
mkdir -p tmp/lib/
mkdir -p tmp/include/tensorflow/c/eager/
mkdir -p tmp/include/tensorflow/core/platform
mkdir -p tmp/include/tsl/platform
mkdir -p tmp/include/xla/tsl/c
cp bazel-bin/tensorflow/libtensorflow.so.$2 tmp/lib/libtensorflow.so
cp bazel-bin/tensorflow/libtensorflow_framework.so.$2 tmp/lib/libtensorflow_framework.so
cp tensorflow/c/eager/c_api.h tmp/include/tensorflow/c/eager/
cp tensorflow/c/c_api.h tensorflow/c/c_api_experimental.h LICENSE tmp/include/tensorflow/c/
(cd tmp && tar -czf ../libtensorflow-$1-$2.tar.gz .)
du -h libtensorflow-$1-$2.tar.gz
cp -av bazel-bin/tensorflow/libtensorflow* tmp/lib/
cp tensorflow/c/eager/*.h tmp/include/tensorflow/c/eager/
cp tensorflow/c/*.h LICENSE tmp/include/tensorflow/c/
cp tensorflow/core/platform/*.h tmp/include/tensorflow/core/platform
cp third_party/xla/third_party/tsl/tsl/platform/*.h tmp/include/tsl/platform
cp third_party/xla/xla/tsl/c/*.h tmp/include/xla/tsl/c
(cd tmp && tar --exclude=*.params -czf /build/libtensorflow-$1-$2.tar.gz .)
du -h /build/libtensorflow-$1-$2.tar.gz
echo "Done."
echo "Done"

View File

@@ -1,9 +0,0 @@
ARG BUILDER_SHA2=06040763c500bd2ebaaa4585d4729c88d2c8ccec94baa7fbe9bbe3dc2827d79d
FROM gcr.io/tensorflow-testing/ml-devinfra-linux-aarch64-cross-compile:infrastructure-public-image-${BUILDER_SHA2}
# see https://docs.docker.com/build/building/variables/#env-usage-example
ARG TF_VERSION=2.18.0
ENV TF_VERSION=$TF_VERSION
COPY ./create_archive.sh .
COPY ./Makefile Makefile

View File

@@ -1,10 +0,0 @@
# -march see https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/i386-and-x86-64-Options.html
all: libtensorflow
download:
wget https://github.com/tensorflow/tensorflow/archive/v$(TF_VERSION).tar.gz
tar -xzf v$(TF_VERSION).tar.gz
cp Makefile *.sh tensorflow-$(TF_VERSION)
libtensorflow:
bazel build -c opt --config cross_compile_linux_arm64 //tensorflow:libtensorflow.so
./create_archive.sh arm64 $(TF_VERSION)

View File

@@ -1,14 +0,0 @@
#!/bin/bash
DEFAULT_TF_VERSION=2.18.0
if [[ "$#" -ge 1 ]]; then
TF_VERSION=$1
elif [[ -z "$TF_VERSION" ]]; then
TF_VERSION=$DEFAULT_TF_VERSION
fi
SHA2_VERSION=$(curl -L https://raw.githubusercontent.com/tensorflow/tensorflow/refs/tags/v${TF_VERSION}/tensorflow/tools/toolchains/cross_compile/config/BUILD | \
grep container-image | awk -F'@' '{ print $2 }' | awk -F':' '{ print $2 }' | tr -d '",')
docker build --build-arg BUILDER_SHA2=$SHA2_VERSION --build-arg TF_VERSION=$TF_VERSION -t photoprism/tensorflow:$TF_VERSION-cross .

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env bash
if [[ -z $1 ]] || [[ -z $2 ]]; then
echo "Usage: $0 [platform] [tf-version]" 1>&2
exit 1
fi
echo "Creating 'libtensorflow-$1-$2.tar.gz'...";
rm -rf tmp
mkdir -p tmp/lib/
mkdir -p tmp/include/tensorflow/c/eager/
mkdir -p tmp/include/tensorflow/core/platform
mkdir -p tmp/include/tsl/platform
mkdir -p tmp/include/xla/tsl/c
cp -av bazel-bin/tensorflow/libtensorflow* tmp/lib/
cp tensorflow/c/eager/*.h tmp/include/tensorflow/c/eager/
cp tensorflow/c/*.h LICENSE tmp/include/tensorflow/c/
cp tensorflow/core/platform/*.h tmp/include/tensorflow/core/platform
cp third_party/xla/third_party/tsl/tsl/platform/*.h tmp/include/tsl/platform
cp third_party/xla/xla/tsl/c/*.h tmp/include/xla/tsl/c
(cd tmp && tar --exclude=*.params -czf /build/libtensorflow-$1-$2.tar.gz .)
du -h /build/libtensorflow-$1-$2.tar.gz
echo "Done."

View File

@@ -0,0 +1,36 @@
build --action_env PYTHON_BIN_PATH="/usr/bin/python"
build --action_env PYTHON_LIB_PATH="/usr/lib/python2.7/dist-packages"
build --python_path="/usr/bin/python"
build --action_env TF_NEED_OPENCL_SYCL="0"
build --action_env TF_NEED_ROCM="0"
build --action_env TF_NEED_CUDA="1"
build --action_env TF_NEED_TENSORRT="1"
build --action_env CUDA_TOOLKIT_PATH="/usr/local/cuda"
build --action_env TF_CUDA_COMPUTE_CAPABILITIES="5.3"
build --action_env TF_CUDA_CLANG="0"
build --action_env TF_CUDA_VERSION="10"
build --action_env GCC_HOST_COMPILER_PATH="/usr/bin/gcc"
build --config=cuda
build --config=nonccl
build --verbose_failures
build --jobs 2
build:opt --copt=-march=armv8-a
build:opt --copt=-Wno-sign-compare
build:opt --conlyopt=-std=c11
build:opt --conlyopt=-D_XOPEN_SOURCE=600
build:opt --cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0"
build:opt --host_copt=-march=armv8-a
build:opt --host_conlyopt=-std=c11
build:opt --host_conlyopt=-D_XOPEN_SOURCE=600
build:opt --host_cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0"
build:opt --host_copt=-Wno-sign-compare
build:opt --define with_default_optimizations=true
build:v2 --define=tf_api_version=2
build:xla --define with_xla_support=true
test --flaky_test_attempts=3
test --test_size_filters=small,medium
test --test_tag_filters=-benchmark-test,-no_oss,-oss_serial
test --build_tag_filters=-benchmark-test,-no_oss
test --test_tag_filters=-gpu
test --build_tag_filters=-gpu
build --action_env TF_CONFIGURE_IOS="0"

View File

@@ -1,10 +1,14 @@
TF_VERSION=1.15.2
# -march see https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/i386-and-x86-64-Options.html
all: libtensorflow
patch:
git apply tensorflow-$(TF_VERSION).diff
download:
wget https://github.com/tensorflow/tensorflow/archive/v$(TF_VERSION).tar.gz
tar -xzf v$(TF_VERSION).tar.gz
cp Makefile *.sh tensorflow-$(TF_VERSION)
cp .tf_configure.bazelrc Makefile ../*.sh tensorflow-$(TF_VERSION)
libtensorflow:
bazel build -c opt //tensorflow:libtensorflow.so --copt=-march=armv8-a
bazel build --jobs 2 --config=opt //tensorflow:libtensorflow.so
./create_archive.sh jetson-nano $(TF_VERSION)

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env bash
if [[ -z $1 ]] || [[ -z $2 ]]; then
echo "Usage: $0 [platform] [tf-version]" 1>&2
exit 1
fi
echo "Creating 'libtensorflow-$1-$2.tar.gz'...";
rm -rf tmp
mkdir -p tmp/lib/
mkdir -p tmp/include/tensorflow/c/eager/
mkdir -p tmp/include/tensorflow/core/platform
mkdir -p tmp/include/tsl/platform
mkdir -p tmp/include/xla/tsl/c
cp -av bazel-bin/tensorflow/libtensorflow* tmp/lib/
cp tensorflow/c/eager/*.h tmp/include/tensorflow/c/eager/
cp tensorflow/c/*.h LICENSE tmp/include/tensorflow/c/
cp tensorflow/core/platform/*.h tmp/include/tensorflow/core/platform
cp third_party/xla/third_party/tsl/tsl/platform/*.h tmp/include/tsl/platform
cp third_party/xla/xla/tsl/c/*.h tmp/include/xla/tsl/c
(cd tmp && tar --exclude=*.params -czf /build/libtensorflow-$1-$2.tar.gz .)
du -h /build/libtensorflow-$1-$2.tar.gz
echo "Done."

View File

@@ -0,0 +1,27 @@
build --action_env PYTHON_BIN_PATH="/usr/local/bin/python3"
build --action_env PYTHON_LIB_PATH="/usr/local/lib/python3.7/site-packages"
build --python_path="/usr/local/bin/python3"
build --action_env TF_NEED_OPENCL_SYCL="0"
build --action_env TF_NEED_ROCM="0"
build --action_env TF_NEED_CUDA="0"
build --action_env TF_NEED_TENSORRT="0"
build --action_env CUDA_TOOLKIT_PATH="/usr/local/cuda"
build --action_env TF_CUDA_COMPUTE_CAPABILITIES="5.3"
build --action_env TF_CUDA_CLANG="0"
build --action_env TF_CUDA_VERSION="10"
build --action_env GCC_HOST_COMPILER_PATH="/usr/bin/gcc"
build --verbose_failures
build:opt --copt=-Wno-sign-compare
build:opt --conlyopt=-std=c11
build:opt --cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0"
build:opt --host_conlyopt=-std=c11
build:opt --host_cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0"
build:opt --host_copt=-Wno-sign-compare
build:opt --define with_default_optimizations=true
build:v2 --define=tf_api_version=2
build:xla --define with_xla_support=true
test --flaky_test_attempts=3
test --test_size_filters=small,medium
test --test_tag_filters=-benchmark-test,-no_oss,-oss_serial
test --build_tag_filters=-benchmark-test,-no_oss
build --action_env TF_CONFIGURE_IOS="0"

View File

@@ -1,16 +1,20 @@
TF_VERSION=1.15.2
# -march see https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/i386-and-x86-64-Options.html
all: libtensorflow libtensorflow-avx libtensorflow-avx2
patch:
git apply tensorflow-$(TF_VERSION).diff
download:
wget https://github.com/tensorflow/tensorflow/archive/v$(TF_VERSION).tar.gz
tar -xzf v$(TF_VERSION).tar.gz
cp Makefile *.sh tensorflow-$(TF_VERSION)
cp .tf_configure.bazelrc Makefile ../*.sh tensorflow-$(TF_VERSION)
libtensorflow:
bazel build -c opt //tensorflow:libtensorflow.so
bazel build --jobs 2 --config=opt //tensorflow:libtensorflow.so
./create_archive.sh osx-cpu $(TF_VERSION)
libtensorflow-avx:
bazel build -c opt //tensorflow:libtensorflow.so --copt=-mavx
bazel build --jobs 2 --config=opt //tensorflow:libtensorflow.so --copt=-march=core-avx-i --host_copt=-march=core-avx-i
./create_archive.sh osx-avx $(TF_VERSION)
libtensorflow-avx2:
bazel build -c opt //tensorflow:libtensorflow.so --copt=-mavx2
./create_archive.sh osx-avx2 $(TF_VERSION)
bazel build --jobs 2 --config=opt //tensorflow:libtensorflow.so --copt=-march=core-avx2 --host_copt=-march=core-avx2
./create_archive.sh osx-avx2 $(TF_VERSION)

14
frontend/.eslintignore Normal file
View File

@@ -0,0 +1,14 @@
coverage/
node_modules/
tests/screenshots/
tests/acceptance/screenshots/
tests/upload-files/
*.html
.idea
.github
.tmp
.local
.cache
.var
!**/.eslintrc.js

108
frontend/.eslintrc.js Normal file
View File

@@ -0,0 +1,108 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
node: true,
mocha: true,
},
extends: [
"eslint:recommended",
"plugin:vue/recommended",
"plugin:prettier/recommended",
"plugin:vue/base",
"plugin:vuetify/base",
],
settings: {
"prettier/prettier": {
// Settings for how to process Vue SFC Blocks
SFCBlocks: {
template: false,
script: false,
style: false,
},
// Use prettierrc for prettier options or not (default: `true`)
usePrettierrc: true,
// Set the options for `prettier.getFileInfo`.
// @see https://prettier.io/docs/en/api.html#prettiergetfileinfofilepath-options
fileInfoOptions: {
// Path to ignore file (default: `'.prettierignore'`)
// Notice that the ignore file is only used for this plugin
ignorePath: ".testignore",
// Process the files in `node_modules` or not (default: `false`)
withNodeModules: false,
},
},
},
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
rules: {
// 'comma-dangle': ['error', 'always-multiline'],
"indent": ["error", 2, { SwitchCase: 1 }],
"linebreak-style": ["error", "unix"],
"quotes": [
"off",
"double",
{
avoidEscape: true,
allowTemplateLiterals: true,
},
],
"semi": ["error", "always"],
"no-unused-vars": ["warn"],
"no-console": 0,
"no-case-declarations": 0,
"no-prototype-builtins": 0,
"vue/no-v-text-v-html-on-component": 0,
"vue/no-v-model-argument": 0,
"vue/valid-model-definition": 0,
"vue/valid-attribute-name": 0,
"vue/singleline-html-element-content-newline": [
"off",
{
ignoreWhenNoAttributes: true,
ignoreWhenEmpty: true,
ignores: [
"pre",
"textarea",
"span",
"translate",
"a",
"v-icon",
"v-text-field",
"v-input",
"v-select",
"v-switch",
"v-checkbox",
"v-img",
],
externalIgnores: [],
},
],
"vue/first-attribute-linebreak": [
"error",
{
singleline: "ignore",
multiline: "ignore",
},
],
"prettier/prettier": [
"warn",
{
printWidth: 120,
semi: true,
singleQuote: false,
bracketSpacing: true,
trailingComma: "es5",
htmlWhitespaceSensitivity: "css",
quoteProps: "consistent",
proseWrap: "never",
},
],
},
};

View File

@@ -5,10 +5,8 @@ tests/acceptance/screenshots/
tests/upload-files/
*.html
.idea
.codex
.github
.tmp
.local
.cache
.gocache
.var

View File

@@ -1,109 +0,0 @@
PhotoPrism — Frontend CODEMAP
Purpose
- Help agents and contributors navigate the Vue 3 + Vuetify 3 app quickly and make safe changes.
- Use Makefile targets and scripts in `frontend/package.json` as sources of truth.
Quick Start
- Build once: `make -C frontend build`
- Watch for changes (inside dev container is fine):
- `make watch-js` from repo root, or
- `cd frontend && npm run watch`
- Unit tests (Vitest): `make vitest-watch` / `make vitest-coverage` or `cd frontend && npm run test`
Directory Map (src)
- `src/app.vue` — root component; UI shell
- `src/app.js` — app bootstrap: creates Vue app, installs Vuetify + plugins, configures router, mounts to `#app`
- `src/app/routes.js` — all route definitions (guards, titles, meta)
- `src/app/session.js``$config` and `$session` singletons wired from server-provided `window.__CONFIG__` and storage
- `src/common/*` — framework-agnostic helpers: `$api` (Axios), `$notify`, `$view`, `$event` (PubSub), i18n (`gettext`), util, fullscreen, map utils, websocket
- `src/component/*` — Vue components; `src/component/components.js` registers global components
- `src/page/*` — route views (Albums, Photos, Places, Settings, Admin, Discover, Help, Login, etc.)
- `src/model/*` — REST models; base `Rest` class (`model/rest.js`) wraps Axios CRUD for collections and entities
- `src/options/*` — UI/theme options, formats, auth options
- `src/css/*` — styles loaded by Webpack
- `src/locales/*` — gettext catalogs; extraction/compile scripts in `package.json`
Runtime & Plugins
- Vue 3 + Vuetify 3 (`createVuetify`) with MDI icons; themes from `src/options/themes.js`
- Router: Vue Router 4, history base at `$config.baseUri + "/library/"`
- I18n: `vue3-gettext` via `common/gettext.js`; extraction with `npm run gettext-extract`, compile with `npm run gettext-compile`
- HTML sanitization: `vue-3-sanitize` + `vue-sanitize-directive`
- Tooltips: `floating-vue`
- Video: HLS.js assigned to `window.Hls`
- PWA: `@lcdp/offline-plugin/runtime` installs when `baseUri === ""`
- WebSocket: `src/common/websocket.js` publishes `websocket.*` events, used by `$session` for client info
HTTP Client
- Axios instance: `src/common/api.js`
- Base URL: `window.__CONFIG__.apiUri` (or `/api/v1` in tests)
- Adds `X-Auth-Token`, `X-Client-Uri`, `X-Client-Version`
- Interceptors drive global progress notifications and token refresh via headers `X-Preview-Token`/`X-Download-Token`
Auth, Session, and Config
- `$session`: `src/common/session.js` — stores `X-Auth-Token` and `session.id` in storage; provides guards and default routes
- `$config`: `src/common/config.js` — reactive view of server config and user settings; sets theme, language, limits; exposes `deny()` for feature flags
- Route guards live in `src/app.js` (router `beforeEach`/`afterEach`) and use `$session` + `$config`
- `$view`: `src/common/view.js` — manages focus/scroll helpers; use `saveWindowScrollPos()` / `restoreWindowScrollPos()` when navigating so infinite-scroll pages land back where users left them; behaviour is covered by `tests/vitest/common/view.test.js`
Models (REST)
- Base class: `src/model/rest.js` provides `search`, `find`, `save`, `update`, `remove` for concrete models (`photo`, `album`, `label`, `subject`, etc.)
- Collection helpers: `src/model/collection.js` adds shared behaviors (for example `setCover`) used by collection-types such as albums and labels.
- Pagination headers used: `X-Count`, `X-Limit`, `X-Offset`
Routing Conventions
- Add pages under `src/page/<area>/...` and import them in `src/app/routes.js`
- Set `meta.requiresAuth`, `meta.admin`, and `meta.settings` as needed
- Use `meta.title` for translated titles; `router.afterEach` updates `document.title`
Theming & UI
- Themes: `src/options/themes.js` registered in Vuetify; default comes from `$config.values.settings.ui.theme`
- Global components: register in `src/component/components.js` when they are broadly reused
Testing
- Vitest config: `frontend/vitest.config.js` (Vue plugin, alias map to `src/*`), `tests/vitest/**/*`
- Run: `cd frontend && npm run test` (or `make test-js` from repo root)
- Acceptance: TestCafe configs in `frontend/tests/acceptance`; run against a live server
Build & Tooling
- Webpack is used for bundling; scripts in `frontend/package.json`:
- `npm run build` (prod), `npm run build-dev` (dev), `npm run watch`
- Lint/format: `npm run lint`, `npm run fmt`
- Security scan: `npm run security:scan` (checks `--ignore-scripts` and forbids `v-html`)
- Make targets (from repo root): `make build-js`, `make watch-js`, `make test-js`
Common HowTos
- Add a page
- Create `src/page/<name>.vue` (or nested directory)
- Add route in `src/app/routes.js` with `name`, `path`, `component`, and `meta`
- Use `$api` for data, `$notify` for UX, `$session` for guards
- `updateQuery(props)` helpers should return a boolean indicating whether a navigation was scheduled (recently standardised across pages); callers can bail early when `false`
- Add a REST model
- Create `src/model/<thing>.js` extending `Rest` and implement `static getCollectionResource()` + `static getModelName()`
- Use in pages/components for CRUD
- Call a backend endpoint
- Use `$api.get/post/put/delete` from `src/common/api.js`
- For auth: `$session.setAuthToken(token)` sets header; router guards redirect to `login` when needed
- Add translations
- Wrap strings with `$gettext(...)` / `$pgettext(...)`
- Extract: `npm run gettext-extract`; compile: `npm run gettext-compile`
Conventions & Safety
- Avoid `v-html`; use `v-sanitize` or `$util.sanitizeHtml()` (build enforces this)
- Keep big components lazy if needed; split views logically under `src/page`
- Respect aliases in `vitest.config.js` when importing (`app`, `common`, `component`, `model`, `options`, `page`)
Frequently Touched Files
- Bootstrap: `src/app.js`, `src/app.vue`
- Router: `src/app/routes.js`
- HTTP: `src/common/api.js`
- Session/Config: `src/common/session.js`, `src/common/config.js`
- Models: `src/model/rest.js` and concrete models (`photo.js`, `album.js`, ...)
- Global components: `src/component/components.js`
See Also
- Backend CODEMAP at repo root (`CODEMAP.md`) for API and server internals
- AGENTS.md for repo-wide rules and test tips

View File

@@ -3,58 +3,24 @@
# Questions? Email us at hello@photoprism.app or visit our website to learn
# more about our team, products and services: https://www.photoprism.app/
UID := $(shell id -u)
GID := $(shell id -g)
PWD := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
NODE_IMAGE ?= node:lts
define DOCKER_NPM
docker run --rm -it \
-v "$(PWD)":/app \
-w /app \
--user "$(UID):$(GID)" \
--read-only --tmpfs /tmp \
-v ~/.cache/npm:/npm-cache \
-e NPM_CONFIG_CACHE=/npm-cache \
-e NPM_CONFIG_AUDIT=false \
-e NPM_CONFIG_FUND=false \
-e NO_UPDATE_NOTIFIER=true \
--cap-drop ALL --security-opt no-new-privileges --pids-limit 256 \
$(NODE_IMAGE) sh -lc
endef
# Declare "make" targets.
all: install build
dep: install-npm install-testcafe install
dep-list:
npx npm-check-updates
help: list
list:
@awk '/^[[:alnum:]]+[^[:space:]]+:/ {printf "%s",substr($$1,1,length($$1)-1); if (match($$0,/#/)) {desc=substr($$0,RSTART+1); sub(/^[[:space:]]+/,"",desc); printf " - %s\n",desc} else printf "\n" }' "$(firstword $(MAKEFILE_LIST))"
notice:
@echo "Creating license report for frontend dependencies..."
license-report --only=prod --config=.report.json > NOTICE
install-npm:
# Keep scripts enabled for npm itself; split other globals and disable scripts for safety
sudo npm install -g npm@latest
sudo npm install -g --ignore-scripts --no-fund --no-audit --no-update-notifier npm-check-updates@latest license-report@latest
sudo npm install --unsafe-perm=true --allow-root -g npm@latest npm-check-updates@latest license-report@latest
install-testcafe:
npm install -g --ignore-scripts --no-fund --no-audit --no-update-notifier testcafe@latest
npm install -g testcafe@latest
install-eslint:
npm install -g --ignore-scripts --no-fund --no-audit --no-update-notifier eslint globals @eslint/eslintrc @eslint/js eslint-config-prettier eslint-formatter-pretty eslint-plugin-html eslint-plugin-import eslint-plugin-node eslint-plugin-prettier eslint-plugin-promise eslint-plugin-vue eslint-webpack-plugin vue-eslint-parser prettier
upgrade:
$(info Securely upgrading NPM dependencies...)
$(DOCKER_NPM) 'npx -y npm@latest update --save --package-lock --ignore-scripts --no-fund --no-audit --no-update-notifier && npx -y npm@latest install --ignore-scripts --no-audit --no-fund --no-update-notifier'
npm-install:
$(info Installing NPM dependencies...)
npm install --save --ignore-scripts --no-audit --no-fund --no-update-notifier
install: npm-install
npm-update:
$(info Updating NPM dependencies in package.lock and package-lock.json...)
npm update --save --package-lock --ignore-scripts --no-audit --no-fund --no-update-notifier
update: npm-update npm-install
security-check: # Scan for missing --ignore-scripts and unsafe v-html
npm run -s security:scan
npm install -g eslint@8 eslint-config-prettier eslint-config-standard eslint-formatter-pretty eslint-plugin-html eslint-plugin-import eslint-plugin-node eslint-plugin-prettier eslint-plugin-promise eslint-plugin-vue eslint-webpack-plugin vue-eslint-parser prettier
install:
npm install --no-update-notifier --no-audit
update:
npm update
watch:
npm run watch
build:
@@ -65,6 +31,8 @@ fmt:
npm run fmt
test:
npm run test
upgrade:
npm run upgrade
testcafe:
npm run testcafe
acceptance-local:

View File

@@ -1,102 +1,97 @@
Package License Copyright
------- ------- ---------
@babel/cli MIT The Babel Team (https://babel.dev/team)
@babel/core MIT The Babel Team (https://babel.dev/team)
@babel/plugin-transform-runtime MIT The Babel Team (https://babel.dev/team)
@babel/preset-env MIT The Babel Team (https://babel.dev/team)
@babel/register MIT The Babel Team (https://babel.dev/team)
@babel/runtime MIT The Babel Team (https://babel.dev/team)
@eslint/eslintrc MIT Nicholas C. Zakas
@eslint/js MIT n/a
@lcdp/offline-plugin MIT Le Comptoir Des Pharmacies <webmaster@lecomptoirdespharmacies.fr>
@mdi/font Apache-2.0 Austin Andrews
@testing-library/jest-dom MIT Ernesto Garcia <gnapse@gmail.com> (http://gnapse.github.io)
@testing-library/react MIT Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)
@vitejs/plugin-react MIT Evan You
@vitejs/plugin-vue MIT Evan You
@vitest/browser MIT n/a
@vitest/coverage-v8 MIT Anthony Fu <anthonyfu117@hotmail.com>
@vitest/ui MIT n/a
@vue/compiler-sfc MIT Evan You
@vue/language-server MIT n/a
@vue/test-utils MIT Lachlan Miller lachlan.miller.1990@outlook.com
@vvo/tzdb MIT Vincent Voyer <vincent@codeagain.com>
axios MIT Matt Zabriskie
axios-mock-adapter MIT Colin Timmermans <colintimmermans@gmail.com>
babel-loader MIT Luis Couto <hello@luiscouto.pt>
babel-plugin-istanbul BSD-3-Clause Thai Pangsakulyanont @dtinth
babel-plugin-polyfill-corejs3 MIT n/a
browserslist MIT Andrey Sitnik <andrey@sitnik.ru>
cheerio MIT Matt Mueller <mattmuelle@gmail.com>
core-js MIT Denis Pushkarev zloirock@zloirock.ru http://zloirock.ru
cross-env MIT Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)
css-loader MIT Tobias Koppers @sokra
cssnano MIT Ben Briggs beneb.info@gmail.com http://beneb.info
easygettext MIT Polyconseil
eslint MIT Nicholas C. Zakas <nicholas+npm@nczconsulting.com>
eslint-config-prettier MIT Simon Lydell
eslint-formatter-pretty MIT Sindre Sorhus sindresorhus@gmail.com https://sindresorhus.com
eslint-plugin-html ISC n/a
eslint-plugin-import MIT Ben Mosher <me@benmosher.com>
eslint-plugin-node MIT Toru Nagashima
eslint-plugin-prettier MIT Teddy Katz
eslint-plugin-vue MIT Toru Nagashima (https://github.com/mysticatea)
eslint-plugin-vuetify MIT Kael Watts-Deuchar <kaelwd@gmail.com>
eslint-webpack-plugin MIT Ricardo Gobbo de Souza <ricardogobbosouza@yahoo.com.br>
eventsource-polyfill MIT amvtek <devel@amvtek.com>
file-loader MIT Tobias Koppers @sokra
file-saver MIT Eli Grey <me@eligrey.com>
floating-vue MIT Guillaume Chau <guillaume.b.chau@gmail.com>
globals MIT Sindre Sorhus sindresorhus@gmail.com https://sindresorhus.com
hls.js Apache-2.0 n/a
i MIT Pavan Kumar Sunkara <pavan.sss1991@gmail.com> (pksunkara.github.com)
jsdom MIT n/a
luxon MIT Isaac Cambron
maplibre-gl BSD-3-Clause n/a
memoize-one MIT Alex Reardon <alexreardon@gmail.com>
mini-css-extract-plugin MIT Tobias Koppers @sokra
minimist MIT James Halliday mail@substack.net http://substack.net
node-storage-shim ISC Michael Nahkies
passive-events-support MIT Ignas Damunskis <ignas3run@gmail.com>
photoswipe MIT Dmytro Semenov (https://dimsemenov.com)
playwright Apache-2.0 Microsoft Corporation
postcss MIT Andrey Sitnik <andrey@sitnik.ru>
postcss-import MIT Maxime Thirouin
postcss-loader MIT Andrey Sitnik <andrey@sitnik.ru>
postcss-preset-env MIT-0 n/a
postcss-reporter MIT David Clark david.dave.clark@gmail.com https://davidtheclark.com
postcss-url MIT Maxime Thirouin
prettier MIT James Long
pubsub-js MIT Morgan Roderick morgan@roderick.dk http://roderick.dk
regenerator-runtime MIT Ben Newman <bn@cs.stanford.edu>
resolve-url-loader MIT bholloway
sanitize-html MIT Apostrophe Technologies, Inc.
sass MIT Natalie Weizenbaum nweiz@google.com https://github.com/nex3
sass-loader MIT J. Tangelder
server MIT Francisco Presencia <public@francisco.io> (https://francisco.io/)
sockette MIT Luke Edwards luke.edwards05@gmail.com lukeed.com
style-loader MIT Tobias Koppers @sokra
svg-url-loader MIT Hovhannes Babayan
tar ISC Isaac Z. Schlueter
url-loader MIT Tobias Koppers @sokra
util MIT Joyent http://www.joyent.com
vite-tsconfig-paths MIT aleclarson
vitest MIT Anthony Fu <anthonyfu117@hotmail.com>
vue MIT Evan You
vue-3-sanitize MIT Vannsl, Vanessa Otto <mail@vannsl.io>
vue-loader MIT Evan You
vue-loader-plugin ISC Ivan liu
vue-luxon MIT Cas Bloem
vue-router MIT Eduardo San Martin Morote posva13@gmail.com
vue-sanitize-directive MIT Leonardo Piccioni de Almeida leopiccionia@gmail.com
vue-style-loader MIT Evan You
vue3-gettext MIT Leo Zurbriggen
vuetify MIT John Leider john@vuetifyjs.com
webpack MIT Tobias Koppers @sokra
webpack-bundle-analyzer MIT Yury Grunin <grunin.ya@ya.ru>
webpack-cli MIT n/a
webpack-hot-middleware MIT Glen Mailer <glen@stainlessed.co.uk>
webpack-manifest-plugin MIT Dane Thurber <dane.thurber@gmail.com>
webpack-md5-hash MIT Kirill Ermolov
webpack-merge MIT Juho Vepsalainen <bebraw@gmail.com>
webpack-plugin-vuetify MIT Kael Watts-Deuchar
Package License Copyright
------- ------- ---------
@babel/cli MIT The Babel Team (https://babel.dev/team)
@babel/core MIT The Babel Team (https://babel.dev/team)
@babel/plugin-transform-runtime MIT The Babel Team (https://babel.dev/team)
@babel/preset-env MIT The Babel Team (https://babel.dev/team)
@babel/register MIT The Babel Team (https://babel.dev/team)
@babel/runtime MIT The Babel Team (https://babel.dev/team)
@lcdp/offline-plugin MIT Le Comptoir Des Pharmacies <webmaster@lecomptoirdespharmacies.fr>
@mdi/font Apache-2.0 Austin Andrews
@vue/compiler-sfc MIT Evan You
@vvo/tzdb MIT Vincent Voyer <vincent@codeagain.com>
axios MIT Matt Zabriskie
axios-mock-adapter MIT Colin Timmermans <colintimmermans@gmail.com>
babel-loader MIT Luis Couto <hello@luiscouto.pt>
babel-plugin-istanbul BSD-3-Clause Thai Pangsakulyanont @dtinth
browserslist MIT Andrey Sitnik <andrey@sitnik.ru>
chai MIT Jake Luer <jake@alogicalparadox.com>
cheerio MIT Matt Mueller <mattmuelle@gmail.com>
chrome-finder ISC gwuhaolin
core-js MIT Denis Pushkarev zloirock@zloirock.ru http://zloirock.ru
cross-env MIT Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)
css-loader MIT Tobias Koppers @sokra
cssnano MIT Ben Briggs beneb.info@gmail.com http://beneb.info
easygettext MIT Polyconseil
eslint MIT Nicholas C. Zakas <nicholas+npm@nczconsulting.com>
eslint-config-prettier MIT Simon Lydell
eslint-formatter-pretty MIT Sindre Sorhus sindresorhus@gmail.com https://sindresorhus.com
eslint-plugin-html ISC n/a
eslint-plugin-import MIT Ben Mosher <me@benmosher.com>
eslint-plugin-node MIT Toru Nagashima
eslint-plugin-prettier MIT Teddy Katz
eslint-plugin-promise ISC jden <jason@denizac.org>
eslint-plugin-vue MIT Toru Nagashima (https://github.com/mysticatea)
eslint-plugin-vuetify MIT Kael Watts-Deuchar <kaelwd@gmail.com>
eslint-webpack-plugin MIT Ricardo Gobbo de Souza <ricardogobbosouza@yahoo.com.br>
eventsource-polyfill MIT amvtek <devel@amvtek.com>
file-loader MIT Tobias Koppers @sokra
file-saver MIT Eli Grey <me@eligrey.com>
floating-vue MIT Guillaume Chau <guillaume.b.chau@gmail.com>
hls.js Apache-2.0 n/a
i MIT Pavan Kumar Sunkara <pavan.sss1991@gmail.com> (pksunkara.github.com)
karma MIT Vojta Jína <vojta.jina@gmail.com>
karma-chrome-launcher MIT Vojta Jina <vojta.jina@gmail.com>
karma-coverage-istanbul-reporter MIT Matt Lewis
karma-htmlfile-reporter MIT Matthias Schuetz MatthiasSchuetz@gmx.de
karma-mocha MIT Vojta Jina <vojta.jina@gmail.com>
karma-verbose-reporter MIT Pier Fumagalli pier@usrz.com
karma-webpack MIT Tobias Koppers @sokra
luxon MIT Isaac Cambron
maplibre-gl BSD-3-Clause n/a
memoize-one MIT Alex Reardon <alexreardon@gmail.com>
mini-css-extract-plugin MIT Tobias Koppers @sokra
minimist MIT James Halliday mail@substack.net http://substack.net
mocha MIT TJ Holowaychuk <tj@vision-media.ca>
node-storage-shim ISC Michael Nahkies
passive-events-support MIT Ignas Damunskis <ignas3run@gmail.com>
photoswipe MIT Dmytro Semenov (https://dimsemenov.com)
postcss MIT Andrey Sitnik <andrey@sitnik.ru>
postcss-import MIT Maxime Thirouin
postcss-loader MIT Andrey Sitnik <andrey@sitnik.ru>
postcss-preset-env MIT-0 n/a
postcss-reporter MIT David Clark david.dave.clark@gmail.com https://davidtheclark.com
postcss-url MIT Maxime Thirouin
prettier MIT James Long
pubsub-js MIT Morgan Roderick morgan@roderick.dk http://roderick.dk
regenerator-runtime MIT Ben Newman <bn@cs.stanford.edu>
resolve-url-loader MIT bholloway
sanitize-html MIT Apostrophe Technologies, Inc.
sass MIT Natalie Weizenbaum nweiz@google.com https://github.com/nex3
sass-loader MIT J. Tangelder
server MIT Francisco Presencia <public@francisco.io> (https://francisco.io/)
sockette MIT Luke Edwards luke.edwards05@gmail.com lukeed.com
standard MIT Feross Aboukhadijeh feross@feross.org https://feross.org
style-loader MIT Tobias Koppers @sokra
svg-url-loader MIT Hovhannes Babayan
tar ISC Isaac Z. Schlueter
url-loader MIT Tobias Koppers @sokra
util MIT Joyent http://www.joyent.com
vue MIT Evan You
vue-3-sanitize MIT Vannsl, Vanessa Otto <mail@vannsl.io>
vue-loader MIT Evan You
vue-loader-plugin ISC Ivan liu
vue-luxon MIT Cas Bloem
vue-router MIT Eduardo San Martin Morote posva13@gmail.com
vue-sanitize-directive MIT Leonardo Piccioni de Almeida leopiccionia@gmail.com
vue-style-loader MIT Evan You
vue3-gettext MIT Leo Zurbriggen
vuetify MIT John Leider john@vuetifyjs.com
webpack MIT Tobias Koppers @sokra
webpack-bundle-analyzer MIT Yury Grunin <grunin.ya@ya.ru>
webpack-cli MIT n/a
webpack-hot-middleware MIT Glen Mailer <glen@stainlessed.co.uk>
webpack-manifest-plugin MIT Dane Thurber <dane.thurber@gmail.com>
webpack-md5-hash MIT Kirill Ermolov
webpack-merge MIT Juho Vepsalainen <bebraw@gmail.com>
webpack-plugin-vuetify MIT Kael Watts-Deuchar

View File

@@ -1,142 +0,0 @@
import { defineConfig, globalIgnores } from "eslint/config";
import globals from "globals";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default defineConfig([
globalIgnores([
"**/coverage/",
"**/node_modules/",
"tests/screenshots/",
"tests/acceptance/screenshots/",
"tests/upload-files/",
"**/*.html",
"**/.idea",
"**/.codex",
"**/.env",
"**/.venv",
"**/.github",
"**/.tmp",
"**/.local",
"**/.cache",
"**/.gocache",
"**/.var",
]),
{
extends: compat.extends(
"eslint:recommended",
"plugin:vue/recommended",
"plugin:prettier/recommended",
"plugin:vue/base",
"plugin:vuetify/base"
),
languageOptions: {
globals: {
...globals.browser,
...globals.commonjs,
...globals.node,
...globals.mocha,
},
ecmaVersion: "latest",
sourceType: "module",
},
settings: {
"prettier/prettier": {
// Settings for how to process Vue SFC Blocks
SFCBlocks: {
template: false,
script: false,
style: false,
},
// Use prettierrc for prettier options or not (default: `true`)
usePrettierrc: true,
// Set the options for `prettier.getFileInfo`.
// @see https://prettier.io/docs/en/api.html#prettiergetfileinfofilepath-options
fileInfoOptions: {
// Path to ignore file (default: `'.prettierignore'`)
// Notice that the ignore file is only used for this plugin
ignorePath: ".testignore",
// Process the files in `node_modules` or not (default: `false`)
withNodeModules: false,
},
},
},
rules: {
"indent": ["error", 2, { SwitchCase: 1 }],
"linebreak-style": ["error", "unix"],
"quotes": [
"off",
"double",
{
avoidEscape: true,
allowTemplateLiterals: true,
},
],
"semi": ["error", "always"],
"no-unused-vars": ["warn"],
"no-console": 0,
"no-case-declarations": 0,
"no-prototype-builtins": 0,
"vue/no-v-text-v-html-on-component": 0,
"vue/no-v-model-argument": 0,
"vue/valid-model-definition": 0,
"vue/valid-attribute-name": 0,
"vue/singleline-html-element-content-newline": [
"off",
{
ignoreWhenNoAttributes: true,
ignoreWhenEmpty: true,
ignores: [
"pre",
"textarea",
"span",
"translate",
"a",
"v-icon",
"v-text-field",
"v-input",
"v-select",
"v-switch",
"v-checkbox",
"v-img",
],
externalIgnores: [],
},
],
"vue/first-attribute-linebreak": [
"error",
{
singleline: "ignore",
multiline: "ignore",
},
],
"prettier/prettier": [
"warn",
{
printWidth: 120,
semi: true,
singleQuote: false,
bracketSpacing: true,
trailingComma: "es5",
htmlWhitespaceSensitivity: "css",
quoteProps: "consistent",
proseWrap: "never",
},
],
},
},
]);

View File

@@ -1,21 +1,43 @@
const glob = require("glob");
const fs = require("fs");
const path = require("path");
const poPath = path.resolve(__dirname, "src/locales");
// Generates a list of existing locales based on the files in src/locales.
const languageCodes = glob.sync(poPath + "/*.po").map((filePath) => {
// Find all .po files in the frontend/src/locales
const poFiles = glob.sync("src/locales/*.po");
if (poFiles.length === 0) {
console.error("No .po files found in src/locales");
process.exit(1);
}
// Find output folder or create the new one in assets/static/locales
const outputDir = path.resolve(__dirname, "../assets/static/locales");
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Copy .po files from frontend/src/locales to assets/static/locales
poFiles.forEach((filePath) => {
const fileName = path.basename(filePath);
const destinationPath = path.join(outputDir, fileName);
fs.copyFileSync(filePath, destinationPath);
});
// Find all languages codes from .po files (cut file names without .po)
const languageCodes = poFiles.map((filePath) => {
const fileName = path.basename(filePath);
return fileName.replace(".po", "");
});
// Generates one JSON file per locale from the gettext *.po files located in src/locales.
// Transform files from .po to .json in the assets/static/locales
module.exports = {
input: {
path: path.resolve(__dirname, "../assets/static/locales"),
include: ["**/*.po"],
},
output: {
path: path.resolve(__dirname, "src/locales"),
potPath: "src/locales/translations.pot",
jsonPath: "json",
path: path.resolve(__dirname, "../assets/static/locales"),
jsonPath: "",
locales: languageCodes,
splitJson: true,
flat: true,
},
};

View File

@@ -168,7 +168,4 @@ module.exports = (config) => {
singleRun: true,
});
// Set default timezone.
process.env.TZ = "UTC";
};

Some files were not shown because too many files have changed in this diff Show More