From 2c99f6a2b10d5c8708998491739de70abe7f49c9 Mon Sep 17 00:00:00 2001 From: AutonetSellCar Deploy Date: Tue, 30 Dec 2025 18:57:16 +0900 Subject: [PATCH] Add deployment scripts and troubleshooting docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - deploy_ru_server2.sh: Russian language deployment script for server2 - update_db_ru.py: Database migration for Russian columns - TROUBLESHOOTING.md: Common issues and solutions ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- TROUBLESHOOTING.md | 241 +++++++++++++++++++++++++++++++++++++++++++ deploy_ru_server2.sh | 90 ++++++++++++++++ update_db_ru.py | 48 +++++++++ 3 files changed, 379 insertions(+) create mode 100644 TROUBLESHOOTING.md create mode 100644 deploy_ru_server2.sh create mode 100644 update_db_ru.py diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..4edee9a --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,241 @@ +# AutonetSellCar ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ๊ฐ€์ด๋“œ + +์ด ๋ฌธ์„œ๋Š” ๋ฐฐํฌ ๋ฐ ์šด์˜ ์ค‘ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 2024-12-30: ์šด์˜์„œ๋ฒ„ ์ด๋ฏธ์ง€ ํ‘œ์‹œ ๋ฌธ์ œ + +### ์ฆ์ƒ +- ๊ฐœ๋ฐœ์„œ๋ฒ„(192.168.0.204)์—์„œ๋Š” ์ด๋ฏธ์ง€๊ฐ€ ์ •์ƒ ํ‘œ์‹œ +- ์šด์˜์„œ๋ฒ„(192.168.0.202)์—์„œ ์™ธ๋ถ€ PC๋กœ ์ ‘์† ์‹œ ์ด๋ฏธ์ง€๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š์Œ +- ๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋„๊ตฌ์—์„œ ์ด๋ฏธ์ง€ ์š”์ฒญ URL์ด `http://localhost:8000/...`์œผ๋กœ ๋˜์–ด ์žˆ์Œ + +### ์›์ธ ๋ถ„์„ + +#### 1์ฐจ ์›์ธ: ์„œ๋ฒ„2 ์†Œ์Šค ์ฝ”๋“œ ๋ฏธ๋™๊ธฐํ™” + +**๋ฌธ์ œ**: ์„œ๋ฒ„2์˜ ํ”„๋ก ํŠธ์—”๋“œ ์†Œ์Šค ์ฝ”๋“œ๊ฐ€ ๊ฐœ๋ฐœ์„œ๋ฒ„์™€ ๋™๊ธฐํ™”๋˜์ง€ ์•Š์•„ ์˜ค๋ž˜๋œ ์ฝ”๋“œ๊ฐ€ ๋ฐฐํฌ๋จ + +| ์œ„์น˜ | ์ฝ”๋“œ | +|------|------| +| ์„œ๋ฒ„4 (๊ฐœ๋ฐœ) | `process.env.NEXT_PUBLIC_API_URL \|\| 'http://localhost:8000'` | +| ์„œ๋ฒ„2 (์šด์˜) | `'http://localhost:8000'` (ํ•˜๋“œ์ฝ”๋”ฉ) | + +**์˜ํ–ฅ๋ฐ›์€ ํŒŒ์ผ**: +- `/admin/hero-banners/page.tsx` +- `/admin/cars/page.tsx` +- ๊ธฐํƒ€ ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€ + +#### 2์ฐจ ์›์ธ: ์ฐจ๋Ÿ‰ ์ƒ์„ธ ํŽ˜์ด์ง€ ํŠน์ˆ˜ ํŒจํ„ด + +**๋ฌธ์ œ**: `/cars/[id]/page.tsx`์—์„œ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ localhost๊ฐ€ ํ•˜๋“œ์ฝ”๋”ฉ๋จ + +```typescript +// ๋ฌธ์ œ ์ฝ”๋“œ +const getImageUrl = (url: string | undefined): string => { + if (!url) return ''; + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + // ํฌํŠธ๋งŒ ๋™์ ์œผ๋กœ ์ถ”์ถœํ•˜๊ณ  localhost๋Š” ํ•˜๋“œ์ฝ”๋”ฉ + const backendPort = process.env.NEXT_PUBLIC_API_URL?.includes('8001') ? 8001 : 8000; + return `http://localhost:${backendPort}${url}`; +}; +``` + +### ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• + +#### Step 1: ์†Œ์Šค ์ฝ”๋“œ ๋™๊ธฐํ™” + +```bash +# ์„œ๋ฒ„4 (Windows)์—์„œ ์‹คํ–‰ +scp -r D:/Workspace/claudeCode/AutonetSellCar.com/frontend/src damon@192.168.0.202:/opt/autonet/production/frontend/ +``` + +#### Step 2: ์ฐจ๋Ÿ‰ ์ƒ์„ธ ํŽ˜์ด์ง€ ์ˆ˜์ • + +```typescript +// ์ˆ˜์ •๋œ ์ฝ”๋“œ (/cars/[id]/page.tsx) +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'; + +const getImageUrl = (url: string | undefined): string => { + if (!url) return ''; + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + return `${API_BASE_URL}${url}`; +}; +``` + +#### Step 3: Docker ์ด๋ฏธ์ง€ ์žฌ๋นŒ๋“œ + +```bash +# ์„œ๋ฒ„2์—์„œ ์‹คํ–‰ +cd /opt/autonet/production/frontend + +# ์บ์‹œ ์—†์ด ์žฌ๋นŒ๋“œ (์ค‘์š”!) +docker build --no-cache -t autonet-frontend-v2 . + +# ์ปจํ…Œ์ด๋„ˆ ๊ต์ฒด +docker stop autonet-frontend +docker rm autonet-frontend +docker run -d --name autonet-frontend -p 3000:3000 autonet-frontend-v2 +``` + +#### Step 4: ํ™•์ธ + +```bash +# localhost:8000์ด ์—†๋Š”์ง€ ํ™•์ธ +docker exec autonet-frontend sh -c "grep -l 'localhost:8000' /app/.next/static/chunks/app/**/*.js 2>/dev/null || echo 'No localhost:8000 found'" + +# ์˜ฌ๋ฐ”๋ฅธ URL์ด ์žˆ๋Š”์ง€ ํ™•์ธ +docker exec autonet-frontend sh -c "grep -o '192.168.0.202:8000' /app/.next/static/chunks/app/**/*.js 2>/dev/null | wc -l" +``` + +### ํ•ต์‹ฌ ๊ฐœ๋…: Next.js ํ™˜๊ฒฝ๋ณ€์ˆ˜ + +> **์ค‘์š”**: Next.js์˜ `NEXT_PUBLIC_*` ํ™˜๊ฒฝ๋ณ€์ˆ˜๋Š” **๋นŒ๋“œ ์‹œ์ **์— JavaScript ๋ฒˆ๋“ค์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + +| ์‹œ์  | ์„ค๋ช… | +|------|------| +| ๋นŒ๋“œ ์‹œ์  | `NEXT_PUBLIC_*` ๋ณ€์ˆ˜ ๊ฐ’์ด JS ์ฝ”๋“œ์— ์ง์ ‘ ์‚ฝ์ž…๋จ | +| ๋Ÿฐํƒ€์ž„ | ์ด๋ฏธ ์‚ฝ์ž…๋œ ๊ฐ’์ด ์‚ฌ์šฉ๋จ (๋ณ€๊ฒฝ ๋ถˆ๊ฐ€) | + +๋”ฐ๋ผ์„œ: +- `.env.production` ์ˆ˜์ • ํ›„ ๋ฐ˜๋“œ์‹œ **์žฌ๋นŒ๋“œ** ํ•„์š” +- Docker ํ™˜๊ฒฝ์—์„œ๋Š” Dockerfile์— `ARG`/`ENV` ์„ค์ • ํ•„์š” + +```dockerfile +# Dockerfile ์˜ˆ์‹œ (builder ๋‹จ๊ณ„) +ARG NEXT_PUBLIC_API_URL=http://192.168.0.202:8000 +ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} +RUN npm run build +``` + +--- + +## ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฏธ์ง€ URL ํŒจํ„ด + +๋ชจ๋“  ํ”„๋ก ํŠธ์—”๋“œ ํŒŒ์ผ์—์„œ ์ด๋ฏธ์ง€ URL์„ ์ƒ์„ฑํ•  ๋•Œ ๋‹ค์Œ ํŒจํ„ด์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: + +```typescript +// ์˜ฌ๋ฐ”๋ฅธ ํŒจํ„ด +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'; + +const getImageUrl = (url: string): string => { + if (!url) return ''; + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + return `${API_BASE_URL}${url}`; +}; +``` + +**์ž˜๋ชป๋œ ํŒจํ„ด (์‚ฌ์šฉ ๊ธˆ์ง€)**: +```typescript +// ํ•˜๋“œ์ฝ”๋”ฉ - ์ ˆ๋Œ€ ์‚ฌ์šฉ ๊ธˆ์ง€! +return `http://localhost:8000${url}`; + +// ํฌํŠธ๋งŒ ์ถ”์ถœ - ์‚ฌ์šฉ ๊ธˆ์ง€! +const port = process.env.NEXT_PUBLIC_API_URL?.includes('8001') ? 8001 : 8000; +return `http://localhost:${port}${url}`; +``` + +--- + +## ๋””๋ฒ„๊น… ๋ช…๋ น์–ด ๋ชจ์Œ + +### ํ”„๋ก ํŠธ์—”๋“œ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ™•์ธ + +```bash +# ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ๋ณ€์ˆ˜ +docker exec autonet-frontend printenv | grep API + +# .env.production ํŒŒ์ผ +cat /opt/autonet/production/frontend/.env.production + +# Dockerfile ARG/ENV ํ™•์ธ +grep -E "(ARG|ENV).*NEXT_PUBLIC" /opt/autonet/production/frontend/Dockerfile +``` + +### ๋นŒ๋“œ๋œ JS ํŒŒ์ผ ๊ฒ€์‚ฌ + +```bash +# localhost:8000 ๊ฒ€์ƒ‰ +docker exec autonet-frontend sh -c "grep -r 'localhost:8000' /app/.next/static/chunks/ | head -5" + +# ์˜ฌ๋ฐ”๋ฅธ URL ๊ฒ€์ƒ‰ +docker exec autonet-frontend sh -c "grep -r '192.168.0.202:8000' /app/.next/static/chunks/ | head -5" +``` + +### ์ด๋ฏธ์ง€ ์ง์ ‘ ์ ‘๊ทผ ํ…Œ์ŠคํŠธ + +```bash +# ๋ฐฑ์—”๋“œ์—์„œ ์ด๋ฏธ์ง€ ์ง์ ‘ ์ ‘๊ทผ +curl -I http://192.168.0.202:8000/uploads/cars/1/image_0.jpg + +# API ์‘๋‹ต์—์„œ ์ด๋ฏธ์ง€ URL ํ™•์ธ +curl -s http://192.168.0.202:8000/api/cars/1 | jq '.images' +``` + +### ์ปจํ…Œ์ด๋„ˆ ์ƒํƒœ ํ™•์ธ + +```bash +docker ps -a | grep autonet +docker logs autonet-frontend --tail 20 +docker logs autonet-backend --tail 20 +``` + +--- + +## ์˜ˆ๋ฐฉ ์กฐ์น˜ + +### 1. ์ฝ”๋“œ ๋™๊ธฐํ™” ์ž๋™ํ™” + +๊ฐœ๋ฐœ ์™„๋ฃŒ ํ›„ ์šด์˜ ์„œ๋ฒ„ ๋ฐฐํฌ ์‹œ ํ•ญ์ƒ ์ตœ์‹  ์ฝ”๋“œ๋ฅผ ๋™๊ธฐํ™”: + +```bash +# ๋ฐฐํฌ ์ „ ๋™๊ธฐํ™” ์Šคํฌ๋ฆฝํŠธ +scp -r frontend/src damon@192.168.0.202:/opt/autonet/production/frontend/ +``` + +### 2. ๋นŒ๋“œ ์ „ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ™•์ธ + +```bash +# ๋นŒ๋“œ ์ „ ํ™•์ธ ์‚ฌํ•ญ +echo "=== .env.production ===" +cat /opt/autonet/production/frontend/.env.production + +echo "=== Dockerfile ENV ===" +grep "NEXT_PUBLIC" /opt/autonet/production/frontend/Dockerfile +``` + +### 3. ๋นŒ๋“œ ํ›„ ๊ฒ€์ฆ + +```bash +# ๋นŒ๋“œ ํ›„ localhost ํ•˜๋“œ์ฝ”๋”ฉ ๊ฒ€์‚ฌ +docker exec autonet-frontend sh -c "grep -r 'localhost:8000' /app/.next/static/chunks/ && echo 'ERROR: localhost found!' || echo 'OK: No localhost hardcoding'" +``` + +--- + +## ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +๋ฐฐํฌ ์‹œ ํ™•์ธํ•ด์•ผ ํ•  ํ•ญ๋ชฉ: + +- [ ] ์„œ๋ฒ„2 ์†Œ์Šค ์ฝ”๋“œ๊ฐ€ ์ตœ์‹ ์ธ๊ฐ€? +- [ ] `.env.production`์— ์˜ฌ๋ฐ”๋ฅธ URL์ด ์„ค์ •๋˜์–ด ์žˆ๋Š”๊ฐ€? +- [ ] Dockerfile์— `ARG`/`ENV`๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ๋Š”๊ฐ€? +- [ ] `--no-cache` ์˜ต์…˜์œผ๋กœ ๋นŒ๋“œํ–ˆ๋Š”๊ฐ€? +- [ ] ๋นŒ๋“œ๋œ JS์— `localhost:8000`์ด ์—†๋Š”๊ฐ€? +- [ ] ์™ธ๋ถ€ PC์—์„œ ์ด๋ฏธ์ง€๊ฐ€ ์ •์ƒ ํ‘œ์‹œ๋˜๋Š”๊ฐ€? + +--- + +## ๋ณ€๊ฒฝ ์ด๋ ฅ + +| ๋‚ ์งœ | ๋ฌธ์ œ | ํ•ด๊ฒฐ | +|------|------|------| +| 2024-12-30 | ์šด์˜์„œ๋ฒ„ ์ด๋ฏธ์ง€ ๋ฏธํ‘œ์‹œ (๋ฐฐ๋„ˆ) | ์†Œ์Šค ์ฝ”๋“œ ๋™๊ธฐํ™” + Docker ์žฌ๋นŒ๋“œ | +| 2024-12-30 | ์ฐจ๋Ÿ‰ ์ƒ์„ธ ์ด๋ฏธ์ง€ ๋ฏธํ‘œ์‹œ | `getImageUrl()` ํ•จ์ˆ˜ ์ˆ˜์ • | diff --git a/deploy_ru_server2.sh b/deploy_ru_server2.sh new file mode 100644 index 0000000..95bb771 --- /dev/null +++ b/deploy_ru_server2.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# ์„œ๋ฒ„2์—์„œ ๋Ÿฌ์‹œ์•„์–ด ์ง€์› ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ + +echo "=== 1. DB ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ๋ฐ ๋ฒˆ์—ญ ์—…๋ฐ์ดํŠธ ===" + +# Docker ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—์„œ DB ์—…๋ฐ์ดํŠธ +docker exec -i autonet-backend python3 << 'EOF' +import sqlite3 +conn = sqlite3.connect("/app/autonet.db") +cursor = conn.cursor() + +# 1. ์ปฌ๋Ÿผ ์ถ”๊ฐ€ (์ด๋ฏธ ์žˆ์œผ๋ฉด ๋ฌด์‹œ) +try: + cursor.execute("ALTER TABLE hero_banners ADD COLUMN title_ru VARCHAR(100)") + print("Added title_ru column") +except: + print("title_ru column already exists") + +try: + cursor.execute("ALTER TABLE hero_banners ADD COLUMN subtitle_ru VARCHAR(200)") + print("Added subtitle_ru column") +except: + print("subtitle_ru column already exists") + +conn.commit() + +# 2. ๋Ÿฌ์‹œ์•„์–ด ๋ฒˆ์—ญ ์ถ”๊ฐ€ +translations = { + "๋ชจํ•˜๋น„ ๋” ๋งˆ์Šคํ„ฐ": ("Mohave The Master", "ะœะพั…ะฐะฒะต ะœะฐัั‚ะตั€", "ะœะพั…ะฐะฒะต ะœะฐัั‚ะตั€"), + "์‹ ํ˜• K5(DL3)": ("New K5 (DL3)", "ะจะธะฝั K5 (DL3)", "ะะพะฒั‹ะน K5 (DL3)"), + "๋” ๋‰ด๊ทธ๋žœ๋“œ์Šคํƒ€๋ ‰์Šค": ("The New Grand Starex", "ะจะธะฝั ะ“ั€ะฐะฝะด ะกั‚ะฐั€ะตะบั", "ะะพะฒั‹ะน ะ“ั€ะฐะฝะด ะกั‚ะฐั€ะตะบั"), + "๋” ๋‰ดSanta Fe": ("The New Santa Fe", "ะจะธะฝั ะกะฐะฝั‚ะฐ ะคะต", "ะะพะฒั‹ะน ะกะฐะฝั‚ะฐ ะคะต"), + "๋” ๋‰ด K7": ("The New K7", "ะจะธะฝั K7", "ะะพะฒั‹ะน K7"), +} + +cursor.execute("SELECT id, title_ko FROM hero_banners") +for row in cursor.fetchall(): + banner_id, title_ko = row + if title_ko in translations: + en, mn, ru = translations[title_ko] + cursor.execute( + "UPDATE hero_banners SET title_en=?, title_mn=?, title_ru=? WHERE id=?", + (en, mn, ru, banner_id) + ) + print(f"Updated banner {banner_id}: {title_ko} -> RU: {ru}") + +conn.commit() + +# 3. ํ™•์ธ +cursor.execute("SELECT id, title_ko, title_en, title_mn, title_ru FROM hero_banners") +print("\n=== Current Banner Data ===") +for row in cursor.fetchall(): + print(row) + +print("\nDB Update Done!") +EOF + +echo "" +echo "=== 2. Backend ์ปจํ…Œ์ด๋„ˆ ์žฌ๋นŒ๋“œ ===" +cd /opt/autonet/production +docker stop autonet-backend || true +docker rm autonet-backend || true +docker build -t autonet-backend-prod ./backend +docker run -d --name autonet-backend \ + -p 8000:8000 \ + -v /opt/autonet/production/backend/uploads:/app/uploads \ + -v /opt/autonet/production/backend/autonet.db:/app/autonet.db \ + autonet-backend-prod + +echo "" +echo "=== 3. Frontend ์ปจํ…Œ์ด๋„ˆ ์žฌ๋นŒ๋“œ ===" +docker stop autonet-frontend || true +docker rm autonet-frontend || true +docker build -t autonet-frontend-prod \ + --build-arg NEXT_PUBLIC_API_URL=http://192.168.0.202:8000 \ + ./frontend +docker run -d --name autonet-frontend \ + -p 3000:3000 \ + -e NEXT_PUBLIC_API_URL=http://192.168.0.202:8000 \ + autonet-frontend-prod + +echo "" +echo "=== 4. ๊ฒฐ๊ณผ ํ™•์ธ ===" +sleep 5 +echo "Testing Russian API..." +curl -s 'http://localhost:8000/api/hero-banners/?lang=ru' | head -200 + +echo "" +echo "=== ๋ฐฐํฌ ์™„๋ฃŒ ===" +docker ps diff --git a/update_db_ru.py b/update_db_ru.py new file mode 100644 index 0000000..94a8447 --- /dev/null +++ b/update_db_ru.py @@ -0,0 +1,48 @@ +import sqlite3 +conn = sqlite3.connect("/app/autonet.db") +cursor = conn.cursor() + +# 1. ์ปฌ๋Ÿผ ์ถ”๊ฐ€ (์ด๋ฏธ ์žˆ์œผ๋ฉด ๋ฌด์‹œ) +try: + cursor.execute("ALTER TABLE hero_banners ADD COLUMN title_ru VARCHAR(100)") + print("Added title_ru column") +except: + print("title_ru column already exists") + +try: + cursor.execute("ALTER TABLE hero_banners ADD COLUMN subtitle_ru VARCHAR(200)") + print("Added subtitle_ru column") +except: + print("subtitle_ru column already exists") + +conn.commit() + +# 2. ๋Ÿฌ์‹œ์•„์–ด ๋ฒˆ์—ญ ์ถ”๊ฐ€ +translations = { + "๋ชจํ•˜๋น„ ๋” ๋งˆ์Šคํ„ฐ": ("Mohave The Master", "ะœะพั…ะฐะฒะต ะœะฐัั‚ะตั€", "ะœะพั…ะฐะฒะต ะœะฐัั‚ะตั€"), + "์‹ ํ˜• K5(DL3)": ("New K5 (DL3)", "ะจะธะฝั K5 (DL3)", "ะะพะฒั‹ะน K5 (DL3)"), + "๋” ๋‰ด๊ทธ๋žœ๋“œ์Šคํƒ€๋ ‰์Šค": ("The New Grand Starex", "ะจะธะฝั ะ“ั€ะฐะฝะด ะกั‚ะฐั€ะตะบั", "ะะพะฒั‹ะน ะ“ั€ะฐะฝะด ะกั‚ะฐั€ะตะบั"), + "๋” ๋‰ดSanta Fe": ("The New Santa Fe", "ะจะธะฝั ะกะฐะฝั‚ะฐ ะคะต", "ะะพะฒั‹ะน ะกะฐะฝั‚ะฐ ะคะต"), + "๋” ๋‰ด K7": ("The New K7", "ะจะธะฝั K7", "ะะพะฒั‹ะน K7"), +} + +cursor.execute("SELECT id, title_ko FROM hero_banners") +for row in cursor.fetchall(): + banner_id, title_ko = row + if title_ko in translations: + en, mn, ru = translations[title_ko] + cursor.execute( + "UPDATE hero_banners SET title_en=?, title_mn=?, title_ru=? WHERE id=?", + (en, mn, ru, banner_id) + ) + print(f"Updated banner {banner_id}: {title_ko} -> EN: {en}, MN: {mn}, RU: {ru}") + +conn.commit() + +# 3. ํ™•์ธ +cursor.execute("SELECT id, title_ko, title_en, title_mn, title_ru FROM hero_banners") +print("\n=== Current Banner Data ===") +for row in cursor.fetchall(): + print(row) + +print("\nDone!")