기존에 Yona 를 구축해서 버전관리를 잘해오고 있는데, 문제는 알림 메일이였다.
지금까지 계속 google의 SMTP로 잘 운영해오다가, 어느 순간 google의 로그인 방식이 보안 정책에 따라, OAuth 방식이 아니면 더 이상 예전 처럼 아이디/패스워드 기반의 로그인을 지원하지 않는 것이다.
그래서 naver.com 내에 계정을 하나 파서 그 안에 있는 POP3/IMAP 기능을 활성화 했고, 거기서 제공하는 SMTP 설정 값을 이용해 Yona의 application.conf 값을 아래와 같이 수정했다.

smtp.startssl = true
smtp.host = smtp.naver.com
smtp.port = 587
smtp.auth = true
#smtp.ssl = true
smtp.password = "xxxxxxx"
smtp.domain = naver.com
smtp.user = "xxxxxxx@naver.com"

설정을 변경한 뒤 Yona를 다시 시작했는데.. 왠걸.. 아래와 같은 에러가 떨어진다.

2024-03-06 13:26:51,466 - [WARN] - from application in play-akka.actor.default-dispatcher-974 
Failed to send a notification: org.apache.commons.mail.HtmlEmail@1720365d
org.apache.commons.mail.EmailException: Sending the email to the following server failed : smtp.naver.com:465
	at org.apache.commons.mail.Email.sendMimeMessage(Email.java:1410)
	at org.apache.commons.mail.Email.send(Email.java:1437)
	at info.schleichardt.play2.mailplugin.MailPlugin$$anonfun$6.apply(MailPlugin.scala:60)
	at info.schleichardt.play2.mailplugin.MailPlugin$$anonfun$6.apply(MailPlugin.scala:54)
	at info.schleichardt.play2.mailplugin.MailPlugin.send(MailPlugin.scala:68)
	at info.schleichardt.play2.mailplugin.api.Mailer$.send(Mailer.scala:8)
	at info.schleichardt.play2.mailplugin.Mailer.send(Mailer.java:15)
	at controllers.ProjectApp.sendTransferRequestMail(ProjectApp.java:775)
	at controllers.ProjectApp.transferProject(ProjectApp.java:634)
	at Routes$$anonfun$routes$1$$anonfun$applyOrElse$191$$anonfun$apply$191.apply(routes_routing.scala:3708)
	at Routes$$anonfun$routes$1$$anonfun$applyOrElse$191$$anonfun$apply$191.apply(routes_routing.scala:3708)
	at play.core.Router$HandlerInvokerFactory$$anon$4.resultCall(Router.scala:264)
	at play.core.Router$HandlerInvokerFactory$JavaActionInvokerFactory$$anon$15$$anon$1.invocation(Router.scala:255)
	at play.core.j.JavaAction$$anon$1.call(JavaAction.scala:55)
	at Global$2.call(Global.java:274)
	at actions.AnonymousCheckAction.call(AnonymousCheckAction.java:57)
	at actions.IsAllowedAction.call(IsAllowedAction.java:68)
	at actions.AbstractProjectCheckAction.call(AbstractProjectCheckAction.java:93)
	at play.db.ebean.TransactionalAction$1.call(TransactionalAction.java:21)
	at play.db.ebean.TransactionalAction$1.call(TransactionalAction.java:18)
	at com.avaje.ebeaninternal.server.core.DefaultServer.execute(DefaultServer.java:715)
	at com.avaje.ebeaninternal.server.core.DefaultServer.execute(DefaultServer.java:709)
	at com.avaje.ebean.Ebean.execute(Ebean.java:1264)
	at play.db.ebean.TransactionalAction.call(TransactionalAction.java:18)
	at play.core.j.JavaAction$$anonfun$11.apply(JavaAction.scala:82)
	at play.core.j.JavaAction$$anonfun$11.apply(JavaAction.scala:82)
	at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24)
	at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24)
	at play.core.j.HttpExecutionContext$$anon$2.run(HttpExecutionContext.scala:40)
	at play.api.libs.iteratee.Execution$trampoline$.execute(Execution.scala:46)
	at play.core.j.HttpExecutionContext.execute(HttpExecutionContext.scala:32)
	at scala.concurrent.impl.Future$.apply(Future.scala:31)
	at scala.concurrent.Future$.apply(Future.scala:485)
	at play.core.j.JavaAction$class.apply(JavaAction.scala:82)
	at play.core.Router$HandlerInvokerFactory$JavaActionInvokerFactory$$anon$15$$anon$1.apply(Router.scala:252)
	at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4$$anonfun$apply$5.apply(Action.scala:130)
	at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4$$anonfun$apply$5.apply(Action.scala:130)
	at play.utils.Threads$.withContextClassLoader(Threads.scala:21)
	at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4.apply(Action.scala:129)
	at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4.apply(Action.scala:128)
	at scala.Option.map(Option.scala:145)
	at play.api.mvc.Action$$anonfun$apply$1.apply(Action.scala:128)
	at play.api.mvc.Action$$anonfun$apply$1.apply(Action.scala:121)
	at play.api.libs.iteratee.Iteratee$$anonfun$mapM$1.apply(Iteratee.scala:483)
	at play.api.libs.iteratee.Iteratee$$anonfun$mapM$1.apply(Iteratee.scala:483)
	at play.api.libs.iteratee.Iteratee$$anonfun$flatMapM$1.apply(Iteratee.scala:519)
	at play.api.libs.iteratee.Iteratee$$anonfun$flatMapM$1.apply(Iteratee.scala:519)
	at play.api.libs.iteratee.Iteratee$$anonfun$flatMap$1$$anonfun$apply$14.apply(Iteratee.scala:496)
	at play.api.libs.iteratee.Iteratee$$anonfun$flatMap$1$$anonfun$apply$14.apply(Iteratee.scala:496)
	at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24)
	at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24)
	at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:41)
	at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(AbstractDispatcher.scala:393)
	at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
	at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
	at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
	at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
