728x90
반응형

Obsidian Sync 없이 PC·Android 동기화하기

Syncthing + Ubuntu 서버로 개인 동기화 환경 구축

Obsidian의 장점 중 하나는 노트가 특정 서비스에 저장되는 것이 아니라 일반 Markdown 파일로 존재한다는 점이다.

덕분에 유료 Obsidian Sync를 사용하지 않아도 파일 동기화 도구를 이용해 여러 기기에서 같은 Vault를 사용할 수 있다.

이번에는 Syncthing을 이용해 다음 환경을 구성했다.

Windows 11 노트북
Android 스마트폰
Android 태블릿
Ubuntu 서버

특히 24시간 켜져 있는 Ubuntu 서버가 있다면 꽤 좋은 구성을 만들 수 있다.


1. 왜 Syncthing인가?

Syncthing은 여러 장치의 폴더를 직접 동기화하는 오픈소스 프로그램이다.

Dropbox나 Google Drive와 달리 반드시 특정 회사의 클라우드 서버에 파일을 올릴 필요가 없다.

구조도 중앙 서버 방식이 아니다.

PC ↔ 스마트폰
PC ↔ 서버
스마트폰 ↔ 서버

처럼 장치끼리 직접 연결된다.

그래서 집에서 PC와 스마트폰이 동시에 켜져 있다면 둘이 직접 동기화할 수도 있다.

여기에 24시간 켜져 있는 Ubuntu 서버를 추가하면 다음과 같은 장점이 생긴다.

PC OFF

스마트폰에서 노트 작성
        ↓
Ubuntu 서버에 저장

PC ON
        ↓
서버에서 변경사항 다운로드

즉 Ubuntu 서버가 사실상 항상 켜져 있는 동기화 허브 역할을 한다.


2. 최종 구성

내가 구성한 환경은 다음과 같다.

              Ubuntu Server
             /      |       \
            /       |        \
       Windows ── Phone ── Tablet

각 장치를 서로 등록했다.

기기 이름은 알아보기 쉽게 다음처럼 정했다.

DESKTOP
SERVER
PHONE
TABLET

Syncthing은 각 설치마다 고유한 Device ID를 생성한다.

Device ID 확인:

Actions → Show ID

다른 기기를 등록할 때는:

Add Remote Device

에서 상대방의 Device ID를 입력하면 된다.

Android에서는 QR 코드를 사용하면 훨씬 편하다.


3. Ubuntu 서버 설정

Ubuntu에 Syncthing을 설치한 뒤 systemd 서비스로 등록했다.

sudo systemctl enable syncthing@<username>.service
sudo systemctl start syncthing@<username>.service

확인:

systemctl status syncthing@<username>.service

이렇게 하면 서버를 재부팅해도 Syncthing이 자동 실행된다.

서버의 Obsidian 저장 경로는 예를 들어 다음처럼 만들 수 있다.

/home/<username>/sync/obsidian

4. 서버 관리 페이지는 외부에 열지 않았다

Syncthing 관리 페이지의 기본 주소는 다음과 같다.

127.0.0.1:8384

서버의 8384 포트를 인터넷에 직접 개방하는 대신 SSH 터널을 사용했다.

Windows에서:

ssh -L 8385:127.0.0.1:8384 <user>@<server-ip>

이후 브라우저에서:

http://localhost:8385

로 접속하면 Ubuntu 서버의 Syncthing 관리 페이지가 열린다.

관리 페이지를 인터넷에 추가로 노출하지 않아도 되기 때문에 개인 서버에서는 꽤 깔끔한 방법이다.

(8385으로 연결한 이유는 windows에서 돌아가는 syncthing 서버도 있기 때문에 충돌을 피하기 위해 이렇게 설정하였다)


5. Obsidian Vault 공유

기존 Windows Vault를 기준으로 폴더를 공유했다.

예를 들면:

Windows
C:\Users\<user>\Documents\Obsidian\Main

Ubuntu
/home/<user>/sync/obsidian

Android
Documents/Obsidian/Main

Tablet
Documents/Obsidian/Main

각 장치에서 Folder Type은:

Send & Receive

로 설정했다.

따라서 PC에서 작성해도 스마트폰으로 전달되고, 스마트폰에서 수정해도 PC로 돌아온다.

Android Obsidian에서는 동기화된 폴더를:

Open folder as vault

로 열면 된다.


6. 기기 추가 설정은 대부분 건드리지 않아도 된다

Add Remote Device 화면에는 꽤 많은 옵션이 있다.

예를 들면:

  • Introducer
  • Auto Accept
  • Pause
  • Untrusted Device
  • Custom Sync Conditions

개인 PC, 스마트폰, 태블릿, 개인 서버처럼 모두 내가 관리하는 기기라면 대부분 기본값으로 두면 된다.

내 설정은 다음과 같다.

Introducer: OFF
Auto Accept: OFF
Paused: OFF
Untrusted Device: OFF

공유할 Obsidian 폴더만 선택했다.


7. 폴더 옆 Password는 무엇인가?

처음 설정하면서 조금 헷갈렸던 부분이다.

폴더를 공유하면 Password 입력란이 나타날 수 있는데, 이것은 Syncthing 로그인 비밀번호가 아니다.

Untrusted Device 기능을 사용할 때 쓰는 암호다.

예를 들어 내가 관리하지 않는 VPS에 데이터를 보관하고 싶다면:

PC
↓ 암호화
VPS

처럼 서버에는 암호화된 데이터만 저장하도록 구성할 수 있다.

하지만 개인 Ubuntu 서버 역시 내가 직접 관리하고 있기 때문에 이 기능은 필요 없었다.

따라서 Password는 비워 두었다.


8. Windows에서도 자동 실행시키기 (선택)

필자는 해당 방법대로 하지 않고 SyncTrazor (Syncthing 윈도우 공식  작업표시줄 프로그램)을 설치했다. 이렇게 하면 윈도우 기본 시작 프로그램으로 등록되어 pc 재부팅 시에도 syncthing이 자동으로 실행된다.

Ubuntu 서버는 항상 켜져 있지만 Windows 노트북은 재부팅할 일이 많다.

Syncthing을 매번 직접 실행하기는 번거롭기 때문에 Windows 작업 스케줄러에 등록했다.

Win + R
→ taskschd.msc
→ 작업 만들기

트리거:

로그온할 때

실행 프로그램:

syncthing.exe

실행 인수:

--no-console --no-browser

