From 31eb4790dc36904c01fdf3bcccf1b97cdb0b3267 Mon Sep 17 00:00:00 2001
From: Jonathan Jogenfors <jonathan@jogenfors.se>
Date: Tue, 6 Feb 2024 12:17:15 +0100
Subject: [PATCH] feat(cli): dockerize (#6858)

* import dockerfile from old cli

* build works

* rename login command

* bump packages

* fix login command

* chore: remove axios dependency from CLI

* move immich script path

* can build docker

* wip

* wip

* don't externalize sdk

* can run docker

* improve entrypoint

* can save auth state between runs

* add docs

* clarify reqs

* fix lint

* bump alpine to 3.19

* add env files for api key

* remove immich cli GHA for now

* Update docs/docs/features/command-line-interface.md

Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>

* remove redundant env variable check

* cleanup

* speling

---------

Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
---
 .github/workflows/docker.yml                  |  3 ++-
 cli/Dockerfile                                | 22 +++++++++----------
 cli/package-lock.json                         | 18 +++++++--------
 cli/package.json                              |  4 ++--
 .../commands/{login.ts => login.command.ts}   |  0
 cli/src/index.ts                              | 11 ++++++----
 cli/test/e2e/login-key.e2e-spec.ts            |  3 +--
 docs/docs/features/command-line-interface.md  | 16 ++++++++++++--
 8 files changed, 46 insertions(+), 31 deletions(-)
 rename cli/src/commands/{login.ts => login.command.ts} (100%)

diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 10ed06510f..a6a6835882 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -44,7 +44,7 @@ jobs:
             platforms: linux/amd64
             device: openvino
             suffix: -openvino
-          
+
           - image: immich-machine-learning
             context: machine-learning
             file: machine-learning/Dockerfile
@@ -57,6 +57,7 @@ jobs:
             file: server/Dockerfile
             platforms: linux/amd64,linux/arm64
             device: cpu
+
     steps:
       - name: Checkout
         uses: actions/checkout@v4
diff --git a/cli/Dockerfile b/cli/Dockerfile
index 0af3c3cef6..02fffca397 100644
--- a/cli/Dockerfile
+++ b/cli/Dockerfile
@@ -1,19 +1,19 @@
-FROM ghcr.io/immich-app/base-server-dev:20240130@sha256:a11ac5c56f0ccce1f218954c07c43caadf489557252ba5b9ca1c5977aaa25999 as test
+FROM node:20-alpine3.19 as core
 
-WORKDIR /usr/src/app/server
-COPY server/package.json server/package-lock.json ./
+WORKDIR /usr/src/open-api/typescript-sdk
+COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
 RUN npm ci
-COPY ./server/ .
+COPY open-api/typescript-sdk/ ./
+RUN npm run build
+
+WORKDIR /usr/src/app
 
-WORKDIR /usr/src/app/cli
 COPY cli/package.json cli/package-lock.json ./
 RUN npm ci
-COPY ./cli/ .
 
-FROM ghcr.io/immich-app/base-server-prod:20240130@sha256:ce23a32154540b906df3c971766bcd991561c60331794e0ebb780947ac48113f
+COPY cli .
+RUN npm run build
 
-VOLUME /usr/src/app/upload
+WORKDIR /import
 
-EXPOSE 3001
-
-ENTRYPOINT ["tini", "--", "/bin/sh"]
+ENTRYPOINT ["node", "/usr/src/app/dist"]
\ No newline at end of file
diff --git a/cli/package-lock.json b/cli/package-lock.json
index 03f20f2154..29216de276 100644
--- a/cli/package-lock.json
+++ b/cli/package-lock.json
@@ -9,7 +9,7 @@
       "version": "2.0.7",
       "license": "MIT",
       "bin": {
-        "immich": "dist/src/index.js"
+        "immich": "dist/index.js"
       },
       "devDependencies": {
         "@immich/sdk": "file:../open-api/typescript-sdk",
@@ -23,7 +23,7 @@
         "@vitest/coverage-v8": "^1.2.2",
         "byte-size": "^8.1.1",
         "cli-progress": "^3.12.0",
-        "commander": "^11.0.0",
+        "commander": "^12.0.0",
         "eslint": "^8.56.0",
         "eslint-config-prettier": "^9.1.0",
         "eslint-plugin-prettier": "^5.1.3",
@@ -2229,12 +2229,12 @@
       "dev": true
     },
     "node_modules/commander": {
-      "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
-      "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+      "version": "12.0.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
+      "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
       "dev": true,
       "engines": {
-        "node": ">=16"
+        "node": ">=18"
       }
     },
     "node_modules/compress-commons": {
@@ -7078,9 +7078,9 @@
       "dev": true
     },
     "commander": {
-      "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
-      "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+      "version": "12.0.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
+      "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
       "dev": true
     },
     "compress-commons": {
diff --git a/cli/package.json b/cli/package.json
index 3c3c4a076b..83451dbe47 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -5,7 +5,7 @@
   "type": "module",
   "exports": "./dist/index.js",
   "bin": {
-    "immich": "./dist/src/index.js"
+    "immich": "./dist/index.js"
   },
   "license": "MIT",
   "keywords": [
@@ -24,7 +24,7 @@
     "@vitest/coverage-v8": "^1.2.2",
     "byte-size": "^8.1.1",
     "cli-progress": "^3.12.0",
-    "commander": "^11.0.0",
+    "commander": "^12.0.0",
     "eslint": "^8.56.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-prettier": "^5.1.3",
diff --git a/cli/src/commands/login.ts b/cli/src/commands/login.command.ts
similarity index 100%
rename from cli/src/commands/login.ts
rename to cli/src/commands/login.command.ts
diff --git a/cli/src/index.ts b/cli/src/index.ts
index cd16885d32..eead2b0272 100644
--- a/cli/src/index.ts
+++ b/cli/src/index.ts
@@ -3,19 +3,22 @@ import { Command, Option } from 'commander';
 import path from 'node:path';
 import os from 'node:os';
 import { version } from '../package.json';
-import { LoginCommand } from './commands/login';
+import { LoginCommand } from './commands/login.command';
 import { LogoutCommand } from './commands/logout.command';
 import { ServerInfoCommand } from './commands/server-info.command';
 import { UploadCommand } from './commands/upload.command';
 
-const homeDirectory = os.homedir();
-const configDirectory = path.join(homeDirectory, '.config/immich/');
+const defaultConfigDirectory = path.join(os.homedir(), '.config/immich/');
 
 const program = new Command()
   .name('immich')
   .version(version)
   .description('Command line interface for Immich')
-  .addOption(new Option('-d, --config', 'Configuration directory').env('IMMICH_CONFIG_DIR').default(configDirectory));
+  .addOption(
+    new Option('-d, --config-directory', 'Configuration directory where auth.yml will be stored')
+      .env('IMMICH_CONFIG_DIR')
+      .default(defaultConfigDirectory),
+  );
 
 program
   .command('upload')
diff --git a/cli/test/e2e/login-key.e2e-spec.ts b/cli/test/e2e/login-key.e2e-spec.ts
index b2c2dcacbb..9d69579610 100644
--- a/cli/test/e2e/login-key.e2e-spec.ts
+++ b/cli/test/e2e/login-key.e2e-spec.ts
@@ -1,7 +1,7 @@
 import { restoreTempFolder, testApp } from '@test-utils';
 import { CLI_BASE_OPTIONS, TEST_AUTH_FILE, deleteAuthFile, setup, spyOnConsole } from 'test/cli-test-utils';
 import { readFile, stat } from 'node:fs/promises';
-import { LoginCommand } from '../../src/commands/login';
+import { LoginCommand } from '../../src/commands/login.command';
 import yaml from 'yaml';
 
 describe(`login-key (e2e)`, () => {
@@ -58,7 +58,6 @@ describe(`login-key (e2e)`, () => {
     await new LoginCommand(CLI_BASE_OPTIONS).run(instanceUrl, apiKey);
 
     const stats = await stat(TEST_AUTH_FILE);
-
     const mode = (stats.mode & 0o777).toString(8);
 
     expect(mode).toEqual('600');
diff --git a/docs/docs/features/command-line-interface.md b/docs/docs/features/command-line-interface.md
index 6f16461185..304583ebac 100644
--- a/docs/docs/features/command-line-interface.md
+++ b/docs/docs/features/command-line-interface.md
@@ -15,10 +15,12 @@ If you are looking to import your Google Photos takeout, we recommend this commu
 
 ## Requirements
 
-- Node.js 20.0 or above
+- Node.js 20 or above
 - Npm
 
-## Installation
+If you can't install node/npm, there is also a Docker version available below.
+
+## Installation (NPM)
 
 ```bash
 npm i -g @immich/cli
@@ -30,6 +32,16 @@ NOTE: if you previously installed the legacy CLI, you will need to uninstall it
 npm uninstall -g immich
 ```
 
+## Installation (Docker)
+
+If npm is not available on your system you can try the Docker version
+
+```bash
+docker run -it -v "$(pwd)":/import:ro -e IMMICH_API_KEY=https://your-immich-instance/api -e IMMICH_API_KEY=your-api-key ghcr.io/immich-app/immich-cli:latest
+```
+
+Please modify the `IMMICH_INSTANCE_URL` and `IMMICH_API_KEY` environment variables as suitable. You can also use a Docker env file to store your sensitive API key.
+
 ## Usage
 
 ```