Caused by: javax.mail.MessagingException: Could not connect to SMTP host: smtp.naver.com, port: 465;
  nested exception is:
	javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
	at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:1972)
	at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:642)
	at javax.mail.Service.connect(Service.java:317)
	at javax.mail.Service.connect(Service.java:176)
	at javax.mail.Service.connect(Service.java:125)
	at javax.mail.Transport.send0(Transport.java:194)
	at javax.mail.Transport.send(Transport.java:124)
	at org.apache.commons.mail.Email.sendMimeMessage(Email.java:1400)
	... 56 more
Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
	at sun.security.ssl.HandshakeContext.<init>(HandshakeContext.java:171)
	at sun.security.ssl.ClientHandshakeContext.<init>(ClientHandshakeContext.java:103)
	at sun.security.ssl.TransportContext.kickstart(TransportContext.java:227)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:433)
	at com.sun.mail.util.SocketFetcher.configureSSLSocket(SocketFetcher.java:548)
	at com.sun.mail.util.SocketFetcher.createSocket(SocketFetcher.java:352)
	at com.sun.mail.util.SocketFetcher.getSocket(SocketFetcher.java:207)
	at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:1938)
	... 63 more

그런데 에러를 찬찬히 살펴보니 아래와 같은 문장이 눈에 딱 띄었다.

Caused by: javax.mail.MessagingException: Could not connect to SMTP host: smtp.naver.com, port: 465;
  nested exception is:
	javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
	at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:1972)
	at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:642)

이상해서, 과거 Yona의 Issue 창을 뒤져보니, 답글 중에 아래와 같은 답변을 발견했다.

https://github.com/yona-projects/yona/issues/746

 

이메일 발송관련 · Issue #746 · yona-projects/yona

