Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions installers/windows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# RustChain Windows Installer Source

This repository contains the source code for the RustChain Proof-of-Antiquity Miner Windows Installer (.exe).

## Features
- **Bundled Python**: Uses PyInstaller to bundle the miner with its own Python environment.
- **System Tray Icon**: Provides a persistent tray icon for status monitoring (Gray=Stopped, Green=Mining, Red=Error) and control.
- **Custom Installer**: NSIS script that prompts for a wallet name, sets up Start Menu shortcuts, and configures auto-start.
- **Non-Admin Install**: Installs to the user's profile directory by default.

## Contents
- `src/tray_app.py`: Python script for the system tray application.
- `src/installer.nsi`: NSIS script for creating the installer executable.
- `build_windows.py`: Helper script to download the latest miner components and build the binary.

## Build Instructions (on Windows)

### 1. Prerequisites
- Python 3.8+
- [NSIS](https://nsis.sourceforge.io/Download) installed and added to PATH.

### 2. Setup Environment
```bash
pip install pyinstaller pystray Pillow requests
```

### 3. Prepare Miner Files
Run the build script to fetch the latest Windows-specific miner scripts:
```bash
python build_windows.py
```

### 4. Build Tray App
Compile the Python tray app into a standalone executable:
```bash
pyinstaller --onefile --noconsole --add-data "rustchain_miner.py;." --add-data "fingerprint_checks.py;." src/tray_app.py
```
This will create `dist/tray_app.exe`.

### 5. Create Installer
Copy `dist/tray_app.exe` to the `src` directory (or update the `.nsi` path) and run:
```bash
makensis src/installer.nsi
```
The final `RustChainMinerInstaller.exe` will be created in the root directory.

## Testing
1. Run `RustChainMinerInstaller.exe`.
2. Follow the prompts to set your wallet name.
3. Verify the miner appears in the system tray and starts mining.
4. Check the logs at `%USERPROFILE%\.rustchain\miner.log`.
34 changes: 34 additions & 0 deletions installers/windows/build_windows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import os
import subprocess
import urllib.request

# Configuration
REPO_BASE_LINUX = "https://raw.githubusercontent.com/Scottcjn/Rustchain/main/miners/linux/"
REPO_BASE_WINDOWS = "https://raw.githubusercontent.com/Scottcjn/Rustchain/main/miners/windows/"
FILES = [
(REPO_BASE_WINDOWS + "rustchain_windows_miner.py", "rustchain_miner.py"),
(REPO_BASE_LINUX + "fingerprint_checks.py", "fingerprint_checks.py")
]

def download_files():
print("Downloading miner files...")
for url, local in FILES:
print(f" {url} -> {local}")
urllib.request.urlretrieve(url, local)

def build_exe():
print("Building tray_app.exe with PyInstaller...")
subprocess.run([
"pyinstaller",
"--onefile",
"--noconsole",
"--add-data", "rustchain_miner.py;.",
"--add-data", "fingerprint_checks.py;.",
"src/tray_app.py"
], check=True)

if __name__ == "__main__":
download_files()
# Note: Building needs PyInstaller and NSIS installed on a Windows host
# build_exe()
print("Code prepared. To build, run on Windows with PyInstaller and NSIS.")
91 changes: 91 additions & 0 deletions installers/windows/src/installer.nsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
!define APPNAME "RustChain Miner"
!define COMPANYNAME "Scottcjn"
!define DESCRIPTION "Proof-of-Antiquity Miner for RustChain"
!define VERSIONMAJOR 3
!define VERSIONMINOR 2
!define VERSIONBUILD 0

!include "MUI2.nsh"
!include "nsDialogs.nsh"

Name "${APPNAME}"
OutFile "RustChainMinerInstaller.exe"
InstallDir "$PROFILE\.rustchain"
RequestExecutionLevel user

Var Dialog
Var Label
Var WalletNameInput
Var WalletName

!define MUI_ABORTWARNING

!insertmacro MUI_PAGE_WELCOME
Page custom WalletPageShow WalletPageLeave
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH

!insertmacro MUI_LANGUAGE "English"

Function WalletPageShow
nsDialogs::Create 1018
Pop $Dialog

${If} $Dialog == error
Abort
${EndIf}

${NSD_CreateLabel} 0 0 100% 12u "Enter your RustChain wallet name:"
Pop $Label

${NSD_CreateText} 0 13u 100% 12u "my-desktop-miner"
Pop $WalletNameInput

nsDialogs::Show
FunctionEnd

Function WalletPageLeave
${NSD_GetText} $WalletNameInput $WalletName
${If} $WalletName == ""
MessageBox MB_OK "Please enter a wallet name."
Abort
${EndIf}
FunctionEnd

Section "Install"
SetOutPath "$INSTDIR"

# Save wallet name to file
FileOpen $0 "$INSTDIR\wallet.txt" w
FileWrite $0 $WalletName
FileClose $0

# These files are expected to be in the same directory as the .nsi during build
File "tray_app.exe"
File "rustchain_miner.py"
File "fingerprint_checks.py"

# Create shortcuts
CreateDirectory "$SMPROGRAMS\${APPNAME}"
CreateShortcut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\tray_app.exe"
CreateShortcut "$SMPROGRAMS\${APPNAME}\Uninstall ${APPNAME}.lnk" "$INSTDIR\uninstall.exe"

# Add to startup
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "${APPNAME}" '"$INSTDIR\tray_app.exe"'

WriteUninstaller "$INSTDIR\uninstall.exe"

# Launch app after install
Exec '"$INSTDIR\tray_app.exe"'
SectionEnd

Section "Uninstall"
# Stop processes
nsExec::Exec 'taskkill /F /IM tray_app.exe'

DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "${APPNAME}"

RMDir /r "$SMPROGRAMS\${APPNAME}"
RMDir /r "$INSTDIR"
SectionEnd
161 changes: 161 additions & 0 deletions installers/windows/src/tray_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import os
import sys
import subprocess
import threading
import time
import webbrowser
import json
from PIL import Image, ImageDraw
import pystray
from pystray import MenuItem as item
import tkinter as tk
from tkinter import messagebox

# Helper for bundled files
def resource_path(relative_path):
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)

