Compare commits

..

1 Commits

Author SHA1 Message Date
Jason Rasmussen
2248413280 feat: disable admin setup 2025-12-16 15:35:57 -05:00
13 changed files with 76 additions and 17 deletions

View File

@@ -35,7 +35,7 @@ jobs:
needs: [get_body, should_run]
if: ${{ needs.should_run.outputs.should_run == 'true' }}
container:
image: ghcr.io/immich-app/mdq:main@sha256:ab9f163cd5d5cec42704a26ca2769ecf3f10aa8e7bae847f1d527cdf075946e6
image: ghcr.io/immich-app/mdq:main@sha256:237cdae7783609c96f18037a513d38088713cf4a2e493a3aa136d0c45490749a
outputs:
checked: ${{ steps.get_checkbox.outputs.checked }}
steps:

View File

@@ -57,7 +57,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -70,7 +70,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
uses: github/codeql-action/autobuild@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -83,6 +83,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
with:
category: '/language:${{matrix.language}}'

View File

@@ -62,7 +62,7 @@ jobs:
ref: main
- name: Install uv
uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

View File

@@ -30,7 +30,7 @@ jobs:
ref: main
- name: Install uv
uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

View File

@@ -571,7 +571,7 @@ jobs:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Install uv
uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
# TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
# with:

View File

@@ -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_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_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"`.
`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.

View File

@@ -58,7 +58,7 @@ export class EnvDto {
IMMICH_MICROSERVICES_METRICS_PORT?: number;
@ValidateBoolean({ optional: true })
IMMICH_PLUGINS_ENABLED?: boolean;
IMMICH_ALLOW_EXTERNAL_PLUGINS?: boolean;
@Optional()
@Matches(/^\//, { message: 'IMMICH_PLUGINS_INSTALL_FOLDER must be an absolute path' })
@@ -113,6 +113,9 @@ export class EnvDto {
@Optional()
IMMICH_THIRD_PARTY_SUPPORT_URL?: string;
@ValidateBoolean({ optional: true })
IMMICH_ALLOW_SETUP?: boolean;
@IsIPRange({ requireCIDR: false }, { each: true })
@Transform(({ value }) =>
value && typeof value === 'string'

View File

@@ -8,6 +8,8 @@ const getEnv = () => {
const resetEnv = () => {
for (const env of [
'IMMICH_ALLOW_EXTERNAL_PLUGINS',
'IMMICH_ALLOW_SETUP',
'IMMICH_ENV',
'IMMICH_WORKERS_INCLUDE',
'IMMICH_WORKERS_EXCLUDE',
@@ -75,6 +77,9 @@ describe('getEnv', () => {
configFile: undefined,
logLevel: undefined,
});
expect(config.plugins.external).toEqual({ allow: false });
expect(config.setup).toEqual({ allow: true });
});
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', () => {
it('should use defaults', () => {
const { database } = getEnv();

View File

@@ -90,6 +90,10 @@ export interface EnvData {
redis: RedisOptions;
setup: {
allow: boolean;
};
telemetry: {
apiPort: number;
microservicesPort: number;
@@ -104,8 +108,10 @@ export interface EnvData {
workers: ImmichWorker[];
plugins: {
enabled: boolean;
installFolder?: string;
external: {
allow: boolean;
installFolder?: string;
};
};
noColor: boolean;
@@ -313,6 +319,10 @@ const getEnv = (): EnvData => {
corePlugin: join(buildFolder, 'corePlugin'),
},
setup: {
allow: dto.IMMICH_ALLOW_SETUP ?? true,
},
storage: {
ignoreMountCheckErrors: !!dto.IMMICH_IGNORE_MOUNT_CHECK_ERRORS,
mediaLocation: dto.IMMICH_MEDIA_LOCATION,
@@ -327,8 +337,10 @@ const getEnv = (): EnvData => {
workers,
plugins: {
enabled: !!dto.IMMICH_PLUGINS_ENABLED,
installFolder: dto.IMMICH_PLUGINS_INSTALL_FOLDER,
external: {
allow: dto.IMMICH_ALLOW_EXTERNAL_PLUGINS ?? false,
installFolder: dto.IMMICH_PLUGINS_INSTALL_FOLDER,
},
},
noColor: !!dto.NO_COLOR,

View File

@@ -165,6 +165,11 @@ export class AuthService extends BaseService {
}
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();
if (adminUser) {
throw new BadRequestException('The server already has an admin');

View File

@@ -80,8 +80,8 @@ export class PluginService extends BaseService {
this.logger.log(`Successfully processed core plugin: ${coreManifest.name} (version ${coreManifest.version})`);
// Load external plugins
if (plugins.enabled && plugins.installFolder) {
await this.loadExternalPlugins(plugins.installFolder);
if (plugins.external.allow && plugins.external.installFolder) {
await this.loadExternalPlugins(plugins.external.installFolder);
}
}

View File

@@ -115,8 +115,9 @@ export class ServerService extends BaseService {
}
async getSystemConfig(): Promise<ServerConfigDto> {
const { setup } = this.configRepository.getEnv();
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);
return {

View File

@@ -75,6 +75,10 @@ const envData: EnvData = {
corePlugin: '/build/corePlugin',
},
setup: {
allow: true,
},
storage: {
ignoreMountCheckErrors: false,
},
@@ -88,8 +92,10 @@ const envData: EnvData = {
workers: [ImmichWorker.Api, ImmichWorker.Microservices],
plugins: {
enabled: true,
installFolder: '/app/data/plugins',
external: {
allow: true,
installFolder: '/app/data/plugins',
},
},
noColor: false,