mirror of
https://github.com/immich-app/immich.git
synced 2025-12-17 09:13:17 +03:00
Compare commits
1 Commits
better-inf
...
feat/disab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2248413280 |
@@ -43,6 +43,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N
|
|||||||
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
|
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
|
||||||
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
|
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
|
||||||
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices |
|
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices |
|
||||||
|
| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` endpoint | `true` | server | api |
|
||||||
|
|
||||||
\*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
|
\*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
|
||||||
`TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution.
|
`TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution.
|
||||||
|
|||||||
@@ -1286,7 +1286,6 @@
|
|||||||
"link_to_oauth": "Link to OAuth",
|
"link_to_oauth": "Link to OAuth",
|
||||||
"linked_oauth_account": "Linked OAuth account",
|
"linked_oauth_account": "Linked OAuth account",
|
||||||
"list": "List",
|
"list": "List",
|
||||||
"live": "Live",
|
|
||||||
"loading": "Loading",
|
"loading": "Loading",
|
||||||
"loading_search_results_failed": "Loading search results failed",
|
"loading_search_results_failed": "Loading search results failed",
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
@@ -1416,7 +1415,6 @@
|
|||||||
"month": "Month",
|
"month": "Month",
|
||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
"motion": "Motion",
|
|
||||||
"move": "Move",
|
"move": "Move",
|
||||||
"move_off_locked_folder": "Move out of locked folder",
|
"move_off_locked_folder": "Move out of locked folder",
|
||||||
"move_to": "Move to",
|
"move_to": "Move to",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widg
|
|||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_bar.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_bar.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/motion_photo_button.widget.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/top_app_bar.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/top_app_bar.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||||
@@ -693,7 +692,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
backgroundDecoration: BoxDecoration(color: backgroundColor),
|
backgroundDecoration: BoxDecoration(color: backgroundColor),
|
||||||
enablePanAlways: true,
|
enablePanAlways: true,
|
||||||
),
|
),
|
||||||
const Positioned(top: -40, left: 0, right: 0, child: MotionPhotoPlayButton()),
|
|
||||||
if (!showingBottomSheet)
|
if (!showingBottomSheet)
|
||||||
const Positioned(
|
const Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
|
||||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
|
||||||
|
|
||||||
class MotionPhotoPlayButton extends ConsumerWidget {
|
|
||||||
const MotionPhotoPlayButton({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final asset = ref.watch(currentAssetNotifier);
|
|
||||||
final isPlaying = ref.watch(isPlayingMotionVideoProvider);
|
|
||||||
final showControls = ref.watch(assetViewerProvider.select((state) => state.showingControls));
|
|
||||||
final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
|
|
||||||
|
|
||||||
if (asset == null || !asset.isMotionPhoto || isShowingSheet) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
return IgnorePointer(
|
|
||||||
ignoring: !showControls,
|
|
||||||
child: AnimatedOpacity(
|
|
||||||
opacity: showControls ? 1.0 : 0.0,
|
|
||||||
duration: Durations.short2,
|
|
||||||
child: SafeArea(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 60),
|
|
||||||
child: Center(
|
|
||||||
child: _MotionButton(
|
|
||||||
isPlaying: isPlaying,
|
|
||||||
onPressed: ref.read(isPlayingMotionVideoProvider.notifier).toggle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MotionButton extends StatelessWidget {
|
|
||||||
final bool isPlaying;
|
|
||||||
final VoidCallback onPressed;
|
|
||||||
|
|
||||||
const _MotionButton({required this.isPlaying, required this.onPressed});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Material(
|
|
||||||
color: Colors.grey[800]!.withValues(alpha: 0.4),
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: onPressed,
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
isPlaying ? Icons.motion_photos_pause_outlined : Icons.play_circle_outline_rounded,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
CurrentPlatform.isAndroid ? 'motion'.t(context: context) : 'live'.t(context: context),
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
@@ -8,6 +7,7 @@ import 'package:immich_mobile/domain/models/events.model.dart';
|
|||||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart';
|
||||||
@@ -50,6 +50,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
final originalTheme = context.themeData;
|
final originalTheme = context.themeData;
|
||||||
|
|
||||||
final actions = <Widget>[
|
final actions = <Widget>[
|
||||||
|
if (asset.isMotionPhoto) const MotionPhotoActionButton(iconOnly: true),
|
||||||
if (album != null && album.isActivityEnabled && album.isShared)
|
if (album != null && album.isActivityEnabled && album.isShared)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.chat_outlined),
|
icon: const Icon(Icons.chat_outlined),
|
||||||
@@ -76,8 +77,6 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
child: AppBar(
|
child: AppBar(
|
||||||
backgroundColor: isShowingSheet ? Colors.transparent : Colors.black.withAlpha(125),
|
backgroundColor: isShowingSheet ? Colors.transparent : Colors.black.withAlpha(125),
|
||||||
leading: const _AppBarBackButton(),
|
leading: const _AppBarBackButton(),
|
||||||
centerTitle: true,
|
|
||||||
title: isShowingSheet ? null : _AssetInfoTitle(asset: asset),
|
|
||||||
iconTheme: const IconThemeData(size: 22, color: Colors.white),
|
iconTheme: const IconThemeData(size: 22, color: Colors.white),
|
||||||
actionsIconTheme: const IconThemeData(size: 22, color: Colors.white),
|
actionsIconTheme: const IconThemeData(size: 22, color: Colors.white),
|
||||||
shape: const Border(),
|
shape: const Border(),
|
||||||
@@ -121,27 +120,3 @@ class _AppBarBackButton extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AssetInfoTitle extends StatelessWidget {
|
|
||||||
final BaseAsset asset;
|
|
||||||
|
|
||||||
const _AssetInfoTitle({required this.asset});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final dateTime = asset.createdAt.toLocal();
|
|
||||||
final currentYear = DateTime.now().year;
|
|
||||||
final isCurrentYear = dateTime.year == currentYear;
|
|
||||||
|
|
||||||
final dateFormatted = isCurrentYear ? DateFormat.MMMd().format(dateTime) : DateFormat.yMMMd().format(dateTime);
|
|
||||||
final timeFormatted = DateFormat.jm().format(dateTime);
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(dateFormatted, style: context.textTheme.bodyMedium?.copyWith(color: Colors.white)),
|
|
||||||
Text(timeFormatted, style: context.textTheme.labelMedium?.copyWith(color: Colors.white70)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class EnvDto {
|
|||||||
IMMICH_MICROSERVICES_METRICS_PORT?: number;
|
IMMICH_MICROSERVICES_METRICS_PORT?: number;
|
||||||
|
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
IMMICH_PLUGINS_ENABLED?: boolean;
|
IMMICH_ALLOW_EXTERNAL_PLUGINS?: boolean;
|
||||||
|
|
||||||
@Optional()
|
@Optional()
|
||||||
@Matches(/^\//, { message: 'IMMICH_PLUGINS_INSTALL_FOLDER must be an absolute path' })
|
@Matches(/^\//, { message: 'IMMICH_PLUGINS_INSTALL_FOLDER must be an absolute path' })
|
||||||
@@ -113,6 +113,9 @@ export class EnvDto {
|
|||||||
@Optional()
|
@Optional()
|
||||||
IMMICH_THIRD_PARTY_SUPPORT_URL?: string;
|
IMMICH_THIRD_PARTY_SUPPORT_URL?: string;
|
||||||
|
|
||||||
|
@ValidateBoolean({ optional: true })
|
||||||
|
IMMICH_ALLOW_SETUP?: boolean;
|
||||||
|
|
||||||
@IsIPRange({ requireCIDR: false }, { each: true })
|
@IsIPRange({ requireCIDR: false }, { each: true })
|
||||||
@Transform(({ value }) =>
|
@Transform(({ value }) =>
|
||||||
value && typeof value === 'string'
|
value && typeof value === 'string'
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ const getEnv = () => {
|
|||||||
|
|
||||||
const resetEnv = () => {
|
const resetEnv = () => {
|
||||||
for (const env of [
|
for (const env of [
|
||||||
|
'IMMICH_ALLOW_EXTERNAL_PLUGINS',
|
||||||
|
'IMMICH_ALLOW_SETUP',
|
||||||
'IMMICH_ENV',
|
'IMMICH_ENV',
|
||||||
'IMMICH_WORKERS_INCLUDE',
|
'IMMICH_WORKERS_INCLUDE',
|
||||||
'IMMICH_WORKERS_EXCLUDE',
|
'IMMICH_WORKERS_EXCLUDE',
|
||||||
@@ -75,6 +77,9 @@ describe('getEnv', () => {
|
|||||||
configFile: undefined,
|
configFile: undefined,
|
||||||
logLevel: undefined,
|
logLevel: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(config.plugins.external).toEqual({ allow: false });
|
||||||
|
expect(config.setup).toEqual({ allow: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('IMMICH_MEDIA_LOCATION', () => {
|
describe('IMMICH_MEDIA_LOCATION', () => {
|
||||||
@@ -84,6 +89,32 @@ describe('getEnv', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('IMMICH_ALLOW_EXTERNAL_PLUGINS', () => {
|
||||||
|
it('should disable plugins', () => {
|
||||||
|
process.env.IMMICH_ALLOW_EXTERNAL_PLUGINS = 'false';
|
||||||
|
const config = getEnv();
|
||||||
|
expect(config.plugins.external).toEqual({ allow: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error for invalid value', () => {
|
||||||
|
process.env.IMMICH_ALLOW_EXTERNAL_PLUGINS = 'invalid';
|
||||||
|
expect(() => getEnv()).toThrowError('IMMICH_ALLOW_EXTERNAL_PLUGINS must be a boolean value');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('IMMICH_ALLOW_SETUP', () => {
|
||||||
|
it('should disable setup', () => {
|
||||||
|
process.env.IMMICH_ALLOW_SETUP = 'false';
|
||||||
|
const { setup } = getEnv();
|
||||||
|
expect(setup).toEqual({ allow: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error for invalid value', () => {
|
||||||
|
process.env.IMMICH_ALLOW_SETUP = 'invalid';
|
||||||
|
expect(() => getEnv()).toThrowError('IMMICH_ALLOW_SETUP must be a boolean value');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('database', () => {
|
describe('database', () => {
|
||||||
it('should use defaults', () => {
|
it('should use defaults', () => {
|
||||||
const { database } = getEnv();
|
const { database } = getEnv();
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ export interface EnvData {
|
|||||||
|
|
||||||
redis: RedisOptions;
|
redis: RedisOptions;
|
||||||
|
|
||||||
|
setup: {
|
||||||
|
allow: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
telemetry: {
|
telemetry: {
|
||||||
apiPort: number;
|
apiPort: number;
|
||||||
microservicesPort: number;
|
microservicesPort: number;
|
||||||
@@ -104,8 +108,10 @@ export interface EnvData {
|
|||||||
workers: ImmichWorker[];
|
workers: ImmichWorker[];
|
||||||
|
|
||||||
plugins: {
|
plugins: {
|
||||||
enabled: boolean;
|
external: {
|
||||||
installFolder?: string;
|
allow: boolean;
|
||||||
|
installFolder?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
noColor: boolean;
|
noColor: boolean;
|
||||||
@@ -313,6 +319,10 @@ const getEnv = (): EnvData => {
|
|||||||
corePlugin: join(buildFolder, 'corePlugin'),
|
corePlugin: join(buildFolder, 'corePlugin'),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setup: {
|
||||||
|
allow: dto.IMMICH_ALLOW_SETUP ?? true,
|
||||||
|
},
|
||||||
|
|
||||||
storage: {
|
storage: {
|
||||||
ignoreMountCheckErrors: !!dto.IMMICH_IGNORE_MOUNT_CHECK_ERRORS,
|
ignoreMountCheckErrors: !!dto.IMMICH_IGNORE_MOUNT_CHECK_ERRORS,
|
||||||
mediaLocation: dto.IMMICH_MEDIA_LOCATION,
|
mediaLocation: dto.IMMICH_MEDIA_LOCATION,
|
||||||
@@ -327,8 +337,10 @@ const getEnv = (): EnvData => {
|
|||||||
workers,
|
workers,
|
||||||
|
|
||||||
plugins: {
|
plugins: {
|
||||||
enabled: !!dto.IMMICH_PLUGINS_ENABLED,
|
external: {
|
||||||
installFolder: dto.IMMICH_PLUGINS_INSTALL_FOLDER,
|
allow: dto.IMMICH_ALLOW_EXTERNAL_PLUGINS ?? false,
|
||||||
|
installFolder: dto.IMMICH_PLUGINS_INSTALL_FOLDER,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
noColor: !!dto.NO_COLOR,
|
noColor: !!dto.NO_COLOR,
|
||||||
|
|||||||
@@ -165,6 +165,11 @@ export class AuthService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async adminSignUp(dto: SignUpDto): Promise<UserAdminResponseDto> {
|
async adminSignUp(dto: SignUpDto): Promise<UserAdminResponseDto> {
|
||||||
|
const { setup } = this.configRepository.getEnv();
|
||||||
|
if (!setup.allow) {
|
||||||
|
throw new BadRequestException('Admin setup is disabled');
|
||||||
|
}
|
||||||
|
|
||||||
const adminUser = await this.userRepository.getAdmin();
|
const adminUser = await this.userRepository.getAdmin();
|
||||||
if (adminUser) {
|
if (adminUser) {
|
||||||
throw new BadRequestException('The server already has an admin');
|
throw new BadRequestException('The server already has an admin');
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ export class PluginService extends BaseService {
|
|||||||
this.logger.log(`Successfully processed core plugin: ${coreManifest.name} (version ${coreManifest.version})`);
|
this.logger.log(`Successfully processed core plugin: ${coreManifest.name} (version ${coreManifest.version})`);
|
||||||
|
|
||||||
// Load external plugins
|
// Load external plugins
|
||||||
if (plugins.enabled && plugins.installFolder) {
|
if (plugins.external.allow && plugins.external.installFolder) {
|
||||||
await this.loadExternalPlugins(plugins.installFolder);
|
await this.loadExternalPlugins(plugins.external.installFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,8 +115,9 @@ export class ServerService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getSystemConfig(): Promise<ServerConfigDto> {
|
async getSystemConfig(): Promise<ServerConfigDto> {
|
||||||
|
const { setup } = this.configRepository.getEnv();
|
||||||
const config = await this.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
const isInitialized = await this.userRepository.hasAdmin();
|
const isInitialized = !setup.allow || (await this.userRepository.hasAdmin());
|
||||||
const onboarding = await this.systemMetadataRepository.get(SystemMetadataKey.AdminOnboarding);
|
const onboarding = await this.systemMetadataRepository.get(SystemMetadataKey.AdminOnboarding);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ const envData: EnvData = {
|
|||||||
corePlugin: '/build/corePlugin',
|
corePlugin: '/build/corePlugin',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setup: {
|
||||||
|
allow: true,
|
||||||
|
},
|
||||||
|
|
||||||
storage: {
|
storage: {
|
||||||
ignoreMountCheckErrors: false,
|
ignoreMountCheckErrors: false,
|
||||||
},
|
},
|
||||||
@@ -88,8 +92,10 @@ const envData: EnvData = {
|
|||||||
workers: [ImmichWorker.Api, ImmichWorker.Microservices],
|
workers: [ImmichWorker.Api, ImmichWorker.Microservices],
|
||||||
|
|
||||||
plugins: {
|
plugins: {
|
||||||
enabled: true,
|
external: {
|
||||||
installFolder: '/app/data/plugins',
|
allow: true,
|
||||||
|
installFolder: '/app/data/plugins',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
noColor: false,
|
noColor: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user