# Configuration
INSTALL_DIR = os.path.expanduser("~/.rustchain")
MINER_SCRIPT = resource_path("rustchain_miner.py")
LOG_FILE = os.path.join(INSTALL_DIR, "miner.log")
WALLET_FILE = os.path.join(INSTALL_DIR, "wallet.txt")

class MinerTrayApp:
def __init__(self):
self.process = None
self.status = "Stopped"
self.icon = None
self.wallet_name = self.load_wallet()
os.makedirs(INSTALL_DIR, exist_ok=True)

def load_wallet(self):
if os.path.exists(WALLET_FILE):
with open(WALLET_FILE, "r") as f:
return f.read().strip()
return "default-miner"

def create_image(self, color):
image = Image.new('RGB', (64, 64), (255, 255, 255))
dc = ImageDraw.Draw(image)
dc.ellipse((10, 10, 54, 54), fill=color, outline="black")
return image

def get_status_color(self):
if self.status == "Mining":
return "green"
elif self.status == "Error":
return "red"
else:
return "gray"

def update_icon(self):
if self.icon:
self.icon.icon = self.create_image(self.get_status_color())
self.icon.title = f"RustChain Miner - {self.status}"

def start_miner(self):
if self.status == "Mining":
return

# Check if venv exists
venv_python = os.path.join(INSTALL_DIR, "venv", "Scripts", "python.exe")
if not os.path.exists(venv_python):
# Fallback to system python if venv not found
venv_python = sys.executable

def run():
try:
self.status = "Mining"
self.update_icon()
self.process = subprocess.Popen(
[venv_python, MINER_SCRIPT, "--wallet", self.wallet_name],
stdout=open(LOG_FILE, "a"),
stderr=subprocess.STDOUT,
creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
)
self.process.wait()
if self.process and self.process.returncode != 0:
self.status = "Error"
else:
self.status = "Stopped"
except Exception as e:
self.status = "Error"
finally:
self.update_icon()

threading.Thread(target=run, daemon=True).start()

def stop_miner(self):
if self.process:
self.process.terminate()
self.process = None
self.status = "Stopped"
self.update_icon()

def view_logs(self):
if os.path.exists(LOG_FILE):
if sys.platform == "win32":
os.startfile(LOG_FILE)
else:
subprocess.run(["xdg-open", LOG_FILE])

def open_status(self):
webbrowser.open("https://50.28.86.131/api/miners")

def quit_app(self, icon):
self.stop_miner()
icon.stop()

def setup_menu(self):
return (
item(f'Status: {self.status}', lambda: None, enabled=False),
item(f'Wallet: {self.wallet_name}', lambda: None, enabled=False),
item('Start Miner', self.start_miner),
item('Stop Miner', self.stop_miner),
item('View Logs', self.view_logs),
item('Network Status', self.open_status),
item('Exit', self.quit_app),
)

def run(self):
self.icon = pystray.Icon(
"RustChain Miner",
self.create_image(self.get_status_color()),
"RustChain Miner",
self.setup_menu()
)

threading.Thread(target=self.status_monitor, daemon=True).start()

# Auto-start miner if wallet is configured
if self.wallet_name != "default-miner":
self.start_miner()

self.icon.run()

def status_monitor(self):
while True:
if self.process and self.process.poll() is not None:
if self.process.returncode != 0:
self.status = "Error"
else:
self.status = "Stopped"
self.process = None
self.update_icon()

# Refresh menu to update status text
if self.icon:
self.icon.menu = pystray.Menu(*self.setup_menu())

time.sleep(5)

if __name__ == "__main__":
app = MinerTrayApp()
app.run()

if __name__ == "__main__":
app = MinerTrayApp()
app.run()
Loading