이렇게 하면 Windows 로그인 시 콘솔창이나 브라우저를 띄우지 않고 Syncthing이 백그라운드에서 실행된다.

노트북에서는 작업 스케줄러의 다음 설정도 확인할 필요가 있다.

AC 전원일 때만 실행 → OFF
배터리 전환 시 중지 → OFF

그렇지 않으면 충전기를 뽑았을 때 동기화가 중지될 수 있다.


9. 스마트폰과 태블릿도 모두 연결

처음에는:

PC ↔ Server ↔ Phone

정도만 구성했지만 이후 Android 태블릿을 추가했다.

태블릿 역시 새로운 Syncthing peer 하나일 뿐이다.

가능하면 다음처럼 모든 장치를 서로 등록하는 편이 좋다.

Windows:
SERVER / PHONE / TABLET

Server:
DESKTOP / PHONE / TABLET

Phone:
DESKTOP / SERVER / TABLET

Tablet:
DESKTOP / SERVER / PHONE

굳이 서버를 통해서만 데이터를 전달하게 할 필요가 없다.

같은 Wi-Fi에 있는 장치끼리는 직접 동기화하고, 그렇지 않을 때 서버가 항상 온라인 저장소 역할을 하는 구조가 더 자연스럽다.


10. .obsidian도 동기화할까?

현재는 Vault 전체를 동기화하고 있다.

따라서:

.obsidian/

폴더도 같이 전달된다.

이 안에는 다음과 같은 설정이 있다.

  • 플러그인
  • 테마
  • 단축키
  • Appearance
  • Vault 설정

PC와 모바일의 Obsidian 환경을 비슷하게 유지할 수 있다는 장점이 있다.

다만 작업공간 상태처럼 기기별로 달라야 하는 설정은 충돌할 수 있다.

문제가 발생한다면 다음 파일 등은 Syncthing Ignore 설정으로 제외할 수 있다.

.obsidian/workspace.json
.obsidian/workspace-mobile.json

현재는 일단 전체 동기화 후 문제가 생길 때 조정하는 방식으로 사용하고 있다.


11. Syncthing은 백업이 아니다 (중요)

이 부분은 꽤 중요하다.

Syncthing은 파일을 복사해 두는 백업 프로그램이 아니라 상태를 동기화하는 프로그램이다.

따라서 스마트폰에서 파일을 실수로 삭제하면:

Phone 삭제
   ↓
Server 삭제
   ↓
PC 삭제
   ↓
Tablet 삭제

처럼 삭제 자체가 동기화된다.

그래서 Ubuntu 서버에서는 File Versioning을 활성화했다.

개인적으로는:

Staggered File Versioning

을 사용하기 좋다.

더 중요한 노트라면 Syncthing과 별도로 서버의 Vault를 restic이나 borg 등으로 백업하는 것이 좋다.


마무리

최종적으로 구성한 구조는 다음과 같다.

Windows PC
    ↕
Ubuntu Server (24시간)
    ↕
Android Phone
    ↕
Android Tablet

+ 가능한 장치끼리는 직접 연결

이 구성의 가장 큰 장점은 단순하다.

  • Obsidian Sync 구독이 필요 없다.
  • 특정 클라우드 서비스에 Vault를 맡길 필요가 없다.
  • Markdown 파일이 그대로 유지된다.
  • PC, 스마트폰, 태블릿을 자유롭게 사용할 수 있다.
  • 24시간 서버가 있으면 기기들이 동시에 켜져 있을 필요도 없다.
  • Obsidian 자체가 바뀌더라도 데이터는 평범한 파일로 남는다.

이미 개인 Ubuntu 서버를 운영하고 있다면 Syncthing을 하나 추가하는 것만으로 꽤 편리한 개인 Obsidian 동기화 환경을 만들 수 있었다.

728x90
반응형
LIST
728x90
반응형

새로운 프로젝트를 위해 멋진 도메인을 구매했는데, support@my-domain.com 같은 메일 주소로 답장까지 보낼 수 있다면 얼마나 프로페셔널해 보일까요?

최근 Cloudflare에서 도메인을 구매하고 Gmail로 메일을 연동하는 과정에서 겪은 시행착오와 해결 방안을 공유합니다. 특히 "메일은 받아지는데 왜 발신은 안 되지?" 혹은 "왜 내 메일이 스팸함에 가지?"라는 고민을 가진 초보 개발자분들에게 이 글이 완벽한 가이드가 될 것입니다.

 

이전 글: https://fclipse.tistory.com/entry/cloudflare-custom-domain-email

 

[tip] 구매한 custom domain으로 이메일 보내고 받는 법 - cloudflare

Cloudflare에서 구매한 도메인을 Gmail과 연동하여 무료로 메일을 주고받는 방법은 크게 두 단계로 나뉩니다. 받는 메일은 Cloudflare의 이메일 라우팅(Email Routing) 기능을 사용하고, 보내는 메일은 Gmail

fclipse.tistory.com

 


1. 시작하며: 왜 수신은 되고 발신은 안 될까?

Cloudflare의 Email Routing은 기본적으로 '포워딩(전달)' 서비스입니다. 내 도메인으로 오는 메일을 내 Gmail로 던져주는 역할만 하죠. 하지만 Gmail에서 내 도메인 이름표를 달고 메일을 발송하려면, 메일을 대신 배달해줄 SMTP 서버(우체국)가 필요합니다.

우리는 별도의 유료 서비스 없이, 우리가 이미 가진 Gmail의 SMTP 서버를 활용하는 영리한 방법을 사용할 것입니다.


2. Gmail을 발신 서버로 설정하기

Step 1: Google 앱 비밀번호 생성

Gmail은 보안상 일반 비밀번호로 외부 연결을 허용하지 않습니다.

  1. Google 계정 설정 > 보안 > 2단계 인증을 활성화하세요.
  2. 하단의 앱 비밀번호 메뉴에서 'Cloudflare SMTP' 등의 이름으로 16자리 비밀번호를 생성해 저장해둡니다.

Step 2: Gmail에 다른 주소 추가

  1. Gmail 설정 > 계정 및 가져오기 > 다른 주소에서 메일 보내기를 클릭합니다.
  2. 내 도메인 메일(예: support@example.com)을 입력합니다. 이때 '별칭으로 간주'는 체크 해제하는 것을 추천합니다. 그래야 개인 메일과 업무용 메일이 명확히 분리됩니다.
  3. SMTP 서버 정보 입력:
    • SMTP 서버: smtp.gmail.com
    • 사용자 이름: 내 Gmail 주소 전체
    • 비밀번호: 아까 만든 16자리 앱 비밀번호

