commit 034c734346e0deb2c0bc515e42b5602f49cad1f1 Author: root2 Date: Fri May 29 13:32:14 2026 +0000 초기 commit — onboard-linux-node.sh + install-eu-windows.ps1 + README diff --git a/README.md b/README.md new file mode 100644 index 0000000..3c30884 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# washtime/tools + +자체 운영 도구 / 셋업 자동화 스크립트 모음. + +이 repo 는 **public** — 누구나 raw URL 로 받을 수 있다 (인증 키 등 비밀은 절대 commit 금지). + +--- + +## 새 리눅스 서버 onboarding + +새 노드 (오라클 프리티어 / 라즈베리파이 / 다른 PC) 에서: + +```bash +bash <(curl -fsSL http://192.168.1.40:3000/washtime/tools/raw/branch/main/onboard-linux-node.sh) +``` + +(외부망에서는 LAN IP 안 보임 → 플린트3 도착 + DDNS 셋업 후 `git.washtime.xxx` 로 교체.) + +스크립트가 인터랙티브로 물어보는 것: +- Gitea host (default: `192.168.1.40`) +- admin API token (`Washtime_GitAdmin` 의 `admin-write` 토큰 — Web UI 의 `Settings → Applications → Generate New Token` 에서 발급) +- 노드 이름 (예: `oracle-jp-1`) +- Clone 할 repo (콤마 구분, 예: `one,family-viewer,tools`) + +자동 처리: +- ed25519 SSH key 생성 + Gitea 에 자동 등록 +- `~/.ssh/config` 에 `gitea` 별칭 추가 +- repo clone +- 1분 주기 auto-pull systemd timer 설치 + +--- + +## 새 Windows PC 에 External Uploader 설치 + +관리자 PowerShell 에서: + +```powershell +iwr -useb http://192.168.1.40:3000/washtime/tools/raw/branch/main/install-eu-windows.ps1 | iex +``` + +(외부망에서는 위 URL 의 host 를 외부 도메인으로 교체.) + +자동 처리: +- 최신 installer (`ExternalUploader-Setup-latest.exe`, 510MB) 자동 다운로드 +- SHA256 + 크기 검증 +- installer 실행 (GUI) +- 설치 완료 후 daemon 이 백그라운드에서 자동 업데이트 처리 — 사용자 액션 더 이상 X + +--- + +## 기타 도구 + +(추후 추가될 운영 자동화 스크립트 — DB 마이그레이션 헬퍼, 백업 검증, 헬스 체크 등) diff --git a/install-eu-windows.ps1 b/install-eu-windows.ps1 new file mode 100644 index 0000000..e467197 --- /dev/null +++ b/install-eu-windows.ps1 @@ -0,0 +1,84 @@ +# washtime External Uploader — Windows 자동 설치 +# 한 줄 실행 (PowerShell 관리자): +# iwr -useb http://192.168.1.40:3000/washtime/tools/raw/branch/main/install-eu-windows.ps1 | iex +# +# 동작: +# 1) 최신 installer .exe 다운로드 (washtime 서버에서) +# 2) 다운로드 검증 (SHA256, 옵션) +# 3) 자동 실행 (silent 또는 GUI) +# 4) 설치 후 auto_updater 가 자동으로 차후 업데이트 처리 +# +# 이후 사용자는 더 이상 액션 불필요 — daemon 이 백그라운드에서 publisher-certs API 보고 +# 자동으로 새 버전 다운로드 + helper.exe 가 swap 처리. + +$ErrorActionPreference = 'Stop' + +# ───── 설정 ───── +$installerUrl = 'http://washtime.asuscomm.com:5055/downloads/ExternalUploader-Setup-latest.exe' +$installerLocal = "$env:TEMP\ExternalUploader-Setup.exe" +$installMode = 'gui' # 'gui' = NSIS GUI / 'silent' = /S 무인설치 + +Write-Host '' +Write-Host '════════════════════════════════════════════════════════' -ForegroundColor Cyan +Write-Host ' washtime External Uploader 자동 설치' -ForegroundColor Cyan +Write-Host '════════════════════════════════════════════════════════' -ForegroundColor Cyan +Write-Host '' + +# ───── 1) 다운로드 ───── +Write-Host "[1/3] installer 다운로드…" -ForegroundColor Yellow +Write-Host " URL: $installerUrl" +Write-Host " 저장: $installerLocal" +try { + $ProgressPreference = 'SilentlyContinue' # iwr 진행률 표시 끔 (속도 위해) + Invoke-WebRequest -Uri $installerUrl -OutFile $installerLocal -UseBasicParsing + $size = (Get-Item $installerLocal).Length + Write-Host " ✔ 다운로드 완료 ($([math]::Round($size/1MB, 1)) MB)" -ForegroundColor Green +} catch { + Write-Host " ✗ 다운로드 실패: $_" -ForegroundColor Red + exit 1 +} + +# ───── 2) 검증 ───── +Write-Host '' +Write-Host '[2/3] 파일 검증…' -ForegroundColor Yellow +$hash = (Get-FileHash $installerLocal -Algorithm SHA256).Hash +Write-Host " SHA256: $hash" +if ($size -lt 100MB) { + Write-Host ' ✗ 파일 크기 비정상 (100MB 미만). 다운로드 손상 의심.' -ForegroundColor Red + exit 1 +} +Write-Host ' ✔ OK' -ForegroundColor Green + +# ───── 3) 설치 ───── +Write-Host '' +Write-Host '[3/3] installer 실행…' -ForegroundColor Yellow +if ($installMode -eq 'silent') { + Write-Host ' silent 모드 (/S)' + $proc = Start-Process -FilePath $installerLocal -ArgumentList '/S' -Wait -PassThru + if ($proc.ExitCode -eq 0) { + Write-Host ' ✔ 설치 완료' -ForegroundColor Green + } else { + Write-Host " ✗ installer exit code: $($proc.ExitCode)" -ForegroundColor Red + exit 1 + } +} else { + Write-Host ' GUI 모드 — installer 창이 열리면 따라가세요' + Start-Process -FilePath $installerLocal -Wait + Write-Host ' ✔ installer 종료됨' -ForegroundColor Green +} + +# ───── 정리 ───── +Remove-Item $installerLocal -Force -ErrorAction SilentlyContinue + +Write-Host '' +Write-Host '════════════════════════════════════════════════════════' -ForegroundColor Cyan +Write-Host ' ✅ 설치 완료' -ForegroundColor Green +Write-Host '════════════════════════════════════════════════════════' -ForegroundColor Cyan +Write-Host '' +Write-Host '다음 단계:' +Write-Host ' 1) 시작 메뉴에서 "ExternalUploader" 실행' +Write-Host ' 2) 시스템 트레이의 아이콘 우클릭 → 설정' +Write-Host ' 3) Memily 폴더 (~/Memily/) 에 미디어 넣으면 자동 업로드' +Write-Host '' +Write-Host '향후 업데이트는 daemon 이 백그라운드에서 자동 처리 (사용자 액션 X).' +Write-Host '' diff --git a/onboard-linux-node.sh b/onboard-linux-node.sh new file mode 100755 index 0000000..8a34e2b --- /dev/null +++ b/onboard-linux-node.sh @@ -0,0 +1,213 @@ +#!/bin/bash +# washtime — 새 리눅스 노드 onboarding +# 한 줄 실행: +# bash <(curl -fsSL http://192.168.1.40:3000/washtime/tools/raw/branch/main/onboard-linux-node.sh) +# +# 작동: +# 1) ssh ed25519 key 생성 (또는 기존 사용) +# 2) Gitea API 로 그 public key 등록 (admin token 인터랙티브 입력) +# 3) ~/.ssh/config 에 gitea host 별칭 추가 +# 4) 지정한 repo clone +# 5) 1분 주기 auto-pull systemd timer 설치 +set -euo pipefail + +GITEA_HOST_DEFAULT="192.168.1.40" +GITEA_SSH_PORT_DEFAULT="2222" +GITEA_API_PORT_DEFAULT="3000" +GITEA_USER_DEFAULT="root2" +ORG_DEFAULT="washtime" + +# ───────── 입력 ───────── +read -rp "Gitea host (default: $GITEA_HOST_DEFAULT): " GITEA_HOST +GITEA_HOST="${GITEA_HOST:-$GITEA_HOST_DEFAULT}" + +read -rp "Gitea SSH port (default: $GITEA_SSH_PORT_DEFAULT): " GITEA_SSH_PORT +GITEA_SSH_PORT="${GITEA_SSH_PORT:-$GITEA_SSH_PORT_DEFAULT}" + +read -rp "Gitea API port (default: $GITEA_API_PORT_DEFAULT): " GITEA_API_PORT +GITEA_API_PORT="${GITEA_API_PORT:-$GITEA_API_PORT_DEFAULT}" + +read -rp "Gitea Web 계정명 (default: $GITEA_USER_DEFAULT): " GITEA_USER +GITEA_USER="${GITEA_USER:-$GITEA_USER_DEFAULT}" + +read -rsp "Gitea admin API token (입력 가려짐): " GITEA_TOKEN +echo + +read -rp "이 노드 이름 (예: oracle-cloud, laptop-mac. SSH key 라벨에 사용): " NODE_NAME +[[ -z "$NODE_NAME" ]] && { echo "노드 이름 필수"; exit 1; } + +read -rp "Org (default: $ORG_DEFAULT): " ORG +ORG="${ORG:-$ORG_DEFAULT}" + +read -rp "Clone 할 repo (콤마 구분, 예: one,family-viewer,tools): " REPOS_STR +[[ -z "$REPOS_STR" ]] && { echo "Repo 필수"; exit 1; } + +read -rp "Clone 위치 (default: \$HOME): " CLONE_BASE +CLONE_BASE="${CLONE_BASE:-$HOME}" + +# ───────── 1) git 설치 ───────── +echo "[1/5] git / curl 설치 확인…" +if ! command -v git &>/dev/null; then + if command -v apt-get &>/dev/null; then + sudo apt-get update -qq && sudo apt-get install -y git curl + elif command -v dnf &>/dev/null; then + sudo dnf install -y git curl + elif command -v yum &>/dev/null; then + sudo yum install -y git curl + else + echo "패키지 매니저 모름. git 직접 설치 필요."; exit 1 + fi +fi + +# ───────── 2) SSH key 생성 ───────── +KEY_FILE="$HOME/.ssh/id_ed25519_gitea" +echo "[2/5] SSH key 확인…" +mkdir -p "$HOME/.ssh" +chmod 700 "$HOME/.ssh" +if [[ ! -f "$KEY_FILE" ]]; then + ssh-keygen -t ed25519 -N "" -C "$(whoami)@$NODE_NAME→gitea" -f "$KEY_FILE" + echo " ✔ 새 key 생성됨" +else + echo " ✔ 기존 key 재사용" +fi +PUBKEY=$(cat "${KEY_FILE}.pub") + +# ───────── 3) Gitea 에 key 등록 ───────── +echo "[3/5] Gitea 에 SSH key 등록…" +RESP=$(curl -fsS -H "Authorization: token $GITEA_TOKEN" \ + -X POST -H "Content-Type: application/json" \ + -d "{\"title\":\"$NODE_NAME\",\"key\":\"$PUBKEY\"}" \ + "http://${GITEA_HOST}:${GITEA_API_PORT}/api/v1/user/keys" 2>&1) || { + # 이미 등록되어 있으면 OK + if echo "$RESP" | grep -q "already exists"; then + echo " ✔ 이미 등록되어있음" + else + echo " ✗ 등록 실패: $RESP"; exit 1 + fi +} +echo " ✔ 등록 완료" + +# ───────── 4) ~/.ssh/config ───────── +echo "[4/5] ~/.ssh/config 설정…" +if ! grep -q "^Host gitea$" "$HOME/.ssh/config" 2>/dev/null; then + cat >> "$HOME/.ssh/config" << EOF + +Host gitea + HostName ${GITEA_HOST} + Port ${GITEA_SSH_PORT} + User git + IdentityFile ${KEY_FILE} + IdentitiesOnly yes + StrictHostKeyChecking accept-new +EOF + chmod 600 "$HOME/.ssh/config" + echo " ✔ gitea 별칭 추가됨" +else + echo " ✔ gitea 별칭 이미 있음" +fi + +# SSH 테스트 +ssh -o BatchMode=yes -T gitea 2>&1 | head -1 || true + +# ───────── 5) repo clone + auto-pull systemd ───────── +echo "[5/5] repo clone + auto-pull systemd…" +IFS=',' read -ra REPOS <<< "$REPOS_STR" + +mkdir -p "$CLONE_BASE/scripts" + +for REPO in "${REPOS[@]}"; do + REPO="${REPO// /}" + TARGET="${CLONE_BASE}/${REPO}" + if [[ -d "$TARGET/.git" ]]; then + echo " ↻ $TARGET 이미 git repo, pull만" + (cd "$TARGET" && git pull --ff-only) || echo " ⚠ $REPO pull 실패" + else + if [[ -d "$TARGET" ]]; then + BAK="${TARGET}.backup-$(date +%Y%m%d-%H%M%S)" + mv "$TARGET" "$BAK" + echo " ↻ 기존 $TARGET 백업: $BAK" + fi + git clone "gitea:${ORG}/${REPO}.git" "$TARGET" || { echo " ✗ $REPO clone 실패"; continue; } + echo " ✔ $REPO clone 완료 → $TARGET" + fi +done + +# auto-pull 스크립트 (단순 ff-merge, service restart 매핑은 노드별 사용자가 추가) +PULL_SH="${CLONE_BASE}/scripts/git-auto-pull.sh" +cat > "$PULL_SH" << EOF +#!/bin/bash +# washtime auto-pull — ${NODE_NAME} +set -e +for REPO in ${REPOS[@]}; do + REPO="\${REPO// /}" + R="${CLONE_BASE}/\$REPO" + [[ ! -d "\$R/.git" ]] && continue + cd "\$R" + OLD=\$(git rev-parse HEAD) + git fetch --quiet origin main 2>&1 + NEW=\$(git rev-parse origin/main) + [[ "\$OLD" == "\$NEW" ]] && continue + echo "[\$(date +%Y-%m-%dT%H:%M:%S)] \$REPO: \$OLD..\$NEW" + if ! git diff --quiet || ! git diff --quiet --cached; then + git stash push -u -m "auto-pull-\$(date +%s)" + fi + git merge --ff-only \$NEW || { echo " ❌ ff 불가"; continue; } + # TODO: 변경 파일별 service restart hook 추가 +done +EOF +chmod +x "$PULL_SH" + +# systemd service + timer (sudo 필요) +SVC_FILE="/etc/systemd/system/washtime-git-pull.service" +TMR_FILE="/etc/systemd/system/washtime-git-pull.timer" + +if [[ ! -f "$SVC_FILE" ]]; then + sudo tee "$SVC_FILE" > /dev/null << EOF +[Unit] +Description=washtime git auto-pull (${NODE_NAME}) +After=network.target + +[Service] +Type=oneshot +User=$(whoami) +WorkingDirectory=${CLONE_BASE} +ExecStart=${PULL_SH} +StandardOutput=append:/tmp/washtime-git-pull.log +StandardError=append:/tmp/washtime-git-pull.log +EOF + sudo tee "$TMR_FILE" > /dev/null << EOF +[Unit] +Description=washtime git auto-pull every 1 min + +[Timer] +OnBootSec=30s +OnUnitActiveSec=60s +AccuracySec=5s +Persistent=true +Unit=washtime-git-pull.service + +[Install] +WantedBy=timers.target +EOF + sudo systemctl daemon-reload + sudo systemctl enable --now washtime-git-pull.timer + echo " ✔ systemd timer 활성 (1분 주기)" +fi + +# 즉시 한 번 실행 +sudo systemctl start washtime-git-pull.service 2>/dev/null || true + +echo +echo "========================================" +echo " ✅ onboarding 완료" +echo "========================================" +echo " 노드: $NODE_NAME" +echo " Clone 위치: $CLONE_BASE" +echo " Repo: ${REPOS_STR}" +echo " Auto-pull: washtime-git-pull.timer (1분 주기)" +echo " Log: /tmp/washtime-git-pull.log" +echo +echo " 강제 pull: sudo systemctl start washtime-git-pull.service" +echo " 로그 보기: tail -f /tmp/washtime-git-pull.log" +echo " 타이머 끄기: sudo systemctl disable --now washtime-git-pull.timer" +echo