Compare commits

..

2 Commits

Author SHA1 Message Date
shenlong-tanwen 425eb2c66e always use at-least 5 isolates 2025-10-01 06:37:26 +05:30
Alex e9616a3fbc fix: isolate freeze app on older ios device 2025-09-30 13:01:51 -05:00
36 changed files with 316 additions and 125 deletions
+1 -1
View File
@@ -1 +1 @@
22.20.0 22.19.0
+5 -4
View File
@@ -38,11 +38,12 @@
<a href="readme_i18n/README_th_TH.md">ภาษาไทย</a> <a href="readme_i18n/README_th_TH.md">ภาษาไทย</a>
</p> </p>
## Disclaimer
> [!WARNING] - ⚠️ The project is under **very active** development.
> ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos! - ⚠️ Expect bugs and breaking changes.
> - ⚠️ **Do not use the app as the only way to store your photos and videos.**
- ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos!
> [!NOTE] > [!NOTE]
> You can find the main documentation, including installation guides, at https://immich.app/. > You can find the main documentation, including installation guides, at https://immich.app/.
+1 -1
View File
@@ -1 +1 @@
22.20.0 22.19.0
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.95", "version": "2.2.94",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",
@@ -68,6 +68,6 @@
"micromatch": "^4.0.8" "micromatch": "^4.0.8"
}, },
"volta": { "volta": {
"node": "22.20.0" "node": "22.19.0"
} }
} }
+1 -1
View File
@@ -1 +1 @@
22.20.0 22.19.0
-32
View File
@@ -1,32 +0,0 @@
---
sidebar_position: 65
---
# One-Click [Cloud Service]
:::note
This version of Immich is provided via cloud service provider's one-click marketplaces. Hosting costs are set by the cloud service providers.
Support for these are provided by the individual cloud service providers.
**Please report issues to the corresponding [Github Repository][github].**
:::
## Installation
Simply goto the providers marketplace and choose Immich, then follow the provided instructions.
## One-Click Immich marketplace providers
### DigitalOcean
https://marketplace.digitalocean.com/apps/immich
### Vultr
https://www.vultr.com/marketplace/apps/immich
## Issues
For issues, open an issue on the associated [GitHub Repository][github].
[github]: https://github.com/imagegenius/docker-immich/
+3 -1
View File
@@ -4,7 +4,9 @@ sidebar_position: 95
# Upgrading # Upgrading
:::tip Breaking changes :::danger Read the release notes
Immich is currently under heavy development, which means you can expect [breaking changes][breaking] and bugs. You should read the release notes prior to updating and take special care when using automated tools like [Watchtower][watchtower].
You can see versions that had breaking changes [here][breaking]. You can see versions that had breaking changes [here][breaking].
::: :::
+3 -2
View File
@@ -1,6 +1,7 @@
Now that you have imported some pictures, you should setup server backups to preserve your memories. Now that you have imported some pictures, you should setup server backups to preserve your memories.
You can do so by following our [backup guide](/administration/backup-and-restore.md). You can do so by following our [backup guide](/administration/backup-and-restore.md).
:::info :::danger
A 3-2-1 backup strategy is still crucial. The team has the responsibility to ensure that the application doesnt cause loss of your precious memories; however, we cannot guarantee that hard drives will not fail, or an electrical event causes unexpected shutdown of your server/system, leading to data loss. Therefore, we still encourage users to follow best practices when safeguarding their data. Keep multiple copies of your most precious data: at least two local copies and one copy offsite in cold storage. Immich is still under heavy development _and_ handles very important data.
It is essential that you set up good backups, and test them.
::: :::
+6 -1
View File
@@ -7,7 +7,7 @@ const prism = require('prism-react-renderer');
const config = { const config = {
title: 'Immich', title: 'Immich',
tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone', tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone',
url: 'https://docs.immich.app', url: 'https://immich.app',
baseUrl: '/', baseUrl: '/',
onBrokenLinks: 'throw', onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn', onBrokenMarkdownLinks: 'warn',
@@ -65,6 +65,11 @@ const config = {
themeConfig: themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */ /** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({ ({
announcementBar: {
id: 'site_announcement_immich',
content: `⚠️ The project is under <strong>very active</strong> development. Expect bugs and changes. Do not use it as <strong>the only way</strong> to store your photos and videos!`,
isCloseable: false,
},
docs: { docs: {
sidebar: { sidebar: {
autoCollapseCategories: false, autoCollapseCategories: false,
+1 -1
View File
@@ -57,6 +57,6 @@
"node": ">=20" "node": ">=20"
}, },
"volta": { "volta": {
"node": "22.20.0" "node": "22.19.0"
} }
} }
-4
View File
@@ -1,8 +1,4 @@
[ [
{
"label": "v2.0.0",
"url": "https://docs.v2.0.0.archive.immich.app"
},
{ {
"label": "v1.144.1", "label": "v1.144.1",
"url": "https://docs.v1.144.1.archive.immich.app" "url": "https://docs.v1.144.1.archive.immich.app"
+1 -1
View File
@@ -1 +1 @@
22.20.0 22.19.0
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "2.0.0", "version": "1.144.1",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
@@ -52,6 +52,6 @@
"vitest": "^3.0.0" "vitest": "^3.0.0"
}, },
"volta": { "volta": {
"node": "22.20.0" "node": "22.19.0"
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "immich-ml" name = "immich-ml"
version = "2.0.0" version = "1.144.1"
description = "" description = ""
authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }] authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }]
requires-python = ">=3.10,<4.0" requires-python = ">=3.10,<4.0"
+1 -1
View File
@@ -1,5 +1,5 @@
[tools] [tools]
node = "22.20.0" node = "22.19.0"
flutter = "3.35.4" flutter = "3.35.4"
pnpm = "10.15.1" pnpm = "10.15.1"
+2 -2
View File
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 3020, "android.injected.version.code" => 3019,
"android.injected.version.name" => "2.0.0", "android.injected.version.name" => "1.144.1",
} }
) )
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') 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')
+1 -1
View File
@@ -22,7 +22,7 @@ platform :ios do
path: "./Runner.xcodeproj", path: "./Runner.xcodeproj",
) )
increment_version_number( increment_version_number(
version_number: "2.0.0" version_number: "1.144.1"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,
@@ -32,9 +32,9 @@ import 'package:immich_mobile/services/upload.service.dart';
import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/bootstrap.dart';
import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/utils/http_ssl_options.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart';
import 'package:immich_mobile/wm_executor.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:worker_manager/worker_manager.dart';
class BackgroundWorkerFgService { class BackgroundWorkerFgService {
final BackgroundWorkerFgHostApi _foregroundHostApi; final BackgroundWorkerFgHostApi _foregroundHostApi;
@@ -93,7 +93,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
await Future.wait( await Future.wait(
[ [
loadTranslations(), loadTranslations(),
workerManager.init(dynamicSpawning: true), workerManagerPatch.init(dynamicSpawning: true),
_ref?.read(authServiceProvider).setOpenApiServiceEndpoint(), _ref?.read(authServiceProvider).setOpenApiServiceEndpoint(),
// Initialize the file downloader // Initialize the file downloader
FileDownloader().configure( FileDownloader().configure(
@@ -198,7 +198,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
_cancellationToken.cancel(); _cancellationToken.cancel();
_logger.info("Cleaning up background worker"); _logger.info("Cleaning up background worker");
final cleanupFutures = [ final cleanupFutures = [
workerManager.dispose().catchError((_) async { workerManagerPatch.dispose().catchError((_) async {
// Discard any errors on the dispose call // Discard any errors on the dispose call
return; return;
}), }),
@@ -130,9 +130,9 @@ class SyncStreamService {
// to acknowledge that the client has processed all the backfill events // to acknowledge that the client has processed all the backfill events
case SyncEntityType.syncAckV1: case SyncEntityType.syncAckV1:
return; return;
// SyncCompleteV1 is used to signal the completion of the sync process. Cleanup stale assets and signal completion // No-op. SyncCompleteV1 is used to signal the completion of the sync process
case SyncEntityType.syncCompleteV1: case SyncEntityType.syncCompleteV1:
return _syncStreamRepository.pruneAssets(); return;
// Request to reset the client state. Clear everything related to remote entities // Request to reset the client state. Clear everything related to remote entities
case SyncEntityType.syncResetV1: case SyncEntityType.syncResetV1:
return _syncStreamRepository.reset(); return _syncStreamRepository.reset();
@@ -591,40 +591,6 @@ class SyncStreamRepository extends DriftDatabaseRepository {
rethrow; rethrow;
} }
} }
Future<void> pruneAssets() async {
try {
await _db.transaction(() async {
final authQuery = _db.authUserEntity.selectOnly()
..addColumns([_db.authUserEntity.id])
..limit(1);
final currentUserId = await authQuery.map((row) => row.read(_db.authUserEntity.id)).getSingleOrNull();
if (currentUserId == null) {
_logger.warning('No authenticated user found during pruneAssets. Skipping asset pruning.');
return;
}
final partnerQuery = _db.partnerEntity.selectOnly()
..addColumns([_db.partnerEntity.sharedById])
..where(_db.partnerEntity.sharedWithId.equals(currentUserId));
final partnerIds = await partnerQuery.map((row) => row.read(_db.partnerEntity.sharedById)).get();
final validUsers = {currentUserId, ...partnerIds.nonNulls};
// Asset is not owned by the current user or any of their partners and is not part of any (shared) album
// Likely a stale asset that was previously shared but has been removed
await _db.remoteAssetEntity.deleteWhere((asset) {
return asset.ownerId.isNotIn(validUsers) &
asset.id.isNotInQuery(
_db.remoteAlbumAssetEntity.selectOnly()..addColumns([_db.remoteAlbumAssetEntity.assetId]),
);
});
});
} catch (error, stack) {
_logger.severe('Error: pruneAssets', error, stack);
// We do not rethrow here as this is a client-only cleanup and should not affect the sync process
}
}
} }
extension on AssetTypeEnum { extension on AssetTypeEnum {
+3 -2
View File
@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:background_downloader/background_downloader.dart'; import 'package:background_downloader/background_downloader.dart';
@@ -38,10 +39,10 @@ import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/utils/http_ssl_options.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart';
import 'package:immich_mobile/utils/licenses.dart'; import 'package:immich_mobile/utils/licenses.dart';
import 'package:immich_mobile/utils/migration.dart'; import 'package:immich_mobile/utils/migration.dart';
import 'package:immich_mobile/wm_executor.dart';
import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbol_data_local.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:timezone/data/latest.dart'; import 'package:timezone/data/latest.dart';
import 'package:worker_manager/worker_manager.dart';
void main() async { void main() async {
ImmichWidgetsBinding(); ImmichWidgetsBinding();
@@ -50,7 +51,7 @@ void main() async {
await Bootstrap.initDomain(isar, drift, logDb); await Bootstrap.initDomain(isar, drift, logDb);
await initApp(); await initApp();
// Warm-up isolate pool for worker manager // Warm-up isolate pool for worker manager
await workerManager.init(dynamicSpawning: true); await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: max(Platform.numberOfProcessors - 1, 5));
await migrateDatabaseIfNeeded(isar, drift); await migrateDatabaseIfNeeded(isar, drift);
HttpSSLOptions.apply(); HttpSSLOptions.apply();
+2 -1
View File
@@ -11,6 +11,7 @@ import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/bootstrap.dart';
import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/utils/http_ssl_options.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart';
import 'package:immich_mobile/wm_executor.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:worker_manager/worker_manager.dart'; import 'package:worker_manager/worker_manager.dart';
@@ -31,7 +32,7 @@ Cancelable<T?> runInIsolateGentle<T>({
throw const InvalidIsolateUsageException(); throw const InvalidIsolateUsageException();
} }
return workerManager.executeGentle((cancelledChecker) async { return workerManagerPatch.executeGentle((cancelledChecker) async {
T? result; T? result;
await runZonedGuarded( await runZonedGuarded(
() async { () async {
+251
View File
@@ -0,0 +1,251 @@
// part of 'package:worker_manager/worker_manager.dart';
// ignore_for_file: implementation_imports, avoid_print
import 'dart:async';
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:worker_manager/src/number_of_processors/processors_io.dart';
import 'package:worker_manager/src/worker/worker.dart';
import 'package:worker_manager/worker_manager.dart';
final workerManagerPatch = _Executor();
// [-2^54; 2^53] is compatible with dart2js, see core.int doc
const _minId = -9007199254740992;
const _maxId = 9007199254740992;
class Mixinable<T> {
late final itSelf = this as T;
}
mixin _ExecutorLogger on Mixinable<_Executor> {
var log = false;
@mustCallSuper
void init() {
logMessage("${itSelf._isolatesCount} workers have been spawned and initialized");
}
void logTaskAdded<R>(String uid) {
logMessage("added task with number $uid");
}
@mustCallSuper
void dispose() {
logMessage("worker_manager have been disposed");
}
@mustCallSuper
void _cancel(Task task) {
logMessage("Task ${task.id} have been canceled");
}
void logMessage(String message) {
if (log) print(message);
}
}
class _Executor extends Mixinable<_Executor> with _ExecutorLogger {
final _queue = PriorityQueue<Task>();
final _pool = <Worker>[];
var _nextTaskId = _minId;
var _dynamicSpawning = false;
var _isolatesCount = numberOfProcessors;
@override
Future<void> init({int? isolatesCount, bool? dynamicSpawning}) async {
if (_pool.isNotEmpty) {
print("worker_manager already warmed up, init is ignored. Dispose before init");
return;
}
if (isolatesCount != null) {
if (isolatesCount < 0) {
throw Exception("isolatesCount must be greater than 0");
}
_isolatesCount = isolatesCount;
}
_dynamicSpawning = dynamicSpawning ?? false;
await _ensureWorkersInitialized();
super.init();
}
@override
Future<void> dispose() async {
_queue.clear();
for (final worker in _pool) {
worker.kill();
}
_pool.clear();
super.dispose();
}
Cancelable<R> execute<R>(Execute<R> execution, {WorkPriority priority = WorkPriority.immediately}) {
return _createCancelable<R>(execution: execution, priority: priority);
}
Cancelable<R> executeNow<R>(ExecuteGentle<R> execution) {
final task = TaskGentle<R>(
id: "",
workPriority: WorkPriority.immediately,
execution: execution,
completer: Completer<R>(),
);
Future<void> run() async {
try {
final result = await execution(() => task.canceled);
task.complete(result, null, null);
} catch (error, st) {
task.complete(null, error, st);
}
}
run();
return Cancelable(completer: task.completer, onCancel: () => _cancel(task));
}
Cancelable<R> executeWithPort<R, T>(
ExecuteWithPort<R> execution, {
WorkPriority priority = WorkPriority.immediately,
required void Function(T value) onMessage,
}) {
return _createCancelable<R>(
execution: execution,
priority: priority,
onMessage: (message) => onMessage(message as T),
);
}
Cancelable<R> executeGentle<R>(ExecuteGentle<R> execution, {WorkPriority priority = WorkPriority.immediately}) {
return _createCancelable<R>(execution: execution, priority: priority);
}
Cancelable<R> executeGentleWithPort<R, T>(
ExecuteGentleWithPort<R> execution, {
WorkPriority priority = WorkPriority.immediately,
required void Function(T value) onMessage,
}) {
return _createCancelable<R>(
execution: execution,
priority: priority,
onMessage: (message) => onMessage(message as T),
);
}
void _createWorkers() {
for (var i = 0; i < _isolatesCount; i++) {
_pool.add(Worker());
}
}
Future<void> _initializeWorkers() async {
await Future.wait(_pool.map((e) => e.initialize()));
}
Cancelable<R> _createCancelable<R>({
required Function execution,
WorkPriority priority = WorkPriority.immediately,
void Function(Object value)? onMessage,
}) {
if (_nextTaskId + 1 == _maxId) {
_nextTaskId = _minId;
}
final id = _nextTaskId.toString();
_nextTaskId++;
late final Task<R> task;
final completer = Completer<R>();
if (execution is Execute<R>) {
task = TaskRegular<R>(id: id, workPriority: priority, execution: execution, completer: completer);
} else if (execution is ExecuteWithPort<R>) {
task = TaskWithPort<R>(
id: id,
workPriority: priority,
execution: execution,
completer: completer,
onMessage: onMessage!,
);
} else if (execution is ExecuteGentle<R>) {
task = TaskGentle<R>(id: id, workPriority: priority, execution: execution, completer: completer);
} else if (execution is ExecuteGentleWithPort<R>) {
task = TaskGentleWithPort<R>(
id: id,
workPriority: priority,
execution: execution,
completer: completer,
onMessage: onMessage!,
);
}
_queue.add(task);
_schedule();
logTaskAdded(task.id);
return Cancelable(completer: task.completer, onCancel: () => _cancel(task));
}
Future<void> _ensureWorkersInitialized() async {
if (_pool.isEmpty) {
_createWorkers();
if (!_dynamicSpawning) {
await _initializeWorkers();
final poolSize = _pool.length;
final queueSize = _queue.length;
for (int i = 0; i <= min(poolSize, queueSize); i++) {
_schedule();
}
}
}
if (_pool.every((worker) => worker.taskId != null)) {
return;
}
if (_dynamicSpawning) {
final freeWorker = _pool.firstWhereOrNull(
(worker) => worker.taskId == null && !worker.initialized && !worker.initializing,
);
await freeWorker?.initialize();
_schedule();
}
}
void _schedule() {
final availableWorker = _pool.firstWhereOrNull((worker) => worker.taskId == null && worker.initialized);
if (availableWorker == null) {
_ensureWorkersInitialized();
return;
}
if (_queue.isEmpty) return;
final task = _queue.removeFirst();
availableWorker
.work(task)
.then(
(value) {
//could be completed already by cancel and it is normal.
//Assuming that worker finished with error and cleaned gracefully
task.complete(value, null, null);
},
onError: (error, st) {
task.complete(null, error, st);
},
)
.whenComplete(() {
if (_dynamicSpawning && _queue.isEmpty) availableWorker.kill();
_schedule();
});
}
@override
void _cancel(Task task) {
task.cancel();
_queue.remove(task);
final targetWorker = _pool.firstWhereOrNull((worker) => worker.taskId == task.id);
if (task is Gentle) {
targetWorker?.cancelGentle();
} else {
targetWorker?.kill();
if (!_dynamicSpawning) targetWorker?.initialize();
}
super._cancel(task);
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 2.0.0 - API version: 1.144.1
- Generator version: 7.8.0 - Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen - Build package: org.openapitools.codegen.languages.DartClientCodegen
+1 -1
View File
@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none' publish_to: 'none'
version: 2.0.0+3020 version: 1.144.1+3019
environment: environment:
sdk: '>=3.8.0 <4.0.0' sdk: '>=3.8.0 <4.0.0'
+1 -1
View File
@@ -9858,7 +9858,7 @@
"info": { "info": {
"title": "Immich", "title": "Immich",
"description": "Immich API", "description": "Immich API",
"version": "2.0.0", "version": "1.144.1",
"contact": {} "contact": {}
}, },
"tags": [], "tags": [],
+1 -1
View File
@@ -1 +1 @@
22.20.0 22.19.0
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "2.0.0", "version": "1.144.1",
"description": "Auto-generated TypeScript SDK for the Immich API", "description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module", "type": "module",
"main": "./build/index.js", "main": "./build/index.js",
@@ -28,6 +28,6 @@
"directory": "open-api/typescript-sdk" "directory": "open-api/typescript-sdk"
}, },
"volta": { "volta": {
"node": "22.20.0" "node": "22.19.0"
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
/** /**
* Immich * Immich
* 2.0.0 * 1.144.1
* DO NOT MODIFY - This file has been generated using oazapfts. * DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts * See https://www.npmjs.com/package/oazapfts
*/ */
+1 -1
View File
@@ -1 +1 @@
22.20.0 22.19.0
+6 -7
View File
@@ -1,13 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
echo "Initializing Immich $IMMICH_SOURCE_REF" echo "Initializing Immich $IMMICH_SOURCE_REF"
# TODO: Update to mimalloc v3 when verified memory isn't released issue is fixed lib_path="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.3"
# lib_path="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.3" if [ -f "$lib_path" ]; then
# if [ -f "$lib_path" ]; then export LD_PRELOAD="$lib_path"
# export LD_PRELOAD="$lib_path" else
# else echo "skipping libmimalloc - path not found $lib_path"
# echo "skipping libmimalloc - path not found $lib_path" fi
# fi
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/lib/jellyfin-ffmpeg/lib" export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/lib/jellyfin-ffmpeg/lib"
SERVER_HOME="$(readlink -f "$(dirname "$0")/..")" SERVER_HOME="$(readlink -f "$(dirname "$0")/..")"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich", "name": "immich",
"version": "2.0.0", "version": "1.144.1",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -161,7 +161,7 @@
"vitest": "^3.0.0" "vitest": "^3.0.0"
}, },
"volta": { "volta": {
"node": "22.20.0" "node": "22.19.0"
}, },
"overrides": { "overrides": {
"sharp": "^0.34.3" "sharp": "^0.34.3"
+2 -2
View File
@@ -29,8 +29,8 @@ export const AlbumUpdateEmail = ({
</Text> </Text>
<Text> <Text>
New media has been added to <strong>{albumName}</strong>. New media has been added to <strong>{albumName}</strong>,
<br /> Check it out! <br /> check it out!
</Text> </Text>
</> </>
); );
@@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { Button, ButtonProps, Text } from '@react-email/components'; import { Button, ButtonProps } from '@react-email/components';
export const ImmichButton = ({ children, ...props }: ButtonProps) => ( export const ImmichButton = ({ children, ...props }: ButtonProps) => (
<Button <Button
{...props} {...props}
className="border bg-immich-primary rounded-full no-underline hover:no-underline text-white hover:text-gray-50 font-bold uppercase" className="py-3 px-8 border bg-immich-primary rounded-full no-underline hover:no-underline text-white hover:text-gray-50 font-bold uppercase"
> >
<Text className="my-3 mx-8">{children}</Text> {children}
</Button> </Button>
); );
+1 -1
View File
@@ -1 +1 @@
22.20.0 22.19.0
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "2.0.0", "version": "1.144.1",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -107,6 +107,6 @@
"vitest": "^3.0.0" "vitest": "^3.0.0"
}, },
"volta": { "volta": {
"node": "22.20.0" "node": "22.19.0"
} }
} }