3. "신뢰할 수 없는 발신자" 경고 해결하기 (DNS 설정)

설정을 마쳤는데 메일을 보내보니 "보낸 사람을 확인할 수 없다"는 경고가 뜨나요? 이건 수신 측 서버가 여러분의 메일을 '사칭 메일'로 의심하기 때문입니다. 이를 해결하기 위해 Cloudflare DNS에 세 가지 조치를 취해야 합니다.

1) SPF (누가 보낼 수 있는가?)

내 도메인을 대신해 메일을 보낼 수 있는 서버 목록을 등록합니다.

  • 기존: v=spf1 include:_spf.mx.cloudflare.net ~all
  • 수정: v=spf1 include:_spf.mx.cloudflare.net include:_spf.google.com ~all
    (Google 서버도 내 도메인을 쓸 수 있다고 허가해주는 것입니다.)

2) DKIM (메일이 변조되었는가?)

메일에 디지털 서명을 입히는 과정입니다. Cloudflare에서 제공하는 기본 DKIM 레코드를 유지하세요.

3) DMARC (인증 실패 시 어떻게 할 것인가?)

최근 보안 트렌드에서 가장 중요한 레코드입니다. 이 레코드가 없으면 스팸 처리될 확률이 높습니다.

  • 타입: TXT
  • 이름: _dmarc
  • 값: v=DMARC1; p=none;
    (p=none은 초기 설정 시 메일 유실을 방지하면서 모니터링하기 가장 안전한 값입니다.)

4. 초보 개발자를 위한 디버깅 팁: PowerShell 활용

설정이 잘 되었는지 브라우저에서만 확인하지 말고, 터미널을 열어 직접 질의해 보세요.

# SPF 레코드 확인
Resolve-DnsName -Name 내도메인 -Type TXT

# DMARC 레코드 확인
Resolve-DnsName -Name _dmarc.내도메인 -Type TXT

결과 값에 내가 입력한 include:_spf.google.com이나 v=DMARC1이 보인다면 설정 성공입니다!


5. 마치며: 인프라의 이해가 성장을 만듭니다

의의: 단순히 클릭 몇 번으로 끝낼 수도 있지만, SMTP, SPF, DKIM, DMARC라는 개념을 이해하는 과정은 네트워크와 보안 인프라를 이해하는 훌륭한 밑거름이 됩니다.

처음 설정을 마친 뒤 경고 문구가 바로 사라지지 않더라도 당황하지 마세요. DNS 전파와 수신 서버의 신뢰도 업데이트에는 시간(최대 24시간)이 필요합니다.


궁금한 점이나 설정 중 막히는 부분이 있다면 댓글로 남겨주세요!

#Cloudflare #Gmail연동 #커스텀도메인 #이메일보안 #SMTP #DMARC

728x90
반응형
LIST
728x90
반응형

1. 개요

  • 현상: pmpt.sh 도메인의 A 레코드를 변경했음에도 불구하고, 루트 도메인 접속 시 이전 사이트(daybreaker42.com)로 연결됨. 반면, 특정 하위 경로(pmpt.sh/prompt/...)는 정상적으로 새 사이트에 연결되는 불일치 발생.
  • 확인 결과: 브라우저의 개발자 도구 내 'Disable Cache' 옵션 활성화 시 정상 동작 확인.

2. 기술적 원인 분석 (Root Cause Analysis)

2.1. HTTP 리다이렉트 캐싱 (Browser-Level)

가장 핵심적인 원인은 브라우저가 이전 서버에서 수신했던 HTTP 301 (Moved Permanently) 응답을 로컬에 영구 캐싱했기 때문입니다.

  • 메커니즘: 브라우저는 301 응답을 받으면 해당 도메인과 목적지 URL의 매핑 정보를 Disk Cache에 저장합니다.
  • 동작 방식: 이후 동일 도메인(pmpt.sh) 접속 시, 네트워크 질의(DNS 조회 및 TCP 핸드셰이크)를 생략하고 즉시 캐싱된 타겟으로 리다이렉트합니다.
  • 경로별 차이: 루트 도메인은 자주 접속하여 캐시가 존재했으나, 특정 UUID가 포함된 하위 경로는 캐시된 매핑 데이터가 없어 서버에 직접 요청을 보냈고, 이 과정에서 업데이트된 DNS 정보를 참조하여 새 사이트로 연결되었습니다.

2.2. DNS TTL (Time To Live) 및 전파 지연

  • 원인: 이전 DNS 레코드의 TTL 설정이 길게 남은 경우, ISP(인터넷 서비스 제공자)의 Recursive DNS 서버가 여전히 예전 IP를 반환할 수 있습니다.
  • 영향: 브라우저 캐시가 비워져 있더라도 시스템 레벨의 DNS 캐시가 살아있다면 구형 IP로의 TCP 연결이 지속됩니다.

3. 아키텍처적 고려사항 및 대응 전략

3.1. 구현 레벨의 리다이렉트 최적화

프로덕션 환경에서는 영구적인 도메인 이전이 확정되기 전까지는 302 Found 또는 307 Temporary Redirect를 사용하는 것이 권장됩니다.

3.2. 인프라 설정 모범 사례

  • TTL 사전 관리: 레코드 변경 24시간 전에 TTL을 300s(5분) 이하로 낮추어 전파 속도를 확보합니다.
  • HSTS 주의: 만약 이전 서버에서 Strict-Transport-Security 헤더를 운용했다면, 브라우저는 무조건 HTTPS 접속을 시도하며 이 과정에서 이전 서버의 인증서 정보를 대조할 수 있어 도메인 이전 시 충돌 위험이 있습니다.

4. 결론 및 향후 조치

  • 현재 상태: 브라우저 캐시 무효화(Disable Cache)를 통해 로컬 환경의 문제가 해결되었음을 확인했습니다.
  • 일반 사용자 대응: 일반 사용자의 경우 수동으로 캐시를 비우기 어려우므로, 서버 측에서 Cache-Control: no-store 헤더를 명시하거나 쿼리 스트링(e.g., ?v=2)을 활용한 캐시 버스팅(Cache Busting)을 검토할 수 있습니다.
  • 모니터링: dig 또는 nslookup 도구를 사용하여 외부망에서의 DNS 전파 상태를 지속적으로 확인하는 것이 필요합니다.

