Skip to content

기능: hwp update 자체 업데이트 + brew 없이 한 줄 설치 스크립트 - #25

Merged
entelecheia merged 1 commit into
mainfrom
feat/self-update
Jul 26, 2026
Merged

기능: hwp update 자체 업데이트 + brew 없이 한 줄 설치 스크립트#25
entelecheia merged 1 commit into
mainfrom
feat/self-update

Conversation

@entelecheia

Copy link
Copy Markdown
Member

릴리스 아카이브를 직접 받아 쓰는 사용자는 갱신 수단이 손밖에 없었다. 설치와 갱신을 둘 다 명령 하나로 끝낸다.

설치 (brew 없이)

curl -fsSL https://raw.githubusercontent.com/STAIxBWLB/hwp-cli/main/scripts/install.sh | sh

기본 ~/.local/bin, --dir·--tag(또는 HWP_INSTALL_DIR·HWP_TAG)로 변경. sha256 대조 후에만 설치하고, PATH에 없으면 안내를 출력한다.

갱신

hwp update [--check] [--tag vX.Y.Z] [--force] [--json]

설치 방식을 스스로 판별한다:

설치 방식 동작
스크립트·릴리스 아카이브·cargo install 아카이브를 받아 실행 중인 바이너리를 제자리 교체
Homebrew(Cellar) brew upgrade hwp 위임 — 직접 덮어쓰면 brew 매니페스트와 어긋나 다음 upgrade가 조용히 되돌린다. brew는 버전 고정 불가라 --tag 거부

설계

새 런타임 크레이트 0개. 네트워크는 curl, 압축 해제는 tar에 위임하고 전부 인자 벡터로 실행한다(셸 문자열 조립 없음). sha2만 직접 의존으로 올렸는데 zip이 이미 끌어오던 크레이트라 Cargo.lock은 1줄만 늘었다.

순수 로직(버전 비교·타깃 매핑·자산 이름·설치 방식 판별·파일 교체)과 부작용(curl/tar 호출)을 분리해 네트워크 없이 단위 테스트한다.

안전장치

  • 태그는 URL·파일명에 닿기 전 v?X.Y.Z 형식 검증 (경로 탈출·질의 주입 차단)
  • 아카이브는 .sha256 자산과 대조한 뒤에만 해제
  • 교체는 대상과 같은 디렉터리에 임시 파일을 만들어 rename (원자적, 실패 시 원본 복원)
  • 교체 후 새 바이너리의 --version으로 확인, 어긋나면 실패 처리
  • curl은 --proto '=https' --tlsv1.2 -f

검증

  • 단위 테스트 10종, 전부 오프라인: 버전 비교(프리릴리스·두 자리 버전), 타깃 매핑, 자산 이름, 태그 검증, 설치 방식 판별, 바이너리 교체 + 실패 시 원본 보존, 체크섬 대조, 아카이브 해제, install.shupdate 자산 규칙 동기화(한쪽만 바뀌면 릴리스 후에야 404로 드러나는 결함 차단).
  • scripts/check.sh 통과, docs/manual/cli-reference.md 자동 재생성.
  • 실제 릴리스로 실기 확인: --check/--json, v0.2.0 롤백, --force 재설치, 이미 최신 분기, brew 감지 + --tag 거부, install.sh 설치 → 그 설치본의 자가 업데이트.

참고

hwp update는 이 PR이 들어간 다음 릴리스(0.4.0)부터 존재하므로, 그 이전 버전은 설치 스크립트로 한 번만 갱신하면 된다. Windows는 릴리스 zip을 쓴다(스크립트는 POSIX 셸 전용, update는 Windows도 동작).

릴리스 아카이브를 직접 받아 쓰는 사용자는 갱신 수단이 손밖에 없었다. 설치와 갱신을
둘 다 명령 하나로 끝낸다.

- `hwp update [--check] [--tag vX.Y.Z] [--force] [--json]`
  설치 방식을 스스로 판별한다. 직접 설치본(스크립트·아카이브·cargo install)은 릴리스
  아카이브를 받아 실행 중인 바이너리를 제자리 교체하고, Homebrew 설치본은
  `brew upgrade hwp`에 위임한다(Cellar를 덮어쓰면 brew 매니페스트와 어긋나 다음
  upgrade가 조용히 되돌린다). brew는 버전 고정이 안 되므로 --tag는 거부한다.