안녕하세요, 프로젝트 이관 중에 확인 메일이 안와서 이상하다 싶어서 확인하니까, 현재 메일에서 계속 오류가 발생합니다. 먼저 제쪽 환경은 아래와 같습니다. [OS] Ubuntu - 16.04.7 LTS (Xenial Xerus) [J

github.com

저 내용에서 중요한 건 Stackoverflow 링크 안의 답글인데, 답글 중에,

JRE_HOME/lib/security/java.security:

파일 안에 있는

jdk.tls.disabledAlgorithms

값을 수정하면 된다는 것이다.

해결 방법

일단, 지금 운영 중인 서버의 Java 파일 중에 java.security를 찾는다.
리눅스를 기준으로 이야기하면 다음과 같이 넣으면 찾을 수 있다.

find | grep java.security

root 권한이 있어야 수정되므로 sudo 로 해당 파일을 열어준다.

그리고 jdk.tls 를 검색하면 아래와 같은 내용을 볼 수 있다.

jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \
    DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \
    include jdk.disabled.namedCurves

저 항목 중에 SSLv3, TLSv1, TLSv1.1 을 삭제하고 저장하도록 한다.

그러면 해결된다.

728x90

다른 곳에서 전체 백업된 SQL을 받아 데이터베이스를 생성했는데, 생성했더니, 데이터베이스의 Data Collate가 "Latin1_General_CI_AS"로 설정되어 있었다. 그래서 간신히 데이터베이스의 설정 값을 바꾸어 "Korean_Wansung_CI_AS"으로 바꾸긴 했다. 그런데 Query를 실행하니까, 웬걸...컬럼들은 여전히 "Latin1_General_CI_AS" 로 되어 있었고, 새로 만든 테이블 내의 값들과 비교하려는데 자꾸 다음과 같은 에러메시지를 뱉어됐다.

Cannot resolve the collation conflict between "Korean_Wansung_CI_AS" and "Latin1_General_CI_AS" in the equal to operation.

데이블 디자이너, 그러니까, 테이블 수정에 들어가 varchar 컬럼마다 데이터 정렬(Data Collate) 값을 바꾸면 되긴 하는데, 이 많은 테이블의 또, 그 컬럼들에 들어가 수정하려니 깝깝했다.

그래서 구글을 통해서 이런 저런 검색한 결과, stackOverflow에서 찾았다.

https://stackoverflow.com/questions/18122773/change-collations-of-all-columns-of-all-tables-in-sql-server

 

Change collations of all columns of all tables in SQL Server

I imported a database with some data to compare with another database. The target database has collation Latin1_General_CI_AS and the source database has SQL_Latin1_General_CP1_CI_AS. I did change ...

stackoverflow.com

아래의 코드 값에서 @collate값만 Korean_Wansung_CI_AS로 변경하고, 수정할 데이터베이스의 쿼리창을 열고 실행했다.

DECLARE @collate nvarchar(100);
DECLARE @table nvarchar(255);
DECLARE @column_name nvarchar(255);
DECLARE @column_id int;
DECLARE @data_type nvarchar(255);
DECLARE @max_length int;
DECLARE @row_id int;
DECLARE @sql nvarchar(max);
DECLARE @sql_column nvarchar(max);

SET @collate = 'Korean_Wansung_CI_AS';

DECLARE local_table_cursor CURSOR FOR

SELECT [name]
FROM sysobjects
WHERE OBJECTPROPERTY(id, N'IsUserTable') = 1

OPEN local_table_cursor
FETCH NEXT FROM local_table_cursor
INTO @table

WHILE @@FETCH_STATUS = 0
BEGIN

    DECLARE local_change_cursor CURSOR FOR

    SELECT ROW_NUMBER() OVER (ORDER BY c.column_id) AS row_id
        , c.name column_name
        , t.Name data_type
        , c.max_length
        , c.column_id
    FROM sys.columns c
    JOIN sys.types t ON c.system_type_id = t.system_type_id
    LEFT OUTER JOIN sys.index_columns ic ON ic.object_id = c.object_id AND ic.column_id = c.column_id
    LEFT OUTER JOIN sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id
    WHERE c.object_id = OBJECT_ID(@table)
    ORDER BY c.column_id

    OPEN local_change_cursor
    FETCH NEXT FROM local_change_cursor
    INTO @row_id, @column_name, @data_type, @max_length, @column_id

    WHILE @@FETCH_STATUS = 0
    BEGIN

        IF (@max_length = -1) OR (@max_length > 4000) SET @max_length = 4000;

        IF (@data_type LIKE '%char%')
        BEGIN TRY
            SET @sql = 'ALTER TABLE ' + @table + ' ALTER COLUMN ' + @column_name + ' ' + @data_type + '(' + CAST(@max_length AS nvarchar(100)) + ') COLLATE ' + @collate
            PRINT @sql
            EXEC sp_executesql @sql
        END TRY
        BEGIN CATCH
          PRINT 'ERROR: Some index or constraint rely on the column' + @column_name + '. No conversion possible.'
          PRINT @sql
        END CATCH

        FETCH NEXT FROM local_change_cursor
        INTO @row_id, @column_name, @data_type, @max_length, @column_id

    END

    CLOSE local_change_cursor
    DEALLOCATE local_change_cursor

    FETCH NEXT FROM local_table_cursor
    INTO @table

END

CLOSE local_table_cursor
DEALLOCATE local_table_cursor

GO

지금 모든 데이터베이스의 모든 테이블의 컬럼들을 한번에 수정할 수 있었다.

다행다행...

728x90

현재 개인 개발 도구를 이용해서 작업 중인데, 네트워크 IP가 미묘하게 겹쳐 업무를 할 때마다 Lan 선을 뽑았다 꼈다를 반복해왔다.

개발 PC에 랜카드를 두개 붙여서 한 쪽은 192.168.30.200 으로 설정해서, 인터넷이나 버전관리 서버에 접속해서 Commit을 했고, 다른 한쪽에는 192.168.0.105 으로 설정한 뒤, Lab 실에 있는 장비와 연결을 했다.

인터넷 하면서 Lab 실 장비를 건드릴 때는 문제가 없는데, 딱! 버전관리 서버를 손대자 문제가 터져나갔다.
그 이유가 192.168.0.x 대역을 버전관리 서버와 Lab실이 동시에 이용해서 이다.
만일 버전관리 서버와 연결하고 싶으면 Lab 실과 연결된 네트워크 랜선을 떼어야 하고,
다시 Lab 장비를 손대려면 다시 붙여야 한다. 만일 Lab 실 장비 손 보다가, 소스 수정할 내용을 Commit을 하고 싶으면 다시 뗀것을 붙여야 했다.

한 두번은 하겠는데, Commit 되는 횟수가 늘어나면 늘어날 수록 점점 귀차니즘이 돌발했다.

그래서 방법을 바꿨다. 그래서 route 라는 명령을 찾게 되었다.

route는 현재 네트워크를 호출할 때 어디로 연결해서 처리할지를 나타내고 수정하는 도구다.
먼저 관리자 권한으로 터미널(관리자)를 연다.

그리고 다음과 같이 명령을 입력하면 현재 설정된 라우팅 정보가 보인다.

저 라우팅 테이블의 내용만 보면, 0.0.0.0 즉 모든 연결은 192.168.30.200 인터페이스에 있는 192.168.30.1 게이트웨이로 보낸다는 의미다. 그리고 192.168.0.0 즉 192.168.0.x 로 된 모든 연결은 192.168.0.105 인터페이스로 보낸다는 의미다.

이걸 보고 해석을 하면, 192.168.30.63  하면, 192.168.30.200 인터페이스를 통해 패킷을 보내고, 192.168.0.3 하면, 192.168.0.105 인터페이스를 통해 패킷을 보낸다.

문제는 192.168.0.2 를 했을 때도, 192.168.0.105를 통해 내보낸다.
내 버전관리 서버는 192.168.30.200을 통해서 VPN을 거쳐 접속을 해야 하는데도 말이다.

그래서 방법을 찾은게 route 명령을 통해 아래와 같이 입력하는 것이다.

route add 192.168.0.2 mask 255.255.255.255 192.168.30.200 -p

192.168.0.2 의 연결에서 Subnet mask를 255.255.255.255를 하면, 딱 저 IP를 입력할 때, 192.168.30.200 인터페이스로 패킷을 보내라는 명령이다. 일단 Subnet mask 부분에서 이해의 폭을 너무 많이 요구하긴 하지만, 최소한 맨 앞의 값과 255.255.255.255 가 붙으면 1개의 IP 라고 읽자.
192.168.0.3 이나 192.168.0.100 같은 경우에는 라우팅 테이블 상에 있는 192.168.0.0 / 255.255.255.0 에 걸려 192.168.0.105 인터페이스로 패킷을 보내지만,
192.168.0.2 는 192.168.0.2 / 255.255.255.255 에 걸려서 192.168.30.200 인터페이스로 패킷을 보낸다고 이해하면 된다.
여기서 인터페이스 라는게 랜카드를 의미하고, 그 랜카드에 설정된 IP 주소라고 읽으면 된다.

맨 마지막의 -p는 영구 연결이라고 해서 네트워크가 리셋되도 일단 살아 있게 만들어 준거다. 없으면 나중에 네트워크 리셋되는 순간 위의 명령만 다시 넣어주면 된다.

나중에 vpn을 통해 이용해야 할 서버의 IP 192.168.0.88 라는게 생기고, 지금과 같이 하고 싶다면,  위의 명령을 이용해서 만들어 줄 수 있다.

route add 192.168.0.88 mask 255.255.255.255 192.168.30.200 -p

 

 

728x90

2022년이니까 햇수로는 2년, 만으로는 1년 반정도 전에 구매.

당시에는 나름 뭐 내적 갈등과 합리화를 통해서 받아드리긴 했는데,
가격이 참 살벌하네;;;;;

딱 10,000 Km 찍어보고 다시 정리해보자

728x90

이게 지금 내가 가진 제품인지 더 확인을 해봐야 겠지만...

도면이 Raddit에 있어서 한번 퍼왔다.
https://www.reddit.com/r/overclocking/comments/r2um0w/default_thermal_pad_thickness_for_sapphire_radeon/

메모리 쪽이 0.75 mm 이고,
파워 쪽이 1.0 mm 인 것 같다.

0.15 쪽은 써멀 구리스로 하면 되니까..

내일 써멀구리스와 써멀패드를 모두 모아와서 분해해 봐야겠다.

728x90

1인 개발자나 스스로 후원자 하면서 세월아 내월아 해도 상관없는 경우를 제외하고, 일감을 받아 업무를 수행하고 돈을 받아가는 형태로 업무를 진행하고 있다면 좀 고려해주었으면 하는 게 있다.


바로 신뢰도다. 
조금은 많이 뭉그뜨려서 표현한 부분이긴 한데, 자기 자신의 속도에 대한 이해도와 현재 업무에 대한 전체적인 파악 및 장악력 같은 내용을 의미한다.
예를 들면, 현재 프로젝트에서 특정 모듈을 개발하는 업무를 받았다고 치자. 여기서 PM 혹은 PL은 얼마나의 시간이 걸릴지를 묻곤 한다. 여기서 그 답을 줄 수 있느냐 없느냐는 부분이다. 물론 성격에 따라 정확한 수치가 아니면 제시할 수 없다고 하는 완벽주의(?)를 가진 분도 있긴 하지만, 그런 부분을 차치하고, 대략적인 수치를 떠올릴 수 있냐 없느냐다. 
이런 대략적인 수치를 뽑으려면 2가지가 요구된다. 


그 하나가 자신의 개발 속도다. 
특정 과제를 해결하는 데 걸리는데 보통 며칠이 걸리는지, 그 과제의 난이도는 어떤지 등에 대해서 기준점을 확실히 하는 것이다. 그래서 특정 과제, 유사 과제 등이 제시되었을 때 어느 정도의 기간 내에 개발할 수 있는지를 곱셈이든, 덧셈이든 해서 값을 도출할 수 있게 된다.

그리고 다른 하나가 주어진 과제에 대한 장악력이다. 
왜 내가 이 부분을 만들어야 하고 -오해하지 말아야 할 부분이 할지 말지를 알기 위한 게 아니다. 반드시 해야 한다. 다만 이렇게 만들어지는 게 어떻게 이용되고 활용되어야 하는지를 의미한다. - , 전체 프로젝트에 어느 부분에 해당하고 어느 정도의 영향력이 있는지 등을 이해하는 능력이다. 하지만 이런 능력은 해당 업무 도메인에 대한 경험치와 비례하기 때문에, 초보 개발자에게 요구하기엔 무리가 있긴 하다. 하지만 이 부분에 대한 대응 가능 여부에서  자신이 초급인지 중급인지 고급인지를 나누게 된다. 

이 업계에서 20여 년간 지내보면서 다양한 동료와 일을 해보면서 느낀 점은 저 부분에 대해서 고민하는지 안하고 피동적으로만 일하는지에 따라 다른 길로 나아간다. 때로는 상급자로, 때로는 금전적 보상으로, 때로는 인정으로 보상을 받으며 일하는지, 아니면 그저 잠깐 인력 지원 레벨에서 끝나 이곳 저곳을 전전하다 결국 적응 실패로 다른 직업으로 전환하거나, 마지못해 일하는 모습만으로 무시 혹은 평가절하 신세를 면치 못하는 모습에 실망하는 모습을 여럿 본 것 같다.

시킨 것만 하고, 미련하게 앞서 생각하다 깨지고 나댄다고 혼날 바엔 조용히 할 일만 하고 따박 따박 월급 받으면서 사는게 제일이야.. 라고 자신있게 말하는 분도 보지만, 결국 활동시기가 저무는 40대 즈음 자신이 해온 일을 쭉 돌아보고 나면 대개는 후회한다고 생각한다.

만일 아직 20대고 30대라면 저 부분에 대해서 고민해보자.
상급자가 겁나게 까칠하거나, 되먹지 않다면 굳이 나대가며 할 필요까지는 없지만, 스스로 계산해보고, 정말 그런지 안그런지를 조용하게 검증해보면서 기준을 잘 세워본다면 상급자로 올라가거나 다른 곳으로 이직하면서 새롭게 적용할 때, 준비된 개발자로 거듭나지 않을까 생각한다.

728x90

Jenkins를 이용해 자동으로 빌드하고, 빌드한 결과물은 Zip으로 압축하여 제공한다.
여기서 빌드 결과물 파일에는 BUILD_NUMBER 값을 이용해 파일명을 만들고 있다.

SET FILENAME=MYSELF_%BUILD_NUMBER%.zip

문제는 저 BUILD_NUMBER 부분인데, 이게 1부터 시작해서 빌드 될 때마다 자동으로 1씩 더해지는 값이다.

그러다보니, File Sort를 해보면, 아래 처럼 될 때가 있다.

MYSELF_1.zip
MYSELF_10.zip
MYSELF_11.zip
MYSELF_2.zip
MYSELF_3.zip
MYSELF_4.zip
MYSELF_5.zip
MYSELF_6.zip
MYSELF_7.zip
MYSELF_8.zip
MYSELF_9.zip

요즘은 운영체제가 좋아지기도 했고, 파일관리도구들도 지능적이여서, 저 숫자부분만 떼서 Sort를 해주기도 하지만, 기본적인 String Sort로 하게되면 저렇게 된다.

방법은 저 파일명의 숫자부분 앞에 "0"을 채우면 된다.

그러니까...

MYSELF_0001.zip
MYSELF_0002.zip
MYSELF_0003.zip
MYSELF_0004.zip
MYSELF_0005.zip
MYSELF_0006.zip
MYSELF_0007.zip
MYSELF_0008.zip
MYSELF_0009.zip
MYSELF_0010.zip
MYSELF_0011.zip

위와 같이 만들 수 있다.

이를 위해서는 Jenkins 빌드 후 파일명 만들 때 다음과 같이 만들면 된다.

SET PADDED_BUILD_NUMBER=000%BUILD_NUMBER%
SET PADDED_BUILD_NUMBER=%PADDED_BUILD_NUMBER:~-4%

SET FILENAME=MYSELF_%PADDED_BUILD_NUMBER%.zip

굳이 설명하자면,

1번째 줄에서는 BUILD_NUMBER라는 값 앞에 "000"을 붙인다.
1이면 0001이든, 951이면 000951 같이 된다.

2번째 줄에서는 뒤에서 4글자만 뽑는다.
0001 이면 0001, 000951 이면 0951 식으로 4글자만 뺀다.

그리고 나머지 줄에서는 그렇게 만든 4글자짜리 BUILD_NUMBER를 이용해, 파일명을 만든 것이다.

핵심은....

:~-4

이다.

%{환경변수 이름}:~{+ 앞에서부터, - 뒤에서 부터}{추출할 갯수}%

 

728x90

Windows 10 부터 미디어 작성도구를 제공해서, USB에 자신의 설치용 Windows을 쉽게 만들수 있다.
다만, 이렇게 만들 때, 다양한 버전이 이 안에 들어가다 보니, 설치 중 Profesional 버전을 설치하고 싶을 때 골라서 설치가 안된다. 특히 설치 대상 PC가 OEM PC이고, 바이오스 상에 Windows 라이선스가 박혀 있는데, 그것도 Home Edition인 경우 매우 곤란했다. 쭉 설치하다보면 Home Edition으로 설치되고 끝. 게다가, 요즘 Windows 11 Home의 경우 Microsoft 계정이 없으면 설치도 안되기 때문에, Profesional로 설치하고 싶은데 매번 아무런 질문도 없이 진행되니...

그러나 구글로 찾아보니.. 나오긴 한다.

1. 설치용 USB를 만든다.
그냥 만든다. Microsoft 사이트에 들어가 Media Creation Tool을 받아서 USB 꼽고 만들어 달라고 하면 자동으로 USB에 설치 미디어를 만들어 준다. 이를 설치할 PC에 연결한 뒤 설치하면 잘 진행된다.
물론 여기에 조금 더 작업을 가미해야 한다.

2. ei.cfg 파일을 {설치용 USB 드라이브명}:\sources 폴더에 만들기.
USB가 만들어지면 드라이브가 잡히는데, 아마도 E:\나 F:\ 정도인데,
E:\sources 혹은 F:\Sources 폴더 안에 Text 파일(노트패드 등으로 작성)을 하나 만든다.
만들 때 파일명을 ei.cfg로 하고 만든다.

3. ei.cfg 안에 다음 내용을 넣는다.

[EditionID]
Professional

[Channel]
Volume 

[VL]
1

자세한 설명은 아래 링크를 통해 내용을 확인하도록 한다.
https://learn.microsoft.com/ko-kr/windows-hardware/manufacture/desktop/windows-setup-edition-configuration-and-product-id-files--eicfg-and-pidtxt?view=windows-11

 

Windows 설치 프로그램 버전 구성 및 제품 ID 파일(EI.cfg 및 PID.txt)

Windows 설치 프로그램 버전 구성 및 제품 ID 파일(EI.cfg 및 PID.txt) 아티클 05/30/2023 기여자 5명 피드백 이 문서의 내용 --> 선택적으로 버전 구성(EI.cfg) 및 제품 ID(PID.txt) 파일을 사용하여 Windows 설치 중

learn.microsoft.com

 

저장이 끝나면 이제 이 USB를 이용해 Windows 11을 설치하면 된다.

 

728x90

+ Recent posts

728x90