ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
detections / detections (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
264 lines
9.6 KiB
Bash
Executable File
264 lines
9.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ARTEX cross-platform release builder.
|
|
#
|
|
# The default mode builds one target and embeds the already-exported frontend.
|
|
# `./build.sh --release` builds and packages all supported desktop/server targets.
|
|
#
|
|
# Environment variables:
|
|
# ARTEX_TARGET_OS=linux One target OS in single-target mode.
|
|
# ARTEX_TARGET_ARCH=amd64 One target arch in single-target mode.
|
|
# ARTEX_TARGETS=linux/amd64,... Comma-separated targets for multi-target mode.
|
|
# ARTEX_BUILD_VERSION=v0.3.3 Version embedded in the binary and archive name.
|
|
# ARTEX_OUTPUT=/path/to/artex Explicit binary path in single-target mode.
|
|
# ARTEX_OUTPUT_DIR=dist Directory for default binary paths.
|
|
# ARTEX_PACKAGE=1 Create a zip archive for each target.
|
|
# ARTEX_PACKAGE_DIR=dist Directory for release archives.
|
|
# ARTEX_COMPRESS=off UPX mode: off, auto, or required.
|
|
# ARTEX_UPX_ARGS="--best --lzma" Arguments passed to UPX.
|
|
# ARTEX_SKIP_FRONTEND=1 Reuse server/webui/dist (for CI artifact builds).
|
|
# ARTEX_SKIP_NPM_CI=1 Skip npm ci while rebuilding the frontend.
|
|
# ARTEX_GOSUMDB=sum.golang.org Go checksum database.
|
|
set -euo pipefail
|
|
|
|
cd "$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
info() { printf '\033[36m[*]\033[0m %s\n' "$*"; }
|
|
ok() { printf '\033[32m[+]\033[0m %s\n' "$*"; }
|
|
warn() { printf '\033[33m[!]\033[0m %s\n' "$*" >&2; }
|
|
die() { printf '\033[31m[x]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
사용법:
|
|
./build.sh 현재 시스템·현재 아키텍처로 컴파일
|
|
./build.sh --target linux/amd64 지정한 대상 하나를 컴파일
|
|
./build.sh --release 지원하는 모든 대상을 컴파일하고 패키징
|
|
|
|
옵션:
|
|
--release Linux, macOS, Windows 의 amd64/arm64 대상을 빌드하고 zip 생성
|
|
--target OS/ARCH 단일 대상을 지정합니다. 예: windows/amd64
|
|
--upx UPX 로 바이너리를 강제 압축합니다(일부 Linux 환경에서 호환성에 영향을 줄 수 있습니다)
|
|
--no-compress UPX 를 쓰지 않고 Go linker 로만 축소한 뒤 zip 을 압축
|
|
--help 도움말 표시
|
|
|
|
여러 대상 목록은 ARTEX_TARGETS 로 재정의할 수 있습니다. 예:
|
|
ARTEX_TARGETS=linux/amd64,windows/amd64 ./build.sh --release
|
|
EOF
|
|
}
|
|
|
|
RELEASE_TARGETS_DEFAULT="linux/amd64,linux/arm64,darwin/amd64,darwin/arm64,windows/amd64"
|
|
ARTEX_RELEASE="${ARTEX_RELEASE:-0}"
|
|
ARTEX_COMPRESS="${ARTEX_COMPRESS:-off}"
|
|
ARTEX_PACKAGE="${ARTEX_PACKAGE:-0}"
|
|
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
--release)
|
|
ARTEX_RELEASE=1
|
|
ARTEX_PACKAGE=1
|
|
shift
|
|
;;
|
|
--target)
|
|
[ "$#" -ge 2 ] || die "--target 에는 OS/ARCH 인자가 필요합니다"
|
|
target_arg="$2"
|
|
case "$target_arg" in
|
|
*/*)
|
|
ARTEX_TARGET_OS="${target_arg%%/*}"
|
|
ARTEX_TARGET_ARCH="${target_arg##*/}"
|
|
ARTEX_TARGETS="$target_arg"
|
|
;;
|
|
*) die "대상은 OS/ARCH 형식이어야 합니다. 예: linux/amd64" ;;
|
|
esac
|
|
shift 2
|
|
;;
|
|
--no-compress)
|
|
ARTEX_COMPRESS=0
|
|
shift
|
|
;;
|
|
--upx)
|
|
ARTEX_COMPRESS=required
|
|
shift
|
|
;;
|
|
--help|-h)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*) die "알 수 없는 인자입니다: $1(사용법은 --help 로 확인하세요)" ;;
|
|
esac
|
|
done
|
|
|
|
command -v go >/dev/null 2>&1 || die "Go 를 찾을 수 없습니다(이 프로젝트는 Go 1.26 이상이 필요합니다)"
|
|
|
|
ARTEX_GOSUMDB="${ARTEX_GOSUMDB:-sum.golang.org}"
|
|
if [ -z "${ARTEX_BUILD_VERSION:-}" ]; then
|
|
if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
ARTEX_BUILD_VERSION="$(git describe --tags --always --dirty)"
|
|
else
|
|
ARTEX_BUILD_VERSION="dev"
|
|
fi
|
|
fi
|
|
# Release tags are commonly passed as v0.3.3; keep the binary version consistent.
|
|
ARTEX_BUILD_VERSION="${ARTEX_BUILD_VERSION#v}"
|
|
ARTEX_OUTPUT_DIR="${ARTEX_OUTPUT_DIR:-dist}"
|
|
ARTEX_PACKAGE_DIR="${ARTEX_PACKAGE_DIR:-$ARTEX_OUTPUT_DIR}"
|
|
ARTEX_UPX_ARGS="${ARTEX_UPX_ARGS:---best --lzma}"
|
|
|
|
if [ "${ARTEX_RELEASE}" = "1" ]; then
|
|
ARTEX_TARGETS="${ARTEX_TARGETS:-$RELEASE_TARGETS_DEFAULT}"
|
|
else
|
|
ARTEX_TARGET_OS="${ARTEX_TARGET_OS:-$(GOSUMDB="$ARTEX_GOSUMDB" go env GOOS)}"
|
|
ARTEX_TARGET_ARCH="${ARTEX_TARGET_ARCH:-$(GOSUMDB="$ARTEX_GOSUMDB" go env GOARCH)}"
|
|
ARTEX_TARGETS="${ARTEX_TARGETS:-${ARTEX_TARGET_OS}/${ARTEX_TARGET_ARCH}}"
|
|
fi
|
|
|
|
if [ "${ARTEX_SKIP_FRONTEND:-0}" = "1" ]; then
|
|
[ -d server/webui/dist ] || die "ARTEX_SKIP_FRONTEND=1 이지만 server/webui/dist 가 없습니다"
|
|
else
|
|
command -v npm >/dev/null 2>&1 || die "npm 을 찾을 수 없습니다(프런트엔드 정적 빌드에는 Node.js/npm 이 필요합니다)"
|
|
command -v rsync >/dev/null 2>&1 || die "rsync 를 찾을 수 없습니다"
|
|
info "프런트엔드 정적 리소스를 빌드합니다"
|
|
if [ "${ARTEX_SKIP_NPM_CI:-0}" != "1" ]; then
|
|
(cd web && npm ci)
|
|
fi
|
|
(cd web && npm run build:static)
|
|
info "프런트엔드 리소스를 server/webui/dist 로 동기화합니다"
|
|
mkdir -p server/webui/dist
|
|
rsync -a --delete web/out/ server/webui/dist/
|
|
fi
|
|
|
|
compress_binary() {
|
|
binary="$1"
|
|
goos="$2"
|
|
case "$ARTEX_COMPRESS" in
|
|
0|off|false|none)
|
|
info "UPX 를 건너뜁니다: $binary"
|
|
return 0
|
|
;;
|
|
auto|required|true|1) ;;
|
|
*) die "ARTEX_COMPRESS 는 off, auto, required 중 하나여야 합니다" ;;
|
|
esac
|
|
|
|
if ! command -v upx >/dev/null 2>&1; then
|
|
if [ "$ARTEX_COMPRESS" = "required" ]; then
|
|
die "ARTEX_COMPRESS=required 이지만 upx 를 찾을 수 없습니다"
|
|
fi
|
|
warn "upx 를 찾을 수 없어 linker 압축 결과를 그대로 둡니다: $binary"
|
|
return 0
|
|
fi
|
|
|
|
before=$(wc -c < "$binary" | tr -d ' ')
|
|
upx_args="$ARTEX_UPX_ARGS"
|
|
[ "$goos" = "darwin" ] && upx_args="$upx_args --force-macos"
|
|
# shellcheck disable=SC2086
|
|
if ! upx $upx_args -- "$binary"; then
|
|
if [ "$ARTEX_COMPRESS" = "required" ]; then
|
|
die "UPX 압축에 실패했습니다: $binary"
|
|
fi
|
|
warn "UPX 가 이 대상 형식을 지원하지 않아 압축하지 않은 바이너리를 그대로 둡니다: $binary"
|
|
return 0
|
|
fi
|
|
after=$(wc -c < "$binary" | tr -d ' ')
|
|
ok "UPX 압축 완료: $binary (${before} -> ${after} bytes)"
|
|
}
|
|
|
|
package_binary() {
|
|
binary="$1"
|
|
goos="$2"
|
|
goarch="$3"
|
|
package_name="artex-${ARTEX_BUILD_VERSION}-${goos}-${goarch}"
|
|
package_root="${ARTEX_PACKAGE_DIR}/${package_name}"
|
|
archive="${ARTEX_PACKAGE_DIR}/${package_name}.zip"
|
|
|
|
command -v zip >/dev/null 2>&1 || die "패키징에는 zip 이 필요합니다"
|
|
rm -rf "$package_root" "$archive"
|
|
mkdir -p "$package_root"
|
|
cp "$binary" "$package_root/"
|
|
# 데몬 시작 스크립트가 정식 진입점입니다. 화면의 원클릭 업데이트는 프로세스가 종료된 뒤 이 스크립트가 다시 띄워 줘야 동작하고,
|
|
# artex 본체를 직접 실행하면 업데이트 후 다시 기동되지 않습니다. 대상 시스템에 맞는 한 벌만 포함합니다.
|
|
if [ "$goos" = "windows" ]; then
|
|
cp start.bat "$package_root/"
|
|
else
|
|
cp start.sh "$package_root/"
|
|
chmod +x "$package_root/start.sh"
|
|
fi
|
|
cp -R skills "$package_root/"
|
|
cp config.example.json "$package_root/"
|
|
if [ -f README.md ]; then cp README.md "$package_root/"; fi
|
|
(cd "$ARTEX_PACKAGE_DIR" && zip -q -r -9 "$(basename "$archive")" "$(basename "$package_root")")
|
|
rm -rf "$package_root"
|
|
ok "릴리스 압축 파일: $archive"
|
|
}
|
|
|
|
build_target() {
|
|
target="$1"
|
|
case "$target" in
|
|
*/*) ;;
|
|
*) die "잘못된 대상입니다: $target(OS/ARCH 형식이어야 합니다)" ;;
|
|
esac
|
|
goos="${target%%/*}"
|
|
goarch="${target##*/}"
|
|
case "$goos" in
|
|
linux|darwin|windows) ;;
|
|
*) die "지원하지 않는 시스템입니다: $goos(linux, darwin, windows 를 지원합니다)" ;;
|
|
esac
|
|
|
|
binary_name="artex"
|
|
[ "$goos" = "windows" ] && binary_name="artex.exe"
|
|
if [ -n "${ARTEX_OUTPUT:-}" ] && [ "$ARTEX_RELEASE" != "1" ]; then
|
|
output="$ARTEX_OUTPUT"
|
|
else
|
|
output="${ARTEX_OUTPUT_DIR}/artex-${goos}-${goarch}/${binary_name}"
|
|
fi
|
|
mkdir -p "$(dirname "$output")"
|
|
|
|
info "${goos}/${goarch} 컴파일, 버전 ${ARTEX_BUILD_VERSION}"
|
|
GOSUMDB="$ARTEX_GOSUMDB" \
|
|
CGO_ENABLED=0 \
|
|
GOOS="$goos" \
|
|
GOARCH="$goarch" \
|
|
go build \
|
|
-tags embedui \
|
|
-trimpath \
|
|
-ldflags "-s -w -buildid= -X main.version=${ARTEX_BUILD_VERSION}" \
|
|
-o "$output" \
|
|
./cmd/artex
|
|
|
|
compress_binary "$output" "$goos"
|
|
if command -v file >/dev/null 2>&1; then file "$output"; fi
|
|
if [ "$ARTEX_PACKAGE" = "1" ]; then package_binary "$output" "$goos" "$goarch"; fi
|
|
ok "컴파일 완료: $output"
|
|
}
|
|
|
|
write_checksums() {
|
|
[ "$ARTEX_PACKAGE" = "1" ] || return 0
|
|
checksum_file="$ARTEX_PACKAGE_DIR/SHA256SUMS"
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
(cd "$ARTEX_PACKAGE_DIR" && for archive in *.zip; do sha256sum "$archive"; done > "$(basename "$checksum_file")")
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
(cd "$ARTEX_PACKAGE_DIR" && for archive in *.zip; do shasum -a 256 "$archive"; done > "$(basename "$checksum_file")")
|
|
else
|
|
warn "sha256sum 또는 shasum 을 찾을 수 없어 SHA256SUMS 를 건너뜁니다"
|
|
return 0
|
|
fi
|
|
ok "체크섬 파일: $checksum_file"
|
|
}
|
|
|
|
mkdir -p "$ARTEX_OUTPUT_DIR"
|
|
if [ "$ARTEX_PACKAGE" = "1" ]; then mkdir -p "$ARTEX_PACKAGE_DIR"; fi
|
|
|
|
old_ifs="$IFS"
|
|
IFS=','
|
|
read -r -a targets <<< "$ARTEX_TARGETS"
|
|
IFS="$old_ifs"
|
|
[ "${#targets[@]}" -gt 0 ] || die "ARTEX_TARGETS 는 비워 둘 수 없습니다"
|
|
for target in "${targets[@]}"; do
|
|
target="${target//[[:space:]]/}"
|
|
[ -n "$target" ] || continue
|
|
build_target "$target"
|
|
done
|
|
|
|
if [ "$ARTEX_PACKAGE" = "1" ]; then
|
|
write_checksums
|
|
info "릴리스 패키지를 생성했습니다: $ARTEX_PACKAGE_DIR"
|
|
fi
|