참조: RFC 7231 (HTTP/1.1 Semantics and Content), RFC 1034 (Domain Names)

728x90
반응형
LIST
728x90
반응형
Cloudflare에서 구매한 도메인을 Gmail과 연동하여 무료로 메일을 주고받는 방법은 크게 두 단계로 나뉩니다. 받는 메일은 Cloudflare의 이메일 라우팅(Email Routing) 기능을 사용하고, 보내는 메일은 Gmail의 SMTP 서버와 앱 비밀번호를 이용해 설정합니다.

1단계: 메일 받기 설정 (Cloudflare Email Routing)

Cloudflare 대시보드에서 내 도메인으로 오는 메일을 내 기존 Gmail 계정으로 전달하는 설정입니다.
  1. Email Routing 활성화: Cloudflare 대시보드에서 해당 도메인을 선택한 후 왼쪽 메뉴의 Email > Email Routing으로 들어갑니다.
  2. 대상 주소 등록: Destination addresses 탭에서 메일을 실제 수신할 본인의 Gmail 주소를 입력하고 인증 메일을 확인합니다.
  3. 라우팅 규칙 생성: Routing rules에서 me@yourdomain.com과 같은 커스텀 주소를 만들고, 이를 방금 인증한 Gmail 주소로 전달(Forward to)하도록 설정합니다.
  4. DNS 레코드 자동 추가: 설정 과정 중 'Add records and enable' 버튼을 눌러 필요한 MX 및 TXT(SPF) 레코드를 자동으로 DNS에 등록합니다.

2단계: 메일 보내기 설정 (Gmail SMTP 연동)

Gmail 인터페이스에서 내 커스텀 도메인 주소를 발신인으로 설정하는 과정입니다.
  1. Google 앱 비밀번호 생성:
    • Google 계정 설정에서 2단계 인증이 활성화되어 있어야 합니다.
    • 보안 메뉴에서 앱 비밀번호(App Passwords)를 검색하여 '메일'용 비밀번호를 생성하고 생성된 16자리 코드를 복사해둡니다.
  2. Gmail에 계정 추가:
    • Gmail 설정(톱니바퀴) > 모든 설정 보기 > 계정 및 가져오기 탭으로 이동합니다.
    • 다른 주소에서 메일 보내기 섹션에서 다른 이메일 주소 추가를 클릭합니다.
  3. SMTP 서버 정보 입력:
    • 이메일 주소: Cloudflare에서 만든 커스텀 도메인 주소(예: me@yourdomain.com)를 입력합니다.
    • SMTP 서버: smtp.gmail.com.
    • 포트: 587 (TLS 사용).
    • 사용자 이름: 본인의 전체 Gmail 주소.
    • 비밀번호: 위에서 복사한 16자리 앱 비밀번호를 입력합니다.
  4. 인증: Gmail로 발송된 확인 코드를 입력하여 설정을 완료합니다.

3단계: SPF 레코드 수정 (스팸 방지)

메일이 스팸함으로 빠지지 않도록 Cloudflare DNS 설정에서 기존 SPF 레코드를 수정해야 합니다.
  • Cloudflare DNS 메뉴에서 기존 v=spf1 include:_spf.mx.cloudflare.net ~all 레코드를 찾아 아래와 같이 수정합니다.
    • 수정 후: v=spf1 include:_spf.google.com include:_spf.mx.cloudflare.net ~all
설정이 완료되면 Gmail에서 메일을 쓸 때 보내는 사람 목록에서 본인의 커스텀 도메인 주소를 선택할 수 있습니다.

 

 

 

다음 글: https://fclipse.tistory.com/entry/cloudflare-custom-domain-email-2

 

[tip] 구매한 custom domain으로 이메일 보내고 받는 법 2

새로운 프로젝트를 위해 멋진 도메인을 구매했는데, support@my-domain.com 같은 메일 주소로 답장까지 보낼 수 있다면 얼마나 프로페셔널해 보일까요?최근 Cloudflare에서 도메인을 구매하고 Gmail로 메

fclipse.tistory.com

 

728x90
반응형
LIST
728x90
반응형

개요

windows powershell에선 .sh 파일을 실행하지 못한다. 하지만 이를 실행할 수 있는 방법이 있다.

환경

windows 11
windows powershell 7.6.0

wsl --version
WSL 버전: 2.6.3.0
커널 버전: 6.6.87.2-1
WSLg 버전: 1.0.71
MSRDC 버전: 1.2.6353
Direct3D 버전: 1.611.1-81528511
DXCore 버전: 10.0.26100.1-240331-1435.ge-release
Windows 버전: 10.0.26200.8246

git --version
git version 2.51.0.windows.2

방법

  1. git 설치
  2. 다음 실행파일을 이용해 .sh 파일을 실행
    & "C:\Program Files\Git\bin\sh.exe" <실행파일 경로>
728x90
반응형
LIST
728x90
반응형


최근 Playwright를 이용해 자동화 봇을 만들고 있었습니다. 로컬이나 실서버 Ubuntu의 .venv 환경에서는 제대로 동작하는 봇이, 유독 도커(Docker) 컨테이너 안으로만 들어가면 로그인 단계에서 Timeout 에러를 뱉으며 멈춰버리는 현상을 겪었습니다.

결론부터 말하자면, 이 문제는 스크립트의 논리 오류가 아니라 도커 환경의 리소스 제한과 브라우저 렌더링 방식의 차이 때문이었습니다. 저와 같은 삽질을 반복하지 않으시길 바라며 해결 과정을 정리합니다.

1. 증상: "로컬에선 되는데 왜 도커만 이래?"

로컬에서 실행할 때(특히 창 모드)는 아무 문제 없던 코드가 도커 내 Headless 환경에서는 다음과 같은 에러를 발생시켰습니다.

Error in automation: Page.click: Timeout 30000ms exceeded.
... <div role="dialog" ... intercepts pointer events

로그를 뜯어보니, 로그인 버튼을 누르려는데 정해진 시간 내에 버튼이 나타나지 않거나, 투명한 다이얼로그(팝업) 레이어가 버튼 위를 덮고 있어 클릭 이벤트가 전달되지 않는 상태였습니다.

2. 원인 분석

핵심 원인 1: 공유 메모리(/dev/shm)의 부족

