Skip to content

Commit f5a6fd1

Browse files
committed
New tool Clone Website to Docker
1 parent 04e131a commit f5a6fd1

File tree

2 files changed

+230
-0
lines changed

2 files changed

+230
-0
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# cw2dt.py (Clone Website to Docker Tool)
2+
3+
This tool clones a website and packages it as a Docker-ready folder, with an optional step to build the Docker image. The workflow is fully automated and uses a modern PySide6 GUI.
4+
5+
## Features
6+
- Clone any public website to a temporary cache
7+
- Automatically generate Dockerfile and Nginx config
8+
- Copy container files to your chosen destination
9+
- Optionally build the Docker image (if Docker is installed)
10+
- Console shows real-time verbose output
11+
- No web server or browser UI required
12+
13+
## Usage
14+
1. Install requirements:
15+
```bash
16+
pip install -r requirements.txt
17+
```
18+
(PySide6 and wget required. Install wget via Homebrew: `brew install wget`)
19+
20+
2. Run the GUI:
21+
```bash
22+
python cw2dt.py
23+
```
24+
25+
3. Enter the website URL, Docker image name, and choose a destination folder.
26+
4. Click "Clone Website & Prepare Docker Output".
27+
5. (Optional) Enable Docker build if Docker is installed.
28+
6. Find the output in your chosen folder, ready for Docker Desktop or CLI.
29+
30+
## Notes
31+
- The `cloned_sites` folder is used as a temporary cache and is cleared after each run.
32+
- Only `cw2dt.py` and this README are required. Old web UI files have been removed.
33+
34+
## Requirements
35+
- Python 3.8+
36+
- PySide6
37+
- wget (system utility)
38+
- Docker (optional, for image build)
39+
40+
---
41+
MIT License
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import sys
2+
import os
3+
import subprocess
4+
import shutil
5+
from PySide6.QtWidgets import (
6+
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QFileDialog, QTextEdit, QCheckBox
7+
)
8+
from PySide6.QtCore import Qt, QThread, Signal
9+
10+
def docker_available():
11+
try:
12+
subprocess.run(['docker', '--version'], capture_output=True, check=True)
13+
return True
14+
except Exception:
15+
return False
16+
17+
class CloneThread(QThread):
18+
progress = Signal(str)
19+
finished = Signal(str)
20+
21+
def __init__(self, url, docker_name, save_path, build_docker):
22+
super().__init__()
23+
self.url = url
24+
self.docker_name = docker_name
25+
self.save_path = save_path
26+
self.build_docker = build_docker
27+
28+
def run(self):
29+
log = []
30+
def log_msg(msg):
31+
log.append(msg)
32+
self.progress.emit(msg)
33+
# Use cloned_sites as temp cache in script directory
34+
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
35+
cache_folder = os.path.join(script_dir, 'cloned_sites')
36+
if not os.path.exists(cache_folder):
37+
os.makedirs(cache_folder, exist_ok=True)
38+
temp_project_folder = os.path.join(cache_folder, self.docker_name)
39+
if os.path.exists(temp_project_folder):
40+
shutil.rmtree(temp_project_folder)
41+
os.makedirs(temp_project_folder, exist_ok=True)
42+
# Clone website using wget
43+
log_msg(f'Cloning {self.url} to {temp_project_folder}')
44+
wget_cmd = [
45+
'wget', '-e', 'robots=off', '--mirror', '--convert-links', '--adjust-extension', '--page-requisites', '--no-parent', self.url, '-P', temp_project_folder
46+
]
47+
try:
48+
result = subprocess.run(wget_cmd, capture_output=True, text=True)
49+
log_msg(result.stdout)
50+
log_msg(result.stderr)
51+
if result.returncode != 0:
52+
log_msg(f'Error cloning website: {result.stderr}')
53+
self.finished.emit('\n'.join(log))
54+
shutil.rmtree(cache_folder)
55+
return
56+
log_msg('Cloning complete.')
57+
except Exception as e:
58+
log_msg(f'Error running wget: {e}')
59+
self.finished.emit('\n'.join(log))
60+
shutil.rmtree(cache_folder)
61+
return
62+
# Write Dockerfile
63+
dockerfile_path = os.path.join(temp_project_folder, 'Dockerfile')
64+
with open(dockerfile_path, 'w') as f:
65+
f.write('FROM nginx:alpine\nCOPY . /usr/share/nginx/html\nEXPOSE 80\nCMD ["nginx", "-g", "daemon off;"]\n')
66+
log_msg('Dockerfile created.')
67+
# Write nginx.conf
68+
nginx_conf_path = os.path.join(temp_project_folder, 'nginx.conf')
69+
with open(nginx_conf_path, 'w') as f:
70+
f.write('# Default Nginx config. Edit as needed.\n')
71+
# Write README
72+
readme_path = os.path.join(temp_project_folder, f'README_{self.docker_name}.md')
73+
with open(readme_path, 'w') as f:
74+
f.write(f"""# Docker Website Container\n\nThis folder contains a complete website clone and Dockerfile, ready to be built into a Docker image on any device with Docker installed.\n\n## Files\n- Dockerfile\n- nginx.conf (optional, for advanced config)\n- Website files (images, videos, assets)\n\n## How to Use\n1. On a Docker-enabled device, open a terminal in this folder.\n2. Build the image:\n - docker build -t {self.docker_name} .\n3. (Optional) Save/export the image as a tar for Docker Desktop:\n - docker save -o {self.docker_name}.tar {self.docker_name}\n4. To run the container:\n - docker run -d -p 8080:80 {self.docker_name}\n - (Change port as needed)\n5. The website will be served by Nginx at http://localhost:8080\n\n## Advanced\n- You can edit nginx.conf and rebuild the image if needed.\n- The Dockerfile is included for reference or customization.\n\n---\n""")
75+
log_msg(f'README created: {readme_path}')
76+
# Copy to chosen destination
77+
output_folder = os.path.join(self.save_path, self.docker_name)
78+
if os.path.exists(output_folder):
79+
shutil.rmtree(output_folder)
80+
shutil.copytree(temp_project_folder, output_folder)
81+
log_msg(f'Container files copied to: {output_folder}')
82+
# Optionally build Docker image
83+
if self.build_docker:
84+
if docker_available():
85+
log_msg('Building Docker image...')
86+
build_cmd = ['docker', 'build', '-t', self.docker_name, output_folder]
87+
try:
88+
result = subprocess.run(build_cmd, capture_output=True, text=True)
89+
log_msg(result.stdout)
90+
log_msg(result.stderr)
91+
if result.returncode != 0:
92+
log_msg(f'Docker build failed: {result.stderr}')
93+
else:
94+
log_msg('Docker build complete.')
95+
except Exception as e:
96+
log_msg(f'Error building Docker image: {e}')
97+
else:
98+
log_msg('Docker is not installed or not available in PATH.')
99+
# Clean up cache
100+
shutil.rmtree(cache_folder)
101+
self.finished.emit('\n'.join(log))
102+
103+
class DockerClonerGUI(QWidget):
104+
def __init__(self):
105+
super().__init__()
106+
self.setWindowTitle('Clone Website to Docker Tool')
107+
self.setGeometry(100, 100, 600, 600)
108+
layout = QVBoxLayout()
109+
110+
layout.addWidget(QLabel('Website URL:'))
111+
self.url_input = QLineEdit()
112+
layout.addWidget(self.url_input)
113+
114+
layout.addWidget(QLabel('Docker Image Name:'))
115+
self.docker_name_input = QLineEdit()
116+
layout.addWidget(self.docker_name_input)
117+
118+
layout.addWidget(QLabel('Destination Folder:'))
119+
hbox = QHBoxLayout()
120+
self.save_path_display = QLineEdit()
121+
self.save_path_display.setReadOnly(True)
122+
hbox.addWidget(self.save_path_display)
123+
browse_btn = QPushButton('Browse')
124+
browse_btn.clicked.connect(self.browse_folder)
125+
hbox.addWidget(browse_btn)
126+
layout.addLayout(hbox)
127+
128+
self.build_checkbox = QCheckBox('Build Docker image after clone (requires Docker)')
129+
layout.addWidget(self.build_checkbox)
130+
131+
self.start_btn = QPushButton('Clone Website & Prepare Docker Output')
132+
self.start_btn.clicked.connect(self.start_clone)
133+
self.start_btn.setEnabled(False)
134+
layout.addWidget(self.start_btn)
135+
136+
layout.addWidget(QLabel('Console Log:'))
137+
self.console = QTextEdit()
138+
self.console.setReadOnly(True)
139+
layout.addWidget(self.console)
140+
141+
self.setLayout(layout)
142+
# Disable Docker build and image name if Docker is not available
143+
if not docker_available():
144+
self.build_checkbox.setEnabled(False)
145+
self.docker_name_input.setEnabled(False)
146+
self.build_checkbox.setToolTip('Docker not found. Install Docker to enable this feature.')
147+
self.docker_name_input.setToolTip('Docker not found. Install Docker to enable this feature.')
148+
# Enable/disable clone button based on required fields
149+
self.url_input.textChanged.connect(self.update_button_state)
150+
self.save_path_display.textChanged.connect(self.update_button_state)
151+
152+
def browse_folder(self):
153+
folder = QFileDialog.getExistingDirectory(self, 'Select Output Directory')
154+
if folder:
155+
self.save_path_display.setText(folder)
156+
157+
def update_button_state(self):
158+
url = self.url_input.text().strip()
159+
save_path = self.save_path_display.text().strip()
160+
self.start_btn.setEnabled(bool(url and save_path))
161+
162+
def start_clone(self):
163+
url = self.url_input.text().strip()
164+
docker_name = self.docker_name_input.text().strip()
165+
save_path = self.save_path_display.text().strip()
166+
build_docker = self.build_checkbox.isChecked()
167+
# Prevent Docker build if Docker name is empty and build is checked
168+
if build_docker and not docker_name:
169+
self.console.append('Docker image name is required to build the image.')
170+
return
171+
self.console.clear()
172+
self.clone_thread = CloneThread(url, docker_name, save_path, build_docker)
173+
self.clone_thread.progress.connect(self.update_console)
174+
self.clone_thread.finished.connect(self.clone_finished)
175+
self.clone_thread.start()
176+
177+
def update_console(self, msg):
178+
self.console.append(msg)
179+
self.console.ensureCursorVisible()
180+
181+
def clone_finished(self, log):
182+
self.console.append('\nProcess finished.')
183+
self.console.ensureCursorVisible()
184+
185+
if __name__ == '__main__':
186+
app = QApplication(sys.argv)
187+
window = DockerClonerGUI()
188+
window.show()
189+
sys.exit(app.exec())

0 commit comments

Comments
 (0)