mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
Merge 72c9003499 into e7d6a066f8
This commit is contained in:
commit
0956b2f745
4 changed files with 459 additions and 0 deletions
282
cli/src/commands/jobs.ts
Normal file
282
cli/src/commands/jobs.ts
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
import {
|
||||
AllJobStatusResponseDto,
|
||||
getAllJobsStatus,
|
||||
JobCommand,
|
||||
JobCountsDto,
|
||||
JobName,
|
||||
JobStatusDto,
|
||||
sendJobCommand,
|
||||
} from '@immich/sdk';
|
||||
import { authenticate, BaseOptions, logError, withError } from 'src/utils';
|
||||
|
||||
interface CommonJobStatusOptions {
|
||||
jsonOutput?: boolean;
|
||||
}
|
||||
|
||||
interface StartJobOptions extends CommonJobStatusOptions {
|
||||
onlyMissing?: boolean;
|
||||
refresh?: boolean;
|
||||
all?: boolean;
|
||||
|
||||
wait?: boolean;
|
||||
}
|
||||
|
||||
type GetJobStatusOptions = CommonJobStatusOptions;
|
||||
type PauseJobOptions = GetJobStatusOptions;
|
||||
type ResumeJobOptions = GetJobStatusOptions;
|
||||
|
||||
const JOB_STATUS_COLUMN_HEADERS: Readonly<Record<keyof JobCountsDto, string>> = {
|
||||
active: 'Active Jobs',
|
||||
delayed: 'Delayed Jobs',
|
||||
paused: 'Paused Jobs',
|
||||
waiting: 'Waiting Jobs',
|
||||
completed: 'Completed Jobs',
|
||||
failed: 'Failed Jobs',
|
||||
};
|
||||
|
||||
/**
|
||||
* Command entry point for getting the status of jobs.
|
||||
* If jobName is provided, it will return the status of that specific job.
|
||||
* If jobName is not provided, it will return the status of all jobs.
|
||||
* @param jobName Name of the job to check status for, or undefined to get all jobs status.
|
||||
* @param baseOptions Immich CLI base options.
|
||||
* @param commandOptions Options for the command, such as whether to output in JSON format.
|
||||
*/
|
||||
export async function getJobsStatus(
|
||||
jobName: JobName | undefined,
|
||||
baseOptions: BaseOptions,
|
||||
commandOptions: GetJobStatusOptions,
|
||||
) {
|
||||
await authenticate(baseOptions);
|
||||
|
||||
if (jobName) {
|
||||
ensureJobNameIsValid(jobName);
|
||||
return getStatusOfSingleJob(jobName, commandOptions);
|
||||
} else {
|
||||
return getStatusOfAllJobs(commandOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Command entry point for starting a job.
|
||||
* It will ask Immich to start the job with the given name.
|
||||
* @param jobName Name of the job to start.
|
||||
* @param baseOptions Immich CLI base options.
|
||||
* @param commandOptions Options for the command, such as whether to output in JSON format.
|
||||
*/
|
||||
export async function startJob(jobName: JobName, baseOptions: BaseOptions, commandOptions: StartJobOptions) {
|
||||
await authenticate(baseOptions);
|
||||
ensureJobNameIsValid(jobName);
|
||||
|
||||
let force: boolean | undefined;
|
||||
|
||||
// Mimic the behavior of the Immich web UI, based on options provided.
|
||||
if (commandOptions.all) {
|
||||
force = true;
|
||||
} else if (commandOptions.onlyMissing) {
|
||||
force = false;
|
||||
} else if (commandOptions.refresh) {
|
||||
force = undefined;
|
||||
}
|
||||
|
||||
const [error, response] = await withError(
|
||||
sendJobCommand({
|
||||
id: jobName,
|
||||
jobCommandDto: {
|
||||
command: JobCommand.Start,
|
||||
force,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logError(error, `Failed to start job: ${jobName}. Got error`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let status = response;
|
||||
|
||||
if (commandOptions.wait && areJobsRunning(status.jobCounts)) {
|
||||
status = await waitForJobCompletion(jobName);
|
||||
}
|
||||
|
||||
if (commandOptions.jsonOutput) {
|
||||
console.log(JSON.stringify(status, undefined, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
const message = commandOptions.wait
|
||||
? `Successfully executed job "${jobName}".`
|
||||
: `Successfully queued execution of job "${jobName}".`;
|
||||
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Command entry point for pausing executions of a job, given its name.
|
||||
* @param jobName Name of the job to pause executions for.
|
||||
* @param baseOptions Immich CLI base options.
|
||||
* @param commandOptions Options for the command, such as whether to output in JSON format.
|
||||
*/
|
||||
export async function pauseJobExecutions(jobName: JobName, baseOptions: BaseOptions, commandOptions: PauseJobOptions) {
|
||||
await authenticate(baseOptions);
|
||||
|
||||
ensureJobNameIsValid(jobName);
|
||||
|
||||
const [error, response] = await withError(
|
||||
sendJobCommand({
|
||||
id: jobName,
|
||||
jobCommandDto: {
|
||||
command: JobCommand.Pause,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logError(error, `Failed to pause executions of job "${jobName}". Got error`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (commandOptions.jsonOutput) {
|
||||
console.log(JSON.stringify(response.queueStatus, undefined, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Successfully paused executions of job "${jobName}".`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Command entry point for resuming executions of a job, given its name.
|
||||
* @param jobName Job name to resume executions for.
|
||||
* @param baseOptions Immich CLI base options.
|
||||
* @param commandOptions Options for the command, such as whether to output in JSON format.
|
||||
*/
|
||||
export async function resumeJobExecutions(
|
||||
jobName: JobName,
|
||||
baseOptions: BaseOptions,
|
||||
commandOptions: ResumeJobOptions,
|
||||
) {
|
||||
await authenticate(baseOptions);
|
||||
|
||||
ensureJobNameIsValid(jobName);
|
||||
|
||||
const [error, response] = await withError(
|
||||
sendJobCommand({
|
||||
id: jobName,
|
||||
jobCommandDto: {
|
||||
command: JobCommand.Resume,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logError(error, `Failed to resume executions of job "${jobName}". Got error`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (commandOptions.jsonOutput) {
|
||||
console.log(JSON.stringify(response.queueStatus, undefined, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Successfully resumed executions of job "${jobName}".`);
|
||||
}
|
||||
|
||||
/**
|
||||
* An utility function to ensure that the provided job name is valid.
|
||||
* If not, it will log an error and exit the process.
|
||||
* @param jobName Name of the job to validate.
|
||||
*/
|
||||
function ensureJobNameIsValid(jobName: JobName): asserts jobName is JobName {
|
||||
const validObjectNames = Object.values(JobName);
|
||||
if (!validObjectNames.includes(jobName)) {
|
||||
console.error(`Invalid job name: ${jobName}. Valid job names are: ${validObjectNames.join(', ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An helper function to get and print to standard output the status of a single job.
|
||||
* @param jobName Name of the job to get the status for.
|
||||
* @param commandOptions Command options, such as whether to output in JSON format.
|
||||
*/
|
||||
async function getStatusOfSingleJob(jobName: JobName, commandOptions: CommonJobStatusOptions) {
|
||||
const status = await requestJobsStatus();
|
||||
const jobStatus = status[jobName];
|
||||
|
||||
const outputPrefix = commandOptions.jsonOutput ? '' : `Status for requested job "${jobName}": `;
|
||||
console.log(outputPrefix, JSON.stringify(jobStatus, undefined, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* An helper function to get and print to standard output the status of all known jobs.
|
||||
* By default, it will print the status in a table format, with one row for each job.
|
||||
* @param commandOptions Command options, such as whether to output in JSON format.
|
||||
*/
|
||||
async function getStatusOfAllJobs(commandOptions: CommonJobStatusOptions) {
|
||||
const status = await requestJobsStatus();
|
||||
|
||||
if (commandOptions.jsonOutput) {
|
||||
console.log(JSON.stringify(status, undefined, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
console.table(
|
||||
Object.entries(status).map(([name, status]) => {
|
||||
const row: Record<string, string | number | boolean> = {
|
||||
name,
|
||||
isQueueActive: status.queueStatus.isActive,
|
||||
isQueuePaused: status.queueStatus.isPaused,
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(status.jobCounts)) {
|
||||
row[JOB_STATUS_COLUMN_HEADERS[key as keyof JobCountsDto]] = value;
|
||||
}
|
||||
|
||||
return row;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* An utility function to wait for a job to complete.
|
||||
* It will poll the job status every second until the job is no longer running.
|
||||
* @param jobName Name of the job to wait for completion.
|
||||
* @returns A promise that resolves to the final status of the job.
|
||||
*/
|
||||
async function waitForJobCompletion(jobName: JobName): Promise<JobStatusDto> {
|
||||
while (true) {
|
||||
const status = await requestJobsStatus();
|
||||
const jobStatus = status[jobName];
|
||||
|
||||
if (!areJobsRunning(jobStatus.jobCounts)) {
|
||||
return jobStatus;
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An helper function to determine if any jobs are currently running.
|
||||
* @param jobCounts Counts of jobs in various states.
|
||||
* @returns True if any jobs are running, false otherwise.
|
||||
*/
|
||||
function areJobsRunning(jobCounts: JobCountsDto): boolean {
|
||||
return !!(jobCounts.active || jobCounts.delayed || jobCounts.paused || jobCounts.waiting);
|
||||
}
|
||||
|
||||
/**
|
||||
* An utility function to request the status of all jobs from the Immich server.
|
||||
* It will log an error and exit the process if the request fails.
|
||||
* @returns A promise that resolves to the status of all jobs.
|
||||
*/
|
||||
async function requestJobsStatus(): Promise<AllJobStatusResponseDto> {
|
||||
const [error, status] = await withError(getAllJobsStatus());
|
||||
if (error) {
|
||||
logError(error, 'Failed to get job status. Error');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import os from 'node:os';
|
|||
import path from 'node:path';
|
||||
import { upload } from 'src/commands/asset';
|
||||
import { login, logout } from 'src/commands/auth';
|
||||
import { getJobsStatus, pauseJobExecutions, resumeJobExecutions, startJob } from 'src/commands/jobs';
|
||||
import { serverInfo } from 'src/commands/server-info';
|
||||
import { version } from '../package.json';
|
||||
|
||||
|
|
@ -34,6 +35,78 @@ program
|
|||
.description('Remove stored credentials')
|
||||
.action(() => logout(program.opts()));
|
||||
|
||||
const jobsCommand = program.createCommand('jobs').description('Manage background jobs');
|
||||
|
||||
jobsCommand
|
||||
.command('pause')
|
||||
.description('Pause executions of all instances of the given job')
|
||||
.usage('<jobName> [options]')
|
||||
.addOption(
|
||||
new Option('-j, --json-output', 'Output detailed information in json format')
|
||||
.env('IMMICH_JSON_OUTPUT')
|
||||
.default(false),
|
||||
)
|
||||
.argument('<jobName>', 'Name of the job to pause executions for')
|
||||
.action((jobName, options) => pauseJobExecutions(jobName, program.opts(), options));
|
||||
|
||||
jobsCommand
|
||||
.command('resume')
|
||||
.description('Resume executions of all instances of the given job')
|
||||
.usage('<jobName> [options]')
|
||||
.addOption(
|
||||
new Option('-j, --json-output', 'Output detailed information in json format')
|
||||
.env('IMMICH_JSON_OUTPUT')
|
||||
.default(false),
|
||||
)
|
||||
.argument('<jobName>', 'Name of the job to resume executions for')
|
||||
.action((jobName, options) => resumeJobExecutions(jobName, program.opts(), options));
|
||||
|
||||
jobsCommand
|
||||
.command('run')
|
||||
.description('Start a specific job')
|
||||
.usage('<jobName> [options]')
|
||||
.addOption(
|
||||
new Option('-w, --wait', 'Wait for the job to complete before returning')
|
||||
.env('IMMICH_WAIT_JOB_COMPLETION')
|
||||
.default(false),
|
||||
)
|
||||
.addOption(
|
||||
new Option('-j, --json-output', 'Output detailed information in json format')
|
||||
.env('IMMICH_JSON_OUTPUT')
|
||||
.default(false),
|
||||
)
|
||||
.addOption(
|
||||
new Option(
|
||||
'--all',
|
||||
'Execute this job on all assets. Depending on the job, this may clear existing data previously created by the job',
|
||||
)
|
||||
.conflicts('refresh')
|
||||
.conflicts('onlyMissing'),
|
||||
)
|
||||
.addOption(
|
||||
new Option('--only-missing', 'Run this job only on assets that have not been processed yet')
|
||||
.default(true)
|
||||
.conflicts('all')
|
||||
.conflicts('refresh'),
|
||||
)
|
||||
.addOption(new Option('--refresh', '(Re)run this job on all assets').conflicts('all').conflicts('onlyMissing'))
|
||||
.argument('<jobName>', 'Name of the job to run')
|
||||
.action((jobName, options) => startJob(jobName, program.opts(), options));
|
||||
|
||||
jobsCommand
|
||||
.command('status')
|
||||
.description('Get the status of all jobs or the status of a specific job')
|
||||
.usage('[jobName] [options]')
|
||||
.addOption(
|
||||
new Option('-j, --json-output', 'Output detailed information in json format')
|
||||
.env('IMMICH_JSON_OUTPUT')
|
||||
.default(false),
|
||||
)
|
||||
.argument('[jobName]', 'Name of the job to check status for')
|
||||
.action((jobName, options) => getJobsStatus(jobName, program.opts(), options));
|
||||
|
||||
program.addCommand(jobsCommand);
|
||||
|
||||
program
|
||||
.command('server-info')
|
||||
.description('Display server information')
|
||||
|
|
|
|||
|
|
@ -46,6 +46,17 @@ Visit the page, open the "Create job" modal from the top right, select "Create D
|
|||
A job will run and trigger a dump, you can verify this worked correctly by checking the logs or the `backups/` folder.
|
||||
This dumps will count towards the last `X` dumps that will be kept based on your settings.
|
||||
|
||||
Alternatively, you can trigger a database dump using the [Immich CLI](/docs/features/command-line-interface.md).
|
||||
This can be done by running the following command in a terminal.
|
||||
|
||||
```bash
|
||||
immich jobs run backupDatabase
|
||||
```
|
||||
|
||||
Since this command is analogous to running the database dump via the Immich web interface, the resulting database dump
|
||||
will be placed in the same `backups/` folder, and it will count towards the last `X` dumps that will be kept based
|
||||
on your settings, just as if it was triggered via the web UI.
|
||||
|
||||
#### Restoring
|
||||
|
||||
We hope to make restoring simpler in future versions, for now you can find the database dumps in the `UPLOAD_LOCATION/backups` folder on your host.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ Immich has a command line interface (CLI) that allows you to perform certain act
|
|||
|
||||
- Upload photos and videos to Immich
|
||||
- Check server version
|
||||
- Manage execution of background jobs
|
||||
|
||||
More features are planned for the future.
|
||||
|
||||
|
|
@ -70,6 +71,7 @@ Options:
|
|||
Commands:
|
||||
login|login-key <url> <key> Login using an API key
|
||||
logout Remove stored credentials
|
||||
jobs Manage background jobs
|
||||
server-info Display server information
|
||||
upload [options] [paths...] Upload assets
|
||||
help [command] display help for command
|
||||
|
|
@ -79,6 +81,8 @@ Commands:
|
|||
|
||||
## Commands
|
||||
|
||||
### Upload
|
||||
|
||||
The upload command supports the following options:
|
||||
|
||||
<details>
|
||||
|
|
@ -112,6 +116,31 @@ Options:
|
|||
|
||||
Note that the above options can read from environment variables as well.
|
||||
|
||||
### Jobs
|
||||
|
||||
The jobs command supports the following options:
|
||||
|
||||
<details>
|
||||
<summary>Options</summary>
|
||||
|
||||
```
|
||||
Usage: immich jobs [options] [command]
|
||||
|
||||
Manage background jobs
|
||||
|
||||
Options:
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
pause [options] <jobName> Pause executions of all instances of the given job
|
||||
resume [options] <jobName> Resume executions of all instances of the given job
|
||||
run [options] <jobName> Start a specific job
|
||||
status [options] [jobName] Get the status of all jobs or the status of a specific job
|
||||
help [command] display help for command
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Quick Start
|
||||
|
||||
You begin by authenticating to your Immich server. For instance:
|
||||
|
|
@ -190,3 +219,67 @@ immich upload --dry-run . | tail -n +6 | jq .newFiles[]
|
|||
The API key can be obtained in the user setting panel on the web interface.
|
||||
|
||||

|
||||
|
||||
### Manage jobs via the CLI
|
||||
|
||||
With the Immich CLI you can also manage background jobs on your server.
|
||||
The `jobs` command allows you to pause, resume, run, or check the status of background jobs.
|
||||
|
||||
Once you are authenticated, you can use the `jobs` command to interact with your Immich server.
|
||||
|
||||
For example, you can run the following command to print out the status of all the background jobs Immich uses.
|
||||
|
||||
```bash
|
||||
immich jobs status
|
||||
```
|
||||
|
||||
Additionally, you can also query the status of a specific job by providing its' name.
|
||||
For example, if you want to know the status of the thumbnail generation after having imported several
|
||||
images, you can run this command:
|
||||
|
||||
```bash
|
||||
immich jobs status thumbnailGeneration
|
||||
```
|
||||
|
||||
You can also trigger execution of a specific job given its' name.
|
||||
|
||||
```bash
|
||||
immich jobs run thumbnailGeneration
|
||||
```
|
||||
|
||||
By default, this will run the thumbnail generation only on those assets that were not already processed.
|
||||
If you want to run the specified job on all assets your Immich instance handles, you can use the `--refresh` option.
|
||||
|
||||
```bash
|
||||
immich jobs run thumbnailGeneration --refresh
|
||||
```
|
||||
|
||||
Alternatively, you can run the same command with the `--all` flag.
|
||||
Depending on the job implementation, the `--all` flag may imply the deletion of the output of previous executions of
|
||||
that job, before running it from scratch on all assets.
|
||||
|
||||
```bash
|
||||
immich jobs run thumbnailGeneration --all
|
||||
```
|
||||
|
||||
If you want to stop executions of a specific job, you can run the command `pause`, followed by the job name.
|
||||
For example, to prevent generation of new thumbnails, you could run this command:
|
||||
|
||||
```bash
|
||||
immich jobs pause thumbnailGeneration
|
||||
```
|
||||
|
||||
Once executions of a specific job are paused, scheduled executions of that job will no longer occur until you
|
||||
resume their execution. This can be done using the `resume` command.
|
||||
For example, to resume the generation of new thumbnails, you can run:
|
||||
|
||||
```bash
|
||||
immich jobs resume thumbnailGeneration
|
||||
```
|
||||
|
||||
We can also use the `run` command to ask the Immich server to create a database dump.
|
||||
This can be done with the following command:
|
||||
|
||||
```bash
|
||||
immich jobs run backupDatabase
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue