새 서버에 배포 스크립트를 실행했는데 sudo apt install nginx 가 안 됩니다. 알고 보니 서버가 Rocky Linux라 apt 가 없습니다. 당황해서 인터넷에서 찾은 명령어를 복붙했더니 이번엔 GPG 에러가 뜨면서 막힙니다. 폐쇄망 서버에서는 dnf install 자체가 안 됩니다.
패키지 매니저를 제대로 이해하면 어떤 배포판 환경에서도 원하는 소프트웨어를 설치하고, 의존성 충돌을 해결하고, 폐쇄망에서도 운영할 수 있습니다.
패키지 관리 (apt / yum / dnf)
— — —
cat /etc/os-release | grep -E 'ID=|ID_LIKE='
sudo apt update
sudo dnf check-update
sudo apt install <패키지명>
sudo dnf install <패키지명>
sudo rpm -ivh <파일명>.rpm
팀에 합류해서 서버에 처음 접속했을 때, apt가 안 먹히는 경우가 있습니다. 회사에서 Ubuntu만 쓴다고 했는데 새 서버는 Rocky Linux였거나, 클라우드 환경이 AWS Amazon Linux 2였거나. 이때 yum이나 dnf를 써야 하는데, 명령어 이름만 다른 게 아니라 패키지 파일 형식도 다르고 리포지토리 설정 위치도 다릅니다. 두 계열의 차이를 모르면 인터넷에서 찾은 설치 명령어가 왜 안 되는지조차 파악하기 어렵습니다. 어떤 환경에 접속하든 당황하지 않으려면 두 생태계의 기본 구조를 한 번은 정리해둬야 합니다.
| 항목 | Debian 계열 | RHEL 계열 |
| 항목 | Debian 계열 | RHEL 계열 |
|---|---|---|
| 대표 배포판 | Ubuntu, Debian, Linux Mint | RHEL, CentOS Stream, Rocky Linux, AlmaLinux, Fedora |
| 패키지 형식 | .deb |
.rpm |
| 저수준 도구 | dpkg |
rpm |
| 고수준 도구 | apt (구: apt-get) |
dnf (구: yum) |
| 리포지토리 설정 위치 | /etc/apt/sources.list, /etc/apt/sources.list.d/ |
/etc/yum.repos.d/ |
| 캐시 위치 | /var/cache/apt/archives/ |
/var/cache/dnf/ |
| 잠금 파일 | /var/lib/dpkg/lock-frontend |
/var/cache/dnf/metadata_lock.pid |
apt는 언제? Ubuntu Server, Debian이 기반인 환경 — 클라우드에서 AWS Ubuntu AMI, GCP Debian 이미지, 개인 개발 환경 등.
yum/dnf는 언제? 엔터프라이즈 환경의 대다수 — RHEL 구독 기반 서버, CentOS Stream, Rocky Linux, AWS AL2(Amazon Linux 2, yum), AL2023(Amazon Linux 2023, dnf).
yum vs dnf: RHEL 8/CentOS 8 이후
dnf가yum을 대체했습니다.yum명령어 자체는dnf에 대한 심볼릭 링크로 동작하므로 기존 스크립트가 호환됩니다. 이 챕터에서는dnf를 기본으로 설명하되yum동작과 차이점을 병기합니다.
인터넷이 없는 폐쇄망 서버에서 .deb 파일 하나를 dpkg -i로 설치했더니 "dependency problems" 오류가 뜨면서 설치가 안 됩니다. 파일 자체는 멀쩡한데 이 패키지가 내부적으로 다른 라이브러리를 필요로 하고, 그 라이브러리가 이 서버에 없기 때문입니다. 반대로 apt remove로 패키지 하나를 지웠는데 그걸 의존하던 다른 패키지가 같이 깨지기도 합니다. apt install이 간단해 보여도 내부에서는 수십 개의 패키지 간 의존 관계를 추적하고 자동으로 해결하고 있습니다. 오프라인 환경이나 의존성 충돌이 발생했을 때 당황하지 않으려면 이 구조를 이해해야 합니다.
패키지 A가 정상 동작하려면 패키지 B의 특정 버전 이상이 필요한 경우, B를 A의 **의존성(Dependency)**이라고 합니다. 패키지 매니저는 이 관계를 자동으로 파악해 함께 설치합니다.
textnginx 설치 요청 └─ libc6 (>= 2.17) 필요 └─ libpcre3 필요 └─ libc6 (>= 2.4) 필요 ← 이미 충족됨 └─ libssl3 필요 └─ libcrypto3 필요
의존성은 필수 여부에 따라 여러 유형으로 나뉩니다.
| 유형 | 설명 | apt 표현 | dnf 표현 |
|---|---|---|---|
| Depends / Requires | 반드시 필요 | Depends: |
Requires: |
| Recommends / Weak | 권장하지만 없어도 동작 | Recommends: |
Recommends: |
| Suggests / Enhances | 선택적 기능 강화 | Suggests: |
Enhances: |
| Conflicts | 동시 설치 불가 | Conflicts: |
Conflicts: |
| Replaces | 다른 패키지 파일 대체 | Replaces: |
Obsoletes: |
왜 의존성을 이해해야 하는가?
- 수동
.deb/.rpm설치 시 의존성이 자동 해결되지 않아 실패합니다. - 오프라인 환경에서 의존성 패키지를 누락하면 설치가 불완전해집니다.
- 서로 다른 패키지가 같은 라이브러리의 호환되지 않는 버전을 요구할 때 충돌이 발생합니다.
— — —
2. 기본 패키지 작업 — 설치, 업데이트, 삭제, 검색
실습 전 디렉토리와 예제 파일을 먼저 준비합니다.
bash# 실습 디렉토리 준비 mkdir -p /tmp/linux/part2/exam_1 && cd /tmp/linux/part2/exam_1 # 패키지 정보 기록용 파일 생성 echo "# 패키지 관리 실습 기록" > /tmp/linux/part2/exam_1/pkg_notes.txt echo "날짜: $(date)" >> /tmp/linux/part2/exam_1/pkg_notes.txt
이제 실습을 진행합니다.
설치부터 삭제, 캐시 정리, 검색까지 실무에서 가장 자주 쓰는 apt 명령어 흐름입니다.
bash# 1. 패키지 목록 새로고침 (설치 전 항상 먼저 실행) sudo apt update # 출력 예시: # Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease # Get:2 http://archive.ubuntu.com/ubuntu jammy-updates InRelease [119 kB] # Get:3 http://security.ubuntu.com/ubuntu jammy-security InRelease [110 kB] # Fetched 229 kB in 1s (185 kB/s) # Reading package lists... Done # Building dependency tree... Done # Reading state information... Done # 12 packages can be upgraded. Run 'apt list --upgradable' to see them. # 2. 패키지 설치 sudo apt install nginx # 3. 여러 패키지 동시 설치 sudo apt install curl wget git vim # 4. 특정 버전 설치 (= 뒤에 버전 지정) sudo apt install nginx=1.18.0-6ubuntu14.4 # 5. 설치된 패키지 업그레이드 (목록 새로고침 선행 필요) sudo apt upgrade # 6. 커널, 의존성 변경을 포함한 전체 업그레이드 sudo apt full-upgrade # 7. 패키지 삭제 (설정 파일 보존) sudo apt remove nginx # 8. 패키지 + 설정 파일까지 완전 삭제 sudo apt purge nginx # 9. 불필요한 의존성 패키지 자동 제거 sudo apt autoremove # 10. 다운로드 캐시 정리 sudo apt clean # 모든 캐시 삭제 sudo apt autoclean # 오래된 캐시만 삭제 # 11. 패키지 검색 apt search nginx apt-cache search "web server" # 12. 패키지 상세 정보 조회 apt show nginx # 출력 예시: # Package: nginx # Version: 1.18.0-6ubuntu14.4 # Priority: optional # Section: web # Origin: Ubuntu # Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> # Installed-Size: 45.1 kB # Depends: nginx-core (<< 1.18.0-6ubuntu14.4.1~) | nginx-full (<< 1.18.0-6ubuntu14.4.1~) | ... # Homepage: https://nginx.net # Description: small, powerful, scalable web/proxy server # 13. 현재 설치된 패키지 목록 dpkg -l dpkg -l | grep nginx # 14. 특정 파일이 어느 패키지에 속하는지 확인 dpkg -S /usr/sbin/nginx # nginx: /usr/sbin/nginx
dnf는 apt와 동작 방식이 유사합니다. 가장 큰 차이는 트랜잭션 히스토리를 기록해 특정 작업을 롤백할 수 있다는 점입니다.
bash# 1. 패키지 목록 새로고침 (dnf는 자동이지만 명시적으로 실행 가능) sudo dnf check-update # 출력 예시: # Last metadata expiration check: 0:05:23 ago on Thu 26 Mar 2026 10:00:00 KST. # nginx.x86_64 1:1.20.1-10.el9 appstream # curl.x86_64 7.76.1-29.el9_4 baseos # openssl.x86_64 1:3.0.7-27.el9 baseos # 2. 패키지 설치 sudo dnf install nginx # 3. 여러 패키지 동시 설치 sudo dnf install curl wget git vim # 4. 특정 버전 설치 sudo dnf install nginx-1:1.20.1-10.el9 # 5. 설치된 패키지 전체 업그레이드 sudo dnf upgrade # 6. 특정 패키지만 업그레이드 sudo dnf upgrade nginx # 7. 패키지 삭제 sudo dnf remove nginx # 8. 불필요한 의존성 자동 제거 sudo dnf autoremove # 9. 캐시 정리 sudo dnf clean all # 10. 패키지 검색 sudo dnf search nginx sudo dnf search "web server" # 11. 패키지 상세 정보 조회 sudo dnf info nginx # 출력 예시: # Available Packages # Name : nginx # Epoch : 1 # Version : 1.20.1 # Release : 10.el9 # Architecture : x86_64 # Size : 37 k # Source : nginx-1.20.1-10.el9.src.rpm # Repository : appstream # Summary : A high performance web server and reverse proxy server # URL : https://nginx.org/ # License : BSD # Description : ... # 12. 설치된 패키지 목록 dnf list installed dnf list installed | grep nginx # 13. 특정 파일이 어느 패키지에 속하는지 확인 rpm -qf /usr/sbin/nginx # nginx-1.20.1-10.el9.x86_64 # 14. 패키지에 포함된 파일 목록 rpm -ql nginx # 15. 업그레이드 이력 확인 (dnf 고유 기능) sudo dnf history # ID | Command line | Date and time | Action | Altered # ----+---------------------------+------------------+--------+-------- # 5 | upgrade nginx | 2026-03-26 09:45 | Upgrade| 1 # 4 | install vim | 2026-03-25 14:22 | Install| 1 # 16. 특정 트랜잭션 롤백 sudo dnf history undo 5
dnf가 리포지토리 + 의존성 자동 해결까지 해주는 고수준 툴이라면, rpm은 .rpm 파일을 직접 다루는 저수준 툴입니다. 인터넷 없는 환경에서 단일 파일 설치, 설치된 패키지 상세 조회, 파일 무결성 검증 등에 사용합니다.
언제 rpm을 쓰나?
- 인터넷 없이
.rpm파일을 직접 받아 설치할 때- 설치 전 패키지 내용/의존성을 미리 확인할 때
- 특정 파일이 어느 패키지 소속인지 역추적할 때
- 패키지 파일 변조 여부를 검증할 때
bash# ─── 설치 / 업그레이드 / 삭제 ─── # 설치 (-i: install, -v: verbose, -h: 진행률 # 표시) sudo rpm -ivh nginx-1.20.1-10.el9.x86_64.rpm # 출력 예시: # Verifying... ################################# [100%] # Preparing... ################################# [100%] # Updating / installing... # 1:nginx-1:1.20.1-10.el9 ################################# [100%] # 업그레이드 (이미 설치된 경우 교체, 없으면 신규 설치) sudo rpm -Uvh nginx-1.24.0-1.el9.x86_64.rpm # 다운그레이드 (이전 버전으로 되돌리기) sudo rpm -Uvh --oldpackage nginx-1.20.1-10.el9.x86_64.rpm # 삭제 (-e: erase) sudo rpm -e nginx # 의존성 오류 무시하고 강제 삭제 (주의: 다른 패키지가 깨질 수 있음) sudo rpm -e --nodeps nginx # 여러 파일 한 번에 설치 sudo rpm -ivh *.rpm # ─── 조회 (설치 전 파일 검사) ─── # 설치 전 .rpm 파일 정보 확인 (-p: package file 대상) rpm -qpi nginx-1.20.1-10.el9.x86_64.rpm # Name : nginx # Version : 1.20.1 # Release : 10.el9 # Architecture: x86_64 # Install Date: (not installed) # Group : System Environment/Daemons # Size : 1580594 # License : BSD # Signature : RSA/SHA256, ... # Summary : A high performance web server and reverse proxy server # 설치 전 패키지에 포함된 파일 목록 미리 보기 rpm -qpl nginx-1.20.1-10.el9.x86_64.rpm # /etc/logrotate.d/nginx # /etc/nginx/nginx.conf # /usr/lib/systemd/system/nginx.service # /usr/sbin/nginx # 설치 전 의존성 확인 rpm -qpR nginx-1.20.1-10.el9.x86_64.rpm # /bin/sh # libcrypto.so.3()(64bit) # libpcre.so.1()(64bit) # ─── 조회 (설치된 패키지 조회) ─── # 특정 패키지 설치 여부 확인 rpm -q nginx # nginx-1.20.1-10.el9.x86_64 (설치됨) # package nginx is not installed (미설치) # 설치된 패키지 상세 정보 rpm -qi nginx # Name : nginx # Version : 1.20.1 # Install Date: Thu 26 Mar 2026 10:00:00 KST # ... # 설치된 패키지가 포함하는 파일 목록 rpm -ql nginx # /etc/nginx/nginx.conf # /usr/sbin/nginx # /usr/lib/systemd/system/nginx.service # 특정 파일이 어느 패키지 소속인지 역추적 (-f: file) rpm -qf /usr/sbin/nginx # nginx-1.20.1-10.el9.x86_64 rpm -qf /etc/passwd # setup-2.13.7-9.el9.noarch # 설치된 모든 패키지 목록 rpm -qa rpm -qa | grep nginx rpm -qa | sort | less # ─── 검증 ─── # 패키지 파일 변조 여부 검증 (원본 체크섬과 비교) rpm -V nginx # S.5....T. c /etc/nginx/nginx.conf ← 설정 파일이 변경된 경우 표시 # (아무 출력 없으면 변조 없음) # 출력 문자 의미: # S: 파일 크기 변경 # M: 권한/파일 타입 변경 # 5: MD5 체크섬 불일치 # T: 수정 시간 변경 # c: 설정 파일(config) # 설치된 모든 패키지 무결성 검증 rpm -Va 2>/dev/null | grep -v "^..5....T c " # 설정 파일 변경은 제외
상황에 따라 rpm을 직접 쓸지 dnf를 쓸지 기준이 달라집니다.
| 상황 | 사용 도구 |
|---|---|
| 리포지토리에서 인터넷으로 설치 | dnf install |
| .rpm 파일 직접 설치 (의존성 자동 해결 포함) | dnf install ./파일.rpm |
| .rpm 파일 직접 설치 (의존성 수동 관리) | rpm -ivh 파일.rpm |
| 패키지 파일 내용 미리 조회 | rpm -qpl 파일.rpm |
| 설치된 파일 역추적 | rpm -qf /경로 |
| 무결성 검증 | rpm -V 패키지명 |
팁:
dnf install ./파일.rpm방식으로 로컬 .rpm을 설치하면 dnf가 의존성까지 자동으로 리포지토리에서 받아줍니다. 순수rpm -ivh는 의존성을 수동으로 모두 갖춰야 합니다.
— — —
3. 리포지토리 추가와 GPG 키 인증
공식 Ubuntu 리포지토리에 없는 소프트웨어(최신 nginx, Docker, HashiCorp 도구 등)는 서드파티 리포지토리를 추가해야 합니다.
sources.list 형식:
textdeb [옵션] URI Suite Component [Component...]
text# 예시: Ubuntu 22.04 (jammy) 공식 리포지토리 deb http://archive.ubuntu.com/ubuntu jammy main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu jammy-updates main restricted universe multiverse deb http://security.ubuntu.com/ubuntu jammy-security main restricted universe multiverse
Docker 공식 리포지토리 추가 (현대적 방식):
bash# 1. 필요한 도구 설치 sudo apt install ca-certificates curl gnupg # 2. GPG 키 저장 디렉토리 생성 sudo install -m 0755 -d /etc/apt/keyrings # 3. Docker GPG 공개키 다운로드 및 저장 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # 4. 리포지토리 파일 생성 (/etc/apt/sources.list.d/ 하위) echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 5. 패키지 목록 새로고침 후 설치 sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io # 결과 확인 cat /etc/apt/sources.list.d/docker.list # deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stable
HashiCorp Terraform 리포지토리 추가:
bash# GPG 키 등록 wget -O- https://apt.releases.hashicorp.com/gpg | \ sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg # 리포지토리 추가 echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \ https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \ sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt update && sudo apt install terraform # 버전 확인 terraform --version # Terraform v1.8.5 # on linux_amd64
RHEL 계열에서 리포지토리는 /etc/yum.repos.d/ 하위의 .repo 파일로 정의합니다.
.repo 파일 형식:
ini[repo-id] name=리포지토리 이름 baseurl=https://패키지-서버-URL/$releasever/$basearch/ enabled=1 gpgcheck=1 gpgkey=https://패키지-서버-URL/gpg-key.pub
EPEL(Extra Packages for Enterprise Linux) 추가:
bash# RHEL/CentOS 9 기준 sudo dnf install epel-release # 또는 직접 URL 지정 sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm # 설치 확인 dnf repolist # repo id repo name # appstream Rocky Linux 9 - AppStream # baseos Rocky Linux 9 - BaseOS # epel Extra Packages for Enterprise Linux 9 - x86_64 # extras Rocky Linux 9 - Extras
HashiCorp 리포지토리 추가 (RPM):
bash# 리포지토리 파일 직접 생성 sudo tee /etc/yum.repos.d/hashicorp.repo <<EOF [hashicorp] name=HashiCorp Stable - \$basearch baseurl=https://rpm.releases.hashicorp.com/RHEL/\$releasever/\$basearch/stable enabled=1 gpgcheck=1 gpgkey=https://rpm.releases.hashicorp.com/gpg EOF # GPG 키 가져오기 (자동) sudo dnf install terraform # 특정 리포지토리만 활성화/비활성화 sudo dnf config-manager --enable epel sudo dnf config-manager --disable epel # 리포지토리 우선순위 확인 dnf repolist -v
리포지토리 변수(`$releasever`, `$basearch`):
bash# $releasever: 현재 배포판 주 버전 (예: 9) # $basearch: 아키텍처 (예: x86_64) python3 -c "import dnf; db = dnf.dnf.Base(); print(db.conf.substitutions)" # {'releasever': '9', 'basearch': 'x86_64'}
— — —
4. GPG 키 인증 원리
서드파티 리포지토리를 추가할 때 "NO_PUBKEY" 경고나 "GPG error" 가 뜨면서 설치가 막히는 상황을 만납니다. 또는 반대로 curl ... | sudo apt-key add - 같은 명령어를 별 생각 없이 실행하게 됩니다. 이게 단순한 절차가 아니라, 패키지 배포자가 자신의 패키지에 서명하고 사용자는 그 서명을 검증한다는 신뢰 체계입니다. 이 메커니즘이 없으면 리포지토리 서버가 해킹당해 악성 패키지가 올라와도 패키지 매니저가 걸러낼 수 없습니다. GPG 흐름을 이해하면 키 에러가 왜 나는지, 어떻게 해결하는지, gpgcheck=0이 왜 위험한지를 납득할 수 있습니다.
패키지 매니저가 리포지토리에서 소프트웨어를 받을 때, 변조 여부를 검증하는 메커니즘이 GPG(GNU Privacy Guard) 서명입니다.
인증 흐름:
text패키지 배포자 사용자 시스템 ───────────── ───────────── 1. 개인키(Private Key)로 2. 공개키(Public Key)를 미리 등록 패키지에 서명 /etc/apt/keyrings/ (apt) nginx-1.24.deb + 서명 해시 /etc/pki/rpm-gpg/ (dnf) 3. 리포지토리 서버에 업로드 4. apt/dnf가 패키지 다운로드 시: - 서명 해시 추출 - 등록된 공개키로 검증 - 불일치 시 설치 거부 (중간자 공격 방어)
apt에서 GPG 키 관리:
bash# 등록된 키 목록 확인 (레거시 방식) sudo apt-key list # 경고: apt-key는 deprecated. 대신 /etc/apt/keyrings/ 사용 권장 # W: Key is stored in legacy trusted.gpg keyring... # 현대적 방식: 개별 .gpg 파일로 관리 ls /etc/apt/keyrings/ # docker.gpg hashicorp-archive-keyring.gpg # GPG 키 내용 확인 gpg --no-default-keyring \ --keyring /etc/apt/keyrings/docker.gpg \ --list-keys # pub rsa4096 2021-10-25 [SC] # 9DC858229FC7DD38854AE2D88D81803C0EBFCD88 # uid [ unknown] Docker Release (CE deb) <docker@docker.com> # sub rsa4096 2021-10-25 [E]
dnf에서 GPG 키 관리:
bash# GPG 키 파일 위치 ls /etc/pki/rpm-gpg/ # RPM-GPG-KEY-Rocky-9 RPM-GPG-KEY-EPEL-9 RPM-GPG-KEY-hashicorp # 수동으로 키 가져오기 sudo rpm --import https://rpm.releases.hashicorp.com/gpg # 등록된 키 확인 rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n' # gpg-pubkey-6f2d4623-638afa42 gpg(HashiCorp Security (HashiCorp Package Signing) <security+packaging@hashicorp.com>) # gpg-pubkey-350d275d-628c1f8e gpg(Rocky Linux (Rocky Linux Official Signing Key) <releng@rockylinux.org>) # gpgcheck=0은 절대 금지 (테스트 후 반드시 복구) # 내부 미러 서버도 자체 GPG 키 서명 권장
보안 핵심 원칙: gpgcheck=0(dnf) 또는 [trusted=yes](apt) 옵션은 GPG 검증을 비활성화합니다. 인터넷 연결 환경에서 이 옵션을 사용하는 것은 중간자 공격에 무방비 상태를 만드는 것과 같습니다. 내부망 미러서버라도 자체 서명 키를 사용하는 것이 권장됩니다.
— — —
5. 소스 빌드 설치 (./configure && make && make install)
패키지 리포지토리에 없거나, 특정 컴파일 옵션이 필요하거나, 최신 버전을 써야 할 때 소스 빌드를 사용합니다.
빌드 도구 준비:
bash# Ubuntu/Debian sudo apt install build-essential autoconf libtool pkg-config # RHEL/Rocky sudo dnf groupinstall "Development Tools" sudo dnf install autoconf libtool pkg-config
예시: nginx를 소스에서 빌드 (추가 모듈 포함)
bash# 1. 의존성 설치 sudo apt install libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev libgd-dev # 2. 소스 다운로드 cd /usr/local/src sudo wget https://nginx.org/download/nginx-1.26.0.tar.gz sudo tar -xzf nginx-1.26.0.tar.gz cd nginx-1.26.0 # 3. 빌드 옵션 구성 (configure) sudo ./configure \ --prefix=/etc/nginx \ --sbin-path=/usr/sbin/nginx \ --modules-path=/usr/lib64/nginx/modules \ --conf-path=/etc/nginx/nginx.conf \ --error-log-path=/var/log/nginx/error.log \ --http-log-path=/var/log/nginx/access.log \ --pid-path=/var/run/nginx.pid \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_gzip_static_module \ --with-stream # configure 출력 예시: # checking for OS # + Linux 5.14.0-570.52.1.el9_6.x86_64 x86_64 # checking for C compiler ... found # ... # Configuration summary # + using system PCRE library # + using system OpenSSL library # + using system zlib library # # nginx path prefix: "/etc/nginx" # nginx binary file: "/usr/sbin/nginx" # 4. 컴파일 (CPU 코어 수만큼 병렬 빌드) sudo make -j$(nproc) # 출력 예시: # make -f objs/Makefile # cc -c -pipe -O -W -Wall -Wpointer-arith ... # cc -o objs/nginx objs/src/core/nginx.o ... # 5. 설치 (기본 설치 위치로 파일 복사) sudo make install # 6. 버전 확인 /usr/sbin/nginx -v # nginx version: nginx/1.26.0 /usr/sbin/nginx -V 2>&1 | grep configure # configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx ...
소스 빌드의 장단점:
| 장점 | 단점 |
|---|---|
| 원하는 모듈/옵션만 포함 가능 | 패키지 매니저로 관리 불가 |
| 최신 버전 즉시 사용 가능 | 업데이트 시 재빌드 필요 |
| 최적화 컴파일 옵션 적용 | 의존성 수동 관리 |
| 특수 환경(임베디드 등) 대응 | 보안 패치 자동화 어려움 |
소스 설치 후 제거:
bash# Makefile에 uninstall 타겟이 있으면: sudo make uninstall # 없으면 직접 삭제 (설치 위치 파악 필요) # 이것이 소스 빌드의 단점 — 패키지처럼 깔끔하게 제거하기 어려움 sudo checkinstall # deb/rpm 패키지로 감싸서 설치하는 대안 도구
— — —
6. 폐쇄망 환경에서의 오프라인 패키지 설치
금융, 공공, 국방 분야의 서버는 보안상 인터넷 접속이 차단된 폐쇄망(Air-gap) 환경에서 운영됩니다. 이 경우 패키지를 미리 준비하거나 내부 미러를 구축해야 합니다.
방법 1: 개별 패키지 파일 직접 설치
bash# [인터넷 연결 서버] 패키지 파일만 다운로드 (설치 안 함) # Ubuntu/Debian apt download nginx # 다운로드: nginx_1.18.0-6ubuntu14.4_amd64.deb # 의존성 포함 전체 다운로드 apt-get install --print-uris -qq nginx | grep "^'" | \ cut -d"'" -f2 | wget -i - -P ./nginx-offline/ # RHEL/Rocky dnf download nginx dnf download --resolve nginx # 의존성 포함 다운로드 # [폐쇄망 서버] USB/SCP 전송 후 설치 # Ubuntu/Debian sudo dpkg -i nginx_1.18.0-6ubuntu14.4_amd64.deb # 의존성 미해결 오류 발생 시: sudo apt install -f # 로컬에서 해결 가능한 의존성 처리 # RHEL/Rocky sudo rpm -ivh nginx-1.20.1-10.el9.x86_64.rpm # 의존성 포함 설치: sudo rpm -ivh *.rpm
방법 2: 로컬 리포지토리 구축 (권장)
bash# [인터넷 연결 서버] 미러링 도구로 리포지토리 복제 # Ubuntu/Debian - apt-mirror sudo apt install apt-mirror sudo tee /etc/apt/mirror.list <<EOF set base_path /srv/apt-mirror set nthreads 20 set _tilde 0 deb http://archive.ubuntu.com/ubuntu jammy main restricted universe deb http://archive.ubuntu.com/ubuntu jammy-updates main restricted universe EOF sudo apt-mirror # 수십 GB 다운로드 # 또는 더 가벼운 방법: 필요한 패키지만 로컬 리포지토리로 mkdir -p /srv/local-repo apt download nginx curl wget vim sudo mv *.deb /srv/local-repo/ cd /srv/local-repo && dpkg-scanpackages . | gzip > Packages.gz # [폐쇄망 서버] 로컬 리포지토리 등록 echo "deb [trusted=yes] file:///srv/local-repo ./" | \ sudo tee /etc/apt/sources.list.d/local.list sudo apt update sudo apt install nginx # 로컬에서 설치 # RHEL/Rocky - 로컬 리포지토리 sudo dnf install createrepo_c mkdir -p /srv/local-repo # RPM 파일들을 /srv/local-repo/ 에 복사 후: sudo createrepo_c /srv/local-repo/ sudo tee /etc/yum.repos.d/local.repo <<EOF [local] name=Local Repository baseurl=file:///srv/local-repo/ enabled=1 gpgcheck=0 EOF sudo dnf install nginx
방법 3: 내부 패키지 미러 서버 구축
bash# Nexus Repository Manager 또는 Artifactory를 사용하는 기업 환경 # 또는 nginx로 단순 HTTP 미러 구축 # 패키지 디렉토리를 nginx로 서비스 sudo tee /etc/nginx/conf.d/local-mirror.conf <<EOF server { listen 8080; server_name mirror.internal; root /srv/packages; autoindex on; location / { try_files \$uri \$uri/ =404; } } EOF # 폐쇄망 서버에서 내부 미러 등록 echo "deb [trusted=yes] http://mirror.internal:8080/ubuntu jammy main" | \ sudo tee /etc/apt/sources.list.d/internal-mirror.list
— — —
7. 의존성 충돌(Broken Packages) 해결
상황: sudo apt install some-package 실행 중 의존성 오류가 발생하고 설치가 중단됩니다. 이전에 패키지를 강제 제거하거나 인터넷 연결이 끊긴 상태에서 설치를 시도했을 때 자주 발생합니다.
outputReading package lists... Done Building dependency tree... Done You might want to run 'apt --fix-broken install' to correct these. The following packages have unmet dependencies: libssl-dev : Depends: libssl3 (= 3.0.2-0ubuntu1.15) but 3.0.2-0ubuntu1.18 is installed python3-dev : Depends: python3.10 (= 3.10.12-1~22.04.7) but 3.10.12-1~22.04.9 is installed E: Broken packages
원인: dpkg 의존성 데이터베이스 불일치입니다. 패키지 설치/제거가 중간에 중단되거나 dpkg --force-* 로 강제 조작한 경우, 설치 완료 전에 연결이 끊긴 경우 발생합니다.
진단: 어느 패키지가 깨진 상태인지 확인합니다.
bash# H=half-installed, U=unpack-only 상태인 패키지 확인 dpkg -l | grep -E "^.{1}[HU]" # dpkg 데이터베이스 전체 일관성 검사 dpkg --audit # 의존성 충돌 상세 확인 apt-cache depends libssl-dev apt-cache rdepends libssl3 # 역방향: 누가 이 패키지에 의존하는가
해결:
bash# 1단계: apt 자동 복구 시도 sudo apt --fix-broken install # 2단계: dpkg 미완료 설정 마무리 sudo dpkg --configure -a # 3단계: 문제 패키지 강제 제거 후 재설치 sudo dpkg --remove --force-remove-reinstreq 문제패키지명 sudo apt install 문제패키지명 # 4단계: 캐시 초기화 후 재시도 sudo apt clean sudo rm -rf /var/lib/apt/lists/* sudo apt update sudo apt --fix-broken install
상황: sudo dnf install python3-requests 실행 중 의존성 충돌 에러가 발생합니다. 서드파티 리포지토리를 추가한 뒤 시스템 Python 패키지와 버전이 맞지 않을 때 흔하게 나타납니다.
outputError: Problem: package python3-urllib3-1.26.5-3.el9.noarch requires python3-six, but none of the providers can be installed - package python3-six-1.15.0-9.el9.noarch conflicts with python3-six < 1.16 - cannot install the best candidate for the job (try to add '--skip-broken' to skip uninstallable packages or '--nobest' to use not only best candidate packages)
원인: 설치하려는 패키지의 의존성 버전이 현재 설치된 패키지와 충돌합니다. 서드파티 리포 추가 이후 패키지 버전 범위가 달라지거나, AppStream 모듈 스트림이 충돌하는 경우 발생합니다.
진단: 어느 패키지가 충돌을 일으키는지 파악합니다.
bash# 충돌 의존성 상세 확인 dnf deplist python3-requests # 여러 버전 후보 목록 확인 dnf repoquery --available python3-six # 현재 설치된 버전 확인 dnf list installed | grep python3-six
해결:
bash# 방법 1: 최우선 후보가 아닌 버전도 허용 (가장 빠른 해결) sudo dnf install python3-requests --nobest # 방법 2: 충돌 패키지를 제외하고 설치 sudo dnf install python3-requests --exclude=python3-six # 방법 3: AppStream 모듈 스트림 전환 (RHEL 8+ 환경) sudo dnf module list python3 sudo dnf module switch-to python3:3.11 sudo dnf install python3-requests
상황: Nginx 같은 소프트웨어를 소스에서 빌드하던 중 ./configure 단계에서 라이브러리를 찾지 못해 멈춥니다. 패키지 관리자로 설치할 수 없는 최신 버전이나 커스텀 옵션이 필요할 때 소스 빌드를 시도하다 만나는 전형적인 에러입니다.
output./configure: error: SSL modules require the OpenSSL library. You can either do not enable the modules, or install the OpenSSL library into the system, or build the OpenSSL library statically from the source with nginx by using --with-openssl=<path> option.
원인: 런타임 라이브러리(libssl)는 설치되어 있지만 컴파일에 필요한 헤더 파일(.h)이 포함된 개발용 패키지(-dev/-devel)가 없습니다. 소스 빌드는 반드시 개발용 패키지가 필요합니다.
진단: 필요한 헤더 파일이 어느 패키지에 포함되어 있는지 확인합니다.
bash# OpenSSL 개발 패키지 설치 여부 확인 dpkg -l | grep libssl-dev # Ubuntu/Debian rpm -q openssl-devel # RHEL/Rocky # pkg-config로 헤더 경로 확인 (설치됐다면 경로가 출력됨) pkg-config --cflags --libs openssl # make 중 "No such file or directory" 에러 시 — 해당 헤더 제공 패키지 검색 apt-file search crypt.h # Ubuntu: apt-file install 필요 dnf provides '*/crypt.h' # RHEL/Rocky
해결:
bash# Ubuntu/Debian: 개발용 헤더 패키지 설치 sudo apt install libssl-dev libxcrypt-dev build-essential # RHEL/Rocky: 개발용 패키지 설치 sudo dnf install openssl-devel libxcrypt-devel gcc make # 패키지 설치 후 configure 재실행 ./configure --with-http_ssl_module make -j$(nproc) sudo make install
— — —
8. 고급 패키지 관리 기법
특정 패키지를 현재 버전에서 고정하여 자동 업그레이드를 막아야 하는 경우가 있습니다(예: 검증된 커널 버전, 특정 앱 버전 고정).
bash# Ubuntu/Debian: 패키지 고정 sudo apt-mark hold nginx # nginx set on hold. # 고정된 패키지 확인 apt-mark showhold # nginx # 고정 해제 sudo apt-mark unhold nginx # 고정 상태에서 업그레이드 시도 시: sudo apt upgrade # The following packages have been kept back: # nginx # 0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded. # dpkg로 직접 고정 echo "nginx hold" | sudo dpkg --set-selections dpkg --get-selections | grep hold # nginx hold # RHEL/Rocky: 패키지 버전 잠금 sudo dnf install 'dnf-command(versionlock)' sudo dnf versionlock add nginx # Adding versionlock on: nginx-1:1.20.1-10.el9.* # 잠금 목록 확인 sudo dnf versionlock list # nginx-1:1.20.1-10.el9.* # 잠금 해제 sudo dnf versionlock delete nginx
bash# Ubuntu: 보안 업데이트 자동 적용 설정 sudo apt install unattended-upgrades sudo dpkg-reconfigure unattended-upgrades # 설정 파일 확인 cat /etc/apt/apt.conf.d/50unattended-upgrades # Unattended-Upgrade::Allowed-Origins { # "${distro_id}:${distro_codename}-security"; # "${distro_id}ESMApps:${distro_codename}-apps-security"; # }; # Unattended-Upgrade::Mail "admin@example.com"; # Unattended-Upgrade::Automatic-Reboot "false"; # 자동 업데이트 주기 설정 cat /etc/apt/apt.conf.d/20auto-upgrades # APT::Periodic::Update-Package-Lists "1"; // 매일 목록 새로고침 # APT::Periodic::Unattended-Upgrade "1"; // 매일 자동 업데이트 # 드라이런으로 미리 확인 sudo unattended-upgrade --dry-run --debug # RHEL: dnf-automatic으로 자동 업데이트 sudo dnf install dnf-automatic sudo tee /etc/dnf/automatic.conf <<EOF [commands] upgrade_type = security # security 또는 default (전체) apply_updates = yes emit_via = email email_to = admin@example.com EOF sudo systemctl enable --now dnf-automatic.timer systemctl status dnf-automatic.timer # ● dnf-automatic.timer - dnf-automatic timer # Loaded: loaded (/usr/lib/systemd/system/dnf-automatic.timer; enabled) # Active: active (waiting) # Trigger: Fri 2026-03-27 06:00:00 KST; 19h left
— — —
9. 현업 적용 — 실무 시나리오
시나리오: 새 운영 서버 프로비저닝
신규 서버를 구성할 때 패키지 관리는 IaC(Infrastructure as Code)와 함께 자동화됩니다.
bash#!/bin/bash # server-bootstrap.sh — 서버 초기 구성 스크립트 set -euo pipefail # OS 감지 if [ -f /etc/debian_version ]; then PKG_MANAGER="apt" elif [ -f /etc/redhat-release ]; then PKG_MANAGER="dnf" fi bootstrap_ubuntu() { echo "[INFO] Ubuntu 서버 초기화 시작" # 패키지 목록 최신화 apt update -qq # 보안 업데이트 우선 적용 apt upgrade -y -o Dpkg::Options::="--force-confdef" \ -o Dpkg::Options::="--force-confold" # 기본 도구 설치 apt install -y \ curl wget git vim \ htop iotop nethogs \ unzip jq \ ca-certificates gnupg \ ufw fail2ban \ logrotate # Docker 리포지토리 추가 및 설치 install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo \ "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ tee /etc/apt/sources.list.d/docker.list > /dev/null apt update -qq apt install -y docker-ce docker-ce-cli containerd.io echo "[INFO] 초기화 완료" } bootstrap_rhel() { echo "[INFO] RHEL 계열 서버 초기화 시작" dnf upgrade -y --security dnf install -y \ curl wget git vim \ htop iotop \ unzip jq \ ca-certificates \ firewalld fail2ban # EPEL 설치 dnf install -y epel-release # Docker (또는 Podman 권장) dnf install -y podman podman-compose echo "[INFO] 초기화 완료" } if [ "$PKG_MANAGER" = "apt" ]; then bootstrap_ubuntu elif [ "$PKG_MANAGER" = "dnf" ]; then bootstrap_rhel fi
보안 패치 정책 예시 (기업 환경):
| 패치 분류 | 적용 기준 | 적용 주기 |
|---|---|---|
| Critical CVE | CVSS 9.0 이상 | 24시간 내 긴급 적용 |
| High CVE | CVSS 7.0~8.9 | 1주일 내 |
| Medium CVE | CVSS 4.0~6.9 | 다음 정기 패치 |
| Low/Informational | CVSS 0~3.9 | 분기별 패치 |
대형 금융사, 공공기관 운영 환경에서의 현실적인 패키지 관리 체계입니다.
전형적인 폐쇄망 아키텍처:
text인터넷 ──▶ DMZ 서버(패키지 미러) ──▶ 내부망 넥서스/Artifactory │ ┌─────────┴─────────┐ ▼ ▼ 개발 서버군 운영 서버군 (dnf/apt 내부 미러 사용)
Nexus Repository Manager로 프록시 미러 구성 (개념):
bash# 각 서버에서 내부 미러를 바라보도록 설정 # Ubuntu 서버 sudo tee /etc/apt/sources.list <<EOF deb http://nexus.internal.company.com/repository/ubuntu-proxy/ jammy main restricted deb http://nexus.internal.company.com/repository/ubuntu-proxy/ jammy-updates main restricted deb http://nexus.internal.company.com/repository/ubuntu-security/ jammy-security main restricted EOF # RHEL 서버 sudo tee /etc/yum.repos.d/internal.repo <<EOF [baseos] name=Internal BaseOS Mirror baseurl=http://nexus.internal.company.com/repository/rhel-baseos/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release [appstream] name=Internal AppStream Mirror baseurl=http://nexus.internal.company.com/repository/rhel-appstream/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release EOF # 검증 sudo dnf repolist # repo id repo name # appstream Internal AppStream Mirror # baseos Internal BaseOS Mirror sudo dnf install nginx # (내부 미러에서 설치됨)
운영 체크리스트:
- 내부 미러 동기화 주기 설정 (일 1회 자동 동기화 권장)
- 미러 서버 디스크 사용량 모니터링 (Ubuntu 전체 미러 ~1.5TB)
- 승인된 패키지만 넥서스에 허용 (화이트리스트 정책)
- 패키지 설치 이력 감사 로그 보존
- 정기 취약점 스캔 (Trivy, Grype 등으로 설치된 패키지 점검)
— — —
10. 요약 — 명령어 치트시트
apt vs dnf 명령어 대조표:
| 작업 | apt (Ubuntu/Debian) | dnf (RHEL/Rocky) |
|---|---|---|
| 목록 새로고침 | apt update |
dnf check-update |
| 패키지 설치 | apt install pkg |
dnf install pkg |
| 패키지 삭제 | apt remove pkg |
dnf remove pkg |
| 완전 삭제 | apt purge pkg |
dnf remove pkg (설정도 삭제) |
| 업그레이드 | apt upgrade |
dnf upgrade |
| 패키지 검색 | apt search term |
dnf search term |
| 패키지 정보 | apt show pkg |
dnf info pkg |
| 파일→패키지 | dpkg -S /path/to/file |
rpm -qf /path/to/file |
| 설치 목록 | dpkg -l |
dnf list installed |
| 자동정리 | apt autoremove |
dnf autoremove |
| 캐시 정리 | apt clean |
dnf clean all |
| 이력 확인 | zcat /var/log/apt/history.log.gz |
dnf history |
| 이력 롤백 | (없음) | dnf history undo |
| 패키지 고정 | apt-mark hold pkg |
dnf versionlock add pkg |
리포지토리 관리 위치:
| 항목 | apt | dnf |
|---|---|---|
| 주 설정 파일 | /etc/apt/sources.list |
(없음) |
| 추가 리포지토리 | /etc/apt/sources.list.d/*.list |
/etc/yum.repos.d/*.repo |
| GPG 키 위치 | /etc/apt/keyrings/*.gpg |
/etc/pki/rpm-gpg/ |
| 패키지 캐시 | /var/cache/apt/archives/ |
/var/cache/dnf/ |
— — —
Docker나 HashiCorp 공식 설치 가이드를 따라 curl ... | sudo apt-key add -를 실행하면 "Warning: apt-key is deprecated"가 뜹니다. Ubuntu 22.04부터 apt-key 방식은 보안상 문제가 있어 deprecated 처리됐기 때문입니다. 기존 방식은 하나의 키링 파일에 모든 외부 리포 키가 섞여서, 특정 리포에서 받은 키가 시스템 전체 패키지를 신뢰하는 구조였습니다. 새 방식은 리포마다 별도 .gpg 파일을 만들어 /etc/apt/keyrings/에 두고, sources.list에서 signed-by= 옵션으로 해당 리포에만 연결합니다. 스크립트나 자동화 코드를 작성할 때 이 구분을 모르면 오래된 방식으로 짜게 됩니다.
Ubuntu 22.04+부터 apt-key는 deprecated입니다. 새로운 방식은 각 리포마다 전용 키링 파일을 사용합니다.
새 방식 (권장):
bash# 1. 키링 저장 디렉토리 생성 sudo mkdir -p /etc/apt/keyrings # 2. GPG 키 다운로드 및 저장 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # 3. sources.list.d에 signed-by 옵션으로 리포 등록 echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 4. 업데이트 및 설치 sudo apt update sudo apt install docker-ce docker-ce-cli
apt-key (구식, 보안 취약):
bash# 이렇게 하면 안 됨 — 모든 리포에서 해당 키 신뢰 curl -fsSL https://example.com/key.gpg | sudo apt-key add - # deprecated!
apt upgrade를 실행했더니 Nginx 버전이 올라가면서 설정 파일 문법이 바뀌어 서비스가 내려간 적이 있습니다. 또는 Python이나 Node.js 관련 패키지가 자동 업그레이드되면서 의존하는 앱이 갑자기 깨지는 경우도 흔합니다. 운영 환경에서는 검증되지 않은 버전 업그레이드가 곧바로 장애로 이어질 수 있기 때문에, 특정 패키지는 버전을 고정해두는 게 안전합니다. apt-mark hold가 가장 간단한 방법이고, 더 세밀한 제어가 필요할 때는 apt pinning으로 우선순위를 직접 지정합니다. 커널 버전 고정, 특정 외부 리포 패키지 우선순위 조정 등에서 이 기능이 실제로 쓰입니다.
운영 서버에서 패키지 버전을 고정해 자동 업데이트로 인한 장애를 방지합니다.
apt pinning (/etc/apt/preferences.d/):
bash# nginx를 특정 버전으로 고정 sudo tee /etc/apt/preferences.d/nginx << 'EOF' Package: nginx Pin: version 1.24.* Pin-Priority: 1001 EOF # 리포지토리 전체 우선순위 낮추기 (외부 리포 패키지가 시스템 패키지 덮어쓰지 않도록) sudo tee /etc/apt/preferences.d/docker << 'EOF' Package: * Pin: origin download.docker.com Pin-Priority: 500 EOF # 현재 pin 상태 확인 apt-cache policy nginx # nginx: # Installed: 1.24.0-1 # Candidate: 1.24.0-1 ← 최신도 이 버전으로 고정됨 # Pin: (not pinned)
Pin-Priority 값 의미:
| 값 | 동작 |
|---|---|
| < 0 | 절대 설치하지 않음 |
| 1-99 | 이미 설치된 경우에만 |
| 100 | 설치되어 있지 않으면 설치 안 함 |
| 500 | 기본값 |
| 1001 | 현재보다 낮은 버전도 강제 설치 |
bash# apt-mark hold (가장 간단한 버전 고정) sudo apt-mark hold nginx sudo apt-mark showhold # 고정된 패키지 목록 # 해제 sudo apt-mark unhold nginx
dnf versionlock (RHEL/Rocky):
bashsudo dnf install 'dnf-command(versionlock)' sudo dnf versionlock add nginx # 현재 버전 고정 sudo dnf versionlock list # 고정 목록 sudo dnf versionlock delete nginx # 해제
다음 모듈에서는 tmux로 터미널 세션을 유지하고, 화면을 분할하여 여러 작업을 동시에 관리하는 방법을 다룹니다.
'Linux' 카테고리의 다른 글
| [Linux] 환경변수 & dotfiles (0) | 2026.05.22 |
|---|---|
| [Linux] tmux & 백그라운드 세션 관리 (0) | 2026.05.22 |
| [Linux]파일 권한 (File Permissions) (0) | 2026.05.22 |
| [Linux] 사용자와 그룹 관리 (0) | 2026.05.22 |
| [Linux]텍스트 편집 기초 — vim과 nano (0) | 2026.05.22 |