[컴] node v20.x 에서 TLS v1.2 사용

node v20.x 에서 TLS v1.2 사용

댓글fetch, axios 에선 TLS v1.2 를 사용할 수 없다고 한다.

axios 는 모르겠지만, fetch 에선 TLS v1.2 로 통신하지 못했다. 그래서 https 를 사용해서 해결했다.

https.request

const options = {
  hostname: 'www.howsmyssl.com',
  port: 443,
  path: '/a/check',
  method: 'GET',
  secureProtocol: "TLSv1_2_method"
}
const res = await https.request(options)

실패: node-fetch 에서 https.Agent 를 사용

이 방법은 적어도 windows 에서는 동작하지 않았다.

import https from "https";
const agent = new https.Agent({
  rejectUnauthorized: false
});
fetch(myUrl, { agent });

아래와 같은 error 가 떴다.

reason: write EPROTO 382D0000:error:0A000152:SSL routines:final_renegotiate:unsafe legacy renegotiation disabled:c:\ws\deps\openssl\openssl\ssl\statem\extensions.c:922:

    at ClientRequest.<anonymous> (D:\a\prog\typescript\nextjs\budgetbuddy\budgetbuddybrain\node_modules\node-fetch\lib\index.js:1501:11)
    at ClientRequest.emit (node:events:518:28)
    at TLSSocket.socketErrorListener (node:_http_client:500:9)
    at TLSSocket.emit (node:events:518:28)
    at emitErrorNT (node:internal/streams/destroy:169:8)
    at emitErrorCloseNT (node:internal/streams/destroy:128:3)
    at processTicksAndRejections (node:internal/process/task_queues:82:21) {
  type: 'system',
  errno: 'EPROTO',
  code: 'EPROTO'
}
...

Axios

axios 는 다음의 글에서 해결책을 제시한다. 확인은 해보지 못했다.

Reference

  1. node.js - Configure https agent to allow only TLS1.2 for outgoing requests - Stack Overflow

[컴] NestJS 설치 및 간단한 api 생성

orm 사용 / nestjs 에서 orm 사용 / nestjs example / nestjs simple api / 사용법

NestJS 설치 및 간단한 api 생성

@nestjs/cli를 사용해서 boiler plate 를 만들 수 있다.

npm i -g @nestjs/cli

원하는 project 이름으로 만들어보자. 여기서는 myapp 으로 했다.

nest new myapp

dev server

package.json 을 참고하면 된다.

npm run start:dev

main.ts 를 보면 알겠지만, 기본 port 는 3000 으로 되어 있다. 브라우저에서 http://localhost:3000 로 접속하자. Hello World 가 보이면 된다.

config

config 파일을 사용하려면 @nestjs/config를 설치해야 한다.

npm i --save @nestjs/config

module 추가

각 기능별로 Module 을 만들어서 추가하면 된다. Application 에는 하나의 root module 이 있고, 이 root module 이 모든 module 을 import 하게 하면 된다. root module 은 nest product 를 new 할 때 만들어진다. src/app.module.ts

여기서는 MyFuncModule 을 만들어보자. Module 은 어떤 controller 를 사용하고, 어떤 provider 를 사용하는지 명시하게 된다.

controller 에서 'myfunc' 을 만들어주면 http://localhost:3000/myfunc/ 를 호출할 수 있다.

src/
  |
  + app.module.ts
  + app.controller.ts
  + app.service.ts
  |
  +-- myfunc/
        |
        + myfunc.controller.ts
        + myfunc.module.ts
        + myfunc.service.ts

AppModule :

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MyFuncModule } from './diper/myfunc.module';

