From 91590ec08d25725d8d13254077beb6c9bd10a8fb Mon Sep 17 00:00:00 2001 From: burnintuna Date: Tue, 21 Jul 2026 02:48:15 +0900 Subject: [PATCH] Add AppImage install and update bundle --- .gitignore | 2 + docs/INSTALL.ko.md | 37 +++++++++++- docs/RELEASING.ko.md | 35 +++++++++--- packaging/build-release-bundle.sh | 55 ++++++++++++++++++ packaging/install-or-update.sh | 93 +++++++++++++++++++++++++++++++ 5 files changed, 211 insertions(+), 11 deletions(-) create mode 100755 packaging/build-release-bundle.sh create mode 100755 packaging/install-or-update.sh diff --git a/.gitignore b/.gitignore index 6ce25f6..e2050fd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ /.appimage-tools/ *.AppImage *.AppImage.zsync +/dist/*.tar.gz +/dist/*.tar.gz.sha256 diff --git a/docs/INSTALL.ko.md b/docs/INSTALL.ko.md index 9fe1ad8..86256bc 100644 --- a/docs/INSTALL.ko.md +++ b/docs/INSTALL.ko.md @@ -15,7 +15,38 @@ sudo pacman -S fuse2 gvfs gvfs-smb - `gvfs`: FTP와 SFTP 접근 - `gvfs-smb`: SMB 공유 접근 -## 2. 다운로드 검증 +## 2. 권장 방법: 통합 번들로 설치 또는 업데이트 + +Release 페이지에서 `Comicviewer-0.1.0-x86_64.tar.gz`와 같은 최신 버전의 +통합 번들을 다운로드합니다. 압축을 풀고 설치 스크립트를 실행합니다. + +```sh +tar -xzf Comicviewer-0.1.0-x86_64.tar.gz +cd Comicviewer-0.1.0-x86_64 +./install-or-update.sh +``` + +스크립트는 AppImage 체크섬을 검증하고 다음 작업을 수행합니다. + +- AppImage를 `~/.local/opt/comicviewer/Comicviewer.AppImage`에 설치 +- `~/.local/bin/comicviewer` 실행 링크 생성 +- 애플리케이션 메뉴용 데스크톱 파일과 아이콘 설치 +- 기존 버전이 있으면 검증된 새 AppImage로 원자적으로 교체 + +이후 재배포된 새 번들을 다운로드해 같은 명령을 실행하면 업데이트됩니다. +사용자 설정, 읽기 기록, 원격 ZIP 캐시는 그대로 유지됩니다. + +제거하려면 압축을 푼 번들 폴더에서 다음을 실행합니다. + +```sh +./install-or-update.sh --uninstall +``` + +이 명령은 프로그램과 함께 저장한 위치 및 인증정보, 읽기 기록, 원격 ZIP 캐시를 +모두 삭제합니다. 삭제한 데이터는 복구할 수 없으므로 필요한 설정은 제거 전에 +별도로 백업합니다. + +## 3. AppImage만 다운로드하는 방법 Release 페이지에서 다음 두 파일을 같은 폴더에 다운로드합니다. @@ -31,7 +62,7 @@ sha256sum -c Comicviewer-0.1.0-x86_64.AppImage.sha256 `성공` 또는 `OK`가 표시되어야 합니다. 검증에 실패하면 파일을 실행하지 말고 다시 다운로드합니다. -## 3. 실행 +## 4. 직접 실행 AppImage에 실행 권한을 부여한 뒤 실행합니다. @@ -47,7 +78,7 @@ FUSE를 사용할 수 없는 환경에서는 다음과 같이 임시 추출 방 ./Comicviewer-0.1.0-x86_64.AppImage --appimage-extract-and-run ``` -## 4. 선택 사항: 사용자 계정에 설치 +## 5. 선택 사항: 수동으로 사용자 계정에 설치 AppImage를 사용자 전용 프로그램 폴더에 설치하고 명령어로 실행하려면 다음을 사용합니다. diff --git a/docs/RELEASING.ko.md b/docs/RELEASING.ko.md index f218f53..b949d4a 100644 --- a/docs/RELEASING.ko.md +++ b/docs/RELEASING.ko.md @@ -39,15 +39,31 @@ LINUXDEPLOY="/path/to/appimage-tools/linuxdeploy-x86_64.AppImage" \ 완성된 AppImage는 `dist/`에 생성됩니다. 배포 전에 직접 실행해 기본 화면이 열리는지 확인합니다. -## 4. 체크섬 생성 +## 4. 통합 배포 번들 생성 -아래 예시의 버전은 실제 릴리스 버전으로 변경합니다. +AppImage 빌드가 끝나면 다음 스크립트로 체크섬과 통합 `tar.gz` 번들을 한 번에 +생성합니다. ```sh -version=0.1.0 +./packaging/build-release-bundle.sh +``` + +스크립트는 `Cargo.toml`에서 버전을 읽고 다음 파일을 생성합니다. + +- `dist/Comicviewer-0.1.0-x86_64.AppImage.sha256` +- `dist/Comicviewer-0.1.0-x86_64.tar.gz` +- `dist/Comicviewer-0.1.0-x86_64.tar.gz.sha256` + +통합 번들에는 AppImage, 체크섬, 설치·업데이트 스크립트, 데스크톱 파일, 아이콘, +한국어 설치 안내문이 포함됩니다. `tar.gz`는 실행 권한과 디렉터리 구조를 +보존하므로 ZIP보다 Linux 배포에 적합합니다. + +생성된 번들의 압축 목록과 체크섬을 확인합니다. + +```sh +tar -tzf dist/Comicviewer-0.1.0-x86_64.tar.gz cd dist -sha256sum "Comicviewer-${version}-x86_64.AppImage" \ - > "Comicviewer-${version}-x86_64.AppImage.sha256" +sha256sum -c Comicviewer-0.1.0-x86_64.tar.gz.sha256 ``` ## 5. 소스 커밋과 태그 @@ -71,6 +87,8 @@ git push origin v0.1.0 Git 서버의 Releases 화면에서 푸시한 태그를 선택해 Release를 생성하고 다음 파일을 첨부합니다. +- `Comicviewer-0.1.0-x86_64.tar.gz` +- `Comicviewer-0.1.0-x86_64.tar.gz.sha256` - `Comicviewer-0.1.0-x86_64.AppImage` - `Comicviewer-0.1.0-x86_64.AppImage.sha256` @@ -80,9 +98,10 @@ Release 설명에는 주요 변경 사항과 함께 다음 요구사항을 안 지원 환경: CachyOS / Arch Linux x86_64 필수 패키지: fuse2, gvfs, gvfs-smb -실행: -chmod +x Comicviewer-0.1.0-x86_64.AppImage -./Comicviewer-0.1.0-x86_64.AppImage +권장 설치 또는 업데이트: +tar -xzf Comicviewer-0.1.0-x86_64.tar.gz +cd Comicviewer-0.1.0-x86_64 +./install-or-update.sh ``` 사용자에게는 [`INSTALL.ko.md`](INSTALL.ko.md) 문서를 함께 안내합니다. diff --git a/packaging/build-release-bundle.sh b/packaging/build-release-bundle.sh new file mode 100755 index 0000000..39b44bd --- /dev/null +++ b/packaging/build-release-bundle.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +package_id="$(cargo pkgid --manifest-path "${root_dir}/Cargo.toml")" +version="${package_id##*@}" +bundle_name="Comicviewer-${version}-x86_64" +appimage_name="${bundle_name}.AppImage" +appimage="${root_dir}/dist/${appimage_name}" +checksum="${appimage}.sha256" +archive="${root_dir}/dist/${bundle_name}.tar.gz" +archive_checksum="${archive}.sha256" +work_dir="$(mktemp -d)" +bundle_dir="${work_dir}/${bundle_name}" +archive_mtime="${SOURCE_DATE_EPOCH:-$(git -C "${root_dir}" log -1 --format=%ct)}" + +cleanup() { + rm -rf "${work_dir}" +} +trap cleanup EXIT + +if [[ ! -f "${appimage}" ]]; then + printf 'AppImage가 없습니다: %s\n' "${appimage}" >&2 + printf '%s\n' "먼저 packaging/build-appimage.sh를 실행하세요." >&2 + exit 1 +fi + +mkdir -p "${bundle_dir}" +( + cd "${root_dir}/dist" + sha256sum "${appimage_name}" > "${appimage_name}.sha256" +) + +install -m 0755 "${appimage}" "${bundle_dir}/${appimage_name}" +install -m 0644 "${checksum}" "${bundle_dir}/${appimage_name}.sha256" +install -m 0755 \ + "${root_dir}/packaging/install-or-update.sh" \ + "${bundle_dir}/install-or-update.sh" +install -m 0644 \ + "${root_dir}/data/io.github.comicviewer.Comicviewer.desktop" \ + "${bundle_dir}/io.github.comicviewer.Comicviewer.desktop" +install -m 0644 \ + "${root_dir}/data/io.github.comicviewer.Comicviewer.svg" \ + "${bundle_dir}/io.github.comicviewer.Comicviewer.svg" +install -m 0644 "${root_dir}/docs/INSTALL.ko.md" "${bundle_dir}/README.ko.md" + +tar --sort=name --owner=0 --group=0 --numeric-owner --mtime="@${archive_mtime}" \ + -C "${work_dir}" -cf - "${bundle_name}" | gzip -n > "${archive}" +( + cd "${root_dir}/dist" + sha256sum "$(basename "${archive}")" > "$(basename "${archive_checksum}")" +) + +printf 'Created %s\n' "${archive}" +printf 'Created %s\n' "${archive_checksum}" diff --git a/packaging/install-or-update.sh b/packaging/install-or-update.sh new file mode 100755 index 0000000..83e3215 --- /dev/null +++ b/packaging/install-or-update.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env sh +set -eu + +app_id="io.github.comicviewer.Comicviewer" +script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +install_dir="${HOME}/.local/opt/comicviewer" +bin_dir="${HOME}/.local/bin" +data_home="${XDG_DATA_HOME:-${HOME}/.local/share}" +config_home="${XDG_CONFIG_HOME:-${HOME}/.config}" +cache_home="${XDG_CACHE_HOME:-${HOME}/.cache}" +applications_dir="${data_home}/applications" +icons_dir="${data_home}/icons/hicolor/scalable/apps" +installed_appimage="${install_dir}/Comicviewer.AppImage" +launcher="${bin_dir}/comicviewer" +installed_desktop="${applications_dir}/${app_id}.desktop" +installed_icon="${icons_dir}/${app_id}.svg" +temporary_appimage="" +temporary_desktop="" + +cleanup() { + [ -z "${temporary_appimage}" ] || rm -f "${temporary_appimage}" + [ -z "${temporary_desktop}" ] || rm -f "${temporary_desktop}" +} +trap cleanup EXIT HUP INT TERM + +refresh_desktop() { + if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database "${applications_dir}" >/dev/null 2>&1 || true + fi +} + +if [ "${1:-}" = "--uninstall" ]; then + rm -f "${launcher}" "${installed_desktop}" "${installed_icon}" \ + "${installed_appimage}" + rm -rf "${config_home}/comicviewer" "${data_home}/comicviewer" \ + "${cache_home}/comicviewer" + rmdir "${install_dir}" 2>/dev/null || true + refresh_desktop + printf '%s\n' "Comicviewer와 설정, 읽기 기록, 캐시를 모두 제거했습니다." + exit 0 +fi + +if [ "$#" -ne 0 ]; then + printf '사용법: %s [--uninstall]\n' "$(basename -- "$0")" >&2 + exit 2 +fi + +set -- "${script_dir}"/Comicviewer-*-x86_64.AppImage +if [ "$#" -ne 1 ] || [ ! -f "$1" ]; then + printf '%s\n' "번들에서 Comicviewer AppImage 한 개를 찾을 수 없습니다." >&2 + exit 1 +fi + +appimage="$1" +checksum="${appimage}.sha256" +desktop_file="${script_dir}/${app_id}.desktop" +icon_file="${script_dir}/${app_id}.svg" + +for required_file in "${checksum}" "${desktop_file}" "${icon_file}"; do + if [ ! -f "${required_file}" ]; then + printf '필수 파일이 없습니다: %s\n' "$(basename -- "${required_file}")" >&2 + exit 1 + fi +done + +(cd "${script_dir}" && sha256sum -c "$(basename -- "${checksum}")") + +mkdir -p "${install_dir}" "${bin_dir}" "${applications_dir}" "${icons_dir}" +temporary_appimage="${install_dir}/.Comicviewer.AppImage.tmp.$$" +temporary_desktop="${applications_dir}/.${app_id}.desktop.tmp.$$" + +install -m 0755 "${appimage}" "${temporary_appimage}" +mv -f "${temporary_appimage}" "${installed_appimage}" +temporary_appimage="" +ln -sfn "${installed_appimage}" "${launcher}" +while IFS= read -r line || [ -n "${line}" ]; do + case "${line}" in + Exec=*) printf 'Exec="%s"\n' "${launcher}" ;; + *) printf '%s\n' "${line}" ;; + esac +done < "${desktop_file}" > "${temporary_desktop}" +chmod 0644 "${temporary_desktop}" +mv -f "${temporary_desktop}" "${installed_desktop}" +temporary_desktop="" +install -m 0644 "${icon_file}" "${installed_icon}" +refresh_desktop + +if ! command -v gio >/dev/null 2>&1; then + printf '%s\n' "주의: 원격 파일 접근을 위해 gvfs와 gvfs-smb를 설치하세요." >&2 +fi + +printf '%s\n' "Comicviewer 설치 또는 업데이트를 완료했습니다." +printf '실행: %s\n' "${launcher}"