Skip to content

Commit 5a7c9a2

Browse files
authored
feat: disable admin setup (#24628)
1 parent f99f5f4 commit 5a7c9a2

File tree

8 files changed

+69
-10
lines changed

8 files changed

+69
-10
lines changed

docs/docs/install/environment-variables.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N
4343
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
4444
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
4545
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices |
46+
| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` endpoint | `true` | server | api |
4647

4748
\*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
4849
`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.

server/src/dtos/env.dto.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class EnvDto {
5858
IMMICH_MICROSERVICES_METRICS_PORT?: number;
5959

6060
@ValidateBoolean({ optional: true })
61-
IMMICH_PLUGINS_ENABLED?: boolean;
61+
IMMICH_ALLOW_EXTERNAL_PLUGINS?: boolean;
6262

6363
@Optional()
6464
@Matches(/^\//, { message: 'IMMICH_PLUGINS_INSTALL_FOLDER must be an absolute path' })
@@ -113,6 +113,9 @@ export class EnvDto {
113113
@Optional()
114114
IMMICH_THIRD_PARTY_SUPPORT_URL?: string;
115115

116+
@ValidateBoolean({ optional: true })
117+
IMMICH_ALLOW_SETUP?: boolean;
118+
116119
@IsIPRange({ requireCIDR: false }, { each: true })
117120
@Transform(({ value }) =>
118121
value && typeof value === 'string'

server/src/repositories/config.repository.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const getEnv = () => {
88

99
const resetEnv = () => {
1010
for (const env of [
11+
'IMMICH_ALLOW_EXTERNAL_PLUGINS',
12+
'IMMICH_ALLOW_SETUP',
1113
'IMMICH_ENV',
1214
'IMMICH_WORKERS_INCLUDE',
1315
'IMMICH_WORKERS_EXCLUDE',
@@ -75,6 +77,9 @@ describe('getEnv', () => {
7577
configFile: undefined,
7678
logLevel: undefined,
7779
});
80+
81+
expect(config.plugins.external).toEqual({ allow: false });
82+
expect(config.setup).toEqual({ allow: true });
7883
});
7984

8085
describe('IMMICH_MEDIA_LOCATION', () => {
@@ -84,6 +89,32 @@ describe('getEnv', () => {
8489
});
8590
});
8691

92+
describe('IMMICH_ALLOW_EXTERNAL_PLUGINS', () => {
93+
it('should disable plugins', () => {
94+
process.env.IMMICH_ALLOW_EXTERNAL_PLUGINS = 'false';
95+
const config = getEnv();
96+
expect(config.plugins.external).toEqual({ allow: false });
97+
});
98+
99+
it('should throw an error for invalid value', () => {
100+
process.env.IMMICH_ALLOW_EXTERNAL_PLUGINS = 'invalid';
101+
expect(() => getEnv()).toThrowError('IMMICH_ALLOW_EXTERNAL_PLUGINS must be a boolean value');
102+
});
103+
});
104+
105+
describe('IMMICH_ALLOW_SETUP', () => {
106+
it('should disable setup', () => {
107+
process.env.IMMICH_ALLOW_SETUP = 'false';
108+
const { setup } = getEnv();
109+
expect(setup).toEqual({ allow: false });
110+
});
111+
112+
it('should throw an error for invalid value', () => {
113+
process.env.IMMICH_ALLOW_SETUP = 'invalid';
114+
expect(() => getEnv()).toThrowError('IMMICH_ALLOW_SETUP must be a boolean value');
115+
});
116+
});
117+
87118
describe('database', () => {
88119
it('should use defaults', () => {
89120
const { database } = getEnv();

server/src/repositories/config.repository.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ export interface EnvData {
9090

9191
redis: RedisOptions;
9292

93+
setup: {
94+
allow: boolean;
95+
};
96+
9397
telemetry: {
9498
apiPort: number;
9599
microservicesPort: number;
@@ -104,8 +108,10 @@ export interface EnvData {
104108
workers: ImmichWorker[];
105109

106110
plugins: {
107-
enabled: boolean;
108-
installFolder?: string;
111+
external: {
112+
allow: boolean;
113+
installFolder?: string;
114+
};
109115
};
110116

111117
noColor: boolean;
@@ -313,6 +319,10 @@ const getEnv = (): EnvData => {
313319
corePlugin: join(buildFolder, 'corePlugin'),
314320
},
315321

322+
setup: {
323+
allow: dto.IMMICH_ALLOW_SETUP ?? true,
324+
},
325+
316326
storage: {
317327
ignoreMountCheckErrors: !!dto.IMMICH_IGNORE_MOUNT_CHECK_ERRORS,
318328
mediaLocation: dto.IMMICH_MEDIA_LOCATION,
@@ -327,8 +337,10 @@ const getEnv = (): EnvData => {
327337
workers,
328338

329339
plugins: {
330-
enabled: !!dto.IMMICH_PLUGINS_ENABLED,
331-
installFolder: dto.IMMICH_PLUGINS_INSTALL_FOLDER,
340+
external: {
341+
allow: dto.IMMICH_ALLOW_EXTERNAL_PLUGINS ?? false,
342+
installFolder: dto.IMMICH_PLUGINS_INSTALL_FOLDER,
343+
},
332344
},
333345

334346
noColor: !!dto.NO_COLOR,

server/src/services/auth.service.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ export class AuthService extends BaseService {
165165
}
166166

167167
async adminSignUp(dto: SignUpDto): Promise<UserAdminResponseDto> {
168+
const { setup } = this.configRepository.getEnv();
169+
if (!setup.allow) {
170+
throw new BadRequestException('Admin setup is disabled');
171+
}
172+
168173
const adminUser = await this.userRepository.getAdmin();
169174
if (adminUser) {
170175
throw new BadRequestException('The server already has an admin');

server/src/services/plugin.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ export class PluginService extends BaseService {
8585
this.logger.log(`Successfully processed core plugin: ${coreManifest.name} (version ${coreManifest.version})`);
8686

8787
// Load external plugins
88-
if (plugins.enabled && plugins.installFolder) {
89-
await this.loadExternalPlugins(plugins.installFolder);
88+
if (plugins.external.allow && plugins.external.installFolder) {
89+
await this.loadExternalPlugins(plugins.external.installFolder);
9090
}
9191
}
9292

server/src/services/server.service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,9 @@ export class ServerService extends BaseService {
115115
}
116116

117117
async getSystemConfig(): Promise<ServerConfigDto> {
118+
const { setup } = this.configRepository.getEnv();
118119
const config = await this.getConfig({ withCache: false });
119-
const isInitialized = await this.userRepository.hasAdmin();
120+
const isInitialized = !setup.allow || (await this.userRepository.hasAdmin());
120121
const onboarding = await this.systemMetadataRepository.get(SystemMetadataKey.AdminOnboarding);
121122

122123
return {

server/test/repositories/config.repository.mock.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ const envData: EnvData = {
7575
corePlugin: '/build/corePlugin',
7676
},
7777

78+
setup: {
79+
allow: true,
80+
},
81+
7882
storage: {
7983
ignoreMountCheckErrors: false,
8084
},
@@ -88,8 +92,10 @@ const envData: EnvData = {
8892
workers: [ImmichWorker.Api, ImmichWorker.Microservices],
8993

9094
plugins: {
91-
enabled: true,
92-
installFolder: '/app/data/plugins',
95+
external: {
96+
allow: true,
97+
installFolder: '/app/data/plugins',
98+
},
9399
},
94100

95101
noColor: false,

0 commit comments

Comments
 (0)