@Module({
  imports: [MyFuncModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

MyFuncModule :

import { Module } from '@nestjs/common';
import { MyFuncController } from './myfunc.controller';
import { MyFuncService } from './myfunc.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { MyFunEntity } from 'src/model/myfunc.entity';

@Module({
  imports: [TypeOrmModule.forFeature([MyFuncEntity]],
  controllers: [MyFuncController],
  providers: [MyFuncService],
})
export class MyFuncModule {}
import { Controller, Get } from '@nestjs/common';
import { MyFuncService } from './myfunc.service';
import { MyFuncEntity } from 'src/model/myfunc.entity';

@Controller('myfunc')
export class MyFuncController {
  constructor(private readonly myFunc: MyFuncService) {}

  @Get()
  async findAll(): Promise<MyFuncEntity[]> {
    return await this.myFunc.findAll();
  }
}
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { MyFuncEntity } from 'src/model/myfunc.entity';
import { Repository } from 'typeorm';

@Injectable()
export class MyFuncService {
  constructor(
    @InjectRepository(MyFuncEntity)
    private readonly myFuncEntityRepository: Repository<MyFuncEntity>,
  ) {}

  async findAll(): Promise<MyFuncEntity[]> {
    return await this.diperRepository.find();
  }
  
  getHello(): string {
    return 'Hello World!';
  }
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class MyFuncEntity {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    price: number

    @Column()
    date: Date

}

Database ORM

기본으로 TypeORM 을 이용한 TypeORMModule 을 제공한다. module 은 설치해야 한다. 다음은 mysql 과 관련한 설치 command 이다. 다른 db 에 대한 정보는 TypeORM - Installation 를 참고하자.

npm install --save @nestjs/typeorm typeorm mysql2

설치를 하고 나서 TypeOrmModule 을 root module 에 import 하면 된다.

  • entites option: 아래 code 에서 entities 에 만들 table 의 정보가 들어가면 된다. 저곳에 명시하지 않으면 해당 table 을 만들지 않는다.
  • autoLoadEntities : 자동으로 entity 를 불러온다. 기본값은 false 이다.
  • synchronize : 이 값은 기본값이 true이다. 이 값을 true로 하면, entity 가 변경내역이 서버 시작 시점에 반영된다. production 에선 이 값을 false 로 두라고 한다.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test', 
      entities: [],     // <---- info for tables
      synchronize: true // <---- 
    }),
  ],
})
export class AppModule {}

DataSource 를 사용하는 법

아래처럼 ConfigModule.forRoot 에서 config file 을 불러올지 정해주고, 그값들을 TypeOrmModule.forRootAsync에서 inject 해서 사용하면 된다.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MyFuncModule } from './diper/myfunc.module';

import { ConfigModule, ConfigService, } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: ['.env.dev.local', '.env.dev'],
      isGlobal: true
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => {
        return ({
          type: 'mysql',
          host: configService.get('HOST'),
          port: +configService.get('PORT'),
          username: configService.get('DATABASE_USER'),
          password: configService.get('DATABASE_PASSWORD'),
          database: configService.get('DATABASE'),
          // entities: [
          //   __dirname + '/**/*.entity{.ts,.js}',
          // ],
          autoLoadEntities: true,
          synchronize: true,
        })
      }
    }),
    MyFuncModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

TypeOrmModule.forRootAsync

TypeOrmModule.forRootAsync 를 사용하면, 무조건 entity 에 대한 table 을 생성하지 않는다. 각 module 에서 자신들이 사용할 entity 를 TypeOrmModule.forFeature([MyFuncEntity]) 처럼 imports에 적어주게 되는데, 이렇게 적힌 것들에 대해서만 생성해 준다.

data migration

최초의 table 의 생성은 ConfigModule.forRoot 의 option entities를 통해 정보를 가져와서 생성하게 된다.

NestJS 에서 제공하진 않고, TypeORM CLI 를 이용한다.(참고) typeorm package 가 이미 설치되어 있으니, 이걸 이용하면 된다.

migration:create :

아래처럼 하면, ./migration/test01.ts 가 생성된다. 그러면 이 test01.ts에 migration 내용을 적으면 된다.

.\node_modules\.bin\typeorm migration:create ./migration/test01

migration:run :

.ts file 을 js 로 바꿔서 사용하기 위해 ts-node 를 사용해야 한다. 다음 커맨드를 이용하면 된다.

npx typeorm-ts-node-commonjs migration:run -- -d path-to-datasource-config

full codes

Refrence

  1. First steps | NestJS - A progressive Node.js framework