도커 컨테이너는 기본적으로 공유 메모리 공간을 64MB로 극단적으로 제한합니다. 하지만 Chromium 브라우저는 복잡한 페이지를 렌더링할 때 이보다 훨씬 많은 메모리를 사용합니다. 메모리가 부족하면 화면 렌더링이 꼬이거나, 닫혔어야 할 팝업이 그대로 남아 버튼을 가리게 됩니다.

핵심 원인 2: 샌드박스 보안 충돌

도커라는 격리 공간 안에서 브라우저가 또 자체 샌드박스를 만들려다 보니 렌더링 스레드가 비정상적으로 작동하는 경우가 많습니다.

핵심 원인 3: Headless 환경의 UI 변형

환경 차이로 인해 평소엔 안 뜨던 보안 안내 팝업이나 공지사항이 도커 환경에서만 튀어나올 수 있습니다. 이때 Playwright의 일반적인 click()은 가려진 요소를 누르지 못하고 타임아웃에 빠집니다.

3. 해결 방안

Step 1. docker-compose 설정 (메모리 해방)

도커 컨테이너가 브라우저 렌더링을 위해 충분한 공유 메모리를 쓸 수 있도록 shm_size를 늘려줍니다.

# docker-compose.yml
services:
  library-bot:
    build: .
    shm_size: '2gb'  # 64MB의 족쇄를 풀어줍니다.

Step 2. 브라우저 실행 옵션(Launch Args) 추가

도커 환경에 최적화된 실행 옵션을 추가합니다. --no-sandbox와 --disable-dev-shm-usage를 많이 사용합니다.

# server.py
self.browser = await self.playwright.chromium.launch(
    headless=True,
    args=[
        "--no-sandbox",             # 샌드박스 비활성화
        "--disable-dev-shm-usage"  # /dev/shm 대신 /tmp 사용
    ]
)

Step 3. 강제 클릭 옵션 (force=True) 적용

팝업이 버튼을 가리고 있더라도, 요소를 찾았다면 무조건 이벤트를 발생시키도록 force=True 옵션을 부여합니다.

# 일반 클릭 대신 강제 클릭 사용
await page.click("button", force=True)
await page.click('button', force=True)

4. 결과: "Success!"

이렇게 설정을 바꾸고 나니, 기존 문제들이 모두 해결되었고, 정상적으로 동작하는 걸 확인할 수 있었습니다. 이번 일로 인해 잘 모르는 환경에선 먼저 어떻게 진행해야 하는지에 대한 기초 지식이 정말 중요하다고 느꼈습니다.


요약

  1. 도커의 shm_size를 늘리자.
  2. --no-sandbox 옵션을 잊지 말자.
  3. 방해물이 있다면 force=True 사용.

참고자료

Selenium 자동화를 위한 Chrome 브라우저 실행 옵션 총정리

도움이 되셨다면 공감 부탁드립니다! 궁금한 점은 댓글로 남겨주세요. :)

728x90
반응형
LIST
728x90
반응형

폴더 정리 방법

1. 5레벨 이하로 파일 구조 설정
2. 폴더 / 파일 앞에 두 자리 숫자로 넘버링
- 섞일 일 없고
- 작업중인 폴더 맨 위로 보내기도 쉬움
- 예외 존재
- 01~99까지

- 99는 아카이브 폴더 - 프로젝트 끝났거나 사용할 일 없다면 99에 넣어주기
작업중인 폴더 많을때 - 눈에 잘 안들어옴 - 아카이브 만들기 (필요할 때만)

3. 개인 ssd 구조
1) 개인 - 개인적 파일들 - 사진, 영상, 공부 자료 등
2) 업무 - 비즈니스, 유튜브, 영상 / 외주 작업 폴더
3) 템플릿 - 효과음, 음성, 폰트 등 모아둔 폴더 / 사무직이라면 필요 없을수도? - 구글 드라이브로 구조 일치

4. quick share 폴더 - 작업중인 폴더 공유 쉽게 - 구글 드라이브에서 사용, 공유중인거 잊지 않을 수 있음

5. 규칙 위배 - 1) 갤러리엔 연도별

큰 틀을 따르되, 상황에 따라 유동적으로 관리

사진 / 영상 등 - 시간순으로 정리하는게 더 편함
외주도 분기별로 네이밍하는게 나음

백개 이상 넘어가는 경우 3자리로

별로 중요 x - 규칙 x

6. 네이밍 규칙 정하기
1) 이름에 날짜 포함
- 연도
- 연도_분기
- 연도_월
- 연도_월_날짜
-> 파일 유형에 따라 나뉨
2) 가나다 순으로

과한 최적화는 효율을 저하 - 한 가지 시스템을 정해 규칙을 고수하기

바로가기 등 활용

참고자료
떼끄 - https://youtu.be/pyj49VW6BFg?si=Fg64grkMrtKxo_dy
jeff su - https://youtu.be/MM-MPS57qKA?si=XDMZbqJ3MFJecIQE

728x90
반응형
LIST
728x90
반응형

안녕하세요! 오늘은 윈도우와 리눅스(Xubuntu 등)를 함께 사용하는 분들이 가장 흔히 겪는 불편함 중 하나인 "부팅 시마다 나타나는 BitLocker(장치 암호화) 복구 키 입력 창" 문제를 해결하는 방법을 정리해 보겠습니다.

1. 왜 자꾸 복구 키를 입력하라고 하나요?

윈도우의 장치 암호화(BitLocker) 기능은 보안 칩(TPM)을 통해 시스템의 안전을 확인합니다. 리눅스 설치를 위해 BIOS에서 Secure Boot(보안 부팅)를 끄거나 부팅 순서를 바꾸면, TPM은 "시스템이 변조되었다"고 판단하여 데이터 보호를 위해 복구 키를 요구하게 됩니다.


2. 보안적으로 암호화를 해제해도 괜찮을까?

결론부터 말씀드리면, 사용 환경에 따라 다릅니다.

  • 해제해도 괜찮은 경우: 노트북을 집에서만 사용하거나, 분실 위험이 낮은 데스크탑인 경우.
  • 유지해야 하는 경우: 카페, 학교 등 외부 이동이 잦은 노트북. (분실 시 SSD를 떼어가도 데이터를 볼 수 없게 보호해 줍니다.)

불편함을 없애는 방법은 두 가지입니다. 보안을 유지하며 해결하거나, 아예 기능을 끄는 것입니다.


3. 해결 방법 A: 보안 부팅(Secure Boot) 켜고 암호화 유지하기 (추천)

보안과 편의성을 모두 잡는 방법입니다. 최신 리눅스(Xubuntu 25.10 등)는 보안 부팅을 지원합니다.

  1. 리눅스에서 MOK 등록 준비:
  • 터미널에서 다음 명령어 입력 후 일회용 비밀번호 설정.
sudo update-secureboot-policy --enroll-key
  1. BIOS 설정:
  • 재부팅 시 BIOS에 진입하여 Secure Boot를 [Enabled]로 변경.
  1. MOK 등록(파란 화면):
  • 리눅스로 부팅될 때 파란 화면(Perform MOK management)이 뜨면 Enroll MOK -> Continue -> Yes를 선택하고 아까 정한 비밀번호 입력.
  1. 윈도우에서 암호화 갱신:
  • 윈도우로 부팅(마지막 키 입력) 후, [설정] > [장치 암호화]에서 기능을 잠시 [끄기] 했다가 다시 [켜기]를 합니다.
  • 이제 시스템이 "보안 부팅이 켜진 상태"를 정상으로 기억하여 더 이상 키를 묻지 않습니다.

4. 해결 방법 B: 장치 암호화 기능 완전히 끄기

설정이 복잡하고 보안보다는 편의성이 중요하다면 암호화 자체를 해제하면 됩니다.

  1. 윈도우 [설정] > [개인 정보 및 보안] > [장치 암호화]로 이동합니다.
  2. [장치 암호화] 스위치를 [끄기]로 바꿉니다.
  3. 복호화 완료까지 대기: 드라이브 용량에 따라 시간이 걸릴 수 있습니다. 완료되면 자물쇠 아이콘이 사라지며, 어떤 환경에서 부팅해도 더 이상 키를 묻지 않습니다.

마치며

리눅스와 윈도우를 오가는 환경에서는 보안 부팅(Secure Boot) 설정이 BitLocker와 긴밀하게 연결되어 있습니다. 가장 권장하는 방법은 방법 A를 통해 보안을 유지하는 것이지만, 개인용 PC이고 매번 뜨는 파란 창이 너무 번거롭다면 방법 B로 해제하는 것도 합리적인 선택입니다.

궁금한 점이 있다면 댓글로 남겨주세요!

728x90
반응형
LIST
728x90
반응형

개요

그럼 이제 본격적으로 개발을 해봐야겠죠.

vscode, android studio, git, curl, flutter 등등 플러터 개발에 필요한 필수 프로그램을 설치하고 개발에 들어갔습니다.
그런데, 일단 문제가 여럿 생겼습니다.

문제

  1. vscode에서 keytools을 인식 못한다는 에러가 자꾸 뜸 - 일부 extension github 로그인이 유지가 안되는 문제
  2. android studio 설치시 공식 홈페이지에 있는 라이브러리가 설치가 안되는 문제
  3. android studio에서 생성한 에뮬레이터가 vscode에서 인식되지 않는 문제
  4. gradle language server initialize가 끝나지 않는 문제
  5. android studio 에뮬레이터가 불안정한 문제 - 2에서 설치를 제대로 하지 못한게 문제인지, 아니면 리눅스 환경에서 돌리는게 불안정해서인지를 몰라도, 에뮬레이터에서 앱을 돌리려 하면 제대로 돌아가지 않는다는 문제가 있었습니다.
  6. bitlocker문제 - 리눅스 설치를 하던 중, safe booting을 꺼야 하는 일이 있었는데 이때 기존 윈도우 디스크에 bitlocker가 설정되어 있어서 윈도우 부팅시마다 키를 입력해줘야 했습니다.

하지만, 이 모든 문제를 겪고도 '속도가 빠르다' 와 '램 사용량이 적다'는 확실한 장점이 있었기에, 리눅스를 선택했습니다.

해결

무엇보다 위 5가지의 문제는 다음 방법으로 해결할 수 있었습니다.

  1. vscode에서 keytools을 인식 못한다는 에러 -> 무시한다
  2. android studio 설치시 공식 홈페이지에 있는 라이브러리가 설치가 안되는 문제 -> 이건 gemini한테 물어봐서 상위 버전 library을 설치함으로서 얼추 해결했습니다. (설치 안해도 돌아가긴 합니다)
  3. android studio에서 생성한 에뮬레이터가 vscode에서 인식되지 않는 문제 -> android studio 켜서 에뮬레이터를 키거나, 5.의 해결책 사용
  4. gradle language server initialize가 끝나지 않는 문제 -> 무시한다
  5. android studio 에뮬레이터가 불안정한 문제 -> android studio에서 wifi 디버깅 기능을 사용해서 다른 기기에서 앱을 돌리니 잘 돌아감.
  6. bitlocker문제 - xubuntu도 safe booting을 지원해서 설치가 완료된 이후, 관련 설정을 마무리했습니다. 다시 설정을 키고, 리눅스에서 mok 서명을 확인한 뒤, 윈도우에 들어가 bitlocker 설정을 껐다가 키면 완료됩니다.

다른 문제들은 개발에 있어 직접적인 문제는 없었지만, 5번 문제가 앱 개발에 있어 상당히 치명적이었습니다. 하지만 다행히 android studio에서 같은 wifi 내 안드로이드 기기를 무선으로 연결해 디버깅할 수 있게 하는 기능을 지원해서 해당 방법으로 다른 기기에서 앱을 돌리니 잘 돌아가는 모습을 볼 수 있었습니다.

추가 적용한 점들

Xubuntu(Xfce 데스크탑 환경)에서 해상도를 기본값으로 맞췄는데 아이콘이나 글씨가 너무 작게 보이는 문제

1. 폰트 DPI 조정을 통한 크기 조절 (가장 효과적)

배율을 1.0(기본값)으로 되돌린 후, DPI 값을 높여서 전체적인 UI 요소와 텍스트 크기를 키우는 방법입니다.

  1. 설정 관리자 > 모양새(Appearance) > 폰트(Fonts) 탭으로 이동합니다.
  2. Custom DPI setting 체크박스를 활성화합니다.
  3. 기본값인 96을 144 로 설정.
  • 숫자가 커질수록 글자와 창의 크기가 선명하게 커집니다.

2. 창 관리자(Window Manager) 테마 변경 - 적용 x

DPI를 높여도 창의 제목 표시줄이나 버튼이 작다면 'HiDPI'용 테마를 선택해야 합니다.

  1. 설정 관리자 > 창 관리자(Window Manager)로 이동합니다.
  2. 스타일 탭에서 이름 뒤에 -hdpi가 붙은 테마(예: Default-hdpi)를 선택합니다.
  3. 이렇게 하면 창 테두리와 최소화/최대화 버튼이 고해상도에 맞게 커집니다.

3. 아이콘 크기 및 테마 수정 - 패널 높이 수정

아이콘이 여전히 작다면 개별적으로 크기를 지정할 수 있습니다.

  • 패널(작업표시줄) 아이콘: 패널 우클릭 > 패널 기본 설정 > 모습 탭에서 '아이콘 크기'를 수동으로 조절하거나 '행 크기'를 키웁니다.
  • 바탕화면 아이콘: 바탕화면 우클릭 > 바탕화면 설정 > 아이콘 탭에서 '아이콘 크기' 숫자를 직접 키웁니다.

4. 브라우저 및 개별 앱 배율 (보너스) - 필요 x

시스템 전체 배율을 1.0으로 뒀기 때문에 웹 브라우저가 작게 보일 수 있습니다.

  • Firefox/Chrome: 설정에서 '기본 페이지 확대/축소'를 125% 또는 150%로 설정하면 화질 저하 없이 깨끗하게 볼 수 있습니다.

클립보드 보이게 하는 기능

[추가 설정] CopyQ 클립보드 매니저 단축키 지정 및 자동 실행

Xubuntu(XFCE) 환경에서 효율적인 클립보드 관리를 위해 CopyQ를 설치한 후, 단축키로 목록을 즉시 호출하고 시스템 시작 시 자동 실행되도록 설정하는 방법입니다.

1. 클립보드 호출 단축키 설정

윈도우의 Win + V처럼 특정 조합(예: Super + V)으로 클립보드 히스토리를 즉시 띄울 수 있습니다.

  • 설정 경로: 설정(Settings) → 키보드(Keyboard) → 응용 프로그램 바로가기
  • 명령어 추가: + 추가 버튼 클릭 후 copyq show 입력
  • 단축키 지정: 원하는 키 조합(예: Super + V) 입력 시 즉시 등록

2. 시스템 시작 시 자동 실행 설정

매번 수동으로 실행할 필요 없이 부팅과 동시에 CopyQ가 활성화되도록 설정합니다.

  • 설정 경로: 설정(Settings) → 세션 및 시작(Session and Startup)

  • 항목 추가: 응용 프로그램 자동 시작 탭에서 + 추가 클릭

  • 내용 입력: * 이름: CopyQ

  • 명령어: copyq

  • 확인: 리스트에 체크되어 있는지 확인 후 저장

zsh 및 oh my zsh 설정

참고 - https://poki.tistory.com/entry/LinuxTerminal-Linux%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-Zsh-Oh-My-Zsh-%EC%84%A4%EC%B9%98-%EA%B0%80%EC%9D%B4%EB%93%9C

총평

이번에 xubuntu을 디스크에 설치하고, 개발을 해보면서 느낀 점은 다음과 같습니다.

  1. 속도와 사용성은 반비례할수밖에 없다. 이걸 해결하려면 돈을 쓰면 된다. (애플)
  2. 윈도우가 사용성은 정말 좋은 편이다. 애플보다 밀린다고 해서 무시할게 아니다. 익숙함이 참 무섭다.
  3. 리눅스는 역시 빠르다. 또한 snap 명령어로 프로그램을 설치하면서 명령어 한번으로 설치되는게 편리하다고 느꼈습니다.
  4. 리눅스에서 설정이 꼬이거나 오류가 나면 문제가 커진다 - 사용자가 적기 때문에 아직 해결되지 않았거나 인터넷에 찾아봐도 해결책이 없을 수 있음. 하지만 AI의 발달로 관련 정보를 찾기 매우 수월해져서 저와 같은 초보 개발자들도 설정을 하기 쉬워진건 있습니다.

또한 이후 다른 개발을 할 때 리눅스가 필요한 경우가 있다면 해당 환경을 사용할 것 같습니다. wsl이 있어 이를 우선적으로 사용은 하겠지만, 역시 빠른게 좋은 것 같습니다.

728x90
반응형
LIST
728x90
반응형

개요


Google Play store에 app을 올리기 위해선 key로 서명을 해야 한다.
평소에는 android studio을 이용해 generate signed appbundle 기능으로 android studio에서 signed app bundle을 만들었다.

그런데 flutter에서 .env파일 사용 시 dotenv을 사용하면 apk에서 .env 값을 볼 수 있다는 글을 보았다.
따라서 flutter_dotenv패키지를 전면 교체 후 build시에 커멘드라인에서 주입하는 방식으로 교체했다.

--dart-define-fron-file=.env

이걸 build 명령시 인자에 추가해주면 더이상 .env을 asset으로 포함시키지 않아도 된다.

명령어는 다음과 같다.

# run flutter
flutter run --dart-define-from-file=.env

# run without debug
flutter run --release --dart-define-from-file=.env

# build appbundle
flutter build appbundle --release --dart-define-from-file=.env

vscode launch.json 설정

또한 vscode 단축키를 눌러 (ctrl + shift + f5) 빌드를 자주 하는데, 이때도 추가로 인자를 넣어줘야 한다.
다음과 같이 설정해서 .env 인자를 추가할 수 있다.

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Flutter",
      "request": "launch",
      "type": "dart",
      "args": [
        "--dart-define-from-file=.env"
      ]
    },
    {
      "name": "Flutter (Run Without Debugging)",  // 새 구성 추가
      "request": "launch",
      "type": "dart",
      "noDebug": true,  // 디버그 모드 비활성화
      "args": [
        "--dart-define-from-file=.env"
      ]
    }
  ]
}

참고 자료

Flutter에서 환경변수 설정하기

Flutter 앱 배포 가이드: 환경변수와 함께 Android & iOS 스토어에 배포하기

문제 상황

1. flutter build appbundle --release --dart-define-from-file=.env으로 빌드시 google play에 appbundle을 업로드하면 서명이 되지 않았다고 뜬다.
사진에서도 '업로드된 모든 번들에 서명해야 합니다' 라고 에러가 떠있는걸 볼 수 있다.

app/build.gradle.kts 파일도 잘 설정을 해줬었는데 이때는 뭐가 문제였는지 몰랐다.

 

2. 또한 기존에는 android studio로 appbundle을 빌드했는데, 이제는 --dart--define-from-file 커맨드가 적용되지 않아 .env파일 주입을 할 수 없었다. (빌드는 되지만, appbundle 업로드 후 test을 받으면 .env파일이 빠져있다.)

 

3. 또한 flutter 공식 홈페이지에서도 android studio을 이용하는 방법 말고는 제대로 appbundle 서명 및 빌드 방법을 알려주지 않는다.

Build and release an Android app

 

Build and release an Android app

How to prepare for and release an Android app to the Play store.

docs.flutter.dev

 

앱 서명  |  Android Studio  |  Android Developers

 

앱 서명  |  Android Studio  |  Android Developers

앱 서명 및 보안과 관련된 중요한 개념을 알아보고, Android 스튜디오를 사용하여 Google Play에 출시하기 위해 앱에 서명하는 방법과 Play 앱 서명을 선택하는 방법을 알아보세요.

developer.android.com

 

해결 전 build.gradle.kts (일부)

...
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
...

android {
    ...

    signingConfigs {
        create("release") {
            if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
                keyAlias = keystoreProperties["keyAlias"] as String
                keyPassword = keystoreProperties["keyPassword"] as String
                storeFile = file(keystoreProperties["storeFile"] as String)
                storePassword = keystoreProperties["storePassword"] as String
            }
        }
    }

    buildTypes {
        release{
            if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
                signingConfig = signingConfigs.getByName("release")
            }
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
...

원인

 


build.gradle.kts에서 key.properties 파일 경로를 찾을 때, file() 메서드를 사용하는데 이게 android/ 폴더가 기본이라고 한다.
따라서 key.properties 파일을 이제까지 찾지 못하고 있던 거였고, 따라서 서명이 되지 않고 있는 거였다.


서명이 제대로 되는 로그는 이렇게 나온다. 로그는 println문을 따로 추가했다.


또한 key 파일이 들어가지 않고 있던걸 다음 로그를 통해 알 수 있었다. key.properties 파일을 불러오는 if문과 else에 각각 성공/실패시 출력을 추가했더니 해당 파일이 들어가지 않고 있단걸 확실히 알 수 있었다.

해결 방법

val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("app/key.properties")
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
    println("key.properties file found and loaded successfully.")
}else{
    println("key.properties file not found. Using default signing configuration.")
}

다음과 같이 build.gradle.kts에서 key.properties 파일 경로를 수정해줬다. key파일 출력문도 추가해준 것을 볼 수 있다.

key.properties 파일을 불러오는 if문과 else문을 추가해준것도 해당 부분이다.


appbundle이 빌드될 때 key 파일이 잘 들어가고, 문제없이 빌드되는걸 볼 수 있다.

참고로 key파일 password가 틀렸을 땐 다음과 같이 비밀번호가 틀렸다고 로그가 나온다.

해결 후 build.gradke.kts 파일

import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.util.Properties
import java.io.FileInputStream

plugins {
    id("com.android.application")
    // START: FlutterFire Configuration
    id("com.google.gms.google-services")
    // END: FlutterFire Configuration
    id("kotlin-android")
    // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
    id("dev.flutter.flutter-gradle-plugin")
}

val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("app/key.properties")
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
    println("key.properties file found and loaded successfully.")
}else{
    println("key.properties file not found. Using default signing configuration.")
}

android {
    namespace = "app_name"
    compileSdk = 36
    ndkVersion = "27.3.13750724"

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    defaultConfig {
        // AndroidManifest.xml에서 ${applicationName} 플레이스홀더를 대체
        manifestPlaceholders += mapOf("applicationName" to "io.flutter.app.FlutterApplication")
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId = "com.yeoseyo.byeolme"
        minSdk = 24
        targetSdk = 36
        // You can update the following values to match your application needs.
        // For more information, see: https://flutter.dev/to/review-gradle-config.
        versionCode = flutter.versionCode
        versionName = flutter.versionName
    }

    signingConfigs {
        create("release") {
            if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
                keyAlias = keystoreProperties["keyAlias"] as String
                keyPassword = keystoreProperties["keyPassword"] as String
                storeFile = file(keystoreProperties["storeFile"] as String)
                storePassword = keystoreProperties["storePassword"] as String
            }else{
                println("WARNING: key.properties file not found or storeFile not set")
                logger.warn("key.properties file not found or storeFile not set")
            }
        }
    }

    buildTypes {
        release{
            if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
                signingConfig = signingConfigs.getByName("release")
            }
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }


//    buildTypes {
//        release {
//            // TODO: Add your own signing config for the release build.
//            // Signing with the debug keys for now, so `flutter run --release` works.
//            signingConfig = signingConfigs.getByName("debug")
//        }
//    }
}

kotlin {
    compilerOptions {
        jvmTarget = JvmTarget.fromTarget("17")
    }
}

flutter {
    source = "../.."
}

//dependencies {
//    // Firebase BoM을 통한 버전 관리 - 모든 Firebase 라이브러리의 호환성을 보장
//    implementation(platform("com.google.firebase:firebase-bom:34.1.0"))
//
//    // Firebase Analytics - 앱 사용 통계 및 분석
//    implementation("com.google.firebase:firebase-analytics")
//
//    // Firebase Firestore - 실시간 NoSQL 데이터베이스
//    implementation("com.google.firebase:firebase-firestore")
//
//    // 추가 Firebase 서비스는 필요시 아래에 추가
//    // Firebase Auth: implementation("com.google.firebase:firebase-auth")
//    // Firebase Storage: implementation("com.google.firebase:firebase-storage")
//    // Firebase Messaging: implementation("com.google.firebase:firebase-messaging")
//}

 

결과

이렇게 해서 문제를 해결할 수 있었다.

느낀 점

  1. 해당 문제를 찾으면서 영어로 자료를 찾아도 잘 나오지 않았었다. ai의 발전으로 stackoverflow 및 기타 커뮤니티에 글이 올라오지 않게 된 부작용이다.
  2. 다만 ai을 이용해서 문제의 원인을 추론하고, 해결할 수 있었다. gemini가 file()이 android/가 기본 폴더라는걸 알려주지 않았더라면 많이 어려웠을 것이다.
  3. 나중에 발견했는데 flutter 공식 홈페이지에선 key.properties 파일을 android 경로에 올리라고 나와 있었다... 항상 공식 문서를 잘 읽어보자.

4. build appbundle

728x90
반응형
LIST

+ Recent posts