Compare commits

...

4 Commits

Author SHA1 Message Date
Alex Tran
8db073941d Up server version for release 2022-06-29 21:54:57 -05:00
Alex
5e281b44e9 Add Podman Support (#278) 2022-06-29 21:49:35 -05:00
Zack Pollard
142ede350e feat: create immich-nginx container to remove default nginx config setup (#280)
* feat: create immich-proxy container to remove default nginx config setup

* infra: make production docker-compose point at release builds for stability

* Fixed nginx config file was overriden by default.conf in nginx container; Fixed docker-compose.dev; Added additional tag 'release' for tagging after release build in Github Action

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2022-06-29 21:24:55 -05:00
Alex
a2e1d4caa2 Update server dependencies and fixed Typeorm API changes in new version (#276)
* Fixed dependencies

* Adapt typeorm API to be compatible with new version

* Fixed typeorm API in tests

* Remove console.log
2022-06-29 13:39:58 -05:00
24 changed files with 1293 additions and 1281 deletions

View File

@@ -91,3 +91,30 @@ jobs:
push: true push: true
tags: | tags: |
altran1502/immich-web:latest altran1502/immich-web:latest
build_and_push_nginx_latest:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Proxy
uses: docker/build-push-action@v3.0.0
with:
context: ./nginx
file: ./nginx/Dockerfile
platforms: linux/arm/v7,linux/amd64,linux/arm64
push: true
tags: |
altran1502/immich-proxy:latest

View File

@@ -93,3 +93,30 @@ jobs:
push: ${{ github.event_name == 'pull_request' }} push: ${{ github.event_name == 'pull_request' }}
tags: | tags: |
altran1502/immich-web:staging altran1502/immich-web:staging
build_and_push_nginx_staging:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Proxy
uses: docker/build-push-action@v3.0.0
with:
context: ./nginx
file: ./nginx/Dockerfile
platforms: linux/arm/v7,linux/amd64,linux/arm64
push: ${{ github.event_name == 'pull_request' }}
tags: |
altran1502/immich-proxy:staging

View File

@@ -43,6 +43,7 @@ jobs:
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: | tags: |
altran1502/immich-server:${{ steps.previoustag.outputs.tag }} altran1502/immich-server:${{ steps.previoustag.outputs.tag }}
altran1502/immich-server:release
build_and_push_machine_learning_release: build_and_push_machine_learning_release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -75,6 +76,7 @@ jobs:
push: true push: true
tags: | tags: |
altran1502/immich-machine-learning:${{ steps.previoustag.outputs.tag }} altran1502/immich-machine-learning:${{ steps.previoustag.outputs.tag }}
altran1502/immich-machine-learning:release
build_and_push_web_release: build_and_push_web_release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -114,3 +116,43 @@ jobs:
target: prod target: prod
tags: | tags: |
altran1502/immich-web:${{ steps.previoustag.outputs.tag }} altran1502/immich-web:${{ steps.previoustag.outputs.tag }}
altran1502/immich-web:release
build_and_push_nginx_release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: "main"
fetch-depth: 0
- name: "Get Previous tag"
id: previoustag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: latest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push immich-proxy release
uses: docker/build-push-action@v3.0.0
with:
context: ./web
file: ./web/Dockerfile
platforms: linux/arm/v7,linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: |
altran1502/immich-proxy:release
altran1502/immich-proxy:${{ steps.previoustag.outputs.tag }}

View File

@@ -7,8 +7,6 @@ services:
context: ../server context: ../server
dockerfile: Dockerfile dockerfile: Dockerfile
command: npm run start:dev immich command: npm run start:dev immich
expose:
- "3000"
volumes: volumes:
- ../server:/usr/src/app - ../server:/usr/src/app
- ${UPLOAD_LOCATION}:/usr/src/app/upload - ${UPLOAD_LOCATION}:/usr/src/app/upload
@@ -27,8 +25,6 @@ services:
context: ../machine-learning context: ../machine-learning
dockerfile: Dockerfile dockerfile: Dockerfile
command: npm run start:dev command: npm run start:dev
expose:
- "3001"
volumes: volumes:
- ../machine-learning:/usr/src/app - ../machine-learning:/usr/src/app
- ${UPLOAD_LOCATION}:/usr/src/app/upload - ${UPLOAD_LOCATION}:/usr/src/app/upload
@@ -94,11 +90,12 @@ services:
ports: ports:
- 5432:5432 - 5432:5432
nginx: immich-proxy:
container_name: proxy_nginx container_name: immich_proxy
image: nginx:latest image: immich-proxy-dev:latest
volumes: build:
- ./settings/nginx-conf:/etc/nginx/conf.d context: ../nginx
dockerfile: Dockerfile
ports: ports:
- 2283:80 - 2283:80
- 2284:443 - 2284:443
@@ -106,6 +103,7 @@ services:
driver: none driver: none
depends_on: depends_on:
- immich-server - immich-server
restart: always
volumes: volumes:
pgdata: pgdata:

View File

@@ -68,11 +68,9 @@ services:
- pgdata:/var/lib/postgresql/data - pgdata:/var/lib/postgresql/data
restart: always restart: always
nginx: immich-proxy:
container_name: proxy_nginx container_name: immich_proxy
image: nginx:latest image: altran1502/immich-proxy:staging
volumes:
- ./settings/nginx-conf:/etc/nginx/conf.d
ports: ports:
- 2283:80 - 2283:80
- 2284:443 - 2284:443

View File

@@ -2,7 +2,7 @@ version: "3.8"
services: services:
immich-server: immich-server:
image: altran1502/immich-server:latest image: altran1502/immich-server:release
entrypoint: ["/bin/sh", "./start-server.sh"] entrypoint: ["/bin/sh", "./start-server.sh"]
volumes: volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload - ${UPLOAD_LOCATION}:/usr/src/app/upload
@@ -16,7 +16,7 @@ services:
restart: always restart: always
immich-microservices: immich-microservices:
image: altran1502/immich-server:latest image: altran1502/immich-server:release
entrypoint: ["/bin/sh", "./start-microservices.sh"] entrypoint: ["/bin/sh", "./start-microservices.sh"]
volumes: volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload - ${UPLOAD_LOCATION}:/usr/src/app/upload
@@ -30,7 +30,7 @@ services:
restart: always restart: always
immich-machine-learning: immich-machine-learning:
image: altran1502/immich-machine-learning:latest image: altran1502/immich-machine-learning:release
entrypoint: ["/bin/sh", "./entrypoint.sh"] entrypoint: ["/bin/sh", "./entrypoint.sh"]
volumes: volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload - ${UPLOAD_LOCATION}:/usr/src/app/upload
@@ -43,7 +43,7 @@ services:
restart: always restart: always
immich-web: immich-web:
image: altran1502/immich-web:latest image: altran1502/immich-web:release
entrypoint: ["/bin/sh", "./entrypoint.sh"] entrypoint: ["/bin/sh", "./entrypoint.sh"]
env_file: env_file:
- .env - .env
@@ -68,11 +68,9 @@ services:
- pgdata:/var/lib/postgresql/data - pgdata:/var/lib/postgresql/data
restart: always restart: always
nginx: immich-proxy:
container_name: proxy_nginx container_name: immich_proxy
image: nginx:latest image: altran1502/immich-proxy:release
volumes:
- ./settings/nginx-conf:/etc/nginx/conf.d
ports: ports:
- 2283:80 - 2283:80
- 2284:443 - 2284:443

3
nginx/Dockerfile Normal file
View File

@@ -0,0 +1,3 @@
FROM nginx:latest
COPY nginx.conf /etc/nginx/conf.d/default.conf

View File

@@ -22,9 +22,9 @@ server {
location /api { location /api {
# Compression # Compression
gzip_static on; gzip_static on;
gzip_min_length 1000; gzip_min_length 1000;
gzip_comp_level 2; gzip_comp_level 2;
proxy_buffering off; proxy_buffering off;
proxy_buffer_size 16k; proxy_buffer_size 16k;
@@ -41,17 +41,17 @@ server {
proxy_set_header Connection "upgrade"; proxy_set_header Connection "upgrade";
proxy_set_header Host $host; proxy_set_header Host $host;
rewrite /api/(.*) /$1 break; rewrite /api/(.*) /$1 break;
proxy_pass http://immich-server:3000; proxy_pass http://immich-server:3001;
} }
location / { location / {
# Compression # Compression
gzip_static on; gzip_static on;
gzip_min_length 1000; gzip_min_length 1000;
gzip_comp_level 2; gzip_comp_level 2;
proxy_buffering off; proxy_buffering off;
proxy_buffer_size 16k; proxy_buffer_size 16k;

View File

@@ -3,7 +3,7 @@ import { AssetAlbumEntity } from '@app/database/entities/asset-album.entity';
import { UserAlbumEntity } from '@app/database/entities/user-album.entity'; import { UserAlbumEntity } from '@app/database/entities/user-album.entity';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { getConnection, Repository, SelectQueryBuilder } from 'typeorm'; import { Repository, SelectQueryBuilder, DataSource } from 'typeorm';
import { AddAssetsDto } from './dto/add-assets.dto'; import { AddAssetsDto } from './dto/add-assets.dto';
import { AddUsersDto } from './dto/add-users.dto'; import { AddUsersDto } from './dto/add-users.dto';
import { CreateAlbumDto } from './dto/create-album.dto'; import { CreateAlbumDto } from './dto/create-album.dto';
@@ -36,10 +36,12 @@ export class AlbumRepository implements IAlbumRepository {
@InjectRepository(UserAlbumEntity) @InjectRepository(UserAlbumEntity)
private userAlbumRepository: Repository<UserAlbumEntity>, private userAlbumRepository: Repository<UserAlbumEntity>,
private dataSource: DataSource,
) {} ) {}
async create(ownerId: string, createAlbumDto: CreateAlbumDto): Promise<AlbumEntity> { async create(ownerId: string, createAlbumDto: CreateAlbumDto): Promise<AlbumEntity> {
return getConnection().transaction(async (transactionalEntityManager) => { return this.dataSource.transaction(async (transactionalEntityManager) => {
// Create album entity // Create album entity
const newAlbum = new AlbumEntity(); const newAlbum = new AlbumEntity();
newAlbum.ownerId = ownerId; newAlbum.ownerId = ownerId;

View File

@@ -154,7 +154,7 @@ export class AssetService {
public async getAssetThumbnail(assetId: string): Promise<StreamableFile> { public async getAssetThumbnail(assetId: string): Promise<StreamableFile> {
try { try {
const asset = await this.assetRepository.findOne({ id: assetId }); const asset = await this.assetRepository.findOne({ where: { id: assetId } });
if (!asset) { if (!asset) {
throw new NotFoundException('Asset not found'); throw new NotFoundException('Asset not found');
} }

View File

@@ -18,22 +18,22 @@ export class AuthService {
) {} ) {}
private async validateUser(loginCredential: LoginCredentialDto): Promise<UserEntity | null> { private async validateUser(loginCredential: LoginCredentialDto): Promise<UserEntity | null> {
const user = await this.userRepository.findOne( const user = await this.userRepository.findOne({
{ email: loginCredential.email }, where: {
{ email: loginCredential.email,
select: [
'id',
'email',
'password',
'salt',
'firstName',
'lastName',
'isAdmin',
'profileImagePath',
'shouldChangePassword',
],
}, },
); select: [
'id',
'email',
'password',
'salt',
'firstName',
'lastName',
'isAdmin',
'profileImagePath',
'shouldChangePassword',
],
});
if (!user) { if (!user) {
return null; return null;

View File

@@ -33,7 +33,7 @@ export class CommunicationGateway implements OnGatewayConnection, OnGatewayDisco
? await this.immichJwtService.validateToken(accessToken) ? await this.immichJwtService.validateToken(accessToken)
: { status: false, userId: null }; : { status: false, userId: null };
if (!res.status) { if (!res.status || res.userId == null) {
client.emit('error', 'unauthorized'); client.emit('error', 'unauthorized');
client.disconnect(); client.disconnect();
return; return;

View File

@@ -15,8 +15,10 @@ export class DeviceInfoService {
async create(createDeviceInfoDto: CreateDeviceInfoDto, authUser: AuthUserDto) { async create(createDeviceInfoDto: CreateDeviceInfoDto, authUser: AuthUserDto) {
const res = await this.deviceRepository.findOne({ const res = await this.deviceRepository.findOne({
deviceId: createDeviceInfoDto.deviceId, where: {
userId: authUser.id, deviceId: createDeviceInfoDto.deviceId,
userId: authUser.id,
},
}); });
if (res) { if (res) {

View File

@@ -38,7 +38,7 @@ export class UserService {
} }
async getUserInfo(authUser: AuthUserDto) { async getUserInfo(authUser: AuthUserDto) {
return this.userRepository.findOne({ id: authUser.id }); return this.userRepository.findOne({ where: { id: authUser.id } });
} }
async getUserCount(isAdmin: boolean) { async getUserCount(isAdmin: boolean) {
@@ -85,7 +85,7 @@ export class UserService {
} }
async updateUser(updateUserDto: UpdateUserDto) { async updateUser(updateUserDto: UpdateUserDto) {
const user = await this.userRepository.findOne(updateUserDto.id); const user = await this.userRepository.findOne({ where: { id: updateUserDto.id } });
if (!user) { if (!user) {
throw new NotFoundException('User not found'); throw new NotFoundException('User not found');
} }
@@ -148,7 +148,7 @@ export class UserService {
async getUserProfileImage(userId: string, res: Res) { async getUserProfileImage(userId: string, res: Res) {
try { try {
const user = await this.userRepository.findOne({ id: userId }); const user = await this.userRepository.findOne({ where: { id: userId } });
if (!user) { if (!user) {
throw new NotFoundException('User not found'); throw new NotFoundException('User not found');
} }

View File

@@ -3,7 +3,7 @@
export const serverVersion = { export const serverVersion = {
major: 1, major: 1,
minor: 14, minor: 15,
patch: 0, patch: 0,
build: 21, build: 21,
}; };

View File

@@ -15,7 +15,7 @@ async function bootstrap() {
app.useWebSocketAdapter(new RedisIoAdapter(app)); app.useWebSocketAdapter(new RedisIoAdapter(app));
await app.listen(3000, () => { await app.listen(3001, () => {
if (process.env.NODE_ENV == 'development') { if (process.env.NODE_ENV == 'development') {
Logger.log('Running Immich Server in DEVELOPMENT environment', 'ImmichServer'); Logger.log('Running Immich Server in DEVELOPMENT environment', 'ImmichServer');
} }

View File

@@ -25,7 +25,7 @@ export class AdminRolesGuard implements CanActivate {
return false; return false;
} }
const user = await this.userRepository.findOne(userId); const user = await this.userRepository.findOne({ where: { id: userId } });
if (!user) { if (!user) {
return false; return false;
} }

View File

@@ -22,7 +22,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
async validate(payload: JwtPayloadDto) { async validate(payload: JwtPayloadDto) {
const { userId } = payload; const { userId } = payload;
const user = await this.usersRepository.findOne({ id: userId }); const user = await this.usersRepository.findOne({ where: { id: userId } });
if (!user) { if (!user) {
throw new UnauthorizedException('Failure to validate JWT payload'); throw new UnauthorizedException('Failure to validate JWT payload');

View File

@@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule'; import { Cron, CronExpression } from '@nestjs/schedule';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { AssetEntity } from '@app/database/entities/asset.entity'; import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
import { InjectQueue } from '@nestjs/bull'; import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull'; import { Queue } from 'bull';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
@@ -44,7 +44,7 @@ export class ScheduleTasksService {
async videoConversion() { async videoConversion() {
const assets = await this.assetRepository.find({ const assets = await this.assetRepository.find({
where: { where: {
type: 'VIDEO', type: AssetType.VIDEO,
mimeType: 'video/quicktime', mimeType: 'video/quicktime',
encodedVideoPath: '', encodedVideoPath: '',
}, },

View File

@@ -1,15 +1,18 @@
import { getConnection } from 'typeorm'; import { DataSource } from 'typeorm';
import { CanActivate, ExecutionContext } from '@nestjs/common'; import { CanActivate, ExecutionContext } from '@nestjs/common';
import { TestingModuleBuilder } from '@nestjs/testing'; import { TestingModuleBuilder } from '@nestjs/testing';
import { AuthUserDto } from '../src/decorators/auth-user.decorator'; import { AuthUserDto } from '../src/decorators/auth-user.decorator';
import { JwtAuthGuard } from '../src/modules/immich-jwt/guards/jwt-auth.guard'; import { JwtAuthGuard } from '../src/modules/immich-jwt/guards/jwt-auth.guard';
import databaseConfig from '@app/database/config/database.config';
type CustomAuthCallback = () => AuthUserDto; type CustomAuthCallback = () => AuthUserDto;
export async function clearDb() { export async function clearDb() {
const entities = getConnection().entityMetadatas; const db = new DataSource(databaseConfig);
const entities = db.entityMetadatas;
for (const entity of entities) { for (const entity of entities) {
const repository = getConnection().getRepository(entity.name); const repository = db.getRepository(entity.name);
await repository.query(`TRUNCATE ${entity.tableName} RESTART IDENTITY CASCADE;`); await repository.query(`TRUNCATE ${entity.tableName} RESTART IDENTITY CASCADE;`);
} }
} }

View File

@@ -8,7 +8,7 @@ async function bootstrap() {
app.useWebSocketAdapter(new RedisIoAdapter(app)); app.useWebSocketAdapter(new RedisIoAdapter(app));
await app.listen(3000, () => { await app.listen(3002, () => {
if (process.env.NODE_ENV == 'development') { if (process.env.NODE_ENV == 'development') {
Logger.log('Running Immich Microservices in DEVELOPMENT environment', 'ImmichMicroservice'); Logger.log('Running Immich Microservices in DEVELOPMENT environment', 'ImmichMicroservice');
} }

View File

@@ -1,6 +1,7 @@
import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
export const databaseConfig: TypeOrmModuleOptions = { export const databaseConfig: PostgresConnectionOptions = {
type: 'postgres', type: 'postgres',
host: process.env.DB_HOSTNAME || 'immich_postgres', host: process.env.DB_HOSTNAME || 'immich_postgres',
port: 5432, port: 5432,
@@ -10,11 +11,7 @@ export const databaseConfig: TypeOrmModuleOptions = {
entities: [__dirname + '/../**/*.entity.{js,ts}'], entities: [__dirname + '/../**/*.entity.{js,ts}'],
synchronize: false, synchronize: false,
migrations: [__dirname + '/../migrations/*.{js,ts}'], migrations: [__dirname + '/../migrations/*.{js,ts}'],
cli: {
migrationsDir: __dirname + '/../migrations',
},
migrationsRun: true, migrationsRun: true,
autoLoadEntities: true,
}; };
export default databaseConfig; export default databaseConfig;

2307
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,19 +26,19 @@
}, },
"dependencies": { "dependencies": {
"@mapbox/mapbox-sdk": "^0.13.3", "@mapbox/mapbox-sdk": "^0.13.3",
"@nestjs/bull": "^0.4.2", "@nestjs/bull": "^0.5.5",
"@nestjs/common": "^8.0.0", "@nestjs/common": "^8.4.7",
"@nestjs/config": "^1.1.6", "@nestjs/config": "^2.1.0",
"@nestjs/core": "^8.0.0", "@nestjs/core": "^8.4.7",
"@nestjs/jwt": "^8.0.0", "@nestjs/jwt": "^8.0.1",
"@nestjs/mapped-types": "*", "@nestjs/mapped-types": "*",
"@nestjs/passport": "^8.1.0", "@nestjs/passport": "^8.2.2",
"@nestjs/platform-express": "^8.0.0", "@nestjs/platform-express": "^8.4.7",
"@nestjs/platform-fastify": "^8.2.6", "@nestjs/platform-fastify": "^8.4.7",
"@nestjs/platform-socket.io": "^8.2.6", "@nestjs/platform-socket.io": "^8.4.7",
"@nestjs/schedule": "^2.0.1", "@nestjs/schedule": "^2.0.1",
"@nestjs/typeorm": "^8.0.3", "@nestjs/typeorm": "^8.1.4",
"@nestjs/websockets": "^8.2.6", "@nestjs/websockets": "^8.4.7",
"@socket.io/redis-adapter": "^7.1.0", "@socket.io/redis-adapter": "^7.1.0",
"axios": "^0.26.0", "axios": "^0.26.0",
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
@@ -61,12 +61,12 @@
"sharp": "^0.28.0", "sharp": "^0.28.0",
"socket.io-redis": "^6.1.1", "socket.io-redis": "^6.1.1",
"systeminformation": "^5.11.0", "systeminformation": "^5.11.0",
"typeorm": "^0.2.41" "typeorm": "^0.3.6"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^8.2.4", "@nestjs/cli": "^8.2.8",
"@nestjs/schematics": "^8.0.0", "@nestjs/schematics": "^8.0.11",
"@nestjs/testing": "^8.0.0", "@nestjs/testing": "^8.4.7",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/bull": "^3.15.7", "@types/bull": "^3.15.7",
"@types/cron": "^2.0.0", "@types/cron": "^2.0.0",