[컴] NestJS 에서 ConfigModule 예시

nestjs config / env / 설정

NestJS 에서 ConfigModule 예시

NestJS 에서는 configuration 을 set할 때 ConfigService를 노출하는 ConfigModule 을 사용하는 것을 권장한다. 이 ConfigService가 알맞은 .env file 을 load 하는 것이다.

직접 만들어도 되고, @nestjs/config package 를 사용해도 된다. @nestjs/config package 내부에서 dotenv 를 사용한다.

npm i --save @nestjs/config

root module 에 아래처럼 ConfigModule을 추가한다.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

import { ConfigModule,ConfigService } from '@nestjs/config';

@Module({
    // 이 때 1번째로 import 를 해야 그 이후 module 에서 ConfigModule을
    // 접근할 수 있다.
  imports: [ConfigModule.forRoot({
      // .env.dev.local 을 찾고, 없으면 .env.dev 를 찾는다.
      // 기본적으로 `.env`를 찾는다.
      envFilePath: ['.env.dev.local', '.env.dev'],
      // true 이면, global module 이 된다. 그래서 root module에서 한번만 import 하면, 
      // 다른 module 에서 import 없이 사용할 수 있다.
      isGlobal: true
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => {
        return ({
          type: 'mysql',
          host: configService.get('HOST'),
          port: +configService.get('PORT'),
          username: configService.get('DATABASE_USER'),
          password: configService.get('DATABASE_PASSWORD'),
          database: configService.get('DATABASE'),
          // entities: [
          //   __dirname + '/**/*.entity{.ts,.js}',
          // ],
          autoLoadEntities: true,
          synchronize: true,
        })
    }}),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

.env file :

DATABASE_USER=test
DATABASE_PASSWORD=test

이제 .env file 의 변수가 load 되면, nestjs 에서 process.env.DATABASE_USER 등을 이용해서 사용할 수 있게 된다. 그리고 또한 ConfigService 에도 저장돼서 ConfigService.get() 를 통해서도 접근할 수 있게 된다. ConfigService provider는 ConfigModule.forRoot()를 통해 등록돼서 사용할 수 있게 된다.

즉 process.env 에 .env file 의 값들을 load 한 것이라 볼 수 있다.

dist directory에 복사

nest build 시에는 .ts file 외에는 자동으로 복사하지 않는다. 그래서 nest-cli.jsoncompilerOptions#assets 에 아래처럼 복사가 되도록 설정을 해놔야 한다.

{
  ...
  "compilerOptions": {
    "assets": [{"include": "../config/*.yaml", "outDir": "./dist/config"}]
  }
}

기타

yaml file, 환경변수 사용등의 자세한 이야기는 ref. 1 을 참고하자.

Reference

  1. Configuration | NestJS - A progressive Node.js framework
  2. Ultimate Guide: NestJS Config & Environment Variables [2022]

[컴][머신러닝] windows 에서 ollama 사용

 

llama / llm / 윈도우즈 /

windows 에서 ollama 사용

다음 링크에서 Ollama windows version 을 download 하자.

설치를 하면 %AppData%\Local\Programs\Ollama에 자동으로 설치된다. 대략 1.6GB 정도 여유공간이 필요하다.

  • %AppData%\Local\Programs\Ollama

아래와 같은 방식으로 실행하면 된다. model 이 없으면 model 을 download 하고, 실행한다. ollama run llama2 를 하면 llama2 model 을 다운로드하게 된다. 대략 3.8GB 이다. 이 model 은 %UserProfile%\.ollama 에 저장된다.

ollama run llama2
>>> Send a message (/? for help)
ollama list

Ollama WebUI

Ollama WebUI 가 있다. 아래는 linux 에서 설치방법이다.

sudo docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
sudo docker run -d --network=host -v ollama-webui:/app/backend/data -e OLLAMA_API_BASE_URL=http://172.17.192.1:11434/api --name ollama-webui --restart always ghcr.io/ollama-webui/ollama-webui:main

[컴] systemctl --user도 linger 설정이 필요하다.

스프링 이유없이 죽는 이유

systemctl --user도 linger 설정이 필요하다.

systemctl --user 를 사용하는 경우에도 linger 설정이 필요하다.

만약 ssh login 을 해서 서버를 띄운 후에 ssh세션을 모두 닫는다면 그 시점에 서버가 종료될 것이다.

sudo loginctl enable-linger <user_id>

현재 linger설정 상태를 확인하려면 아래와 같은 command를 치면 된다.

loginctl user-status <user_id>

$ loginctl user-status admin
admin (1000)
           Since: Thu 2024-03-28 06:16:00 UTC; 4h 42min ago
           State: active
        Sessions: 642 *639
          Linger: no
            Unit: user-1000.slice
                  ├─session-639.scope
                  │ ├─67433 sshd: admin [priv]
                  │ ├─67456 sshd: admin@pts/0
                  │ ├─67457 -bash
                  │ ├─67906 loginctl user-status admin
                  │ └─67907 pager
                  ├─session-642.scope
                  │ ├─67469 sshd: admin [priv]
                  │ ├─67476 sshd: admin@notty
                  │ └─67477 /usr/lib/openssh/sftp-server
                  └─user@1000.service
                    ├─app.slice
                    │ └─myserver.service
                    │   └─67481 /usr/bin/java -Dspring.profiles.active=prod -Xms512M -Xmx1G -XX:+UseZGC -jar /services/myserver/myserver-0.0.1-SNAPSHOT.jar
                    └─init.scope
                      ├─67436 /lib/systemd/systemd --user
                      └─67437 (sd-pam)

개인적인 경험

처음에 systemctl --user 를 사용할 떄는 이것이 systemctl 과 같다고 봐서, daemon 으로 동작하기에 따로 nohup 등의 설정이 필요없다고 생각했다. 그래서 systemctl --user를 이용해서 웹서버를 실행했었다.

하지만 이 서버가 종종 gracefully shutdown 으로 죽었다.(spring server 였다.) 당연히 웹서버(WAS) 로그에는 남아있는 것이 없었다.

원인을 찾다가 deamon.log를 보게 되었다. 여기서 systemd가 stop 된 것을 보고 웹서버(WAS)가 죽은 이유를 추측해 볼 수 있었다. 그래서 결국 linger 설정이 필요한 것을 알았다.

cat /var/log/daemon.log | grep "Mar 28 06:12"

...
Mar 28 06:12:45 i-0127a77bf7ac67eaf systemd[1]: Stopping User Manager for UID 1000...
Mar 28 06:12:45 i-0127a77bf7ac67eaf systemd[104663]: Stopped target Main User Target.
Mar 28 06:12:45 i-0127a77bf7ac67eaf systemd[104663]: Stopping PMS api server(fish)...

Reference

  1. https://github.com/systemd/systemd/issues/8486#issuecomment-374318982

[컴] jenkins 의 plugin list 를 추출하는 법

jenkins plugin 설치 / batch / cli 에서 설치 / 뽑기

jenkins 의 plugin list 를 추출하는 법

jenkins web page 에서 추출하기

https:///script 로 가서

Jenkins.instance.pluginManager.plugins.each{
  plugin -> 
    println ("${plugin.getShortName()}:${plugin.getVersion()}")
}

jenkins cli 로 뽑기

cli 는 다음처럼 받을 수 있다.

curl https:///jnlpJars/jenkins-cli.jar -o jenkins-cli.jar
// get_plugin_list.groovy
def plugins = jenkins.model.Jenkins.instance.getPluginManager().getPlugins()
plugins.each {println "${it.getShortName()}: ${it.getVersion()}"}
java -jar jenkins-cli.jar -s https:// -auth "namh:who1sthegoodman" groovy = < get_plugin_list.groovy >  plugins.txt

jenkins cli 로 plugin 설치

testing - How to install jenkins plugins from command line? - Stack Overflow

java -jar jenkins-cli.jar -s https://inhouse.foodpang.co/ci -auth <username>:<password> install-plugin <plugin_name1> <plugin_name2> ...

Reference

  1. How to get a list of installed Jenkins plugins with name and version pair - Stack Overflow