- `scripts/install.sh` — curl 한 줄 설치(기본 ~/.local/bin, --dir·--tag 지원).
  설치본은 이후 `hwp update`로 갱신된다.

설계: 새 런타임 크레이트 0개. 네트워크는 curl, 압축 해제는 tar에 위임하고 전부 인자
벡터로 실행한다(셸 문자열 조립 없음). sha2만 직접 의존으로 올렸는데 zip이 이미 끌어오던
크레이트라 의존성 그래프는 그대로다.

안전장치: 태그는 URL·파일명에 닿기 전 `v?X.Y.Z` 형식 검증(경로 탈출 차단), 아카이브는
sha256 대조 후에만 해제, 교체는 대상과 같은 디렉터리에서 rename(실패 시 원본 복원),
교체 후 새 바이너리의 --version으로 확인.

테스트 10종(전부 오프라인): 버전 비교(프리릴리스·두 자리 버전 포함), 타깃 매핑, 자산
이름, 태그 검증, 설치 방식 판별, 바이너리 교체·실패 복원, 체크섬 대조, 아카이브 해제,
install.sh↔update 자산 규칙 동기화(한쪽만 바뀌면 릴리스 후 404로 드러나는 것 방지).

실기 확인: --check/--json, v0.2.0 롤백, --force 재설치, 이미 최신, brew 감지·--tag 거부,
install.sh 설치 후 자가 업데이트까지 실제 릴리스로 통과.
Copilot AI review requested due to automatic review settings July 26, 2026 22:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0d92ddee3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +397 to +400
replace_binary(&exe, &new_exe)?;
let _ = std::fs::remove_dir_all(&work);

let ver = verify_installed(&exe, &latest)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the old executable until validation succeeds

When a checksum-valid release binary cannot run on the host—for example because the Linux build requires a newer libc—or reports an unexpected version, replace_binary has already removed its backup before verify_installed runs. The update then returns an error while leaving the unusable new executable installed and the previously working binary lost; retain the backup through validation and restore it on validation failure.

Useful? React with 👍 / 👎.

Comment on lines +341 to +346
if latest == current && !force {
return report(
json,
json!({"current": current, "latest": latest, "updated": false, "reason": "already-latest"}),
&format!("이미 {current} 입니다 (다시 받으려면 --force)"),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid default downgrades when no tag was requested

When the installed version is newer than GitHub's latest release, such as a prerelease or a build installed from unreleased main, available is false but this equality-only guard still proceeds to download and install the older release. Rollbacks are documented as an explicit --tag operation, so the untagged path should stop whenever the fetched version is not newer unless --force explicitly permits otherwise.

Useful? React with 👍 / 👎.

/// 태그가 `v?X.Y.Z[-pre]` 꼴인지 확인한다. URL·파일명에 그대로 들어가므로
/// 경로 탈출(`../`)·질의 문자열 주입을 여기서 막는다.
pub fn validate_tag(tag: &str) -> Result<()> {
let body = tag.strip_prefix('v').unwrap_or(tag);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize accepted unprefixed tags before constructing URLs

For hwp update --tag 0.3.0, validation deliberately accepts the value after treating v as optional, but target_tag later remains 0.3.0, producing release and asset URLs such as /download/0.3.0/hwp-0.3.0-.... The release workflow publishes v0.3.0 and hwp-v0.3.0-..., so this accepted CLI input consistently ends in a 404; either require the prefix or add it before constructing names and URLs.

Useful? React with 👍 / 👎.

Comment on lines +359 to +363
let status = Command::new("brew")
.args(["upgrade", BIN])
.status()
.map_err(|e| anyhow!("brew 실행 실패({e}) — 직접 `brew upgrade {BIN}`을 실행하세요"))?;
if !status.success() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify the installed version after delegating to Homebrew

When a GitHub release exists but the update-formula job in .github/workflows/release.yml is still running or has failed—as that workflow explicitly allows—brew upgrade hwp can succeed without installing target_tag. This branch nevertheless reports updated: true and says the latest version was installed; run the resulting executable's --version or otherwise confirm the formula version before reporting success.

Useful? React with 👍 / 👎.

@entelecheia
entelecheia merged commit 36314b7 into main Jul 26, 2026
3 checks passed
@entelecheia
entelecheia deleted the feat/self-update branch July 26, 2026 23:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants