Compare commits
378 Commits
v1.8.0_12-
...
v1.29.4_44
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10239161fd | ||
|
|
242f10952d | ||
|
|
e997bd371b | ||
|
|
400167f4ef | ||
|
|
572f6d833d | ||
|
|
2e06be5155 | ||
|
|
62121470a8 | ||
|
|
e3ccc3ee6b | ||
|
|
ece94f6bdc | ||
|
|
03fc0703c0 | ||
|
|
0d13b25f56 | ||
|
|
75c2067836 | ||
|
|
824da6a07b | ||
|
|
2c2ea24dc4 | ||
|
|
47b73a5b64 | ||
|
|
6b3f8e548d | ||
|
|
0ea483f901 | ||
|
|
97aed8ef23 | ||
|
|
0ee3fe9157 | ||
|
|
434770155f | ||
|
|
7e8bf94543 | ||
|
|
8d8944705c | ||
|
|
7c9c1a5169 | ||
|
|
1a6c16d8ea | ||
|
|
ccf792f9d3 | ||
|
|
789bc8563c | ||
|
|
99a50f70dd | ||
|
|
9bef411056 | ||
|
|
e79e92c60f | ||
|
|
858ad43d3b | ||
|
|
5761765ea7 | ||
|
|
6abc733763 | ||
|
|
4271e24e59 | ||
|
|
9e4ed2214b | ||
|
|
011332e509 | ||
|
|
5403ef4d84 | ||
|
|
31739aca02 | ||
|
|
8f2e7b6f65 | ||
|
|
4ed647c43d | ||
|
|
f88ff4fb5c | ||
|
|
cc4881d633 | ||
|
|
d856b35afc | ||
|
|
b6d025da09 | ||
|
|
cc79ff1ca3 | ||
|
|
131aa2b6be | ||
|
|
02a6b73122 | ||
|
|
d87366c095 | ||
|
|
4f7a3afbfc | ||
|
|
6725954b70 | ||
|
|
4fe535e5e8 | ||
|
|
aed94bfc4c | ||
|
|
de996c0a81 | ||
|
|
1a39aa4da5 | ||
|
|
1f4ba73da7 | ||
|
|
836b174d33 | ||
|
|
853a65aef1 | ||
|
|
566039b93f | ||
|
|
18a7ff8726 | ||
|
|
6ffdf167fe | ||
|
|
6b702b13e4 | ||
|
|
f476bd985b | ||
|
|
92c4f0598b | ||
|
|
a337402124 | ||
|
|
209e6332b3 | ||
|
|
645bd8a109 | ||
|
|
9a471d80f7 | ||
|
|
de0c59efe7 | ||
|
|
c19d26f4f3 | ||
|
|
2edfc75c8a | ||
|
|
4c977d2c1f | ||
|
|
1425f2ec78 | ||
|
|
b081eda76f | ||
|
|
7f6837c751 | ||
|
|
a467936e73 | ||
|
|
2677ddccaa | ||
|
|
564ace3ddf | ||
|
|
a81ef7497c | ||
|
|
caa7b07398 | ||
|
|
6976a7241e | ||
|
|
172eda3ce5 | ||
|
|
552340add7 | ||
|
|
bd92dde117 | ||
|
|
617c54ab81 | ||
|
|
c76f7804ab | ||
|
|
0799aa2c72 | ||
|
|
b80dca74ef | ||
|
|
f5f00e0f6c | ||
|
|
75d2d82d05 | ||
|
|
5172242f88 | ||
|
|
25e68cf826 | ||
|
|
e527685ebf | ||
|
|
e745cb5e4b | ||
|
|
dfaa4969da | ||
|
|
f980a2f27a | ||
|
|
6b7c97c02a | ||
|
|
fdd9f37abd | ||
|
|
a09bba454c | ||
|
|
4be9aa091b | ||
|
|
33b810de74 | ||
|
|
44ccb1eec1 | ||
|
|
bef38c670c | ||
|
|
025d7bf192 | ||
|
|
5ad2d62039 | ||
|
|
a128833e68 | ||
|
|
87f7b0849a | ||
|
|
4596a8ee01 | ||
|
|
f9b1b12b10 | ||
|
|
68b1655e7f | ||
|
|
658b64df74 | ||
|
|
e344503834 | ||
|
|
bf2760ffef | ||
|
|
db2ed2d881 | ||
|
|
fb0fa742f5 | ||
|
|
3b55cdc0be | ||
|
|
0efcc99f3e | ||
|
|
7a85164a1e | ||
|
|
ba2cda8955 | ||
|
|
9048be4c8e | ||
|
|
83716ae1bc | ||
|
|
5cd4d2d158 | ||
|
|
13bb6d469b | ||
|
|
8e4c4c34e4 | ||
|
|
3125d04f32 | ||
|
|
c436c57cc9 | ||
|
|
7f9f825589 | ||
|
|
da9aed5c11 | ||
|
|
10ef3509dd | ||
|
|
3dc538f9e6 | ||
|
|
1e29ff322d | ||
|
|
9c30d58b10 | ||
|
|
013a0f8324 | ||
|
|
07b58f46f9 | ||
|
|
566e118a19 | ||
|
|
0e18c88534 | ||
|
|
068d06b9ee | ||
|
|
0cf7606ec9 | ||
|
|
25338ce02f | ||
|
|
4805d86a7c | ||
|
|
33b1410d82 | ||
|
|
f35ebec7c6 | ||
|
|
3aa6ee0320 | ||
|
|
cdb0aa00d8 | ||
|
|
9de7b8d3a7 | ||
|
|
4a28a46612 | ||
|
|
16561d15ff | ||
|
|
9642ad2820 | ||
|
|
e2169a26c2 | ||
|
|
f697922f32 | ||
|
|
1390d01763 | ||
|
|
86f780871c | ||
|
|
c1b22125fd | ||
|
|
30f069a5db | ||
|
|
2bf6cd9241 | ||
|
|
87d2a954a3 | ||
|
|
a388c5a642 | ||
|
|
4b34f017ca | ||
|
|
5c1d1dd5a1 | ||
|
|
1580d27c23 | ||
|
|
4b9187928c | ||
|
|
5b7236f6ad | ||
|
|
6fb439b580 | ||
|
|
a8334b5c27 | ||
|
|
e1cac93945 | ||
|
|
081f9f5bce | ||
|
|
25ccc5660d | ||
|
|
b6d3e578f2 | ||
|
|
52377c2dcf | ||
|
|
5c78f707fe | ||
|
|
bd5ed1b684 | ||
|
|
e89339b813 | ||
|
|
0b69feda40 | ||
|
|
339f7f776f | ||
|
|
7e6ccbad21 | ||
|
|
aac53e5cdc | ||
|
|
cbec75a175 | ||
|
|
bf04d9eb39 | ||
|
|
3058c894b1 | ||
|
|
e57e279fe1 | ||
|
|
f43c58fc6d | ||
|
|
dea304ac39 | ||
|
|
b46e834220 | ||
|
|
46f4905259 | ||
|
|
28c7736ecd | ||
|
|
f881981c44 | ||
|
|
953d18e795 | ||
|
|
b45024a97e | ||
|
|
3dcdfa0166 | ||
|
|
2079583866 | ||
|
|
b68358766b | ||
|
|
cf2b9eddfa | ||
|
|
8c184dc4d4 | ||
|
|
e8d1f89a47 | ||
|
|
0e85b0fd8f | ||
|
|
f7dc916e80 | ||
|
|
03e7a254a2 | ||
|
|
0ac9fe5a54 | ||
|
|
dc61fd925f | ||
|
|
2aea08726f | ||
|
|
746bec908b | ||
|
|
8102e3b3f5 | ||
|
|
1ba998aa68 | ||
|
|
2de34f70ce | ||
|
|
8b9fd67d6f | ||
|
|
97238a1621 | ||
|
|
ef4136d327 | ||
|
|
6dbca8d478 | ||
|
|
a305db9e6f | ||
|
|
59c1ea3097 | ||
|
|
03457f5d32 | ||
|
|
2336a6159c | ||
|
|
e4c4b53fcd | ||
|
|
83cbf51704 | ||
|
|
2ebb755f00 | ||
|
|
ec1c3a86f5 | ||
|
|
969f770df0 | ||
|
|
9c3f848fa8 | ||
|
|
1ea6425cd1 | ||
|
|
052db5d748 | ||
|
|
a35460cb84 | ||
|
|
ae93bbe2a7 | ||
|
|
3b97c7729b | ||
|
|
6021124688 | ||
|
|
1d34976dd0 | ||
|
|
02bde51caf | ||
|
|
bef1e2e3db | ||
|
|
be3e3e5d7e | ||
|
|
c028c7db4e | ||
|
|
c129023821 | ||
|
|
cbdb8fa51f | ||
|
|
c6ecfb679a | ||
|
|
5d03e9bda8 | ||
|
|
d8b26c6da8 | ||
|
|
2e61cf3183 | ||
|
|
45e2335b86 | ||
|
|
2bbc44c5ab | ||
|
|
012428416d | ||
|
|
7134f93eb8 | ||
|
|
1887b5a860 | ||
|
|
ef17668871 | ||
|
|
e9909b179a | ||
|
|
09f8bdef6d | ||
|
|
2a9b09f359 | ||
|
|
1f6a3ccac7 | ||
|
|
1f40fc1de9 | ||
|
|
20b94ef0bb | ||
|
|
72c334e5e0 | ||
|
|
e7f35822af | ||
|
|
bd2152d568 | ||
|
|
b1d7ef03e2 | ||
|
|
aa74417d11 | ||
|
|
229b009b7f | ||
|
|
bece6253d5 | ||
|
|
ae7e582ec8 | ||
|
|
d69470e207 | ||
|
|
c60e852226 | ||
|
|
a205478a29 | ||
|
|
22d30522e1 | ||
|
|
19b1fad274 | ||
|
|
9a6dfacf9b | ||
|
|
7f236c5b18 | ||
|
|
25985c732d | ||
|
|
9ce50b7e3d | ||
|
|
f5e93a8179 | ||
|
|
2b5cef156c | ||
|
|
f3032f74a4 | ||
|
|
58ec7553ea | ||
|
|
357f7d1c31 | ||
|
|
e6d30d72fa | ||
|
|
355038a91a | ||
|
|
97d9b80baa | ||
|
|
b6814fad57 | ||
|
|
7586c65103 | ||
|
|
633170d743 | ||
|
|
c5be7827c3 | ||
|
|
e84c705e31 | ||
|
|
36162509e0 | ||
|
|
76bf1c0379 | ||
|
|
32b847c26e | ||
|
|
a45d6fdf57 | ||
|
|
c071e64a7e | ||
|
|
663f12851e | ||
|
|
c4ef523564 | ||
|
|
992f792c0a | ||
|
|
97611fa057 | ||
|
|
32240777c3 | ||
|
|
6065ff8caa | ||
|
|
8db073941d | ||
|
|
5e281b44e9 | ||
|
|
142ede350e | ||
|
|
a2e1d4caa2 | ||
|
|
5f00d8b9c6 | ||
|
|
2e85e18020 | ||
|
|
40a8115101 | ||
|
|
d02b97e1c1 | ||
|
|
485b152beb | ||
|
|
c918f5b001 | ||
|
|
cca2f7d178 | ||
|
|
baf533de35 | ||
|
|
dfc0d6eee7 | ||
|
|
7948cb8110 | ||
|
|
568436f188 | ||
|
|
04b59318f9 | ||
|
|
1a3d05ffc3 | ||
|
|
2f2db74d73 | ||
|
|
ef097d15dd | ||
|
|
caaa474c23 | ||
|
|
63bebd92e0 | ||
|
|
ad36b8b10f | ||
|
|
18c22d2a6c | ||
|
|
73024edba9 | ||
|
|
a360c0a3d7 | ||
|
|
34657f820f | ||
|
|
8840911f22 | ||
|
|
4aa66f4156 | ||
|
|
799a1c99f2 | ||
|
|
c4247bfea3 | ||
|
|
1e3464fe47 | ||
|
|
b7603fd150 | ||
|
|
517a3363d6 | ||
|
|
3511b69fc8 | ||
|
|
e6efc61b3b | ||
|
|
360c1d9a15 | ||
|
|
e3449f9c8f | ||
|
|
a8e723d722 | ||
|
|
d116234523 | ||
|
|
dce2bc7508 | ||
|
|
2bf764f560 | ||
|
|
587b77e70b | ||
|
|
53cd9fd8bf | ||
|
|
a8220172f8 | ||
|
|
397f8c70b4 | ||
|
|
68ff5377b0 | ||
|
|
b359dc3cb6 | ||
|
|
5b036067ed | ||
|
|
b9f38162d5 | ||
|
|
ab6909bfbd | ||
|
|
53c3c916a6 | ||
|
|
6924aa5eb1 | ||
|
|
a3b45d62b6 | ||
|
|
b34de624ce | ||
|
|
7886c42742 | ||
|
|
d476b15312 | ||
|
|
bdf38e7668 | ||
|
|
e33566a04a | ||
|
|
c28251b8b4 | ||
|
|
337db1c508 | ||
|
|
ad2a1ba901 | ||
|
|
fa6f6f8e9f | ||
|
|
a44043a4e5 | ||
|
|
87b15c60c0 | ||
|
|
2c83e52c15 | ||
|
|
55c5027539 | ||
|
|
ce06af0c9b | ||
|
|
baaf7ad153 | ||
|
|
4a25e7dc22 | ||
|
|
f90563d18c | ||
|
|
8352ecd3b9 | ||
|
|
69b34a4364 | ||
|
|
6023c3c624 | ||
|
|
171e7ffa77 | ||
|
|
d9f918005a | ||
|
|
e8ade4866b | ||
|
|
bbfa789a4e | ||
|
|
a779c3803c | ||
|
|
79dea504b0 | ||
|
|
4900fecd10 | ||
|
|
adfaab7eb2 | ||
|
|
c5adbea6e1 | ||
|
|
bb89fa4aab | ||
|
|
43d639104d | ||
|
|
a1792a7d94 | ||
|
|
373b6918f8 | ||
|
|
f1396761b0 | ||
|
|
335bb0707c | ||
|
|
7a51e0dd4d | ||
|
|
5b42899dde | ||
|
|
229357df2b | ||
|
|
8d5626620b |
2
.github/FUNDING.yml
vendored
@@ -1,4 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: alextran1502
|
||||
custom: https://www.buymeacoffee.com/altran1502?new=1
|
||||
custom: https://www.buymeacoffee.com/altran1502
|
||||
|
||||
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,15 +1,29 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
title: '[BUG] <title>'
|
||||
labels: bug, need triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Note: Please search to see if an issue already exists for the bug you encountered.
|
||||
-->
|
||||
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Task List**
|
||||
|
||||
*Please complete the task list below. We need this information to help us reproduce the bug or point out problems in your setup. You are not providing enough info may delay our effort to help you.*
|
||||
|
||||
- [ ] I have read thoroughly the README setup and installation instructions.
|
||||
- [ ] I have included my `docker-compose` file.
|
||||
- [ ] I have included my redacted `.env` file.
|
||||
- [ ] I have included information on my machine, and environment.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
@@ -23,13 +37,10 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Version [e.g. 22]
|
||||
**System**
|
||||
- Phone OS [iOS, Android]: `<version>`
|
||||
- Server Version: `<version>`
|
||||
- Mobile App Version: `<version>`
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
32
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Feature Request
|
||||
description: Request a feature that you would like for the app
|
||||
title: "[Feature]: "
|
||||
labels: ["feature", "need triage"]
|
||||
assignees:
|
||||
- ""
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this feature request!
|
||||
|
||||
- type: textarea
|
||||
id: feature-detail
|
||||
attributes:
|
||||
label: Feature detail
|
||||
placeholder: Describe the feature you would like to see for the app
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
description: Choose the platform for the feature
|
||||
options:
|
||||
- Web
|
||||
- Mobile App
|
||||
- Server
|
||||
validations:
|
||||
required: true
|
||||
130
.github/workflows/build_push_docker_latest.yml
vendored
@@ -4,61 +4,117 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
|
||||
build_and_push_server_latest:
|
||||
# This image include both the server and microservices - the two containers can be slitted into separated
|
||||
# service with its coressponding entry file.
|
||||
build_and_push_server_monorepo_latest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "main" # branch
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1.2.0
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push Immich
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
- name: Build and push Immich Mono Repo
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./server
|
||||
file: ./server/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
push: true
|
||||
tags: |
|
||||
altran1502/immich-server:latest
|
||||
|
||||
build_and_push_microservice_latest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "main" # branch
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1.2.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and Push Microservices
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
with:
|
||||
context: ./microservices
|
||||
file: ./microservices/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: |
|
||||
altran1502/immich-microservices:latest
|
||||
build_and_push_machine_learning_latest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and Push Machine Learning
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./machine-learning
|
||||
file: ./machine-learning/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
altran1502/immich-machine-learning:latest
|
||||
|
||||
build_and_push_web_latest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and Push Web
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./web
|
||||
file: ./web/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||
target: prod
|
||||
push: true
|
||||
tags: |
|
||||
altran1502/immich-web:latest
|
||||
|
||||
build_and_push_nginx_latest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and Push Proxy
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./nginx
|
||||
file: ./nginx/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
altran1502/immich-proxy:latest
|
||||
|
||||
126
.github/workflows/build_push_docker_staging.yml
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
name: Build and Push Docker Image - Staging
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
# This image include both the server and microservices - the two containers can be slitted into separated
|
||||
# service with its coressponding entry file.
|
||||
build_and_push_server_monorepo_staging:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ github.repository == 'immich-app/immich' }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push Immich Mono Repo
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./server
|
||||
file: ./server/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name == 'pull_request' && github.repository == 'immich-app/immich' }}
|
||||
tags: |
|
||||
altran1502/immich-server:staging
|
||||
|
||||
build_and_push_machine_learning_staging:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ github.repository == 'immich-app/immich' }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and Push Machine Learning
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./machine-learning
|
||||
file: ./machine-learning/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name == 'pull_request' && github.repository == 'immich-app/immich' }}
|
||||
tags: |
|
||||
altran1502/immich-machine-learning:staging
|
||||
|
||||
build_and_push_web_staging:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ github.repository == 'immich-app/immich' }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and Push Web
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./web
|
||||
file: ./web/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||
target: prod
|
||||
push: ${{ github.event_name == 'pull_request' && github.repository == 'immich-app/immich' }}
|
||||
tags: |
|
||||
altran1502/immich-web:staging
|
||||
|
||||
build_and_push_nginx_staging:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ github.repository == 'immich-app/immich' }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and Push Proxy
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./nginx
|
||||
file: ./nginx/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name == 'pull_request' && github.repository == 'immich-app/immich' }}
|
||||
tags: |
|
||||
altran1502/immich-proxy:staging
|
||||
121
.github/workflows/build_push_server_release.yml
vendored
@@ -6,36 +6,36 @@ on:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build_and_push_server_release:
|
||||
build_and_push_server_monorepo_release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "main"
|
||||
ref: "main"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Get Previous tag'
|
||||
|
||||
- name: "Get Previous tag"
|
||||
id: previoustag
|
||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||
with:
|
||||
fallback: latest
|
||||
fallback: latest
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1.2.0
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push immich-server release
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./server
|
||||
file: ./server/Dockerfile
|
||||
@@ -43,41 +43,116 @@ jobs:
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: |
|
||||
altran1502/immich-server:${{ steps.previoustag.outputs.tag }}
|
||||
altran1502/immich-server:release
|
||||
|
||||
build_and_push_microservice_release:
|
||||
build_and_push_machine_learning_release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "main"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Get Previous tag'
|
||||
- name: "Get Previous tag"
|
||||
id: previoustag
|
||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||
with:
|
||||
fallback: latest
|
||||
fallback: latest
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and Push Machine Learning
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./machine-learning
|
||||
file: ./machine-learning/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
altran1502/immich-machine-learning:${{ steps.previoustag.outputs.tag }}
|
||||
altran1502/immich-machine-learning:release
|
||||
|
||||
build_and_push_web_release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "main"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: "Get Previous tag"
|
||||
id: previoustag
|
||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||
with:
|
||||
fallback: latest
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1.2.0
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push immich-microservices release
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
- name: Build and push immich-web release
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./microservices
|
||||
file: ./microservices/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64
|
||||
context: ./web
|
||||
file: ./web/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
target: prod
|
||||
tags: |
|
||||
altran1502/immich-web:${{ steps.previoustag.outputs.tag }}
|
||||
altran1502/immich-web:release
|
||||
|
||||
build_and_push_nginx_release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "main"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: "Get Previous tag"
|
||||
id: previoustag
|
||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||
with:
|
||||
fallback: latest
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push immich-proxy release
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: ./nginx
|
||||
file: ./nginx/Dockerfile
|
||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: |
|
||||
altran1502/immich-microservices:${{ steps.previoustag.outputs.tag }}
|
||||
altran1502/immich-proxy:release
|
||||
altran1502/immich-proxy:${{ steps.previoustag.outputs.tag }}
|
||||
|
||||
19
.github/workflows/github-repo-stats.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: github-repo-stats
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run this once per day, towards the end of the day for keeping the most
|
||||
# recent data point most meaningful (hours are interpreted in UTC).
|
||||
- cron: "0 23 * * *"
|
||||
workflow_dispatch: # Allow for running this manually.
|
||||
|
||||
jobs:
|
||||
j1:
|
||||
name: github-repo-stats
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: run-ghrs
|
||||
# Use latest release.
|
||||
uses: jgehrcke/github-repo-stats@RELEASE
|
||||
with:
|
||||
ghtoken: ${{ secrets.GHRS_GITHUB_API_TOKEN }}
|
||||
41
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Test
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
e2e-tests:
|
||||
name: Run end-to-end test suites
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run Immich Server 2E2 Test
|
||||
run: docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test up --abort-on-container-exit --exit-code-from immich-server-test
|
||||
|
||||
server-unit-tests:
|
||||
name: Run server unit test suites and checks
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run tests
|
||||
run: cd server && npm ci && npm run check:all
|
||||
|
||||
web-unit-tests:
|
||||
name: Run web unit test suites and checks
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run tests
|
||||
run: cd web && npm ci && npm run check:all
|
||||
134
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation
|
||||
in our community a harassment-free experience for everyone, regardless
|
||||
of age, body size, visible or invisible disability, ethnicity, sex
|
||||
characteristics, gender identity and expression, level of experience,
|
||||
education, socio-economic status, nationality, personal appearance,
|
||||
race, religion, or sexual identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open,
|
||||
welcoming, diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for
|
||||
our community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our
|
||||
mistakes, and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or
|
||||
political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in
|
||||
a professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our
|
||||
standards of acceptable behavior and will take appropriate and fair
|
||||
corrective action in response to any behavior that they deem
|
||||
inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit,
|
||||
or reject comments, commits, code, wiki edits, issues, and other
|
||||
contributions that are not aligned to this Code of Conduct, and will
|
||||
communicate reasons for moderation decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also
|
||||
applies when an individual is officially representing the community in
|
||||
public spaces. Examples of representing our community include using an
|
||||
official e-mail address, posting via an official social media account,
|
||||
or acting as an appointed representative at an online or offline
|
||||
event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported to the community leaders responsible for enforcement
|
||||
at our Discord channel. All complaints
|
||||
will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and
|
||||
security of the reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in
|
||||
determining the consequences for any action they deem in violation of
|
||||
this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior
|
||||
deemed unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders,
|
||||
providing clarity around the nature of the violation and an
|
||||
explanation of why the behavior was inappropriate. A public apology
|
||||
may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued
|
||||
behavior. No interaction with the people involved, including
|
||||
unsolicited interaction with those enforcing the Code of Conduct, for
|
||||
a specified period of time. This includes avoiding interactions in
|
||||
community spaces as well as external channels like social
|
||||
media. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards,
|
||||
including sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or
|
||||
public communication with the community for a specified period of
|
||||
time. No public or private interaction with the people involved,
|
||||
including unsolicited interaction with those enforcing the Code of
|
||||
Conduct, is allowed during this period. Violating these terms may lead
|
||||
to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of
|
||||
community standards, including sustained inappropriate behavior,
|
||||
harassment of an individual, or aggression toward or disparagement of
|
||||
classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction
|
||||
within the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor
|
||||
Covenant][homepage], version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of
|
||||
conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the
|
||||
FAQ at https://www.contributor-covenant.org/faq. Translations are
|
||||
available at https://www.contributor-covenant.org/translations.
|
||||
23
Makefile
@@ -1,14 +1,29 @@
|
||||
dev:
|
||||
docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
|
||||
dev-new:
|
||||
rm -rf ./server/dist && docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
|
||||
dev-update:
|
||||
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
|
||||
dev-scale:
|
||||
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich_server=3 --remove-orphans
|
||||
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
|
||||
|
||||
stage:
|
||||
docker-compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans
|
||||
|
||||
pull-stage:
|
||||
docker-compose -f ./docker/docker-compose.staging.yml pull
|
||||
|
||||
test-e2e:
|
||||
docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test -p immich-test-e2e up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build
|
||||
|
||||
prod:
|
||||
docker-compose -f ./docker/docker-compose.yml up --build -V --remove-orphans
|
||||
|
||||
prod-scale:
|
||||
docker-compose -f ./docker/docker-compose.yml up --build -V --scale immich_server=3 --scale immich_microservices=3 --remove-orphans
|
||||
docker-compose -f ./docker/docker-compose.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
|
||||
|
||||
api:
|
||||
cd ./server && npm run api:generate
|
||||
9
NOTES.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# TODO
|
||||
|
||||
Server scenario with web
|
||||
|
||||
[ ] 1 user exist without admin right -> make admin on first check
|
||||
|
||||
[ ] 2 users exist without admin right -> ask user to choose which account will be the admin
|
||||
|
||||
[ X ] No users exist -> prompt signup form for Admin
|
||||
284
README.md
@@ -1,3 +1,9 @@
|
||||
<h1 align="center"> Immich </h1>
|
||||
<p align="center"> <b>High performance self-hosted photo and video backup solution.</b> </p>
|
||||
<p align="center">
|
||||
<img src="design/feature-panel.png" title="Immich Logo">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: MIT"></a>
|
||||
<a href="https://github.com/alextran1502/immich"><img src="https://img.shields.io/github/stars/alextran1502/immich.svg?style=for-the-badge&logo=github&color=3F51B5&label=Stars&logoColor=000000&labelColor=ececec" alt="Star on Github"></a>
|
||||
@@ -8,207 +14,239 @@
|
||||
<img src="https://img.shields.io/teamcity/http/immichci.little-home.net/s/Immich_BuildAndPublishIOSToTestFlight.svg?style=for-the-badge&label=iOS&logo=teamcity&logoColor=000000&labelColor=ececec" alt="iOS Build"/>
|
||||
</a>
|
||||
<a href="https://actions-badge.atrox.dev/alextran1502/immich/goto?ref=main">
|
||||
<img alt="Build Status" src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Falextran1502%2Fimmich%2Fbadge%3Fref%3Dmain&style=for-the-badge&label=Server Docker&logo=docker&labelColor=ececec" />
|
||||
<img alt="Build Status" src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Falextran1502%2Fimmich%2Fbadge%3Fref%3Dmain&style=for-the-badge&label=Github Action&logo=github&labelColor=ececec&logoColor=000000" />
|
||||
</a>
|
||||
<a href="https://discord.gg/D8JsnBEuKb">
|
||||
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Immich%20Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" atl="Immich Discord"/>
|
||||
</a>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<p align="center">
|
||||
<img src="design/immich-logo.svg" width="200" title="Immich Logo">
|
||||
</p>
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
# Immich
|
||||
## Content
|
||||
- [Features](#features)
|
||||
- [Screenshots](#screenshots)
|
||||
- [Installation](#installation)
|
||||
- [Update](#update)
|
||||
- [Mobile App](#-mobile-app)
|
||||
- [Development](#development)
|
||||
- [Support](#support)
|
||||
- [Known Issues](#known-issues)
|
||||
|
||||
Self-hosted photo and video backup solution directly from your mobile phone.
|
||||
# Features
|
||||
|
||||

|
||||
> ⚠️ WARNING: **NOT READY FOR PRODUCTION! DO NOT USE TO STORE YOUR ASSETS**. This project is under heavy development, there will be continuous functions, features and api changes.
|
||||
|
||||
Loading ~4000 images/videos
|
||||
| Features | Mobile | Web |
|
||||
| - | - | - |
|
||||
| Upload and view videos and photos | Yes | Yes
|
||||
| Auto backup when the app is opened | Yes | N/A
|
||||
| Selective album(s) for backup | Yes | N/A
|
||||
| Download photos and videos to local device | Yes | Yes
|
||||
| Multi-user support | Yes | Yes
|
||||
| Album | Yes | Yes
|
||||
| Shared Albums | Yes | Yes
|
||||
| Quick navigation with draggable scrollbar | Yes | Yes
|
||||
| Support RAW (HEIC, HEIF, DNG, Apple ProRaw) | Yes | Yes
|
||||
| Metadata view (EXIF, map) | Yes | Yes
|
||||
| Search by metadata, objects and image tags | Yes | No
|
||||
| Administrative functions (user management) | N/A | Yes
|
||||
| Background backup | Android | N/A
|
||||
| Virtual scroll | N/A | Yes
|
||||
|
||||
## Screenshots
|
||||
|
||||
<p align="left">
|
||||
<img src="design/nsc1.png" width="150" title="Login With Custom URL">
|
||||
<img src="design/nsc2.png" width="150" title="Backup Setting Info">
|
||||
<img src="design/home-screen.jpeg" width="150" title="Home Screen">
|
||||
<img src="design/search-screen.jpeg" width="150" title="Curated Search Info">
|
||||
<img src="design/shared-albums.png" width="150" title="Shared Albums">
|
||||
<img src="design/nsc6.png" width="150" title="EXIF Info">
|
||||
<br/>
|
||||
|
||||
</p>
|
||||
# Screenshots
|
||||
|
||||
# Note
|
||||
### Mobile
|
||||
| | | | | |
|
||||
| - | - | - | - | - |
|
||||
| <img src="design/login-screen.png" width="150" title="Login With Custom URL"> <p align="center"> Login with custom URL </p> | <img src="design/backup-screen.png" width="150" title="Backup Setting Info"> <p align="center"> Backup Settings </p> | <img src="design/selective-backup-screen.png" width="150" title="Backup Setting Info"> <p align="center"> Backup selection </p> | <img src="design/home-screen.jpeg" width="150" title="Home Screen"> <p align="center"> Home Screen </p> | <img src="design/search-screen.jpeg" width="150" title="Curated Search Info"> <p align="center"> Curated search </p> |
|
||||
| <img src="design/shared-albums.png" width="150" title="Shared Albums"> <p align="center"> Shared albums </p> | <img src="design/nsc6.png" width="150" title="EXIF Info"> <p align="center"> EXIF info </p> | <img src="https://media.giphy.com/media/y8ZeaAigGmNvlSoKhU/giphy.gif" width="150" title="Loading ~4000 images/videos"> <p align="center"> Loading ~4000 images/videos </p> |
|
||||
|
||||
**!! NOT READY FOR PRODUCTION! DO NOT USE TO STORE YOUR ASSETS !!**
|
||||
### Web
|
||||
| Home Dashboard | Image view |
|
||||
| - | - |
|
||||
|<img src="design/web-home.jpeg" width="100%" title="Home Dashboard"> | <img src="design/web-detail.jpeg" width="100%" title="Detail">|
|
||||
|
||||
This project is under heavy development, there will be continous functions, features and api changes.
|
||||
|
||||
# Features
|
||||
<br/>
|
||||
|
||||
- Upload and view assets (videos/images).
|
||||
- Download asset to local device.
|
||||
- Multi-user supported.
|
||||
- Quick navigation with drag scroll bar.
|
||||
- Auto Backup.
|
||||
- Support HEIC/HEIF Backup.
|
||||
- Extract and display EXIF info.
|
||||
- Real-time render from multi-device upload event.
|
||||
- Image Tagging/Classification based on ImageNet dataset
|
||||
- Object detection based on COCO SSD.
|
||||
- Search assets based on tags and exif data (lens, make, model, orientation)
|
||||
- [Optional] Reverse geocoding using Mapbox (Generous free-tier of 100,000 search/month)
|
||||
- Show asset's location information on map (OpenStreetMap).
|
||||
- Show curated places on the search page
|
||||
- Show curated objects on the search page
|
||||
- Shared album with users on the same server
|
||||
# Project Details
|
||||
## 💾 System Requirements
|
||||
|
||||
# System Requirement
|
||||
- **OS**: Preferred unix-based operating system (Ubuntu, Debian, MacOS...etc).
|
||||
|
||||
**OS**: Preferred Linux-based operating system (Ubuntu, Debian, MacOS...etc). I haven't tested with `Docker for Windows` as well as `WSL` on Windows
|
||||
- **RAM**: At least 2GB, preferred 4GB.
|
||||
|
||||
**RAM**: At least 2GB, preffered 4GB.
|
||||
- **Core**: At least 2 cores, preferred 4 cores.
|
||||
|
||||
**Cores**: At least 2 cores, preffered 4 cores.
|
||||
## 🔩 Technology Stack
|
||||
|
||||
# Development and Testing out the application
|
||||
|
||||
You can use docker compose for development and testing out the application, there are several services that compose Immich:
|
||||
There are several services that compose Immich:
|
||||
|
||||
1. **NestJs** - Backend of the application
|
||||
2. **PostgreSQL** - Main database of the application
|
||||
3. **Redis** - For sharing websocket instance between docker instances and background tasks message queue.
|
||||
4. **Nginx** - Load balancing and optimized file uploading.
|
||||
5. **TensorFlow** - Object Detection and Image Classification.
|
||||
2. **SvelteKit** - Web frontend of the application
|
||||
3. **PostgreSQL** - Main database of the application
|
||||
4. **Redis** - For sharing websocket instance between docker instances and background tasks message queue.
|
||||
5. **Nginx** - Load balancing and optimized file uploading.
|
||||
6. **TensorFlow** - Object Detection (COCO SSD) and Image Classification (ImageNet).
|
||||
|
||||
## Step 1: Populate .env file
|
||||
|
||||
Navigate to `docker` directory and run
|
||||
<br/>
|
||||
|
||||
```
|
||||
cp .env.example .env
|
||||
```
|
||||
# Installation
|
||||
|
||||
Then populate the value in there.
|
||||
NOTE: When using a reverse proxy in front of Immich (such as NGINX), the reverse proxy might require extra configuration to allow large files to be uploaded (such as client_max_body_size in the case of NGINX).
|
||||
|
||||
Notice that if set `ENABLE_MAPBOX` to `true`, you will have to provide `MAPBOX_KEY` for the server to run.
|
||||
## Testing One-step installation (not recommended for production)
|
||||
|
||||
Pay attention to the key `UPLOAD_LOCATION`, this directory must exist and is owned by the user that run the `docker-compose` command below.
|
||||
> ⚠️ *This installation method is for evaluating Immich before futher customization to meet the users' needs.*
|
||||
|
||||
**Example**
|
||||
*Applicable system: Ubuntu, Debian, MacOS*
|
||||
|
||||
- In the shell, from the directory of your choice, run the following command:
|
||||
|
||||
```bash
|
||||
# Database
|
||||
DB_USERNAME=postgres
|
||||
DB_PASSWORD=postgres
|
||||
DB_DATABASE_NAME=immich
|
||||
|
||||
# Upload File Config
|
||||
UPLOAD_LOCATION=<put-the-path-of-the-upload-folder-here>
|
||||
|
||||
# JWT SECRET
|
||||
JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
|
||||
|
||||
# MAPBOX
|
||||
## ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
|
||||
ENABLE_MAPBOX=false
|
||||
MAPBOX_KEY=
|
||||
curl -o- https://raw.githubusercontent.com/immich-app/immich/main/install.sh | bash
|
||||
```
|
||||
|
||||
## Step 2: Start the server
|
||||
This script will download the `docker-compose.yml` file and the `.env` file, then populate the necessary information, and finally run the `docker-compose up` or `docker compose up` (based on your docker's version) command.
|
||||
|
||||
To start, run
|
||||
The web application will be available at `http://<machine-ip-address>:2283`, and the server URL for the mobile app will be `http://<machine-ip-address>:2283/api`.
|
||||
|
||||
The directory which is used to store the backup file is `./immich-app/immich-data`.
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
## Custom installation (Recommended)
|
||||
|
||||
### Step 1 - Download necessary files
|
||||
|
||||
- Create a directory called `immich-app` and cd into it.
|
||||
|
||||
- Get `docker-compose.yml`
|
||||
|
||||
```bash
|
||||
docker-compose -f ./docker/docker-compose.yml up
|
||||
wget https://raw.githubusercontent.com/immich-app/immich/main/docker/docker-compose.yml
|
||||
```
|
||||
|
||||
If you have a few thousand photos/videos, I suggest running docker-compose with scaling option for the `immich_server` container to handle high I/O load when using fast scrolling.
|
||||
- Get `.env`
|
||||
|
||||
```bash
|
||||
docker-compose -f ./docker/docker-compose.yml up --scale immich_server=5
|
||||
wget -O .env https://raw.githubusercontent.com/immich-app/immich/main/docker/.env.example
|
||||
```
|
||||
|
||||
### Step 2 - Populate .env file with custom information
|
||||
|
||||
The server will be running at `http://your-ip:2283` through `Nginx`
|
||||
<a href="https://github.com/immich-app/immich/blob/main/docker/.env.example" target="_blank"><b>See the example <code>.env</code> file</b></a>
|
||||
|
||||
## Step 3: Register User
|
||||
* Populate custom database information if necessary.
|
||||
* Populate `UPLOAD_LOCATION` as prefered location for storing backup assets.
|
||||
* Populate a secret value for `JWT_SECRET`, you can use this command: `openssl rand -base64 128`
|
||||
* [Optional] Populate Mapbox value to use reverse geocoding.
|
||||
* [Optional] Populate `TZ` as your timezone, default is `Etc/UTC`.
|
||||
|
||||
Use the command below on your terminal to create user as we don't have user interface for this function yet.
|
||||
### Step 3 - Start the containers
|
||||
|
||||
- Run `docker-compose up` or `docker compose up` (based on your docker's version)
|
||||
|
||||
### Step 4 - Register admin user
|
||||
|
||||
- Navigate to the web at `http://<machine-ip-address>:2283` and follow the prompts to register admin user.
|
||||
<p align="center">
|
||||
<img src="design/admin-registration-form.png" width="300" title="Admin Registration">
|
||||
</p>
|
||||
|
||||
- You can add and manage users from the administration page.
|
||||
<p align="center">
|
||||
<img src="design/admin-interface.png" width="500" title="Admin User Management">
|
||||
</p>
|
||||
|
||||
### Step 5 - Access the mobile app
|
||||
|
||||
- Login the mobile app with the server endpoint URL at `http://<machine-ip-address>:2283/api`
|
||||
<p align="center">
|
||||
<img src="design/login-screen.jpeg" width="250" title="Example login screen">
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
## Update
|
||||
|
||||
If you have installed, you can update the application by navigate to the directory that contains the `docker-compose.yml` file and run the following command:
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'http://your-server-ip:2283/auth/signUp' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "testuser@email.com",
|
||||
"password": "password"
|
||||
}'
|
||||
docker-compose pull && docker-compose up -d
|
||||
```
|
||||
|
||||
## Step 4: Run mobile app
|
||||
# Mobile app
|
||||
|
||||
The app is distributed on several platforms below.
|
||||
| F-Droid | Google Play | iOS |
|
||||
| - | - | - |
|
||||
| <a href="https://f-droid.org/packages/app.alextran.immich"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80"></a> | <p align="left"> <a href="https://play.google.com/store/apps/details?id=app.alextran.immich"><img src="design/google-play-qr-code.png" width="200" title="Google Play Store"></a> <p/> | <p align="left"> <a href="https://apps.apple.com/us/app/immich/id1613945652"><img src="design/ios-qr-code.png" width="200" title="Apple App Store"></a> <p/> |
|
||||
|
||||
## F-Droid
|
||||
You can get the app on F-droid by clicking the image below.
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/packages/app.alextran.immich)
|
||||
> *The Play/App Store version might be lagging behind the latest release due to the review process.*
|
||||
|
||||
|
||||
## Android
|
||||
<br/>
|
||||
|
||||
#### Get the app on Google Play Store [here](https://play.google.com/store/apps/details?id=app.alextran.immich)
|
||||
# Development
|
||||
|
||||
*The App version might be lagging behind the latest release due to the review process.*
|
||||
The development environment can be started from the root of the project after populating the `.env` file with the command:
|
||||
|
||||
<p align="left">
|
||||
<img src="design/google-play-qr-code.png" width="200" title="Google Play Store">
|
||||
<p/>
|
||||
```bash
|
||||
make dev # required Makefile installed on the system.
|
||||
```
|
||||
|
||||
## iOS
|
||||
All servers and web container are hot reload for quick feedback loop.
|
||||
|
||||
#### Get the app on Apple AppStore [here](https://apps.apple.com/us/app/immich/id1613945652):
|
||||
## Note for developers
|
||||
### 1 - OpenAPI
|
||||
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). When you add a new or modify an existing endpoint, you must run the generate command below to update the client SDK.
|
||||
|
||||
*The App version might be lagging behind the latest release due to the review process.*
|
||||
```bash
|
||||
npm run api:generate # Run from server directory
|
||||
```
|
||||
You can find the generated client SDK in the [`web/src/api`](web/src/api) for Typescript SDK and [`mobile/openapi`](mobile/openapi) for Dart SDK.
|
||||
|
||||
|
||||
<p align="left">
|
||||
<img src="design/ios-qr-code.png" width="200" title="Apple App Store">
|
||||
<p/>
|
||||
<br/>
|
||||
|
||||
# Support
|
||||
|
||||
If you like the app, find it helpful, and want to support me to offset the cost of publishing to AppStores, you can sponsor the project with [**Github Sponsore**](https://github.com/sponsors/alextran1502), or one time donation with Buy Me a coffee link below.
|
||||
If you like the app, find it helpful, and want to support me to offset the cost of publishing to AppStores, you can sponsor the project with [**one time**](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) or monthly donation from [**Github Sponsor**](https://github.com/sponsors/alextran1502).
|
||||
|
||||
[](https://www.buymeacoffee.com/altran1502)
|
||||
You can also donate using crypto currency with the following addresses:
|
||||
|
||||
This is also a meaningful way to give me motivation and encounragment to continue working on the app.
|
||||
<p align="" style="display: flex; place-items: center; gap: 15px" title="Bitcoin(BTC)"><img src="design/bitcoin.png" width="25" title="Bitcoin"> <b>Bitcoin</b>: <code>1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX</code></p>
|
||||
|
||||
Cheer! 🎉
|
||||
<p align="" style="display: flex; place-items: center; gap: 15px" title="Cardano(ADA)"> <img src="design/cardano.png" width="30" title="Cardano"> <b>Cardano</b>: <code>addr1qyy567vqhqrr3p7vpszr5p264gw89sqcwts2z8wqy4yek87cdmy79zazyjp7tmwhkluhk3krvslkzfvg0h43tytp3f5q49nycc</code> </p>
|
||||
|
||||
# Known Issue
|
||||
|
||||
This is also a meaningful way to give me motivation and encouragement to continue working on the app.
|
||||
|
||||
Cheers! 🎉
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
# Known Issues
|
||||
|
||||
## TensorFlow Build Issue
|
||||
|
||||
*This is a known issue on RaspberryPi 4 arm64-v7 and incorrect Promox setup*
|
||||
*This is a known issue for incorrect Proxmox setup*
|
||||
|
||||
TensorFlow doesn't run with older CPU architecture, it requires CPU with AVX and AVX2 instruction set. If you encounter the error `illegal instruction core dump` when running the docker-compose command above, check for your CPU flags with the command and make sure you see `AVX` and `AVX2`:
|
||||
TensorFlow doesn't run with older CPU architecture, it requires a CPU with AVX and AVX2 instruction set. If you encounter the error `illegal instruction core dump` when running the docker-compose command above, check for your CPU flags with the command and make sure you see `AVX` and `AVX2`:
|
||||
|
||||
```bash
|
||||
more /proc/cpuinfo | grep flags
|
||||
```
|
||||
|
||||
If you are running virtualization in Promox, the VM doesn't have the flag enable.
|
||||
If you are running virtualization in Proxmox, the VM doesn't have the flag enabled.
|
||||
|
||||
You need to change the CPU type from `kvm64` to `host` under VMs hardware tab.
|
||||
|
||||
`Hardware > Processors > Edit > Advanced > Type (dropdown menu) > host`
|
||||
|
||||
Otherwise you can:
|
||||
- edit `docker-compose.yml` file and comment the whole `immich_microservices` service **which will disable machine learning features like object detection and image classification**
|
||||
- switch to a different VM/desktop with different architecture.
|
||||
|
||||
5
SECURITY.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security issues to `alex.tran1502@gmail.com`
|
||||
BIN
design/admin-interface.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
design/admin-registration-form.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
design/backup-screen.png
Normal file
|
After Width: | Height: | Size: 308 KiB |
BIN
design/bitcoin.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
design/cardano.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
design/feature-panel.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
design/login-screen.jpeg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
design/login-screen.png
Normal file
|
After Width: | Height: | Size: 278 KiB |
BIN
design/nsc1.png
|
Before Width: | Height: | Size: 176 KiB |
BIN
design/nsc2.png
|
Before Width: | Height: | Size: 303 KiB |
BIN
design/selective-backup-screen.png
Normal file
|
After Width: | Height: | Size: 570 KiB |
BIN
design/web-admin.jpeg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
design/web-detail.jpeg
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
design/web-home.jpeg
Normal file
|
After Width: | Height: | Size: 206 KiB |
32
dev-setup.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Development Setup
|
||||
|
||||
## Lint / format extensions
|
||||
|
||||
Setting these in the IDE give a better developer experience auto-formatting code on save and providing instant feedback on lint issues.
|
||||
|
||||
### VSCode
|
||||
Install Prettier, ESLint and Svelte extensions.
|
||||
|
||||
in User `settings.json` (`cmd + shift + p` and search for Open User Settings JSON) add the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"[javascript][typescript][css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[svelte]": {
|
||||
"editor.defaultFormatter": "svelte.svelte-vscode",
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"svelte.enable-ts-plugin": true,
|
||||
"eslint.validate": ["javascript", "svelte"]
|
||||
}
|
||||
```
|
||||
|
||||
## Running tests / checks
|
||||
|
||||
In both server and web:
|
||||
`npm run check:all`
|
||||
@@ -1,15 +1,79 @@
|
||||
###################################################################################
|
||||
# Database
|
||||
###################################################################################
|
||||
|
||||
DB_HOSTNAME=immich_postgres
|
||||
DB_USERNAME=postgres
|
||||
DB_PASSWORD=postgres
|
||||
DB_DATABASE_NAME=immich
|
||||
|
||||
# Optional Database settings:
|
||||
# DB_PORT=5432
|
||||
|
||||
|
||||
|
||||
|
||||
###################################################################################
|
||||
# Redis
|
||||
###################################################################################
|
||||
|
||||
REDIS_HOSTNAME=immich_redis
|
||||
|
||||
# Optional Redis settings:
|
||||
# REDIS_PORT=6379
|
||||
# REDIS_DBINDEX=0
|
||||
# REDIS_PASSWORD=
|
||||
# REDIS_SOCKET=
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
###################################################################################
|
||||
# Upload File Config
|
||||
###################################################################################
|
||||
|
||||
UPLOAD_LOCATION=absolute_location_on_your_machine_where_you_want_to_store_the_backup
|
||||
|
||||
|
||||
###################################################################################
|
||||
# Log message level - [simple|verbose]
|
||||
###################################################################################
|
||||
|
||||
LOG_LEVEL=simple
|
||||
|
||||
|
||||
###################################################################################
|
||||
# JWT SECRET
|
||||
###################################################################################
|
||||
|
||||
JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
|
||||
|
||||
|
||||
|
||||
|
||||
###################################################################################
|
||||
# MAPBOX
|
||||
## ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
|
||||
####################################################################################
|
||||
|
||||
# ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
|
||||
ENABLE_MAPBOX=false
|
||||
MAPBOX_KEY=
|
||||
MAPBOX_KEY=
|
||||
|
||||
|
||||
####################################################################################
|
||||
# WEB - Optional
|
||||
####################################################################################
|
||||
|
||||
# Custom message on the login page, should be written in HTML form.
|
||||
# For example PUBLIC_LOGIN_PAGE_MESSAGE="This is a demo instance of Immich.<br><br>Email: <i>demo@demo.de</i><br>Password: <i>demo</i>"
|
||||
|
||||
PUBLIC_LOGIN_PAGE_MESSAGE=
|
||||
|
||||
# For correctly display your local time zone on the web, you can set the time zone here.
|
||||
# Should work fine by default value, however, in case of incorrect timezone in EXIF, this value
|
||||
# should be set to the correct timezone.
|
||||
# Command to get timezone:
|
||||
# - Linux: curl -s http://ip-api.com/json/ | grep -oP '(?<=timezone":")(.*?)(?=")'
|
||||
|
||||
# TZ=Etc/UTC
|
||||
22
docker/.env.test
Normal file
@@ -0,0 +1,22 @@
|
||||
# Database
|
||||
DB_HOSTNAME=immich-database-test
|
||||
DB_USERNAME=postgres
|
||||
DB_PASSWORD=postgres
|
||||
DB_DATABASE_NAME=e2e_test
|
||||
|
||||
# Redis
|
||||
REDIS_HOSTNAME=immich_redis_test
|
||||
|
||||
# Upload File Config
|
||||
UPLOAD_LOCATION=./upload
|
||||
|
||||
# JWT SECRET
|
||||
JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
|
||||
|
||||
# MAPBOX
|
||||
## ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
|
||||
ENABLE_MAPBOX=false
|
||||
|
||||
# WEB
|
||||
MAPBOX_KEY=
|
||||
VITE_SERVER_ENDPOINT=http://localhost:2283/api
|
||||
@@ -1,14 +1,13 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
immich_server:
|
||||
image: immich-server-dev:1.8.0
|
||||
immich-server:
|
||||
image: immich-server-dev:latest
|
||||
build:
|
||||
context: ../server
|
||||
dockerfile: Dockerfile
|
||||
command: npm run start:dev
|
||||
expose:
|
||||
- "3000"
|
||||
target: builder
|
||||
command: npm run start:dev immich
|
||||
volumes:
|
||||
- ../server:/usr/src/app
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
@@ -20,19 +19,16 @@ services:
|
||||
depends_on:
|
||||
- redis
|
||||
- database
|
||||
networks:
|
||||
- immich_network
|
||||
|
||||
immich_microservices:
|
||||
image: immich-microservices-dev:1.8.0
|
||||
immich-machine-learning:
|
||||
image: immich-machine-learning-dev:latest
|
||||
build:
|
||||
context: ../microservices
|
||||
context: ../machine-learning
|
||||
dockerfile: Dockerfile
|
||||
target: builder
|
||||
command: npm run start:dev
|
||||
expose:
|
||||
- "3001"
|
||||
volumes:
|
||||
- ../microservices:/usr/src/app
|
||||
- ../machine-learning:/usr/src/app
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
- /usr/src/app/node_modules
|
||||
env_file:
|
||||
@@ -41,15 +37,48 @@ services:
|
||||
- NODE_ENV=development
|
||||
depends_on:
|
||||
- database
|
||||
networks:
|
||||
- immich_network
|
||||
|
||||
immich-microservices:
|
||||
image: immich-microservices:latest
|
||||
build:
|
||||
context: ../server
|
||||
dockerfile: Dockerfile
|
||||
target: builder
|
||||
command: npm run start:dev microservices
|
||||
volumes:
|
||||
- ../server:/usr/src/app
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
- /usr/src/app/node_modules
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
depends_on:
|
||||
- database
|
||||
- immich-server
|
||||
|
||||
immich-web:
|
||||
image: immich-web-dev:1.9.0
|
||||
build:
|
||||
context: ../web
|
||||
dockerfile: Dockerfile
|
||||
target: dev
|
||||
command: npm run dev --host
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 3000:3000
|
||||
- 24678:24678
|
||||
volumes:
|
||||
- ../web:/usr/src/app
|
||||
- /usr/src/app/node_modules
|
||||
restart: always
|
||||
depends_on:
|
||||
- immich-server
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:6.2
|
||||
networks:
|
||||
- immich_network
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
@@ -65,25 +94,20 @@ services:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
networks:
|
||||
- immich_network
|
||||
|
||||
nginx:
|
||||
container_name: proxy_nginx
|
||||
image: nginx:latest
|
||||
volumes:
|
||||
- ./settings/nginx-conf:/etc/nginx/conf.d
|
||||
immich-proxy:
|
||||
container_name: immich_proxy
|
||||
image: immich-proxy-dev:latest
|
||||
build:
|
||||
context: ../nginx
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- 2283:80
|
||||
- 2284:443
|
||||
- 2283:8080
|
||||
logging:
|
||||
driver: none
|
||||
networks:
|
||||
- immich_network
|
||||
depends_on:
|
||||
- immich_server
|
||||
- immich-server
|
||||
restart: always
|
||||
|
||||
networks:
|
||||
immich_network:
|
||||
volumes:
|
||||
pgdata:
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
immich_server:
|
||||
image: immich-server-dev:1.8.0
|
||||
build:
|
||||
context: ../server
|
||||
dockerfile: Dockerfile
|
||||
command: npm run start:dev
|
||||
expose:
|
||||
- "3000"
|
||||
volumes:
|
||||
- ../server:/usr/src/app
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
- /usr/src/app/node_modules
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
- redis
|
||||
- database
|
||||
networks:
|
||||
- immich_network
|
||||
|
||||
immich_microservices:
|
||||
image: immich-microservices-dev:1.8.0
|
||||
build:
|
||||
context: ../microservices
|
||||
dockerfile: Dockerfile
|
||||
command: npm run start:dev
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [ gpu ]
|
||||
expose:
|
||||
- "3001"
|
||||
volumes:
|
||||
- ../microservices:/usr/src/app
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
- /usr/src/app/node_modules
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
- database
|
||||
- immich_server
|
||||
networks:
|
||||
- immich_network
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:6.2
|
||||
networks:
|
||||
- immich_network
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: postgres:14
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||
PG_DATA: /var/lib/postgresql/data
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
networks:
|
||||
- immich_network
|
||||
|
||||
nginx:
|
||||
container_name: proxy_nginx
|
||||
image: nginx:latest
|
||||
volumes:
|
||||
- ./settings/nginx-conf:/etc/nginx/conf.d
|
||||
ports:
|
||||
- 2283:80
|
||||
- 2284:443
|
||||
logging:
|
||||
driver: none
|
||||
networks:
|
||||
- immich_network
|
||||
depends_on:
|
||||
- immich_server
|
||||
|
||||
networks:
|
||||
immich_network:
|
||||
volumes:
|
||||
pgdata:
|
||||
83
docker/docker-compose.staging.yml
Normal file
@@ -0,0 +1,83 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
immich-server:
|
||||
image: altran1502/immich-server:staging
|
||||
entrypoint: ["/bin/sh", "./start-server.sh"]
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
depends_on:
|
||||
- redis
|
||||
- database
|
||||
restart: always
|
||||
|
||||
immich-microservices:
|
||||
image: altran1502/immich-server:staging
|
||||
entrypoint: ["/bin/sh", "./start-microservices.sh"]
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
depends_on:
|
||||
- redis
|
||||
- database
|
||||
restart: always
|
||||
|
||||
immich-machine-learning:
|
||||
image: altran1502/immich-machine-learning:staging
|
||||
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
depends_on:
|
||||
- database
|
||||
restart: always
|
||||
|
||||
immich-web:
|
||||
image: altran1502/immich-web:staging
|
||||
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:6.2
|
||||
restart: always
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: postgres:14
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||
PG_DATA: /var/lib/postgresql/data
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
restart: always
|
||||
|
||||
immich-proxy:
|
||||
container_name: immich_proxy
|
||||
image: altran1502/immich-proxy:staging
|
||||
ports:
|
||||
- 2283:8080
|
||||
logging:
|
||||
driver: none
|
||||
depends_on:
|
||||
- immich-server
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
46
docker/docker-compose.test.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
immich-server-test:
|
||||
image: immich-server-test
|
||||
build:
|
||||
context: ../server
|
||||
dockerfile: Dockerfile
|
||||
target: builder
|
||||
command: npm run test:e2e
|
||||
expose:
|
||||
- "3000"
|
||||
volumes:
|
||||
- ../server:/usr/src/app
|
||||
- /usr/src/app/node_modules
|
||||
env_file:
|
||||
- .env.test
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
depends_on:
|
||||
- immich-redis-test
|
||||
- immich-database-test
|
||||
networks:
|
||||
- immich-test-network
|
||||
immich-redis-test:
|
||||
container_name: immich-redis-test
|
||||
image: redis:6.2
|
||||
networks:
|
||||
- immich-test-network
|
||||
immich-database-test:
|
||||
container_name: immich-database-test
|
||||
image: postgres:14
|
||||
env_file:
|
||||
- .env.test
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||
PG_DATA: /var/lib/postgresql/data
|
||||
volumes:
|
||||
- /var/lib/postgresql/data
|
||||
networks:
|
||||
- immich-test-network
|
||||
|
||||
networks:
|
||||
immich-test-network:
|
||||
@@ -1,11 +1,9 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
immich_server:
|
||||
image: altran1502/immich-server:v1.8.0_12-dev
|
||||
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
||||
expose:
|
||||
- "3000"
|
||||
immich-server:
|
||||
image: altran1502/immich-server:release
|
||||
entrypoint: ["/bin/sh", "./start-server.sh"]
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
env_file:
|
||||
@@ -15,15 +13,25 @@ services:
|
||||
depends_on:
|
||||
- redis
|
||||
- database
|
||||
networks:
|
||||
- immich_network
|
||||
restart: unless-stopped
|
||||
restart: always
|
||||
|
||||
immich_microservices:
|
||||
image: altran1502/immich-microservices:v1.8.0_12-dev
|
||||
immich-microservices:
|
||||
image: altran1502/immich-server:release
|
||||
entrypoint: ["/bin/sh", "./start-microservices.sh"]
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
depends_on:
|
||||
- redis
|
||||
- database
|
||||
restart: always
|
||||
|
||||
immich-machine-learning:
|
||||
image: altran1502/immich-machine-learning:release
|
||||
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
||||
expose:
|
||||
- "3001"
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
env_file:
|
||||
@@ -32,15 +40,21 @@ services:
|
||||
- NODE_ENV=production
|
||||
depends_on:
|
||||
- database
|
||||
networks:
|
||||
- immich_network
|
||||
restart: unless-stopped
|
||||
restart: always
|
||||
|
||||
immich-web:
|
||||
image: altran1502/immich-web:release
|
||||
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- PUBLIC_TZ=${TZ}
|
||||
restart: always
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:6.2
|
||||
networks:
|
||||
- immich_network
|
||||
restart: always
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
@@ -54,27 +68,18 @@ services:
|
||||
PG_DATA: /var/lib/postgresql/data
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
networks:
|
||||
- immich_network
|
||||
restart: always
|
||||
|
||||
nginx:
|
||||
container_name: proxy_nginx
|
||||
image: nginx:latest
|
||||
volumes:
|
||||
- ./settings/nginx-conf:/etc/nginx/conf.d
|
||||
immich-proxy:
|
||||
container_name: immich_proxy
|
||||
image: altran1502/immich-proxy:release
|
||||
ports:
|
||||
- 2283:80
|
||||
- 2284:443
|
||||
- 2283:8080
|
||||
logging:
|
||||
driver: none
|
||||
networks:
|
||||
- immich_network
|
||||
depends_on:
|
||||
- immich_server
|
||||
- immich-server
|
||||
restart: always
|
||||
|
||||
networks:
|
||||
immich_network:
|
||||
volumes:
|
||||
pgdata:
|
||||
pgdata:
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
# events {
|
||||
# worker_connections 1000;
|
||||
# }
|
||||
|
||||
server {
|
||||
|
||||
gzip on;
|
||||
gzip_min_length 1000;
|
||||
gunzip on;
|
||||
|
||||
client_max_body_size 50000M;
|
||||
|
||||
listen 80;
|
||||
access_log off;
|
||||
|
||||
location / {
|
||||
|
||||
# Compression
|
||||
gzip_static on;
|
||||
gzip_min_length 1000;
|
||||
gzip_comp_level 2;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_buffer_size 16k;
|
||||
proxy_busy_buffers_size 24k;
|
||||
proxy_buffers 64 4k;
|
||||
proxy_force_ranges on;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass http://immich_server:3000;
|
||||
}
|
||||
}
|
||||
91
install.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
echo "Starting Immich installation..."
|
||||
|
||||
ip_address=$(hostname -I | awk '{print $1}')
|
||||
|
||||
release_version=$(curl --silent "https://api.github.com/repos/immich-app/immich/releases/latest" |
|
||||
grep '"tag_name":' |
|
||||
sed -E 's/.*"([^"]+)".*/\1/')
|
||||
RED='\033[0;31m'
|
||||
GREEN='\032[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
get_release_version() {
|
||||
curl --silent "https://api.github.com/repos/immich-app/immich/releases/latest" | # Get latest release from GitHub api
|
||||
grep '"tag_name":' | # Get tag line
|
||||
sed -E 's/.*"([^"]+)".*/\1/' # Pluck JSON value
|
||||
}
|
||||
|
||||
create_immich_directory() {
|
||||
echo "Creating Immich directory..."
|
||||
mkdir -p ./immich-app/immich-data
|
||||
}
|
||||
|
||||
download_docker_compose_file() {
|
||||
echo "Downloading docker-compose.yml..."
|
||||
curl -L https://raw.githubusercontent.com/immich-app/immich/$release_version/docker/docker-compose.yml -o ./immich-app/docker-compose.yml >/dev/null 2>&1
|
||||
}
|
||||
|
||||
download_dot_env_file() {
|
||||
echo "Downloading .env file..."
|
||||
curl -L https://raw.githubusercontent.com/immich-app/immich/$release_version/docker/.env.example -o ./immich-app/.env >/dev/null 2>&1
|
||||
}
|
||||
|
||||
populate_upload_location() {
|
||||
echo "Populating default UPLOAD_LOCATION value..."
|
||||
|
||||
cd ./immich-app/immich-data
|
||||
|
||||
upload_location=$(pwd)
|
||||
|
||||
# Replace value of UPLOAD_LOCATION in .env with upload_location path
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' "s|UPLOAD_LOCATION=.*|UPLOAD_LOCATION=$upload_location|" ../.env
|
||||
else
|
||||
sed -i "s|UPLOAD_LOCATION=.*|UPLOAD_LOCATION=$upload_location|" ../.env
|
||||
fi
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
start_docker_compose() {
|
||||
echo "Starting Immich's docker containers"
|
||||
|
||||
if docker compose &>/dev/null; then
|
||||
docker_bin="docker compose"
|
||||
elif docker-compose &>/dev/null; then
|
||||
docker_bin="docker-compose"
|
||||
else
|
||||
echo 'Cannot find `docker compose` or `docker-compose`.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if $docker_bin up --remove-orphans -d; then
|
||||
show_friendly_message
|
||||
exit 0
|
||||
else
|
||||
echo "Could not start. Check for errors above."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
show_friendly_message() {
|
||||
echo "Succesfully deployed Immich!"
|
||||
echo "You can access the website at http://$ip_address:2283 and the server URL for the mobile app is http://$ip_address:2283/api"
|
||||
echo "The backup (or upload) location is $upload_location"
|
||||
echo "---------------------------------------------------"
|
||||
echo "If you want to configure custom information of the server, including the database, Redis information, or the backup (or upload) location, etc.
|
||||
|
||||
1. First bring down the containers with the command 'docker-compose down' in the immich-app directory,
|
||||
|
||||
2. Then change the information that fits your needs in the '.env' file,
|
||||
|
||||
3. Finally, bring the containers back up with the command 'docker-compose up --remove-orphans -d' in the immich-app directory"
|
||||
|
||||
}
|
||||
|
||||
# MAIN
|
||||
create_immich_directory
|
||||
download_docker_compose_file
|
||||
download_dot_env_file
|
||||
populate_upload_location
|
||||
start_docker_compose
|
||||
35
localizely.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
config_version: 1.0
|
||||
project_id: ead34689-ec52-41d9-b675-09bc85a6cbd7
|
||||
file_type: json
|
||||
upload:
|
||||
files:
|
||||
- file: mobile/assets/i18n/en-US.json
|
||||
locale_code: en-US
|
||||
- file: mobile/assets/i18n/de-DE.json
|
||||
locale_code: de-DE
|
||||
- file: mobile/assets/i18n/fr-FR.json
|
||||
locale_code: fr-FR
|
||||
- file: mobile/assets/i18n/it-IT.json
|
||||
locale_code: it-IT
|
||||
- file: mobile/assets/i18n/nl-NL.json
|
||||
locale_code: nl-NL
|
||||
- file: mobile/assets/i18n/ko-KR.json
|
||||
locale_code: ko-KR
|
||||
- file: mobile/assets/i18n/da-DK.json
|
||||
locale_code: da-DK
|
||||
download:
|
||||
files:
|
||||
- file: mobile/assets/i18n/en-US.json
|
||||
locale_code: en-US
|
||||
- file: mobile/assets/i18n/de-DE.json
|
||||
locale_code: de-DE
|
||||
- file: mobile/assets/i18n/fr-FR.json
|
||||
locale_code: fr-FR
|
||||
- file: mobile/assets/i18n/it-IT.json
|
||||
locale_code: it-IT
|
||||
- file: mobile/assets/i18n/nl-NL.json
|
||||
locale_code: nl-NL
|
||||
- file: mobile/assets/i18n/ko-KR.json
|
||||
locale_code: ko-KR
|
||||
- file: mobile/assets/i18n/da-DK.json
|
||||
locale_code: da-DK
|
||||
@@ -32,4 +32,6 @@ lerna-debug.log*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
upload/
|
||||
42
machine-learning/Dockerfile
Normal file
@@ -0,0 +1,42 @@
|
||||
# Build stage
|
||||
FROM node:16-bullseye-slim as builder
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install gcc g++ make cmake python3 python3-pip ffmpeg -y
|
||||
|
||||
RUN npm ci
|
||||
RUN npm rebuild @tensorflow/tfjs-node --build-from-source
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
|
||||
# Prod stage
|
||||
FROM node:16-bullseye-slim
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
COPY entrypoint.sh ./
|
||||
|
||||
RUN mkdir -p /usr/src/app/dist \
|
||||
&& mkdir -p /usr/src/app/node_modules \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y ffmpeg \
|
||||
&& rm -rf /var/cache/apt/lists
|
||||
|
||||
COPY --from=builder /usr/src/app/node_modules ./node_modules
|
||||
COPY --from=builder /usr/src/app/dist ./dist
|
||||
|
||||
RUN npm prune --production
|
||||
|
||||
# CMD [ "node", "dist/main" ]
|
||||
3
machine-learning/entrypoint.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
# npm run typeorm migration:run
|
||||
# npm run start:prod
|
||||
node dist/main.js
|
||||
@@ -28,11 +28,11 @@
|
||||
"@nestjs/typeorm": "^8.0.3",
|
||||
"@tensorflow-models/coco-ssd": "^2.2.2",
|
||||
"@tensorflow-models/mobilenet": "^2.1.0",
|
||||
"@tensorflow/tfjs": "^3.15.0",
|
||||
"@tensorflow/tfjs-converter": "^3.15.0",
|
||||
"@tensorflow/tfjs-core": "^3.15.0",
|
||||
"@tensorflow/tfjs-node": "^3.15.0",
|
||||
"@tensorflow/tfjs-node-gpu": "^3.15.0",
|
||||
"@tensorflow/tfjs": "^3.19.0",
|
||||
"@tensorflow/tfjs-converter": "^3.19.0",
|
||||
"@tensorflow/tfjs-core": "^3.19.0",
|
||||
"@tensorflow/tfjs-node": "^3.19.0",
|
||||
"@tensorflow/tfjs-node-gpu": "^3.19.0",
|
||||
"@trpc/server": "^9.20.3",
|
||||
"pg": "^8.7.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
@@ -2,8 +2,8 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||
|
||||
export const databaseConfig: TypeOrmModuleOptions = {
|
||||
type: 'postgres',
|
||||
host: 'immich_postgres',
|
||||
port: 5432,
|
||||
host: process.env.DB_HOSTNAME || 'immich_postgres',
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE_NAME,
|
||||
@@ -5,9 +5,9 @@ import { ImageClassifierService } from './image-classifier.service';
|
||||
export class ImageClassifierController {
|
||||
constructor(
|
||||
private readonly imageClassifierService: ImageClassifierService,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
@Post('/tagImage')
|
||||
@Post('/tag-image')
|
||||
async tagImage(@Body('thumbnailPath') thumbnailPath: string) {
|
||||
return await this.imageClassifierService.tagImage(thumbnailPath);
|
||||
}
|
||||
@@ -5,17 +5,17 @@ import { Logger } from '@nestjs/common';
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
await app.listen(3001, () => {
|
||||
await app.listen(3003, () => {
|
||||
if (process.env.NODE_ENV == 'development') {
|
||||
Logger.log(
|
||||
'Running Immich Microservices in DEVELOPMENT environment',
|
||||
'Running Immich Machine Learning in DEVELOPMENT environment',
|
||||
'IMMICH MICROSERVICES',
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV == 'production') {
|
||||
Logger.log(
|
||||
'Running Immich Microservices in PRODUCTION environment',
|
||||
'Running Immich Machine Learning in PRODUCTION environment',
|
||||
'IMMICH MICROSERVICES',
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { ObjectDetectionService } from './object-detection.service';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
@Controller('object-detection')
|
||||
export class ObjectDetectionController {
|
||||
constructor(
|
||||
private readonly objectDetectionService: ObjectDetectionService,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
@Post('/detectObject')
|
||||
@Post('/detect-object')
|
||||
async detectObject(@Body('thumbnailPath') thumbnailPath: string) {
|
||||
return await this.objectDetectionService.detectObject(thumbnailPath);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
devenv/
|
||||
3
machine_learning/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
__pycache__/
|
||||
devenv/
|
||||
app/upload
|
||||
@@ -1,25 +0,0 @@
|
||||
## GPU Build
|
||||
# FROM tensorflow/tensorflow:latest-gpu as gpu
|
||||
|
||||
# WORKDIR /code
|
||||
|
||||
# COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
# RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# COPY ./app /code/app
|
||||
|
||||
|
||||
## CPU BUILD
|
||||
FROM python:3.8 as cpu
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install ffmpeg libsm6 libxext6 -y
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
COPY ./app /code/app
|
||||
|
Before Width: | Height: | Size: 193 KiB |
@@ -1,37 +0,0 @@
|
||||
from tensorflow.keras.applications import InceptionV3
|
||||
from tensorflow.keras.applications.inception_v3 import preprocess_input, decode_predictions
|
||||
from tensorflow.keras.preprocessing import image
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import cv2
|
||||
IMG_SIZE = 299
|
||||
PREDICTION_MODEL = InceptionV3(weights='imagenet')
|
||||
|
||||
|
||||
def classify_image(image_path: str):
|
||||
img_path = f'./app/{image_path}'
|
||||
# img = image.load_img(img_path, target_size=(IMG_SIZE, IMG_SIZE))
|
||||
|
||||
target_image = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
|
||||
resized_target_image = cv2.resize(target_image, (IMG_SIZE, IMG_SIZE))
|
||||
|
||||
x = image.img_to_array(resized_target_image)
|
||||
x = np.expand_dims(x, axis=0)
|
||||
x = preprocess_input(x)
|
||||
|
||||
preds = PREDICTION_MODEL.predict(x)
|
||||
result = decode_predictions(preds, top=3)[0]
|
||||
payload = []
|
||||
for _, value, _ in result:
|
||||
payload.append(value)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def warm_up():
|
||||
img_path = f'./app/test.png'
|
||||
img = image.load_img(img_path, target_size=(IMG_SIZE, IMG_SIZE))
|
||||
x = image.img_to_array(img)
|
||||
x = np.expand_dims(x, axis=0)
|
||||
x = preprocess_input(x)
|
||||
PREDICTION_MODEL.predict(x)
|
||||
@@ -1,46 +0,0 @@
|
||||
from pydantic import BaseModel
|
||||
from fastapi import FastAPI
|
||||
|
||||
from .object_detection import object_detection
|
||||
from .image_classifier import image_classifier
|
||||
|
||||
from tf2_yolov4.anchors import YOLOV4_ANCHORS
|
||||
from tf2_yolov4.model import YOLOv4
|
||||
|
||||
|
||||
HEIGHT, WIDTH = (640, 960)
|
||||
|
||||
# Warm up model
|
||||
image_classifier.warm_up()
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class TagImagePayload(BaseModel):
|
||||
thumbnail_path: str
|
||||
|
||||
|
||||
@app.post("/tagImage")
|
||||
async def post_root(payload: TagImagePayload):
|
||||
image_path = payload.thumbnail_path
|
||||
|
||||
if image_path[0] == '.':
|
||||
image_path = image_path[2:]
|
||||
|
||||
return image_classifier.classify_image(image_path=image_path)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def test():
|
||||
|
||||
object_detection.run_detection()
|
||||
# image = tf.io.read_file("./app/cars.jpg")
|
||||
# image = tf.image.decode_image(image)
|
||||
# image = tf.image.resize(image, (HEIGHT, WIDTH))
|
||||
# images = tf.expand_dims(image, axis=0) / 255.0
|
||||
|
||||
# model = YOLOv4(
|
||||
# (HEIGHT, WIDTH, 3),
|
||||
# 80,
|
||||
# YOLOV4_ANCHORS,
|
||||
# "darknet",
|
||||
# )
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
|
||||
def run_detection():
|
||||
print("run detection")
|
||||
|
Before Width: | Height: | Size: 345 KiB |
@@ -1,8 +0,0 @@
|
||||
opencv-python==4.5.5.64
|
||||
fastapi>=0.68.0,<0.69.0
|
||||
pydantic>=1.8.0,<2.0.0
|
||||
uvicorn>=0.15.0,<0.16.0
|
||||
tensorflow==2.8.0
|
||||
numpy==1.22.2
|
||||
pillow==9.0.1
|
||||
tf2_yolov4==0.1.0
|
||||
@@ -1,16 +0,0 @@
|
||||
FROM node:16-bullseye-slim
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install gcc g++ make cmake python3 python3-pip ffmpeg -y
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
@@ -1,2 +0,0 @@
|
||||
# npm run typeorm migration:run
|
||||
npm run build && npm run start:prod
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from '../src/app.module';
|
||||
|
||||
// End to End test
|
||||
describe('AppController (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('/ (GET)', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Hello World!');
|
||||
});
|
||||
});
|
||||
2
mobile/.gitignore
vendored
@@ -24,7 +24,7 @@
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
**/ios/
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
|
||||
@@ -1,10 +1,45 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
# This file should be version controlled.
|
||||
|
||||
version:
|
||||
revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b
|
||||
revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
channel: stable
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
- platform: android
|
||||
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
- platform: ios
|
||||
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
- platform: linux
|
||||
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
- platform: macos
|
||||
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
- platform: web
|
||||
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
- platform: windows
|
||||
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
|
||||
@@ -21,9 +21,18 @@ linter:
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
use_build_context_synchronously: false
|
||||
require_trailing_commas: true
|
||||
unrelated_type_equality_checks: true
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
analyzer:
|
||||
exclude:
|
||||
- openapi/
|
||||
- openapi/test/
|
||||
- lib/generated_plugin_registrant.dart
|
||||
|
||||
3
mobile/android/.gitignore
vendored
@@ -11,3 +11,6 @@ GeneratedPluginRegistrant.java
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
|
||||
# Fastlane
|
||||
/fastlane/report.xml
|
||||
|
||||
@@ -51,7 +51,7 @@ android {
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "app.alextran.immich"
|
||||
minSdkVersion 21
|
||||
minSdkVersion 23
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
@@ -80,6 +80,8 @@ flutter {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
implementation "androidx.concurrent:concurrent-futures:$concurrent_version"
|
||||
implementation "com.google.guava:guava:$guava_version"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich">
|
||||
<application android:label="Immich" android:name="${applicationName}" android:usesCleartextTraffic="true" android:icon="@mipmap/ic_launcher">
|
||||
<application android:label="Immich" android:name="${applicationName}" android:usesCleartextTraffic="true" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
|
||||
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
@@ -12,6 +12,7 @@
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
<service android:name=".AppClearedService" android:stopWithTask="false" />
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
||||
@@ -23,4 +24,11 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -1,20 +0,0 @@
|
||||
// Generated file.
|
||||
// If you wish to remove Flutter's multidex support, delete this entire file.
|
||||
|
||||
package io.flutter.app;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.multidex.MultiDex;
|
||||
|
||||
/**
|
||||
* Extension of {@link io.flutter.app.FlutterApplication}, adding multidex support.
|
||||
*/
|
||||
public class FlutterMultiDexApplication extends FlutterApplication {
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
MultiDex.install(this);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.example.immich_mobile
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package app.alextran.immich
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
|
||||
/**
|
||||
* Catches the event when either the system or the user kills the app
|
||||
* (does not apply on force close!)
|
||||
*/
|
||||
class AppClearedService() : Service() {
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent) {
|
||||
ContentObserverWorker.workManagerAppClearedWorkaround(applicationContext)
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package app.alextran.immich
|
||||
|
||||
import android.content.Context
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
/**
|
||||
* Android plugin for Dart `BackgroundService`
|
||||
*
|
||||
* Receives messages/method calls from the foreground Dart side to manage
|
||||
* the background service, e.g. start (enqueue), stop (cancel)
|
||||
*/
|
||||
class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||
|
||||
private var methodChannel: MethodChannel? = null
|
||||
private var context: Context? = null
|
||||
|
||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
|
||||
}
|
||||
|
||||
private fun onAttachedToEngine(ctx: Context, messenger: BinaryMessenger) {
|
||||
context = ctx
|
||||
methodChannel = MethodChannel(messenger, "immich/foregroundChannel")
|
||||
methodChannel?.setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
onDetachedFromEngine()
|
||||
}
|
||||
|
||||
private fun onDetachedFromEngine() {
|
||||
methodChannel?.setMethodCallHandler(null)
|
||||
methodChannel = null
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
val ctx = context!!
|
||||
when(call.method) {
|
||||
"enable" -> {
|
||||
val args = call.arguments<ArrayList<*>>()!!
|
||||
ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putLong(BackupWorker.SHARED_PREF_CALLBACK_KEY, args.get(0) as Long)
|
||||
.putString(BackupWorker.SHARED_PREF_NOTIFICATION_TITLE, args.get(1) as String)
|
||||
.apply()
|
||||
ContentObserverWorker.enable(ctx, immediate = args.get(2) as Boolean)
|
||||
result.success(true)
|
||||
}
|
||||
"configure" -> {
|
||||
val args = call.arguments<ArrayList<*>>()!!
|
||||
val requireUnmeteredNetwork = args.get(0) as Boolean
|
||||
val requireCharging = args.get(1) as Boolean
|
||||
ContentObserverWorker.configureWork(ctx, requireUnmeteredNetwork, requireCharging)
|
||||
result.success(true)
|
||||
}
|
||||
"disable" -> {
|
||||
ContentObserverWorker.disable(ctx)
|
||||
BackupWorker.stopWork(ctx)
|
||||
result.success(true)
|
||||
}
|
||||
"isEnabled" -> {
|
||||
result.success(ContentObserverWorker.isEnabled(ctx))
|
||||
}
|
||||
"isIgnoringBatteryOptimizations" -> {
|
||||
result.success(BackupWorker.isIgnoringBatteryOptimizations(ctx))
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val TAG = "BackgroundServicePlugin"
|
||||
@@ -0,0 +1,328 @@
|
||||
package app.alextran.immich
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.PowerManager
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.concurrent.futures.ResolvableFuture
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.ListenableWorker
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkInfo
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.embedding.engine.dart.DartExecutor
|
||||
import io.flutter.embedding.engine.loader.FlutterLoader
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.view.FlutterCallbackInformation
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Worker executed by Android WorkManager to perform backup in background
|
||||
*
|
||||
* Starts the Dart runtime/engine and calls `_nativeEntry` function in
|
||||
* `background.service.dart` to run the actual backup logic.
|
||||
* Called by Android WorkManager when all constraints for the work are met,
|
||||
* i.e. battery is not low and optionally Wifi and charging are active.
|
||||
*/
|
||||
class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ctx, params), MethodChannel.MethodCallHandler {
|
||||
|
||||
private val resolvableFuture = ResolvableFuture.create<Result>()
|
||||
private var engine: FlutterEngine? = null
|
||||
private lateinit var backgroundChannel: MethodChannel
|
||||
private val notificationManager = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
private val isIgnoringBatteryOptimizations = isIgnoringBatteryOptimizations(applicationContext)
|
||||
private var timeBackupStarted: Long = 0L
|
||||
|
||||
override fun startWork(): ListenableFuture<ListenableWorker.Result> {
|
||||
|
||||
Log.d(TAG, "startWork")
|
||||
|
||||
val ctx = applicationContext
|
||||
|
||||
if (!flutterLoader.initialized()) {
|
||||
flutterLoader.startInitialization(ctx)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Create a Notification channel if necessary
|
||||
createChannel()
|
||||
}
|
||||
val title = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getString(SHARED_PREF_NOTIFICATION_TITLE, NOTIFICATION_DEFAULT_TITLE)!!
|
||||
if (isIgnoringBatteryOptimizations) {
|
||||
// normal background services can only up to 10 minutes
|
||||
// foreground services are allowed to run indefinitely
|
||||
// requires battery optimizations to be disabled (either manually by the user
|
||||
// or by the system learning that immich is important to the user)
|
||||
setForegroundAsync(createForegroundInfo(title))
|
||||
} else {
|
||||
showBackgroundInfo(title)
|
||||
}
|
||||
engine = FlutterEngine(ctx)
|
||||
|
||||
flutterLoader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
|
||||
runDart()
|
||||
}
|
||||
|
||||
return resolvableFuture
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the Dart runtime/engine and calls `_nativeEntry` function in
|
||||
* `background.service.dart` to run the actual backup logic.
|
||||
*/
|
||||
private fun runDart() {
|
||||
val callbackDispatcherHandle = applicationContext.getSharedPreferences(
|
||||
SHARED_PREF_NAME, Context.MODE_PRIVATE).getLong(SHARED_PREF_CALLBACK_KEY, 0L)
|
||||
val callbackInformation = FlutterCallbackInformation.lookupCallbackInformation(callbackDispatcherHandle)
|
||||
val appBundlePath = flutterLoader.findAppBundlePath()
|
||||
|
||||
engine?.let { engine ->
|
||||
backgroundChannel = MethodChannel(engine.dartExecutor, "immich/backgroundChannel")
|
||||
backgroundChannel.setMethodCallHandler(this@BackupWorker)
|
||||
engine.dartExecutor.executeDartCallback(
|
||||
DartExecutor.DartCallback(
|
||||
applicationContext.assets,
|
||||
appBundlePath,
|
||||
callbackInformation
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStopped() {
|
||||
Log.d(TAG, "onStopped")
|
||||
// called when the system has to stop this worker because constraints are
|
||||
// no longer met or the system needs resources for more important tasks
|
||||
Handler(Looper.getMainLooper()).postAtFrontOfQueue {
|
||||
backgroundChannel.invokeMethod("systemStop", null)
|
||||
}
|
||||
// cannot await/get(block) on resolvableFuture as its already cancelled (would throw CancellationException)
|
||||
// instead, wait for 5 seconds until forcefully stopping backup work
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
stopEngine(null)
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
|
||||
private fun stopEngine(result: Result?) {
|
||||
if (result != null) {
|
||||
Log.d(TAG, "stopEngine result=${result}")
|
||||
resolvableFuture.set(result)
|
||||
}
|
||||
engine?.destroy()
|
||||
engine = null
|
||||
clearBackgroundNotification()
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, r: MethodChannel.Result) {
|
||||
when (call.method) {
|
||||
"initialized" -> {
|
||||
timeBackupStarted = SystemClock.uptimeMillis()
|
||||
backgroundChannel.invokeMethod(
|
||||
"onAssetsChanged",
|
||||
null,
|
||||
object : MethodChannel.Result {
|
||||
override fun notImplemented() {
|
||||
stopEngine(Result.failure())
|
||||
}
|
||||
|
||||
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
|
||||
stopEngine(Result.failure())
|
||||
}
|
||||
|
||||
override fun success(receivedResult: Any?) {
|
||||
val success = receivedResult as Boolean
|
||||
stopEngine(if(success) Result.success() else Result.retry())
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
"updateNotification" -> {
|
||||
val args = call.arguments<ArrayList<*>>()!!
|
||||
val title = args.get(0) as String
|
||||
val content = args.get(1) as String
|
||||
if (isIgnoringBatteryOptimizations) {
|
||||
setForegroundAsync(createForegroundInfo(title, content))
|
||||
} else {
|
||||
showBackgroundInfo(title, content)
|
||||
}
|
||||
}
|
||||
"showError" -> {
|
||||
val args = call.arguments<ArrayList<*>>()!!
|
||||
val title = args.get(0) as String
|
||||
val content = args.get(1) as String
|
||||
val individualTag = args.get(2) as String?
|
||||
showError(title, content, individualTag)
|
||||
}
|
||||
"clearErrorNotifications" -> clearErrorNotifications()
|
||||
"hasContentChanged" -> {
|
||||
val lastChange = applicationContext
|
||||
.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getLong(SHARED_PREF_LAST_CHANGE, timeBackupStarted)
|
||||
val hasContentChanged = lastChange > timeBackupStarted;
|
||||
timeBackupStarted = SystemClock.uptimeMillis()
|
||||
r.success(hasContentChanged)
|
||||
}
|
||||
else -> r.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showError(title: String, content: String, individualTag: String?) {
|
||||
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ERROR_ID)
|
||||
.setContentTitle(title)
|
||||
.setTicker(title)
|
||||
.setContentText(content)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setOnlyAlertOnce(true)
|
||||
.build()
|
||||
notificationManager.notify(individualTag, NOTIFICATION_ERROR_ID, notification)
|
||||
}
|
||||
|
||||
private fun clearErrorNotifications() {
|
||||
notificationManager.cancel(NOTIFICATION_ERROR_ID)
|
||||
}
|
||||
|
||||
private fun showBackgroundInfo(title: String = NOTIFICATION_DEFAULT_TITLE, content: String? = null) {
|
||||
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setTicker(title)
|
||||
.setContentText(content)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
notificationManager.notify(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
private fun clearBackgroundNotification() {
|
||||
notificationManager.cancel(NOTIFICATION_ID)
|
||||
}
|
||||
|
||||
private fun createForegroundInfo(title: String = NOTIFICATION_DEFAULT_TITLE, content: String? = null): ForegroundInfo {
|
||||
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setTicker(title)
|
||||
.setContentText(content)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
return ForegroundInfo(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createChannel() {
|
||||
val foreground = NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW)
|
||||
notificationManager.createNotificationChannel(foreground)
|
||||
val error = NotificationChannel(NOTIFICATION_CHANNEL_ERROR_ID, NOTIFICATION_CHANNEL_ERROR_ID, NotificationManager.IMPORTANCE_DEFAULT)
|
||||
notificationManager.createNotificationChannel(error)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SHARED_PREF_NAME = "immichBackgroundService"
|
||||
const val SHARED_PREF_CALLBACK_KEY = "callbackDispatcherHandle"
|
||||
const val SHARED_PREF_NOTIFICATION_TITLE = "notificationTitle"
|
||||
const val SHARED_PREF_LAST_CHANGE = "lastChange"
|
||||
|
||||
private const val TASK_NAME_BACKUP = "immich/BackupWorker"
|
||||
private const val NOTIFICATION_CHANNEL_ID = "immich/backgroundService"
|
||||
private const val NOTIFICATION_CHANNEL_ERROR_ID = "immich/backgroundServiceError"
|
||||
private const val NOTIFICATION_DEFAULT_TITLE = "Immich"
|
||||
private const val NOTIFICATION_ID = 1
|
||||
private const val NOTIFICATION_ERROR_ID = 2
|
||||
private const val ONE_MINUTE = 60000L
|
||||
|
||||
/**
|
||||
* Enqueues the BackupWorker to run once the constraints are met
|
||||
*/
|
||||
fun enqueueBackupWorker(context: Context,
|
||||
requireWifi: Boolean = false,
|
||||
requireCharging: Boolean = false,
|
||||
delayMilliseconds: Long = 0L) {
|
||||
val workRequest = buildWorkRequest(requireWifi, requireCharging, delayMilliseconds)
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(TASK_NAME_BACKUP, ExistingWorkPolicy.KEEP, workRequest)
|
||||
Log.d(TAG, "enqueueBackupWorker: BackupWorker enqueued")
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the constraints of an already enqueued BackupWorker
|
||||
*/
|
||||
fun updateBackupWorker(context: Context,
|
||||
requireWifi: Boolean = false,
|
||||
requireCharging: Boolean = false) {
|
||||
try {
|
||||
val wm = WorkManager.getInstance(context)
|
||||
val workInfoFuture = wm.getWorkInfosForUniqueWork(TASK_NAME_BACKUP)
|
||||
val workInfoList = workInfoFuture.get(1000, TimeUnit.MILLISECONDS)
|
||||
if (workInfoList != null) {
|
||||
for (workInfo in workInfoList) {
|
||||
if (workInfo.getState() == WorkInfo.State.ENQUEUED) {
|
||||
val workRequest = buildWorkRequest(requireWifi, requireCharging)
|
||||
wm.enqueueUniqueWork(TASK_NAME_BACKUP, ExistingWorkPolicy.REPLACE, workRequest)
|
||||
Log.d(TAG, "updateBackupWorker updated BackupWorker constraints")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "updateBackupWorker: BackupWorker not enqueued")
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "updateBackupWorker failed: ${e}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the currently running worker (if any) and removes it from the work queue
|
||||
*/
|
||||
fun stopWork(context: Context) {
|
||||
WorkManager.getInstance(context).cancelUniqueWork(TASK_NAME_BACKUP)
|
||||
Log.d(TAG, "stopWork: BackupWorker cancelled")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the app is ignoring battery optimizations
|
||||
*/
|
||||
fun isIgnoringBatteryOptimizations(ctx: Context): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val pwrm = ctx.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
val name = ctx.packageName
|
||||
return pwrm.isIgnoringBatteryOptimizations(name)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun buildWorkRequest(requireWifi: Boolean = false,
|
||||
requireCharging: Boolean = false,
|
||||
delayMilliseconds: Long = 0L): OneTimeWorkRequest {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(if (requireWifi) NetworkType.UNMETERED else NetworkType.CONNECTED)
|
||||
.setRequiresBatteryNotLow(true)
|
||||
.setRequiresCharging(requireCharging)
|
||||
.build();
|
||||
|
||||
val work = OneTimeWorkRequest.Builder(BackupWorker::class.java)
|
||||
.setConstraints(constraints)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, ONE_MINUTE, TimeUnit.MILLISECONDS)
|
||||
.setInitialDelay(delayMilliseconds, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
return work
|
||||
}
|
||||
|
||||
private val flutterLoader = FlutterLoader()
|
||||
}
|
||||
}
|
||||
|
||||
private const val TAG = "BackupWorker"
|
||||
@@ -0,0 +1,137 @@
|
||||
package app.alextran.immich
|
||||
|
||||
import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.Operation
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Worker executed by Android WorkManager observing content changes (new photos/videos)
|
||||
*
|
||||
* Immediately enqueues the BackupWorker when running.
|
||||
* As this work is not triggered periodically, but on content change, the
|
||||
* worker enqueues itself again after each run.
|
||||
*/
|
||||
class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
|
||||
|
||||
override fun doWork(): Result {
|
||||
if (!isEnabled(applicationContext)) {
|
||||
return Result.failure()
|
||||
}
|
||||
if (getTriggeredContentUris().size > 0) {
|
||||
startBackupWorker(applicationContext, delayMilliseconds = 0)
|
||||
}
|
||||
enqueueObserverWorker(applicationContext, ExistingWorkPolicy.REPLACE)
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SHARED_PREF_SERVICE_ENABLED = "serviceEnabled"
|
||||
const val SHARED_PREF_REQUIRE_WIFI = "requireWifi"
|
||||
const val SHARED_PREF_REQUIRE_CHARGING = "requireCharging"
|
||||
|
||||
private const val TASK_NAME_OBSERVER = "immich/ContentObserver"
|
||||
|
||||
/**
|
||||
* Enqueues the `ContentObserverWorker`.
|
||||
*
|
||||
* @param context Android Context
|
||||
*/
|
||||
fun enable(context: Context, immediate: Boolean = false) {
|
||||
// migration to remove any old active background task
|
||||
WorkManager.getInstance(context).cancelUniqueWork("immich/photoListener")
|
||||
|
||||
enqueueObserverWorker(context, ExistingWorkPolicy.KEEP)
|
||||
Log.d(TAG, "enabled ContentObserverWorker")
|
||||
if (immediate) {
|
||||
startBackupWorker(context, delayMilliseconds = 5000)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the `BackupWorker` to run when all constraints are met.
|
||||
*
|
||||
* @param context Android Context
|
||||
* @param requireWifi if true, task only runs if connected to wifi
|
||||
* @param requireCharging if true, task only runs if device is charging
|
||||
*/
|
||||
fun configureWork(context: Context,
|
||||
requireWifi: Boolean = false,
|
||||
requireCharging: Boolean = false) {
|
||||
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(SHARED_PREF_SERVICE_ENABLED, true)
|
||||
.putBoolean(SHARED_PREF_REQUIRE_WIFI, requireWifi)
|
||||
.putBoolean(SHARED_PREF_REQUIRE_CHARGING, requireCharging)
|
||||
.apply()
|
||||
BackupWorker.updateBackupWorker(context, requireWifi, requireCharging)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the currently running worker (if any) and removes it from the work queue
|
||||
*/
|
||||
fun disable(context: Context) {
|
||||
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().putBoolean(SHARED_PREF_SERVICE_ENABLED, false).apply()
|
||||
WorkManager.getInstance(context).cancelUniqueWork(TASK_NAME_OBSERVER)
|
||||
Log.d(TAG, "disabled ContentObserverWorker")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the user has enabled the background backup service
|
||||
*/
|
||||
fun isEnabled(ctx: Context): Boolean {
|
||||
return ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getBoolean(SHARED_PREF_SERVICE_ENABLED, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue and replace the worker without the content trigger but with a short delay
|
||||
*/
|
||||
fun workManagerAppClearedWorkaround(context: Context) {
|
||||
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
|
||||
.setInitialDelay(500, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
WorkManager
|
||||
.getInstance(context)
|
||||
.enqueueUniqueWork(TASK_NAME_OBSERVER, ExistingWorkPolicy.REPLACE, work)
|
||||
.getResult()
|
||||
.get()
|
||||
Log.d(TAG, "workManagerAppClearedWorkaround")
|
||||
}
|
||||
|
||||
private fun enqueueObserverWorker(context: Context, policy: ExistingWorkPolicy) {
|
||||
val constraints = Constraints.Builder()
|
||||
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
|
||||
.setTriggerContentUpdateDelay(5000, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(TASK_NAME_OBSERVER, policy, work)
|
||||
}
|
||||
|
||||
private fun startBackupWorker(context: Context, delayMilliseconds: Long) {
|
||||
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
val requireWifi = sp.getBoolean(SHARED_PREF_REQUIRE_WIFI, true)
|
||||
val requireCharging = sp.getBoolean(SHARED_PREF_REQUIRE_CHARGING, false)
|
||||
BackupWorker.enqueueBackupWorker(context, requireWifi, requireCharging, delayMilliseconds)
|
||||
sp.edit().putLong(BackupWorker.SHARED_PREF_LAST_CHANGE, SystemClock.uptimeMillis()).apply()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private const val TAG = "ContentObserverWorker"
|
||||
@@ -1,6 +1,25 @@
|
||||
package app.alextran.immich
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import android.os.Bundle
|
||||
import android.content.Intent
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
flutterEngine.getPlugins().add(BackgroundServicePlugin())
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
try {
|
||||
startService(Intent(getBaseContext(), AppClearedService::class.java));
|
||||
} catch (e: Exception) {
|
||||
// startService must not be called when app is in background (crashes app)
|
||||
// there is nothing we can do
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.6.10'
|
||||
ext.work_version = '2.7.1'
|
||||
ext.concurrent_version = '1.1.0'
|
||||
ext.guava_version = '31.0.1-android'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
json_key_file("/Users/alex/Documents/immich-fastlane-googleplaystore-key.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
|
||||
package_name("app.alextran.immich") # e.g. com.krausefx.app
|
||||
json_key_file("/Users/alex/Documents/immich-play-store-key.json")
|
||||
package_name("app.alextran.immich")
|
||||
|
||||
@@ -16,10 +16,25 @@
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Build Android"
|
||||
lane :build do
|
||||
gradle(
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
)
|
||||
end
|
||||
|
||||
desc "Update AAB to PlayStore"
|
||||
lane :beta do
|
||||
upload_to_play_store(track: 'beta', aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||
desc "Build and Release Android"
|
||||
lane :release do
|
||||
gradle(
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 44,
|
||||
"android.injected.version.name" => "1.29.4",
|
||||
}
|
||||
)
|
||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -15,13 +15,21 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
|
||||
|
||||
## Android
|
||||
|
||||
### android beta
|
||||
### android build
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android beta
|
||||
[bundle exec] fastlane android build
|
||||
```
|
||||
|
||||
Update AAB to PlayStore
|
||||
Build Android
|
||||
|
||||
### android release
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android release
|
||||
```
|
||||
|
||||
Build and Release Android
|
||||
|
||||
----
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
* New Feature - Selection backup. User can now select a combination of albums to be included or excluded during the backup process, and only unique photos, and videos that are not overlapping between the two groups will be backup.
|
||||
* Bug fix - Show correct count of backup and remainder assets.
|
||||
@@ -0,0 +1 @@
|
||||
* Hotfix: Permission is being requested now when open backup screen on Android10
|
||||
@@ -0,0 +1 @@
|
||||
* User can now upload profile picture from the home page control drawer.
|
||||
@@ -0,0 +1,2 @@
|
||||
* Update to Material Design 3
|
||||
* Fixed back button navigation - no longer return back to the home page
|
||||
@@ -0,0 +1 @@
|
||||
* Added announcement pop-up when a new released is pushed out in Github.
|
||||
@@ -0,0 +1,2 @@
|
||||
* Fixed crash issue when upload large file on slow network
|
||||
* Updated album to conform with server refactoring of SharedAlbum to Album
|
||||
@@ -0,0 +1,2 @@
|
||||
* Added zoom functionality to the image viewer
|
||||
* Fixed issue with the user is logged out after turning off the app
|
||||
@@ -0,0 +1 @@
|
||||
* Fixed WebSocket endpoint to confirm with the new settings on the server
|
||||
@@ -0,0 +1,3 @@
|
||||
* Fixed app does not resume back up when reopening a closed app
|
||||
* Fixed wrong asset count on the upload page
|
||||
* Added mechanism to change the password of new user on the first login (except Admin)
|
||||
@@ -0,0 +1,2 @@
|
||||
* Fixed admin is forced to change password upon logging in on mobile app
|
||||
* Fixed change password form validation
|
||||