본문으로 건너뛰기
Buildroot Practical · 7/20

Buildroot 보드 Customize — overlay·post-build·post-image 흐름

· Hawk · 6분 읽기

#한 줄 요약

“패키지로 풀 수 없는 보드별 차이는 세 가지 hook으로 해결한다.” — overlay는 파일 복사, post-build는 rootfs 수정, post-image는 flash 이미지 조립입니다.

Buildroot는 패키지 시스템이 강력하지만, 실제 보드에는 항상 패키지로 풀기 애매한 차이가 남습니다. 한 줄짜리 /etc/hostname, 보드별 /etc/network/interfaces, MAC 주소를 박는 udev rule, 마지막에 dd로 SD 카드 이미지를 만드는 절차 같은 것들입니다.

이 장은 그 빈틈을 메우는 세 hook을 다룹니다. BR2_ROOTFS_OVERLAY, BR2_ROOTFS_POST_BUILD_SCRIPT, BR2_ROOTFS_POST_IMAGE_SCRIPT. 셋 다 defconfig에 한 줄씩 적는 단순한 메커니즘이지만, 잘 쓰면 보드 패키지를 따로 만들 필요가 거의 사라집니다.

#세 hook의 위치

빌드 흐름에서 셋이 어디에 끼는지부터 정리합니다.

세 hook의 빌드 흐름 내 위치

Hook시점수정 대상전형적 용도
BR2_ROOTFS_OVERLAY패키지 install 직후$(TARGET_DIR) 트리보드별 conf 파일 추가
BR2_ROOTFS_POST_BUILD_SCRIPToverlay 직후, fakeroot 직전$(TARGET_DIR) 트리동적 콘텐츠 생성, 권한 조정
BR2_ROOTFS_POST_IMAGE_SCRIPT모든 이미지 생성 직후output/images/flash 이미지 조립, SD 카드 layout

#BR2_ROOTFS_OVERLAY — 파일 트리 복사

가장 단순한 hook입니다. 디렉터리 하나를 가리키면 그 안의 모든 파일이 그대로 $(TARGET_DIR)로 복사됩니다. rsync 한 줄과 같다고 보면 됩니다.

defconfig에 다음 한 줄을 추가합니다.

BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/rootfs-overlay"

디렉터리는 target rootfs의 루트 기준으로 구성합니다.

board/acme/edge/rootfs-overlay/
├── etc/
│ ├── hostname
│ ├── motd
│ ├── network/
│ │ └── interfaces
│ └── init.d/
│ └── S99acme
└── opt/
└── acme/
└── config.json

위 트리는 빌드 후 $(TARGET_DIR)/etc/hostname 등으로 정확히 그 위치에 복사됩니다. 권한도 overlay 디렉터리의 권한 그대로 유지됩니다. 따라서 S99acme 같은 init 스크립트는 chmod +x로 미리 실행 권한을 줘 두어야 합니다.

#Overlay의 함정

세 가지가 자주 발생합니다.

1. 패키지 install이 overlay를 덮는 게 아니라, overlay가 패키지 install을 덮는다. 같은 경로 파일이 둘 다 있으면 overlay가 이깁니다. /etc/inittab을 overlay에 넣으면 busybox가 설치한 기본 inittab을 덮어씁니다.

2. Symlink는 의도대로 들어간다. overlay의 symlink는 cp -a로 복사되므로 링크 자체가 보존됩니다. 절대 경로 symlink면 target에서도 절대 경로로 평가됩니다.

3. 빈 디렉터리도 복사된다. /var/log/acme처럼 빈 디렉터리만 필요하면 overlay에 빈 디렉터리를 두면 됩니다. 단 .gitkeep 같은 placeholder가 그대로 rootfs에 남으니 주의합니다.

#여러 overlay 합치기

BR2_ROOTFS_OVERLAY는 공백으로 구분된 여러 디렉터리를 받습니다.

BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_ACME_PATH)/board/common/rootfs-overlay \
$(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/rootfs-overlay"

뒤에 오는 overlay가 앞 overlay를 덮어씁니다. 공통 → 보드 → 변형 순으로 쌓는 패턴이 흔합니다.

#BR2_ROOTFS_POST_BUILD_SCRIPT — 마지막 수정

overlay로 안 되는 것이 있습니다. 빌드 시점에 값이 정해지는 파일들입니다. 빌드 날짜, git hash, 자동 생성된 conf, 권한 조정 같은 것들입니다.

defconfig에 다음을 추가합니다.

BR2_ROOTFS_POST_BUILD_SCRIPT="$(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/post-build.sh"

스크립트는 첫 인자로 $(TARGET_DIR) 절대 경로를 받습니다. 추가 환경 변수도 풍부합니다.

변수의미
$1$(TARGET_DIR) 절대 경로
$BR2_CONFIG.config 파일의 절대 경로
$HOST_DIRhost 도구 디렉터리
$STAGING_DIRstaging 디렉터리 (target sysroot)
$BUILD_DIR패키지 빌드 디렉터리
$BINARIES_DIRoutput/images/
$BASE_DIRoutput/

전형적인 post-build 스크립트입니다.

board/acme/edge/post-build.sh
#!/bin/sh
# Called after rootfs assembly, before fakeroot/image creation.
set -e
TARGET_DIR="$1"
# 1. Build-time stamp
date +"%Y-%m-%d %H:%M:%S" > "$TARGET_DIR/etc/build-date"
git -C "$BR2_EXTERNAL_ACME_PATH" rev-parse --short HEAD \
> "$TARGET_DIR/etc/build-revision" 2>/dev/null || echo "unknown" \
> "$TARGET_DIR/etc/build-revision"
# 2. Adjust permissions for SUID binaries
chmod 4755 "$TARGET_DIR/usr/bin/acme-fw"
# 3. Generate /etc/issue from template
sed "s/@VERSION@/$(cat "$TARGET_DIR/etc/build-revision")/" \
"$BR2_EXTERNAL_ACME_PATH/board/acme/edge/issue.in" \
> "$TARGET_DIR/etc/issue"
# 4. Remove unwanted files
rm -f "$TARGET_DIR/etc/init.d/S40network" # we use systemd-networkd
# 5. Strip world-write from /tmp template (kept by busybox install)
chmod 1777 "$TARGET_DIR/tmp"

#post-build vs overlay 구분

자주 헷갈리는 지점입니다. 규칙은 단순합니다.

  • 정적이고 그대로 복사하면 되는 파일 → overlay.
  • 빌드 시점에 값을 채워야 하는 파일 → post-build.
  • 권한 조정은 post-build에서. overlay에서 mode를 강제할 방법이 없습니다.
  • 기존 파일 삭제는 post-build에서. overlay는 삭제를 표현할 수 없습니다.

#Owner/Permission 표 — device table

권한·소유권을 체계적으로 관리하려면 device table을 사용합니다. defconfig에서 BR2_ROOTFS_DEVICE_TABLE을 가리킵니다.

BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt $(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/device_table.txt"

테이블 형식은 다음과 같습니다.

#<name> <type> <mode> <uid> <gid> <major> <minor> <start> <inc> <count>
/etc/shadow f 600 0 0 - - - - -
/opt/acme d 755 0 0 - - - - -
/opt/acme/private d 700 500 500 - - - - -
/dev/acme0 c 660 0 100 250 0 - - -

fakeroot 단계에서 적용되므로 최종 image에 반영됩니다. post-build에서 chmod로 일일이 잡는 것보다 device table이 훨씬 안전합니다.

#BR2_ROOTFS_POST_IMAGE_SCRIPT — 이미지 조립

마지막 hook입니다. 모든 rootfs 형식과 부트로더·커널이 output/images/에 모인 호출됩니다. SD 카드 이미지OTA 패키지를 조립하는 자리입니다.

defconfig에 다음을 추가합니다.

BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/post-image.sh"
BR2_ROOTFS_POST_SCRIPT_ARGS="acme-edge"

BR2_ROOTFS_POST_SCRIPT_ARGS는 두 hook(post-build, post-image) 모두에 전달되는 추가 인자입니다. 보드 식별자나 production/dev 플래그를 넘기는 용도로 씁니다.

post-image 스크립트의 인자·변수입니다.

변수의미
$1$BINARIES_DIR (= output/images/)
$2, $3, …BR2_ROOTFS_POST_SCRIPT_ARGS의 토큰들
$BR2_CONFIG.config 절대 경로
$HOST_DIRhost 도구 디렉터리
$TARGET_DIRtarget rootfs (read-only로 보세요)

전형적인 SD 카드 이미지 조립 스크립트입니다. genimage를 호출하는 형태가 표준입니다.

board/acme/edge/post-image.sh
#!/bin/sh
# Assemble final flashable SD card image from output/images/.
set -e
BINARIES_DIR="$1"
BOARD_NAME="$2"
GENIMAGE_CFG="$BR2_EXTERNAL_ACME_PATH/board/acme/edge/genimage.cfg"
GENIMAGE_TMP="$BINARIES_DIR/genimage.tmp"
rm -rf "$GENIMAGE_TMP"
genimage \
--rootpath "$TARGET_DIR" \
--tmppath "$GENIMAGE_TMP" \
--inputpath "$BINARIES_DIR" \
--outputpath "$BINARIES_DIR" \
--config "$GENIMAGE_CFG"
# Compress final image
gzip -kf "$BINARIES_DIR/sdcard.img"
echo "[post-image] sdcard.img.gz ready: $(du -h "$BINARIES_DIR/sdcard.img.gz" | cut -f1)"

genimage는 Buildroot가 자동으로 host에 빌드해 주는 도구입니다(BR2_PACKAGE_HOST_GENIMAGE=y로 활성화). genimage.cfg는 다음과 같이 생겼습니다.

image boot.vfat {
vfat {
files = {
"MLO",
"u-boot.img",
"zImage",
"am335x-boneblack.dtb",
}
}
size = 16M
}
image sdcard.img {
hdimage {}
partition u-boot {
partition-type = 0xC
bootable = "true"
image = "boot.vfat"
}
partition rootfs {
partition-type = 0x83
image = "rootfs.ext4"
size = 512M
}
}

이 구성으로 만들어진 sdcard.img는 그대로 dd if=sdcard.img of=/dev/sdX로 SD에 굽습니다. 자세한 SD 굽기 흐름은 Ch 10: 실전 — BeagleBone Black에서 다룹니다.

#세 hook을 모두 사용하는 보드 트리

종합 예시입니다. ACME edge 보드의 전체 트리 구성입니다.

br2-acme/
└── board/acme/edge/
├── rootfs-overlay/
│ ├── etc/
│ │ ├── hostname # "acme-edge\n"
│ │ ├── motd # ASCII banner
│ │ └── network/interfaces
│ └── opt/acme/config.json
├── device_table.txt # SUID, /opt/acme private dir
├── issue.in # template with @VERSION@
├── post-build.sh # build-date, revision, sed
├── post-image.sh # genimage → sdcard.img
└── genimage.cfg # partition layout

defconfig 발췌입니다.

BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/rootfs-overlay"
BR2_ROOTFS_POST_BUILD_SCRIPT="$(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/post-build.sh"
BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/post-image.sh"
BR2_ROOTFS_POST_SCRIPT_ARGS="acme-edge production"
BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt $(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/device_table.txt"
BR2_PACKAGE_HOST_GENIMAGE=y

각 파일이 어떤 defconfig 옵션으로 참조되는지 그림으로 보면 다음과 같습니다.

보드 디렉터리 파일과 참조 옵션 매핑

#Troubleshooting

#overlay의 파일이 안 들어간다

가장 흔한 원인은 path입니다. overlay 루트의 직접 자식이 etc인지, rootfs-overlay인지 확인합니다. BR2_ROOTFS_OVERLAY가 가리키는 디렉터리 바로 아래가 rootfs의 /로 매핑됩니다.

$ ls -la $(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/rootfs-overlay
drwxr-xr-x etc/ ← 이게 맞음
drwxr-xr-x opt/
# 아니라
drwxr-xr-x rootfs/ ← 이러면 /rootfs/etc/... 로 들어감

#post-build 스크립트가 실행되지 않는다

세 가지를 확인합니다. 실행 권한(chmod +x), shebang(#!/bin/sh), 그리고 defconfig의 경로. defconfig를 수정한 뒤에 make olddefconfig 또는 savedefconfig를 거쳐야 빌드에 반영됩니다.

#post-image에서 genimage가 없다고 한다

BR2_PACKAGE_HOST_GENIMAGE=y가 빠졌습니다. defconfig에 추가하고 다시 빌드합니다. host 패키지는 target 빌드를 시작하기 전에 준비되므로 한 번 활성화하면 매번 자동으로 갖춰집니다.

#한 번 빌드한 뒤 overlay만 바꿔 다시 빌드하고 싶다

make 한 번만 다시 돌리면 됩니다. 패키지가 모두 빌드되어 있으면 rootfs assembly만 다시 수행합니다. 더 명시적으로는 make target-finalize로 fakeroot 단계만 다시 돌릴 수 있습니다.

$ make target-finalize
$ make

post-build·post-image도 매 make마다 실행됩니다. 멱등(idempotent)하게 작성하는 것이 중요합니다.

#BR2_ROOTFS_POST_SCRIPT_ARGS가 post-image에 안 넘어간다

인자는 두 hook 모두에 같은 값으로 넘어갑니다. post-image에서는 $1$BINARIES_DIR이므로 사용자 인자는 $2부터 시작합니다. post-build에서는 $1$TARGET_DIR이고 $2부터 사용자 인자입니다.

#정리

  • 세 hook은 어떤 시점어떤 대상을 수정하는지에 따라 구분됩니다.
  • BR2_ROOTFS_OVERLAY는 정적 파일 트리 복사. 권한·삭제는 표현 불가.
  • BR2_ROOTFS_POST_BUILD_SCRIPT$(TARGET_DIR) 마지막 수정. 빌드 정보 주입, 권한 조정, 파일 삭제.
  • BR2_ROOTFS_POST_IMAGE_SCRIPToutput/images/에서 flash 이미지 조립. genimage가 표준 도구.
  • 권한·SUID·디바이스 노드는 BR2_ROOTFS_DEVICE_TABLE로 일괄 관리합니다.
  • 모든 경로는 $(BR2_EXTERNAL_<NAME>_PATH) 기준으로 표기합니다.
  • 세 스크립트는 매 빌드마다 실행되므로 멱등하게 작성합니다.

#다음 장 예고

다음 편은 Ch 8: 출력 파일시스템 — initramfs, squashfs, ext4, cpio.

#관련 항목