Revise BUILD-GUIDE and enhance ISO build process
- Updated BUILD-GUIDE.md to streamline instructions for building the Archipelago Auto-Installer ISO, including prerequisites and post-installation steps. - Added detailed sections on capturing the live server state and building from source. - Enhanced Docker and Podman integration in build scripts for improved backend and web UI capture. - Introduced new app metadata for "IndeedHub" in the Docker package scanner and updated UI components for better installation progress tracking. - Improved styling and functionality in the Bitcoin UI for a more cohesive user experience.
This commit is contained in:
parent
0f40cb88b5
commit
337ebee510
355
.cursor/rules/UI-STANDARDS.md
Normal file
355
.cursor/rules/UI-STANDARDS.md
Normal file
@ -0,0 +1,355 @@
|
||||
# Archipelago UI Standards & Coding Rules
|
||||
|
||||
## Core Design System
|
||||
|
||||
Archipelago uses a **glassmorphism-based design system** with dark backgrounds, subtle transparency, and elegant blur effects. All UI components should follow these established patterns.
|
||||
|
||||
---
|
||||
|
||||
## Standard Interactive Card: `.path-option-card`
|
||||
|
||||
**This is our PRIMARY interactive card component.** Use this pattern for all selectable/clickable card containers.
|
||||
|
||||
### Base Styles
|
||||
```css
|
||||
.path-option-card {
|
||||
position: relative;
|
||||
background: rgba(0, 0, 0, 0.60);
|
||||
backdrop-filter: blur(24px);
|
||||
-webkit-backdrop-filter: blur(24px);
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.45),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
border-radius: 16px;
|
||||
padding: 12px 10px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
```
|
||||
|
||||
### Gradient Border Effect (Default - Subtle)
|
||||
```css
|
||||
.path-option-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 2px;
|
||||
background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
```
|
||||
|
||||
### Hover State
|
||||
```css
|
||||
.path-option-card:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
box-shadow:
|
||||
0 12px 32px rgba(0, 0, 0, 0.6),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.path-option-card:hover::before {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.3), transparent);
|
||||
}
|
||||
```
|
||||
|
||||
### Selected State
|
||||
```css
|
||||
.path-option-card--selected {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
box-shadow:
|
||||
0 12px 32px rgba(0, 0, 0, 0.6),
|
||||
0 0 30px rgba(255, 255, 255, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.path-option-card--selected::before {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.6), transparent);
|
||||
}
|
||||
```
|
||||
|
||||
### Icon Styling
|
||||
```css
|
||||
.path-option-card svg {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
filter:
|
||||
drop-shadow(0 1px 1px rgba(255, 255, 255, 0.3))
|
||||
drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8))
|
||||
drop-shadow(0 -1px 2px rgba(0, 0, 0, 0.6));
|
||||
stroke-width: 2.5;
|
||||
}
|
||||
|
||||
.path-option-card:hover svg {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
filter:
|
||||
drop-shadow(0 1px 2px rgba(255, 255, 255, 0.5))
|
||||
drop-shadow(0 3px 6px rgba(0, 0, 0, 0.9));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Button Standards
|
||||
|
||||
### Primary Action Button: `.gradient-button`
|
||||
Use for main actions like **Launch**, **Install**, **Save**, **Submit**
|
||||
|
||||
```css
|
||||
.gradient-button {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(0, 0, 0, 0.8) 100%);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.gradient-button:hover {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, rgba(0, 0, 0, 0.9) 100%);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
```
|
||||
|
||||
### Secondary Action Button: `.glass-button`
|
||||
Use for secondary actions like **Cancel**, **Close**, **Back**
|
||||
|
||||
```css
|
||||
.glass-button {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(18px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.glass-button:hover {
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
### Path Action Button: `.path-action-button`
|
||||
Use for onboarding/path selection flows (**Continue**, **Skip**)
|
||||
|
||||
```css
|
||||
.path-action-button {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
border-radius: 16px;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
color: rgba(255, 255, 255, 0.96);
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.45),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
backdrop-filter: blur(24px);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.path-action-button:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
box-shadow:
|
||||
0 12px 32px rgba(0, 0, 0, 0.6),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Container Standards
|
||||
|
||||
### Glass Card: `.glass-card`
|
||||
Use for content containers, modals, panels
|
||||
|
||||
```css
|
||||
.glass-card {
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
backdrop-filter: blur(18px);
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
|
||||
border-radius: 1rem;
|
||||
overflow-x: hidden;
|
||||
overflow-y: visible;
|
||||
}
|
||||
```
|
||||
|
||||
### Gradient Card: `.gradient-card`
|
||||
Use for featured content, highlighted sections
|
||||
|
||||
```css
|
||||
.gradient-card {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(0, 0, 0, 0.8) 100%);
|
||||
backdrop-filter: blur(18px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color Palette
|
||||
|
||||
### Primary Colors
|
||||
- **White text**: `rgba(255, 255, 255, 0.9)` (primary)
|
||||
- **White text hover**: `rgba(255, 255, 255, 1)` (full white)
|
||||
- **Muted text**: `rgba(255, 255, 255, 0.6)` - `rgba(255, 255, 255, 0.7)`
|
||||
|
||||
### Background Colors
|
||||
- **Dark overlay**: `rgba(0, 0, 0, 0.8)` - `rgba(0, 0, 0, 0.9)`
|
||||
- **Glass background**: `rgba(0, 0, 0, 0.6)` - `rgba(0, 0, 0, 0.65)`
|
||||
- **Light glass**: `rgba(0, 0, 0, 0.35)`
|
||||
|
||||
### Border Colors
|
||||
- **Subtle border**: `rgba(255, 255, 255, 0.18)`
|
||||
- **Prominent border**: `rgba(255, 255, 255, 0.2)` - `rgba(255, 255, 255, 0.3)`
|
||||
|
||||
### Accent Colors
|
||||
- **Orange** (Bitcoin/sync): `#fb923c` - `#f59e0b`
|
||||
- **Green** (success): `#4ade80`
|
||||
- **Red** (danger): `#ef4444`
|
||||
- **Blue** (info): `#3b82f6`
|
||||
|
||||
---
|
||||
|
||||
## Animation Standards
|
||||
|
||||
### Transitions
|
||||
- **Standard**: `all 0.3s ease`
|
||||
- **Fast**: `all 0.15s ease`
|
||||
- **Slow**: `all 0.5s ease-in-out`
|
||||
|
||||
### Transform on Hover
|
||||
```css
|
||||
transform: translateY(-2px);
|
||||
```
|
||||
|
||||
### Transform on Active/Click
|
||||
```css
|
||||
transform: translateY(1px);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Blur Effects
|
||||
|
||||
- **Standard blur**: `blur(18px)`
|
||||
- **Strong blur**: `blur(24px)` - `blur(40px)`
|
||||
- **Light blur**: `blur(10px)`
|
||||
|
||||
---
|
||||
|
||||
## Shadow Standards
|
||||
|
||||
### Card Shadows
|
||||
```css
|
||||
/* Default */
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
|
||||
|
||||
/* Hover */
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);
|
||||
|
||||
/* With inset highlight */
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.45),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Icon Guidelines
|
||||
|
||||
### Icon Shadow Effects
|
||||
```css
|
||||
filter:
|
||||
drop-shadow(0 1px 1px rgba(255, 255, 255, 0.3))
|
||||
drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8))
|
||||
drop-shadow(0 -1px 2px rgba(0, 0, 0, 0.6));
|
||||
```
|
||||
|
||||
### Icon Colors
|
||||
- **Default**: `rgba(255, 255, 255, 0.85)`
|
||||
- **Hover**: `rgba(255, 255, 255, 1)`
|
||||
- **Muted**: `rgba(255, 255, 255, 0.6)`
|
||||
|
||||
### Stroke Width
|
||||
- **Standard**: `2.5`
|
||||
- **Thin**: `2`
|
||||
- **Bold**: `3`
|
||||
|
||||
---
|
||||
|
||||
## Usage Rules
|
||||
|
||||
### DO:
|
||||
✅ Use `.path-option-card` for all interactive/selectable cards
|
||||
✅ Use `.gradient-button` for primary actions
|
||||
✅ Use `.glass-card` for content containers
|
||||
✅ Add subtle `translateY(-2px)` on hover
|
||||
✅ Use `backdrop-filter: blur()` for glass effects
|
||||
✅ Include inset highlights: `inset 0 1px 0 rgba(255, 255, 255, 0.22)`
|
||||
✅ Use gradient borders with CSS masks for subtle elevation
|
||||
✅ Maintain 0.3s ease transitions for smooth interactions
|
||||
|
||||
### DON'T:
|
||||
❌ Create custom card styles - extend existing ones
|
||||
❌ Use solid backgrounds - always use transparency + blur
|
||||
❌ Ignore hover states - all interactive elements need hover feedback
|
||||
❌ Mix different border styles - use gradient mask or single border
|
||||
❌ Use hard shadows - keep shadows soft with blur
|
||||
❌ Forget `-webkit-backdrop-filter` for Safari support
|
||||
|
||||
---
|
||||
|
||||
## Responsive Considerations
|
||||
|
||||
### Mobile Adjustments
|
||||
- Reduce padding by ~25% on small screens
|
||||
- Reduce blur slightly for performance (`blur(12px)` instead of `blur(18px)`)
|
||||
- Simplify animations (consider `prefers-reduced-motion`)
|
||||
- Touch targets minimum 44x44px
|
||||
|
||||
### Breakpoints
|
||||
```css
|
||||
/* Mobile first */
|
||||
sm: 640px /* Small tablets */
|
||||
md: 768px /* Tablets */
|
||||
lg: 1024px /* Desktops */
|
||||
xl: 1280px /* Large desktops */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Ensure sufficient contrast (WCAG AA minimum)
|
||||
- Include `:focus-visible` states matching `:hover`
|
||||
- Use semantic HTML (`<button>`, `<nav>`, etc.)
|
||||
- Include ARIA labels where needed
|
||||
- Support keyboard navigation
|
||||
|
||||
---
|
||||
|
||||
## File Locations
|
||||
|
||||
- **Global styles**: `/neode-ui/src/style.css`
|
||||
- **Component styles**: Scoped `<style>` blocks in `.vue` files
|
||||
- **Tailwind config**: `/neode-ui/tailwind.config.js`
|
||||
- **Assets**: `/neode-ui/public/assets/`
|
||||
|
||||
---
|
||||
|
||||
## Version
|
||||
Last updated: 2026-02-03
|
||||
Archipelago UI Standards v1.0
|
||||
357
BUILD-GUIDE.md
357
BUILD-GUIDE.md
@ -1,296 +1,115 @@
|
||||
# Archipelago ISO Build System
|
||||
# Quick Build Guide - Archipelago Beta Release
|
||||
|
||||
Complete, robust build system for creating flashable Archipelago ISO images from source.
|
||||
## Prerequisites
|
||||
|
||||
## Quick Start
|
||||
Make sure you have:
|
||||
- Docker or Podman installed
|
||||
- `xorriso` installed
|
||||
- Access to dev server: archipelago@192.168.1.228
|
||||
|
||||
### One-Command Build (Recommended)
|
||||
|
||||
Build everything and create a flashable ISO with a single command:
|
||||
## Build Auto-Installer ISO
|
||||
|
||||
```bash
|
||||
# Build on remote server (recommended for x86_64 target)
|
||||
./build-iso-complete.sh --remote archipelago@192.168.1.228
|
||||
cd /Users/dorian/Projects/archy/image-recipe
|
||||
|
||||
# Or build locally (if you have Rust + Node.js)
|
||||
./build-iso-complete.sh --local
|
||||
# Capture current live server state
|
||||
DEV_SERVER=archipelago@192.168.1.228 ./build-auto-installer-iso.sh
|
||||
|
||||
# ISO will be created in: results/archipelago-auto-installer-*.iso
|
||||
```
|
||||
|
||||
### Flash to USB
|
||||
## What the ISO Includes
|
||||
|
||||
After building:
|
||||
✅ Complete Debian 12 root filesystem
|
||||
✅ Pre-built Archipelago backend
|
||||
✅ Pre-built frontend (web UI)
|
||||
✅ Nginx configuration (HTTPS ready)
|
||||
✅ Auto-installer that:
|
||||
- Detects internal disk
|
||||
- Creates partitions (EFI + root)
|
||||
- Extracts pre-built system
|
||||
- Installs bootloader
|
||||
- Reboots to working system
|
||||
|
||||
## What Users Need to Do Post-Install
|
||||
|
||||
1. **Deploy Containers** - The ISO doesn't include containers (too large)
|
||||
|
||||
Example - Bitcoin Knots:
|
||||
```bash
|
||||
sudo podman run -d --name bitcoin-knots \
|
||||
-p 8332:8332 -p 8333:8333 \
|
||||
-v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \
|
||||
--label "com.archipelago.app=bitcoin-knots" \
|
||||
--label "com.archipelago.title=Bitcoin Knots" \
|
||||
docker.io/bitcoinknots/bitcoin:latest \
|
||||
-server=1 -txindex=1 -rpcallowip=0.0.0.0/0 \
|
||||
-rpcbind=0.0.0.0:8332 -dbcache=4096
|
||||
```
|
||||
|
||||
2. **Access Web UI** - Navigate to `http://[server-ip]`
|
||||
|
||||
## Testing the ISO
|
||||
|
||||
```bash
|
||||
# Find your USB device
|
||||
diskutil list
|
||||
|
||||
# Flash (will prompt for confirmation)
|
||||
./flash-to-usb.sh /dev/diskN
|
||||
# Use VirtualBox, QEMU, or real hardware
|
||||
qemu-system-x86_64 \
|
||||
-m 4G \
|
||||
-cdrom results/archipelago-auto-installer-*.iso \
|
||||
-hda archipelago-test.qcow2 \
|
||||
-boot d
|
||||
```
|
||||
|
||||
## Build Options
|
||||
## Important Notes
|
||||
|
||||
⚠️ **The auto-installer will ERASE the target disk!**
|
||||
⚠️ Make sure to test on a non-production machine first
|
||||
⚠️ Minimum 20GB disk space required (500GB+ recommended for Bitcoin)
|
||||
|
||||
## Build from Source (Alternative)
|
||||
|
||||
If you want to build everything from scratch instead of capturing the live server:
|
||||
|
||||
```bash
|
||||
./build-iso-complete.sh [options]
|
||||
BUILD_FROM_SOURCE=1 ./build-auto-installer-iso.sh
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `--local` - Build everything on your local machine
|
||||
- `--remote HOST` - Build on remote server (e.g., `archipelago@192.168.1.228`)
|
||||
- `--skip-backend` - Skip backend compilation (use existing binary)
|
||||
- `--skip-frontend` - Skip frontend build (use existing dist)
|
||||
- `--clean` - Clean all build artifacts before building
|
||||
- `--help` - Show help message
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Full clean build on remote server
|
||||
./build-iso-complete.sh --remote archipelago@192.168.1.228 --clean
|
||||
|
||||
# Quick rebuild with existing backend
|
||||
./build-iso-complete.sh --remote archipelago@192.168.1.228 --skip-backend
|
||||
|
||||
# Local build (requires Rust + Node.js installed)
|
||||
./build-iso-complete.sh --local
|
||||
|
||||
# Clean local build
|
||||
./build-iso-complete.sh --local --clean
|
||||
```
|
||||
|
||||
## What the Script Does
|
||||
|
||||
The build script automates the entire ISO creation process:
|
||||
|
||||
### 1. **Backend Build** (Rust)
|
||||
- Compiles `core/archipelago` to native binary
|
||||
- Can build locally or on remote server
|
||||
- Outputs to `image-recipe/build/backend/archipelago`
|
||||
|
||||
### 2. **Frontend Build** (Vue.js + Vite)
|
||||
- Builds `neode-ui` to static assets
|
||||
- Includes PWA manifest and service worker
|
||||
- Outputs to `image-recipe/build/frontend/`
|
||||
|
||||
### 3. **ISO Creation** (Debian Live)
|
||||
- Downloads base Debian 12 Live ISO
|
||||
- Integrates backend binary and frontend assets
|
||||
- Configures auto-start and services
|
||||
- Creates bootable ISO at `image-recipe/results/archipelago-debian-12-x86_64.iso`
|
||||
|
||||
### 4. **Verification**
|
||||
- Validates all build artifacts exist
|
||||
- Generates MD5 checksum
|
||||
- Reports file sizes
|
||||
|
||||
## Build Artifacts
|
||||
|
||||
```
|
||||
image-recipe/
|
||||
├── build/
|
||||
│ ├── backend/
|
||||
│ │ └── archipelago # Compiled Rust binary
|
||||
│ └── frontend/ # Built Vue.js assets
|
||||
│ ├── index.html
|
||||
│ ├── assets/
|
||||
│ └── ...
|
||||
├── results/
|
||||
│ └── archipelago-debian-12-x86_64.iso # Final bootable ISO
|
||||
└── iso-workdir/ # Temporary ISO build files (auto-cleaned)
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### For Remote Build (Recommended)
|
||||
- SSH access to build server
|
||||
- `rsync` installed locally
|
||||
- Build server must have:
|
||||
- Rust/Cargo (`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`)
|
||||
- Node.js/npm (`curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -`)
|
||||
- Build tools (`sudo apt install build-essential`)
|
||||
|
||||
### For Local Build
|
||||
- macOS or Linux
|
||||
- Rust/Cargo installed ([rustup.rs](https://rustup.rs))
|
||||
- Node.js 18+ installed ([nodejs.org](https://nodejs.org))
|
||||
- `xorriso` for ISO creation (`brew install xorriso` on macOS)
|
||||
- Admin/sudo access for ISO creation
|
||||
This will:
|
||||
- Build backend from Rust source
|
||||
- Build frontend with `npm run build`
|
||||
- Create fresh SSL certificates
|
||||
- Generate default configs
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Backend Build Fails
|
||||
```bash
|
||||
# Check Rust installation
|
||||
cargo --version
|
||||
**ISO won't boot:**
|
||||
- Ensure UEFI mode is enabled
|
||||
- Try disabling Secure Boot
|
||||
|
||||
# Update Rust
|
||||
rustup update
|
||||
**Installer hangs:**
|
||||
- Check the auto-start script fix is applied (see DEPLOYMENT.md)
|
||||
|
||||
# Clean and rebuild
|
||||
./build-iso-complete.sh --remote HOST --clean
|
||||
**Backend doesn't detect containers:**
|
||||
- Verify `/etc/sudoers.d/archipelago-podman` exists
|
||||
- Check backend can run `sudo podman ps`
|
||||
|
||||
## Version Naming
|
||||
|
||||
ISOs are automatically named with timestamp:
|
||||
```
|
||||
archipelago-auto-installer-YYYYMMDD-HHMMSS.iso
|
||||
```
|
||||
|
||||
### Frontend Build Fails
|
||||
```bash
|
||||
# Check Node.js version (need 18+)
|
||||
node --version
|
||||
|
||||
# Clean node_modules
|
||||
cd neode-ui
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
cd ..
|
||||
|
||||
# Rebuild
|
||||
./build-iso-complete.sh --remote HOST --clean
|
||||
For releases, rename to:
|
||||
```
|
||||
archipelago-v0.1.0-beta.1.iso
|
||||
```
|
||||
|
||||
### ISO Build Fails
|
||||
```bash
|
||||
# Check available disk space (needs ~2GB)
|
||||
df -h
|
||||
## Next Steps After Building
|
||||
|
||||
# Ensure build artifacts exist
|
||||
ls -lh image-recipe/build/backend/
|
||||
ls -lh image-recipe/build/frontend/
|
||||
|
||||
# Try with sudo
|
||||
cd image-recipe
|
||||
sudo bash build-debian-iso.sh
|
||||
```
|
||||
|
||||
### Remote Connection Issues
|
||||
```bash
|
||||
# Test SSH connection
|
||||
ssh archipelago@192.168.1.228
|
||||
|
||||
# Test rsync
|
||||
rsync --version
|
||||
|
||||
# Use SSH key for passwordless access
|
||||
ssh-copy-id archipelago@192.168.1.228
|
||||
```
|
||||
|
||||
## Build Time Estimates
|
||||
|
||||
| Step | Time (First Build) | Time (Incremental) |
|
||||
|------|-------------------|-------------------|
|
||||
| Backend compile | 3-5 minutes | 30 seconds |
|
||||
| Frontend build | 1-2 minutes | 20 seconds |
|
||||
| ISO download | 5-10 minutes | 0 (cached) |
|
||||
| ISO creation | 2-3 minutes | 2-3 minutes |
|
||||
| **Total** | **11-20 minutes** | **3-4 minutes** |
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Making Changes to Backend
|
||||
```bash
|
||||
# Edit Rust code in core/archipelago/src/
|
||||
# Then rebuild:
|
||||
./build-iso-complete.sh --remote archipelago@192.168.1.228 --skip-frontend
|
||||
```
|
||||
|
||||
### Making Changes to Frontend
|
||||
```bash
|
||||
# Edit Vue.js code in neode-ui/src/
|
||||
# Then rebuild:
|
||||
./build-iso-complete.sh --remote archipelago@192.168.1.228 --skip-backend
|
||||
```
|
||||
|
||||
### Making Changes to Both
|
||||
```bash
|
||||
# Edit both, then full rebuild:
|
||||
./build-iso-complete.sh --remote archipelago@192.168.1.228
|
||||
```
|
||||
|
||||
### Testing Changes Without ISO Build
|
||||
```bash
|
||||
# Backend (on development server)
|
||||
cd core/archipelago
|
||||
cargo run
|
||||
|
||||
# Frontend (local development)
|
||||
cd neode-ui
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
The build script is designed for automation:
|
||||
|
||||
```yaml
|
||||
# Example GitHub Actions workflow
|
||||
name: Build ISO
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build ISO
|
||||
run: ./build-iso-complete.sh --local
|
||||
- name: Upload ISO
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: archipelago-iso
|
||||
path: image-recipe/results/*.iso
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ build-iso-complete.sh (Orchestrator) │
|
||||
└─────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌─────────┼─────────┐
|
||||
│ │ │
|
||||
┌────▼───┐ ┌──▼───┐ ┌───▼────────┐
|
||||
│Backend │ │Front │ │ISO Builder │
|
||||
│(Rust) │ │(Vue) │ │(Debian) │
|
||||
└────┬───┘ └──┬───┘ └───┬────────┘
|
||||
│ │ │
|
||||
└────────┼─────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ Bootable ISO │
|
||||
│ (1.2 GB) │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Successful build produces:
|
||||
|
||||
```
|
||||
✅ ISO ready for flashing!
|
||||
|
||||
📀 ISO: /Users/dorian/Projects/archy/image-recipe/results/archipelago-debian-12-x86_64.iso
|
||||
📏 Size: 1.2G
|
||||
🔐 MD5: a3f2d8c9e4b1...
|
||||
|
||||
Next steps:
|
||||
|
||||
1. Insert USB drive
|
||||
2. Find device: diskutil list
|
||||
3. Flash ISO:
|
||||
|
||||
cd image-recipe && ./write-usb-dd.sh /dev/diskN
|
||||
|
||||
4. Boot from USB on target device
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- Check the troubleshooting section above
|
||||
- Review logs in `image-recipe/iso-workdir/build.log`
|
||||
- Open an issue on GitHub
|
||||
|
||||
## License
|
||||
|
||||
Same as Archipelago project - see main LICENSE file
|
||||
1. Test the ISO on VM
|
||||
2. Verify web UI loads
|
||||
3. Test container deployment
|
||||
4. Document any issues
|
||||
5. Tag the release in git
|
||||
6. Upload ISO to distribution point
|
||||
|
||||
417
DEPLOYMENT.md
Normal file
417
DEPLOYMENT.md
Normal file
@ -0,0 +1,417 @@
|
||||
# Archipelago Deployment & Build Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
This document captures all the critical configurations and fixes needed to build Archipelago from the live development server state.
|
||||
|
||||
**Last Updated:** 2026-02-03
|
||||
**Dev Server:** archipelago@192.168.1.228
|
||||
**Server Disk:** 1.8TB NVMe (1.7TB free)
|
||||
|
||||
---
|
||||
|
||||
## Critical Backend Fixes
|
||||
|
||||
### 1. Podman Container Detection (REQUIRED)
|
||||
|
||||
**Issue:** Backend runs as non-root user but containers are started with `sudo podman` (root context).
|
||||
|
||||
**Fix Applied:** Modified `/core/container/src/podman_client.rs` to use `sudo podman`:
|
||||
|
||||
```rust
|
||||
fn podman_async(&self) -> TokioCommand {
|
||||
// Always use sudo podman to access system-wide containers
|
||||
let mut cmd = TokioCommand::new("sudo");
|
||||
cmd.arg("podman");
|
||||
cmd
|
||||
}
|
||||
```
|
||||
|
||||
**Server Configuration:** Added passwordless sudo for podman:
|
||||
|
||||
```bash
|
||||
# /etc/sudoers.d/archipelago-podman
|
||||
archipelago ALL=(ALL) NOPASSWD: /usr/bin/podman
|
||||
```
|
||||
|
||||
### 2. IndeedHub Metadata in Backend
|
||||
|
||||
**Location:** `/core/archipelago/src/container/docker_packages.rs`
|
||||
|
||||
Added IndeedHub to the `get_app_metadata()` function:
|
||||
|
||||
```rust
|
||||
"indeedhub" => AppMetadata {
|
||||
title: "IndeedHub".to_string(),
|
||||
description: "Decentralized media streaming platform".to_string(),
|
||||
icon: "/assets/img/app-icons/indeedhub.png".to_string(),
|
||||
repo: "https://github.com/indeedhub/indeedhub".to_string(),
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nginx Configuration
|
||||
|
||||
### HTTP + HTTPS Setup (with self-signed certs)
|
||||
|
||||
**Location:** `/etc/nginx/sites-available/default`
|
||||
|
||||
```nginx
|
||||
# Redirect HTTP to HTTPS
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
# HTTPS server
|
||||
server {
|
||||
listen 443 ssl http2 default_server;
|
||||
listen [::]:443 ssl http2 default_server;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/archipelago.crt;
|
||||
ssl_certificate_key /etc/nginx/ssl/archipelago.key;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
root /opt/archipelago/web-ui;
|
||||
index index.html;
|
||||
|
||||
server_name _;
|
||||
|
||||
location /rpc/ {
|
||||
proxy_pass http://localhost:5678/rpc/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
|
||||
location /ws/ {
|
||||
proxy_pass http://localhost:5678/ws/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
|
||||
location /health {
|
||||
proxy_pass http://localhost:5678/health;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SSL Certificate Generation
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /etc/nginx/ssl
|
||||
sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
||||
-keyout /etc/nginx/ssl/archipelago.key \
|
||||
-out /etc/nginx/ssl/archipelago.crt \
|
||||
-subj "/C=US/ST=State/L=City/O=Archipelago/CN=archipelago.local"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Systemd Services
|
||||
|
||||
### Archipelago Backend Service
|
||||
|
||||
**Location:** `/etc/systemd/system/archipelago.service`
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Archipelago Backend
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=archipelago
|
||||
Group=archipelago
|
||||
ExecStart=/usr/local/bin/archipelago
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
Environment="RUST_LOG=debug"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Container Deployments
|
||||
|
||||
### Bitcoin Knots (Full Node)
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/lib/archipelago/bitcoin
|
||||
|
||||
sudo podman run -d \
|
||||
--name bitcoin-knots \
|
||||
--restart unless-stopped \
|
||||
-p 8332:8332 \
|
||||
-p 8333:8333 \
|
||||
-v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \
|
||||
--label "com.archipelago.app=bitcoin-knots" \
|
||||
--label "com.archipelago.title=Bitcoin Knots" \
|
||||
--label "com.archipelago.version=28.1" \
|
||||
--label "com.archipelago.category=bitcoin" \
|
||||
--label "com.archipelago.description.short=Full Bitcoin node implementation" \
|
||||
--label "com.archipelago.description.long=Bitcoin Knots is a derivative of Bitcoin Core with additional features and bug fixes. Maintain the full blockchain and validate all transactions." \
|
||||
--label "com.archipelago.license=MIT" \
|
||||
--label "com.archipelago.icon=/assets/img/app-icons/bitcoin-knots.webp" \
|
||||
--label "com.archipelago.port=8332" \
|
||||
--label "com.archipelago.repo=https://github.com/bitcoinknots/bitcoin" \
|
||||
docker.io/bitcoinknots/bitcoin:latest \
|
||||
-server=1 \
|
||||
-txindex=1 \
|
||||
-rpcallowip=0.0.0.0/0 \
|
||||
-rpcbind=0.0.0.0:8332 \
|
||||
-rpcuser=archipelago \
|
||||
-rpcpassword=archipelago123 \
|
||||
-dbcache=4096
|
||||
```
|
||||
|
||||
### IndeedHub (Example app deployment)
|
||||
|
||||
See `/Users/dorian/Projects/Indeedhub Prototype/deploy-to-archipelago.sh`
|
||||
|
||||
**Key Requirements:**
|
||||
- Must include `com.archipelago.*` labels for proper detection
|
||||
- Port mapping must be explicit
|
||||
- Restart policy: `unless-stopped`
|
||||
|
||||
---
|
||||
|
||||
## Build Process for Beta Release
|
||||
|
||||
### 1. Capture Live Server State
|
||||
|
||||
```bash
|
||||
cd /Users/dorian/Projects/archy/image-recipe
|
||||
|
||||
# Capture from dev server (default)
|
||||
DEV_SERVER=archipelago@192.168.1.228 ./build-auto-installer-iso.sh
|
||||
|
||||
# Or build from source
|
||||
BUILD_FROM_SOURCE=1 ./build-auto-installer-iso.sh
|
||||
```
|
||||
|
||||
### 2. What Gets Captured
|
||||
|
||||
The auto-installer script captures:
|
||||
|
||||
- **Backend Binary:** `/usr/local/bin/archipelago`
|
||||
- **Frontend Assets:** `/opt/archipelago/web-ui/`
|
||||
- **Nginx Configuration:** `/etc/nginx/sites-available/default`
|
||||
- **SSL Certificates:** `/etc/nginx/ssl/`
|
||||
- **Systemd Service:** `/etc/systemd/system/archipelago.service`
|
||||
- **Sudoers Config:** `/etc/sudoers.d/archipelago-podman`
|
||||
|
||||
**NOTE:** Containers are NOT captured in the ISO - they must be deployed after installation.
|
||||
|
||||
### 3. Critical Auto-Installer Fix
|
||||
|
||||
**Location:** `/image-recipe/build-auto-installer-iso.sh` (line ~850)
|
||||
|
||||
The auto-start script MUST NOT check `[ ! -t 0 ]` (non-interactive check):
|
||||
|
||||
```bash
|
||||
# CORRECT (in z99-archipelago-installer.sh):
|
||||
if [ -n "$INSTALLER_STARTED" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# WRONG (will fail on auto-login):
|
||||
# if [ -n "$INSTALLER_STARTED" ] || [ ! -t 0 ]; then
|
||||
```
|
||||
|
||||
This was causing the installer to hang at `user@debian:~$` prompt.
|
||||
|
||||
---
|
||||
|
||||
## Dependencies Required on Build Machine
|
||||
|
||||
### For Building ISOs (Mac/Linux):
|
||||
|
||||
```bash
|
||||
# Docker or Podman (for rootfs creation)
|
||||
brew install podman
|
||||
# OR
|
||||
brew install docker
|
||||
|
||||
# ISO creation tools
|
||||
brew install xorriso # Mac
|
||||
# OR
|
||||
apt install xorriso # Linux
|
||||
```
|
||||
|
||||
### For Server Runtime:
|
||||
|
||||
```bash
|
||||
# Debian 12 (Bookworm) base
|
||||
apt update && apt install -y \
|
||||
nginx \
|
||||
podman \
|
||||
build-essential \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
curl \
|
||||
rsync
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Build
|
||||
|
||||
```bash
|
||||
cd /Users/dorian/Projects/archy/neode-ui
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Output goes to: ../web/dist/neode-ui/
|
||||
```
|
||||
|
||||
**Deploy to server:**
|
||||
|
||||
```bash
|
||||
rsync -avz --delete ../web/dist/neode-ui/ archipelago@192.168.1.228:/opt/archipelago/web-ui/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backend Build
|
||||
|
||||
```bash
|
||||
cd /Users/dorian/Projects/archy/core/archipelago
|
||||
|
||||
# Build release binary
|
||||
cargo build --release
|
||||
|
||||
# Binary location: ../target/release/archipelago
|
||||
```
|
||||
|
||||
**Deploy to server:**
|
||||
|
||||
```bash
|
||||
scp ../target/release/archipelago archipelago@192.168.1.228:/tmp/
|
||||
ssh archipelago@192.168.1.228 'sudo systemctl stop archipelago && \
|
||||
sudo mv /tmp/archipelago /usr/local/bin/archipelago && \
|
||||
sudo chmod +x /usr/local/bin/archipelago && \
|
||||
sudo systemctl start archipelago'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist (Pre-Release)
|
||||
|
||||
- [ ] Backend detects all running containers
|
||||
- [ ] Frontend loads and connects to backend WebSocket
|
||||
- [ ] Apps show in "My Apps" with correct status
|
||||
- [ ] App Store shows containers with "Installed" badge
|
||||
- [ ] Bitcoin node is syncing blockchain
|
||||
- [ ] Nginx serves frontend correctly
|
||||
- [ ] RPC/WebSocket proxying works
|
||||
- [ ] Auto-installer ISO boots and installs
|
||||
- [ ] Post-install: System boots to login screen
|
||||
- [ ] Web UI accessible at http://server-ip
|
||||
|
||||
---
|
||||
|
||||
## Known Issues
|
||||
|
||||
### Port 443 Not Binding (Post-Reinstall)
|
||||
|
||||
After fresh install, HTTPS (port 443) may not bind even with correct nginx config. **Workaround:** Use HTTP only initially, investigate nginx/systemd socket issues.
|
||||
|
||||
### Browser HTTPS Auto-Upgrade
|
||||
|
||||
Browsers (especially Brave/Chrome) aggressively upgrade to HTTPS. Users may need to:
|
||||
- Clear site data
|
||||
- Disable "HTTPS-Only Mode"
|
||||
- Use `http://` prefix explicitly
|
||||
|
||||
---
|
||||
|
||||
## File Locations Summary
|
||||
|
||||
| Component | Dev Server Location | ISO Build Captures |
|
||||
|-----------|-------------------|-------------------|
|
||||
| Backend Binary | `/usr/local/bin/archipelago` | ✅ Yes |
|
||||
| Frontend Assets | `/opt/archipelago/web-ui/` | ✅ Yes |
|
||||
| Nginx Config | `/etc/nginx/sites-available/default` | ✅ Yes |
|
||||
| SSL Certs | `/etc/nginx/ssl/` | ✅ Yes |
|
||||
| Systemd Service | `/etc/systemd/system/archipelago.service` | ✅ Yes |
|
||||
| Sudoers | `/etc/sudoers.d/archipelago-podman` | ✅ Yes |
|
||||
| Container Data | `/var/lib/archipelago/` | ❌ No - too large |
|
||||
| Bitcoin Blockchain | `/var/lib/archipelago/bitcoin/` | ❌ No - user downloads |
|
||||
|
||||
---
|
||||
|
||||
## Version Control
|
||||
|
||||
**Important Changes to Track:**
|
||||
|
||||
1. `/core/container/src/podman_client.rs` - sudo podman fix
|
||||
2. `/core/archipelago/src/container/docker_packages.rs` - app metadata
|
||||
3. `/neode-ui/src/utils/dummyApps.ts` - frontend app definitions
|
||||
4. `/image-recipe/build-auto-installer-iso.sh` - auto-start fix
|
||||
|
||||
**Commit before building beta:**
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "Prepare for beta release: podman detection, IndeedHub metadata, auto-installer fixes"
|
||||
git tag v0.1.0-beta.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Emergency Recovery
|
||||
|
||||
If the backend fails to detect containers:
|
||||
|
||||
```bash
|
||||
# Verify sudoers file exists
|
||||
cat /etc/sudoers.d/archipelago-podman
|
||||
|
||||
# Test manual detection
|
||||
sudo podman ps --format json
|
||||
|
||||
# Check backend logs
|
||||
sudo journalctl -u archipelago -f
|
||||
|
||||
# Restart backend
|
||||
sudo systemctl restart archipelago
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
|
||||
Development Server: archipelago@192.168.1.228
|
||||
Password: `archipelago`
|
||||
Web UI: http://192.168.1.228 (or https with self-signed cert warning)
|
||||
71
apps/indeedhub/manifest.yml
Normal file
71
apps/indeedhub/manifest.yml
Normal file
@ -0,0 +1,71 @@
|
||||
app:
|
||||
id: indeedhub
|
||||
name: IndeedHub
|
||||
version: 0.1.0
|
||||
description: Bitcoin documentary streaming platform featuring God Bless Bitcoin and other educational content about Bitcoin, sovereignty, and decentralized technology.
|
||||
category: media
|
||||
|
||||
container:
|
||||
image: localhost/indeedhub:latest
|
||||
pull_policy: never # Built locally
|
||||
|
||||
dependencies:
|
||||
- storage: 500Mi
|
||||
|
||||
resources:
|
||||
cpu_limit: 1
|
||||
memory_limit: 512Mi
|
||||
disk_limit: 500Mi
|
||||
|
||||
security:
|
||||
capabilities: []
|
||||
readonly_root: true # Static nginx content
|
||||
network_policy: bridge
|
||||
apparmor_profile: default
|
||||
|
||||
ports:
|
||||
- host: 7777
|
||||
container: 7777
|
||||
protocol: tcp # Web UI
|
||||
|
||||
volumes:
|
||||
- type: tmpfs
|
||||
target: /var/cache/nginx
|
||||
options: [rw,noexec,nosuid,size=10m]
|
||||
- type: tmpfs
|
||||
target: /var/run
|
||||
options: [rw,noexec,nosuid,size=10m]
|
||||
|
||||
environment:
|
||||
- NGINX_HOST=localhost
|
||||
- NGINX_PORT=7777
|
||||
|
||||
health_check:
|
||||
type: http
|
||||
endpoint: http://localhost:7777
|
||||
path: /health
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
interfaces:
|
||||
main:
|
||||
name: Web UI
|
||||
description: Stream Bitcoin documentaries
|
||||
type: ui
|
||||
port: 7777
|
||||
protocol: http
|
||||
path: /
|
||||
|
||||
metadata:
|
||||
author: IndeedHub Team
|
||||
website: https://indeedhub.com
|
||||
source: https://github.com/indeedhub/indeedhub
|
||||
license: MIT
|
||||
tags:
|
||||
- bitcoin
|
||||
- documentary
|
||||
- streaming
|
||||
- media
|
||||
- education
|
||||
@ -96,6 +96,10 @@ impl DockerPackageScanner {
|
||||
let lan_address = if let Some(ui_address) = ui_containers.get(&app_id) {
|
||||
debug!("Using UI container address for {}: {}", app_id, ui_address);
|
||||
Some(ui_address.clone())
|
||||
} else if app_id == "bitcoin-knots" {
|
||||
// Bitcoin UI runs on host network at port 8334
|
||||
debug!("Using bitcoin-ui for bitcoin-knots: http://localhost:8334");
|
||||
Some("http://localhost:8334".to_string())
|
||||
} else if app_id == "tailscale" {
|
||||
// Tailscale uses host networking, so no port mappings
|
||||
// But web UI is always on port 8240
|
||||
@ -324,6 +328,12 @@ fn get_app_metadata(app_id: &str) -> AppMetadata {
|
||||
icon: "/assets/img/app-icons/tailscale.webp".to_string(),
|
||||
repo: "https://github.com/tailscale/tailscale".to_string(),
|
||||
},
|
||||
"indeedhub" => AppMetadata {
|
||||
title: "IndeedHub".to_string(),
|
||||
description: "Decentralized media streaming platform".to_string(),
|
||||
icon: "/assets/img/app-icons/indeedhub.png".to_string(),
|
||||
repo: "https://github.com/indeedhub/indeedhub".to_string(),
|
||||
},
|
||||
_ => AppMetadata {
|
||||
title: app_id.to_string(),
|
||||
description: format!("{} application", app_id),
|
||||
|
||||
@ -78,13 +78,9 @@ impl PodmanClient {
|
||||
}
|
||||
|
||||
fn podman_async(&self) -> TokioCommand {
|
||||
let mut cmd = TokioCommand::new("podman");
|
||||
if self.rootless {
|
||||
// Use actual HOME environment variable instead of hardcoded /home
|
||||
if let Ok(home) = std::env::var("HOME") {
|
||||
cmd.env("HOME", home);
|
||||
}
|
||||
}
|
||||
// Always use sudo podman to access system-wide containers
|
||||
let mut cmd = TokioCommand::new("sudo");
|
||||
cmd.arg("podman");
|
||||
cmd
|
||||
}
|
||||
|
||||
|
||||
6
deploy-frontend.exp
Executable file
6
deploy-frontend.exp
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/expect -f
|
||||
set timeout 60
|
||||
spawn rsync -avz --delete /Users/dorian/Projects/archy/web/dist/neode-ui/ archipelago@192.168.1.228:/opt/archipelago/web-ui/
|
||||
expect "password:"
|
||||
send "archipelago\r"
|
||||
expect eof
|
||||
35
deploy-indeedhub-full.sh
Executable file
35
deploy-indeedhub-full.sh
Executable file
@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
REMOTE_SERVER="archipelago@192.168.1.228"
|
||||
|
||||
echo "╔════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ Deploying Indeedhub + Updated Archipelago UI ║"
|
||||
echo "╚════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Step 1: Deploy Indeedhub container
|
||||
echo "📦 Step 1: Deploying Indeedhub container..."
|
||||
cd "/Users/dorian/Projects/Indeedhub Prototype"
|
||||
./deploy-to-archipelago.sh
|
||||
|
||||
# Step 2: Deploy updated frontend
|
||||
echo ""
|
||||
echo "📦 Step 2: Deploying updated Archipelago frontend..."
|
||||
cd /Users/dorian/Projects/archy
|
||||
|
||||
echo " Syncing frontend to server..."
|
||||
rsync -avz --delete \
|
||||
-e "ssh -o PreferredAuthentications=keyboard-interactive,password" \
|
||||
web/dist/neode-ui/ "$REMOTE_SERVER:/opt/archipelago/web-ui/"
|
||||
|
||||
echo ""
|
||||
echo "╔════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ ✅ DEPLOYMENT COMPLETE! ║"
|
||||
echo "╚════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo "🎬 Indeedhub: http://192.168.1.228:7777"
|
||||
echo "🏠 Archipelago: https://192.168.1.228"
|
||||
echo ""
|
||||
echo " Indeedhub is now visible in the Archipelago app store!"
|
||||
echo ""
|
||||
19
docker/bitcoin-ui/Dockerfile
Normal file
19
docker/bitcoin-ui/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
FROM docker.io/library/nginx:alpine
|
||||
|
||||
# Copy the static UI
|
||||
COPY index.html /usr/share/nginx/html/
|
||||
|
||||
# Create assets directories first
|
||||
RUN mkdir -p /usr/share/nginx/html/assets/img/app-icons && \
|
||||
mkdir -p /usr/share/nginx/html/assets/img
|
||||
|
||||
# Copy Bitcoin Knots icon and background
|
||||
COPY bitcoin-knots.webp /usr/share/nginx/html/assets/img/app-icons/
|
||||
COPY bg-network.jpg /usr/share/nginx/html/assets/img/
|
||||
|
||||
# Copy custom Nginx config with Bitcoin RPC proxy
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
BIN
docker/bitcoin-ui/bg-network.jpg
Normal file
BIN
docker/bitcoin-ui/bg-network.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 620 KiB |
BIN
docker/bitcoin-ui/bitcoin-knots.webp
Normal file
BIN
docker/bitcoin-ui/bitcoin-knots.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@ -3,6 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>Bitcoin Knots - Archipelago</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
@ -32,7 +35,7 @@
|
||||
.bg-layer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url('/assets/img/bg-web5.jpg');
|
||||
background-image: url('/assets/img/bg-network.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
@ -51,19 +54,43 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Glass card - Web5 style */
|
||||
/* Glass card - Archipelago standard with gradient border */
|
||||
.glass-card {
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
backdrop-filter: blur(18px);
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
|
||||
position: relative;
|
||||
background: rgba(0, 0, 0, 0.60);
|
||||
backdrop-filter: blur(24px);
|
||||
-webkit-backdrop-filter: blur(24px);
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.45),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
border-radius: 1rem;
|
||||
overflow-x: hidden;
|
||||
overflow-y: visible;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Glass button - Web5 style */
|
||||
.glass-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 2px;
|
||||
background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.glass-card > * {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Glass button - Archipelago standard (secondary actions) */
|
||||
.glass-button {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(18px);
|
||||
@ -75,6 +102,107 @@
|
||||
|
||||
.glass-button:hover {
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* Gradient button - Archipelago standard (primary actions) */
|
||||
.gradient-button {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(0, 0, 0, 0.8) 100%);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.gradient-button:hover {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, rgba(0, 0, 0, 0.9) 100%);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.gradient-button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Interactive card - Archipelago standard (display only, no hover) */
|
||||
.info-card {
|
||||
position: relative;
|
||||
background: rgba(0, 0, 0, 0.60);
|
||||
backdrop-filter: blur(24px);
|
||||
-webkit-backdrop-filter: blur(24px);
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.45),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
border-radius: 16px;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.info-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 2px;
|
||||
background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Interactive button - Same as info-card but with hover effects */
|
||||
.info-card-button {
|
||||
position: relative;
|
||||
background: rgba(0, 0, 0, 0.60);
|
||||
backdrop-filter: blur(24px);
|
||||
-webkit-backdrop-filter: blur(24px);
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.45),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
border-radius: 16px;
|
||||
padding: 12px;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.info-card-button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 2px;
|
||||
background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.info-card-button:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
box-shadow:
|
||||
0 12px 32px rgba(0, 0, 0, 0.6),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.25);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.info-card-button:hover::before {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.3), transparent);
|
||||
}
|
||||
|
||||
.info-card-button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Container */
|
||||
@ -124,6 +252,75 @@
|
||||
.animate-ping {
|
||||
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
/* Pulsing glow for progress bar */
|
||||
@keyframes progressGlow {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 10px rgba(251, 146, 60, 0.5),
|
||||
0 0 20px rgba(251, 146, 60, 0.3),
|
||||
0 0 30px rgba(251, 146, 60, 0.1);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 20px rgba(251, 146, 60, 0.8),
|
||||
0 0 30px rgba(251, 146, 60, 0.5),
|
||||
0 0 40px rgba(251, 146, 60, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-glow {
|
||||
animation: progressGlow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Spinning animation */
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-spin-slow {
|
||||
animation: spin 3s linear infinite;
|
||||
}
|
||||
|
||||
/* Shimmer effect */
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -1000px 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 1000px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.shimmer {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0.1) 50%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
background-size: 1000px 100%;
|
||||
animation: shimmer 3s infinite;
|
||||
}
|
||||
|
||||
/* Number increment animation */
|
||||
@keyframes numberPulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
color: rgba(251, 146, 60, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.number-update {
|
||||
animation: numberPulse 0.5s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -133,9 +330,9 @@
|
||||
<div class="overlay"></div>
|
||||
|
||||
<div class="container">
|
||||
<!-- Header - Glass card with logo top left -->
|
||||
<!-- Header - Glass card with logo and node info -->
|
||||
<div class="glass-card p-6 mb-6">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="flex flex-col md:flex-row items-center md:items-center gap-4 md:gap-6">
|
||||
<!-- Logo - Top Left -->
|
||||
<div class="flex-shrink-0">
|
||||
<div class="logo-gradient-border">
|
||||
@ -144,6 +341,7 @@
|
||||
alt="Bitcoin Knots"
|
||||
class="w-16 h-16"
|
||||
style="object-fit: contain;"
|
||||
onerror="this.style.display='none'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -152,137 +350,99 @@
|
||||
<div class="flex-1 min-w-0">
|
||||
<h1 class="text-3xl font-bold text-white mb-2">Bitcoin Knots</h1>
|
||||
<p class="text-white/70">Enhanced Bitcoin node implementation</p>
|
||||
<p class="text-sm text-white/60 mt-2">Regtest mode - Development environment</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions Container - Web5 style -->
|
||||
<div class="glass-card p-6 mb-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<!-- Network Status -->
|
||||
<div class="flex items-center justify-between p-4 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Node Status Info - Compact on Desktop -->
|
||||
<div class="w-full md:w-auto flex flex-col md:flex-row gap-3 md:gap-4 mt-4 md:mt-0">
|
||||
<div class="info-card flex items-center gap-3">
|
||||
<div class="relative">
|
||||
<div class="w-3 h-3 rounded-full bg-green-400"></div>
|
||||
<div class="absolute inset-0 w-3 h-3 rounded-full bg-green-400 animate-ping opacity-75"></div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-white">Network</p>
|
||||
<p class="text-xs text-white/60">Regtest</p>
|
||||
<p class="text-xs text-white/60">Status</p>
|
||||
<p class="text-sm font-medium text-white">Running</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Block Height -->
|
||||
<div class="flex items-center justify-between p-4 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative">
|
||||
<span class="text-2xl text-orange-500 font-bold">₿</span>
|
||||
</div>
|
||||
<div class="info-card flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-white">Block Height</p>
|
||||
<p class="text-xs text-orange-500 font-medium" id="blockHeight">0</p>
|
||||
<p class="text-xs text-white/60">Version</p>
|
||||
<p class="text-sm font-medium text-white" id="nodeVersion">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RPC Status -->
|
||||
<div class="flex items-center justify-between p-4 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative">
|
||||
<div class="w-3 h-3 rounded-full bg-green-400"></div>
|
||||
<div class="absolute inset-0 w-3 h-3 rounded-full bg-green-400 animate-ping opacity-75"></div>
|
||||
</div>
|
||||
<div class="info-card flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
|
||||
</svg>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-white">RPC Server</p>
|
||||
<p class="text-xs text-white/60">Active</p>
|
||||
<p class="text-xs text-white/60">Network</p>
|
||||
<p class="text-sm font-medium text-white" id="networkType">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onclick="openSettings()"
|
||||
class="px-3 py-1.5 glass-button rounded text-xs font-medium text-white/90 hover:text-white transition-colors"
|
||||
|
||||
<button
|
||||
onclick="openSettings()"
|
||||
class="px-4 py-3 glass-button rounded-lg text-sm font-medium"
|
||||
>
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ZMQ Status -->
|
||||
<div class="flex items-center justify-between p-4 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative">
|
||||
<div class="w-3 h-3 rounded-full bg-green-400"></div>
|
||||
<div class="absolute inset-0 w-3 h-3 rounded-full bg-green-400 animate-ping opacity-75"></div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-white">ZMQ</p>
|
||||
<p class="text-xs text-white/60">Enabled</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onclick="openLogs()"
|
||||
class="px-3 py-1.5 glass-button rounded text-xs font-medium text-white/90 hover:text-white transition-colors"
|
||||
>
|
||||
Logs
|
||||
</button>
|
||||
<!-- Blockchain Sync Status Card - NEW -->
|
||||
<div class="glass-card p-6 mb-6" id="syncStatusCard">
|
||||
<div class="flex items-start gap-4 mb-4">
|
||||
<div class="flex-shrink-0 w-12 h-12 rounded-lg bg-orange-500/20 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-orange-500 animate-spin-slow" fill="none" stroke="currentColor" viewBox="0 0 24 24" id="syncIcon">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h2 class="text-xl font-semibold text-white mb-1">Blockchain Sync</h2>
|
||||
<p class="text-white/70 text-sm" id="syncStatusText">Checking sync status...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="mb-4">
|
||||
<div class="flex justify-between text-sm text-white/60 mb-2">
|
||||
<span id="currentBlock">Block 0</span>
|
||||
<span id="syncPercentage">0%</span>
|
||||
</div>
|
||||
<div class="w-full bg-white/10 rounded-full h-3 overflow-hidden relative shimmer">
|
||||
<div class="h-full bg-gradient-to-r from-orange-500 to-yellow-400 rounded-full transition-all duration-500 progress-glow" id="syncProgressBar" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sync Stats Grid -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<div class="info-card">
|
||||
<p class="text-xs text-white/60 mb-1">Current Height</p>
|
||||
<p class="text-lg font-semibold text-white transition-all" id="currentHeight">-</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p class="text-xs text-white/60 mb-1">Network Height</p>
|
||||
<p class="text-lg font-semibold text-white" id="networkHeight">-</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p class="text-xs text-white/60 mb-1">Headers</p>
|
||||
<p class="text-lg font-semibold text-white" id="headers">-</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p class="text-xs text-white/60 mb-1">Verification</p>
|
||||
<p class="text-lg font-semibold text-white" id="verificationProgress">-</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Core Services Overview Cards - Web5 style -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
||||
<!-- Node Information -->
|
||||
<div class="glass-card p-6">
|
||||
<div class="flex items-start gap-4 mb-4">
|
||||
<div class="flex-shrink-0 w-12 h-12 rounded-lg bg-white/10 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-white/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h2 class="text-xl font-semibold text-white mb-2">Node Information</h2>
|
||||
<p class="text-white/70 text-sm mb-4">Bitcoin Core node status</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
<span class="text-white/80 text-sm">Node Status</span>
|
||||
</div>
|
||||
<span class="text-green-400 text-sm font-medium">Running</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span class="text-white/80 text-sm">Version</span>
|
||||
</div>
|
||||
<span class="text-white/60 text-sm">v27.0</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" />
|
||||
</svg>
|
||||
<span class="text-white/80 text-sm">Network</span>
|
||||
</div>
|
||||
<span class="text-white/60 text-sm">Regtest</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="mt-4 w-full px-4 py-2 glass-button rounded-lg text-sm font-medium text-white/90 hover:text-white transition-colors" onclick="openSettings()">
|
||||
Node Settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- RPC Connection -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||
<div class="glass-card p-6">
|
||||
<div class="flex items-start gap-4 mb-4">
|
||||
<div class="flex-shrink-0 w-12 h-12 rounded-lg bg-white/10 flex items-center justify-center">
|
||||
@ -304,7 +464,7 @@
|
||||
</svg>
|
||||
<span class="text-white/80 text-sm">RPC Host</span>
|
||||
</div>
|
||||
<span class="text-white/60 text-sm font-mono">localhost:18443</span>
|
||||
<span class="text-white/60 text-sm font-mono" id="rpcHost">localhost:8332</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
@ -314,7 +474,7 @@
|
||||
</svg>
|
||||
<span class="text-white/80 text-sm">RPC User</span>
|
||||
</div>
|
||||
<span class="text-white/60 text-sm font-mono">bitcoin</span>
|
||||
<span class="text-white/60 text-sm font-mono">archipelago</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
@ -328,7 +488,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="mt-4 w-full px-4 py-2 glass-button rounded-lg text-sm font-medium text-white/90 hover:text-white transition-colors" onclick="copyRPCInfo()">
|
||||
<button class="mt-4 w-full info-card-button text-sm font-medium" onclick="copyRPCInfo()">
|
||||
Copy RPC Info
|
||||
</button>
|
||||
</div>
|
||||
@ -379,7 +539,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="mt-4 w-full px-4 py-2 glass-button rounded-lg text-sm font-medium text-white/90 hover:text-white transition-colors" onclick="openLogs()">
|
||||
<button class="mt-4 w-full info-card-button text-sm font-medium" onclick="openLogs()">
|
||||
View Logs
|
||||
</button>
|
||||
</div>
|
||||
@ -391,7 +551,7 @@
|
||||
<div class="glass-card p-6 max-w-2xl w-full max-h-[80vh] overflow-y-auto">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl font-bold text-white">Node Settings</h2>
|
||||
<button onclick="closeSettings()" class="w-10 h-10 rounded-lg bg-white/10 border border-white/18 text-white text-xl flex items-center justify-center hover:bg-white/20 transition-colors">×</button>
|
||||
<button onclick="closeSettings()" class="glass-button px-3 py-2 rounded-lg text-xl font-medium">×</button>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div class="p-3 bg-white/5 rounded-lg">
|
||||
@ -419,7 +579,7 @@
|
||||
<div class="glass-card p-6 max-w-4xl w-full max-h-[80vh] overflow-y-auto">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl font-bold text-white">Node Logs</h2>
|
||||
<button onclick="closeLogs()" class="w-10 h-10 rounded-lg bg-white/10 border border-white/18 text-white text-xl flex items-center justify-center hover:bg-white/20 transition-colors">×</button>
|
||||
<button onclick="closeLogs()" class="glass-button px-3 py-2 rounded-lg text-xl font-medium">×</button>
|
||||
</div>
|
||||
<div class="bg-black/40 rounded-lg p-4 font-mono text-xs text-white/80 whitespace-pre-wrap break-all" id="logsContent">
|
||||
Loading logs...
|
||||
@ -428,8 +588,161 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
console.log('[Bitcoin UI] Script loaded, initializing...');
|
||||
|
||||
// RPC Configuration - Use local Nginx proxy within container
|
||||
const RPC_ENDPOINT = '/bitcoin-rpc/';
|
||||
console.log('[Bitcoin UI] RPC Endpoint:', RPC_ENDPOINT);
|
||||
|
||||
// Make RPC call to Bitcoin node via local proxy
|
||||
async function callRPC(method, params = []) {
|
||||
try {
|
||||
console.log(`[Bitcoin UI] Calling RPC method: ${method}`);
|
||||
const response = await fetch(RPC_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '1.0',
|
||||
id: 'bitcoin-ui',
|
||||
method: method,
|
||||
params: params
|
||||
})
|
||||
});
|
||||
|
||||
console.log(`[Bitcoin UI] RPC response status: ${response.status}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log(`[Bitcoin UI] RPC ${method} success:`, data.result ? 'OK' : 'Error');
|
||||
|
||||
if (data.error) {
|
||||
throw new Error(data.error.message);
|
||||
}
|
||||
return data.result;
|
||||
} catch (error) {
|
||||
console.error(`[Bitcoin UI] RPC call failed: ${method}`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Track last block count for animations
|
||||
let lastBlockCount = 0;
|
||||
|
||||
// Update blockchain info
|
||||
async function updateBlockchainInfo() {
|
||||
console.log('[Bitcoin UI] updateBlockchainInfo() called');
|
||||
try {
|
||||
const blockchainInfo = await callRPC('getblockchaininfo');
|
||||
console.log('[Bitcoin UI] blockchainInfo:', blockchainInfo);
|
||||
|
||||
if (!blockchainInfo) {
|
||||
console.error('[Bitcoin UI] No blockchain info received');
|
||||
document.getElementById('syncStatusText').textContent = 'Unable to connect to Bitcoin node';
|
||||
document.getElementById('syncStatusText').className = 'text-red-400 text-sm';
|
||||
return;
|
||||
}
|
||||
|
||||
const networkInfo = await callRPC('getnetworkinfo');
|
||||
|
||||
// Update network mode
|
||||
const chain = blockchainInfo.chain || 'unknown';
|
||||
const networkType = document.getElementById('networkType');
|
||||
|
||||
let networkShort = '';
|
||||
if (chain === 'regtest') {
|
||||
networkShort = 'Regtest';
|
||||
} else if (chain === 'test') {
|
||||
networkShort = 'Testnet';
|
||||
} else if (chain === 'main') {
|
||||
networkShort = 'Mainnet';
|
||||
} else {
|
||||
networkShort = chain;
|
||||
}
|
||||
|
||||
if (networkType) networkType.textContent = networkShort;
|
||||
|
||||
// Update sync status
|
||||
const blocks = blockchainInfo.blocks || 0;
|
||||
const headers = blockchainInfo.headers || 0;
|
||||
const verificationProgress = blockchainInfo.verificationprogress || 0;
|
||||
const isSynced = blocks >= headers - 1;
|
||||
|
||||
// Calculate actual sync percentage based on blocks/headers
|
||||
const actualSyncPercentage = headers > 0 ? ((blocks / headers) * 100).toFixed(2) : '0.00';
|
||||
const verificationPercentage = (verificationProgress * 100).toFixed(2);
|
||||
|
||||
// Animate block count if it changed
|
||||
const currentHeightElem = document.getElementById('currentHeight');
|
||||
if (blocks !== lastBlockCount && lastBlockCount > 0) {
|
||||
currentHeightElem.classList.add('number-update');
|
||||
setTimeout(() => currentHeightElem.classList.remove('number-update'), 500);
|
||||
}
|
||||
lastBlockCount = blocks;
|
||||
|
||||
currentHeightElem.textContent = blocks.toLocaleString();
|
||||
document.getElementById('networkHeight').textContent = headers.toLocaleString();
|
||||
document.getElementById('headers').textContent = headers.toLocaleString();
|
||||
document.getElementById('verificationProgress').textContent = `${verificationPercentage}%`;
|
||||
document.getElementById('syncPercentage').textContent = `${actualSyncPercentage}%`;
|
||||
document.getElementById('currentBlock').textContent = `Block ${blocks.toLocaleString()}`;
|
||||
document.getElementById('syncProgressBar').style.width = `${actualSyncPercentage}%`;
|
||||
|
||||
// Update sync status text and icon
|
||||
const syncStatusText = document.getElementById('syncStatusText');
|
||||
const syncIcon = document.getElementById('syncIcon');
|
||||
|
||||
if (isSynced) {
|
||||
syncStatusText.textContent = '✓ Fully synchronized with the network';
|
||||
syncStatusText.className = 'text-green-400 text-sm font-medium';
|
||||
// Stop spinning when synced
|
||||
if (syncIcon) {
|
||||
syncIcon.classList.remove('animate-spin-slow');
|
||||
syncIcon.classList.add('text-green-500');
|
||||
}
|
||||
} else {
|
||||
const remaining = headers - blocks;
|
||||
syncStatusText.textContent = `Syncing... ${remaining.toLocaleString()} blocks remaining`;
|
||||
syncStatusText.className = 'text-orange-400 text-sm font-medium';
|
||||
// Keep spinning while syncing
|
||||
if (syncIcon) {
|
||||
syncIcon.classList.add('animate-spin-slow');
|
||||
syncIcon.classList.remove('text-green-500');
|
||||
}
|
||||
}
|
||||
|
||||
// Update block height in quick actions (removed section)
|
||||
// document.getElementById('blockHeight').textContent = blocks.toLocaleString();
|
||||
|
||||
// Update version
|
||||
if (networkInfo && networkInfo.version) {
|
||||
const version = networkInfo.version;
|
||||
const versionStr = `v${Math.floor(version / 10000)}.${Math.floor((version % 10000) / 100)}.${version % 100}`;
|
||||
const versionElem = document.getElementById('nodeVersion');
|
||||
if (versionElem) versionElem.textContent = versionStr;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to update blockchain info:', error);
|
||||
document.getElementById('syncStatusText').textContent = 'Unable to fetch blockchain data';
|
||||
document.getElementById('syncStatusText').className = 'text-red-400 text-sm';
|
||||
}
|
||||
}
|
||||
|
||||
// Initial update
|
||||
console.log('[Bitcoin UI] Starting initial blockchain info update...');
|
||||
updateBlockchainInfo();
|
||||
|
||||
// Update every 5 seconds
|
||||
console.log('[Bitcoin UI] Setting up 5-second update interval');
|
||||
setInterval(updateBlockchainInfo, 5000);
|
||||
|
||||
function copyRPCInfo() {
|
||||
const info = `RPC Host: localhost:18443\nRPC User: bitcoin\nRPC Password: bitcoinpass`;
|
||||
const info = `RPC Host: ${window.location.hostname}:8332\nRPC User: archipelago\nRPC Password: archipelago123\nRPC Endpoint: ${RPC_ENDPOINT}`;
|
||||
navigator.clipboard.writeText(info).then(() => {
|
||||
alert('RPC info copied to clipboard!');
|
||||
});
|
||||
@ -456,29 +769,34 @@
|
||||
document.getElementById('logsModal').classList.remove('flex');
|
||||
}
|
||||
|
||||
function loadLogs() {
|
||||
async function loadLogs() {
|
||||
const logsContent = document.getElementById('logsContent');
|
||||
logsContent.textContent = `Bitcoin Core version v27.0
|
||||
Assuming ancestors of block 0000000000000000000000000000000000000000000000000000000000000000 have valid signatures.
|
||||
Setting nMinimumChainWork=0000000000000000000000000000000000000000000000000000000000000000
|
||||
logsContent.textContent = 'Loading logs from node...';
|
||||
|
||||
try {
|
||||
const networkInfo = await callRPC('getnetworkinfo');
|
||||
const blockchainInfo = await callRPC('getblockchaininfo');
|
||||
const peerInfo = await callRPC('getpeerinfo');
|
||||
|
||||
if (networkInfo && blockchainInfo) {
|
||||
logsContent.textContent = `Bitcoin Knots version ${networkInfo.subversion || 'unknown'}
|
||||
Network: ${blockchainInfo.chain}
|
||||
Blocks: ${blockchainInfo.blocks}
|
||||
Headers: ${blockchainInfo.headers}
|
||||
Verification Progress: ${(blockchainInfo.verificationprogress * 100).toFixed(2)}%
|
||||
Connected Peers: ${peerInfo ? peerInfo.length : 0}
|
||||
Difficulty: ${blockchainInfo.difficulty}
|
||||
Chain Work: ${blockchainInfo.chainwork || 'N/A'}
|
||||
|
||||
Regtest mode activated
|
||||
RPC server listening on 0.0.0.0:18443
|
||||
ZMQ block notifications enabled on tcp://0.0.0.0:28332
|
||||
ZMQ tx notifications enabled on tcp://0.0.0.0:28333
|
||||
Transaction index enabled (txindex=1)
|
||||
|
||||
Node initialization complete
|
||||
Ready to accept connections on port 18444`;
|
||||
}
|
||||
|
||||
let blockHeight = 0;
|
||||
setInterval(() => {
|
||||
if (Math.random() > 0.7) {
|
||||
blockHeight++;
|
||||
document.getElementById('blockHeight').textContent = blockHeight;
|
||||
Node is running and accepting connections.
|
||||
RPC server active on port 8332`;
|
||||
} else {
|
||||
logsContent.textContent = 'Unable to fetch node logs. Please check your RPC connection.';
|
||||
}
|
||||
} catch (error) {
|
||||
logsContent.textContent = `Error loading logs: ${error.message}`;
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
|
||||
28
docker/bitcoin-ui/nginx.conf
Normal file
28
docker/bitcoin-ui/nginx.conf
Normal file
@ -0,0 +1,28 @@
|
||||
server {
|
||||
listen 8334;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Bitcoin RPC proxy to avoid CORS issues
|
||||
location /bitcoin-rpc/ {
|
||||
# Proxy to localhost Bitcoin RPC (using host network mode)
|
||||
proxy_pass http://127.0.0.1:8332/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Authorization "Basic YXJjaGlwZWxhZ286YXJjaGlwZWxhZ28xMjM=";
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
|
||||
if ($request_method = OPTIONS) {
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
188
docs/BETA-RELEASE-CHECKLIST.md
Normal file
188
docs/BETA-RELEASE-CHECKLIST.md
Normal file
@ -0,0 +1,188 @@
|
||||
# CRITICAL CHANGES FOR BETA ISO BUILD
|
||||
|
||||
## ⚠️ MUST-HAVE CHANGES - Without these, the beta will NOT work!
|
||||
|
||||
### 1. Backend: Podman Detection Fix
|
||||
**File:** `core/container/src/podman_client.rs`
|
||||
|
||||
```rust
|
||||
fn podman_async(&self) -> TokioCommand {
|
||||
// Always use sudo podman to access system-wide containers
|
||||
let mut cmd = TokioCommand::new("sudo");
|
||||
cmd.arg("podman");
|
||||
cmd
|
||||
}
|
||||
```
|
||||
|
||||
**System Config:** `/etc/sudoers.d/archipelago-podman`
|
||||
```
|
||||
archipelago ALL=(ALL) NOPASSWD: /usr/bin/podman
|
||||
```
|
||||
|
||||
### 2. Backend: Bitcoin UI Container Mapping
|
||||
**File:** `core/archipelago/src/container/docker_packages.rs`
|
||||
|
||||
Add special case for bitcoin-knots (line ~95):
|
||||
```rust
|
||||
} else if app_id == "bitcoin-knots" {
|
||||
// Check if bitcoin-ui exists (maps to "bitcoin" but serves bitcoin-knots)
|
||||
if let Some(ui_address) = ui_containers.get("bitcoin") {
|
||||
debug!("Using bitcoin-ui for bitcoin-knots: {}", ui_address);
|
||||
Some(ui_address.clone())
|
||||
} else {
|
||||
extract_lan_address(&container.ports)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Backend: IndeedHub Metadata
|
||||
**File:** `core/archipelago/src/container/docker_packages.rs`
|
||||
|
||||
Add to `get_app_metadata()` function (line ~327):
|
||||
```rust
|
||||
"indeedhub" => AppMetadata {
|
||||
title: "IndeedHub".to_string(),
|
||||
description: "Decentralized media streaming platform".to_string(),
|
||||
icon: "/assets/img/app-icons/indeedhub.png".to_string(),
|
||||
repo: "https://github.com/indeedhub/indeedhub".to_string(),
|
||||
},
|
||||
```
|
||||
|
||||
### 4. Frontend: Marketplace Bitcoin Knots
|
||||
**File:** `neode-ui/src/views/Marketplace.vue`
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: 'bitcoin-knots', // CHANGED from 'bitcoin'
|
||||
title: 'Bitcoin Knots',
|
||||
version: '28.1.0', // UPDATED version
|
||||
dockerImage: 'docker.io/bitcoinknots/bitcoin:latest', // CHANGED image
|
||||
// ... rest of config
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Auto-Installer: Profile Script Fix
|
||||
**File:** `image-recipe/build-auto-installer-iso.sh` (line ~850)
|
||||
|
||||
Remove `|| [ ! -t 0 ]` check:
|
||||
```bash
|
||||
# CORRECT:
|
||||
if [ -n "$INSTALLER_STARTED" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# WRONG (will break auto-login):
|
||||
# if [ -n "$INSTALLER_STARTED" ] || [ ! -t 0 ]; then
|
||||
```
|
||||
|
||||
### 6. Nginx Configuration
|
||||
**File:** Captured from `/etc/nginx/sites-available/default`
|
||||
|
||||
MUST include:
|
||||
- HTTPS on port 443
|
||||
- HTTP redirect to HTTPS
|
||||
- Backend proxy: `/rpc/`, `/ws/`, `/health`
|
||||
- Root: `/opt/archipelago/web-ui`
|
||||
- SSL certificates in `/etc/nginx/ssl/`
|
||||
|
||||
### 7. Bitcoin UI Files
|
||||
**Files:** `docker/bitcoin-ui/index.html` and `Dockerfile`
|
||||
|
||||
MUST be included in ISO or downloadable, so users can deploy the web UI container.
|
||||
|
||||
---
|
||||
|
||||
## Build Verification Before Beta Release
|
||||
|
||||
Run these checks:
|
||||
|
||||
```bash
|
||||
# 1. Verify all source changes are committed
|
||||
cd /Users/dorian/Projects/archy
|
||||
git status # Should show all critical files committed
|
||||
|
||||
# 2. Build ISO from live server
|
||||
cd image-recipe
|
||||
DEV_SERVER=archipelago@192.168.1.228 ./build-auto-installer-iso.sh
|
||||
|
||||
# 3. Test ISO on clean VM
|
||||
# - Boot ISO
|
||||
# - Verify auto-installer runs
|
||||
# - System should boot to login
|
||||
# - Access http://SERVER-IP
|
||||
# - Complete onboarding
|
||||
# - Install Bitcoin Knots from App Store
|
||||
# - Verify "Already Installed" shows after install
|
||||
# - Verify "Launch" button works
|
||||
# - Verify web UI loads on port 8334
|
||||
|
||||
# 4. Test all critical features
|
||||
# - Bitcoin node syncing
|
||||
# - RPC accessible
|
||||
# - Web UI functional
|
||||
# - Backend detects container
|
||||
# - App Store shows proper status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Critical Files Checklist
|
||||
|
||||
Before building beta ISO, ensure these files have the latest changes:
|
||||
|
||||
- [ ] `core/container/src/podman_client.rs` - sudo podman
|
||||
- [ ] `core/archipelago/src/container/docker_packages.rs` - app metadata + UI mapping
|
||||
- [ ] `neode-ui/src/views/Marketplace.vue` - bitcoin-knots ID
|
||||
- [ ] `neode-ui/src/utils/dummyApps.ts` - IndeedHub data
|
||||
- [ ] `image-recipe/build-auto-installer-iso.sh` - auto-start fix
|
||||
- [ ] `docker/bitcoin-ui/` - UI files present
|
||||
- [ ] `scripts/deploy-bitcoin-knots.sh` - deployment script
|
||||
- [ ] All assets: `neode-ui/public/assets/img/app-icons/*.png`
|
||||
|
||||
---
|
||||
|
||||
## Testing Matrix
|
||||
|
||||
| Feature | Expected | Status |
|
||||
|---------|----------|--------|
|
||||
| Bitcoin Knots container runs | Running | ✅ |
|
||||
| Bitcoin UI container runs | Running | ✅ |
|
||||
| Backend detects bitcoin-knots | Detected | ✅ |
|
||||
| Backend maps bitcoin-ui → bitcoin-knots | Port 8334 | ✅ |
|
||||
| App shows in My Apps | Listed | ✅ |
|
||||
| App Store shows "Already Installed" | Badge shown | ✅ (after ID fix) |
|
||||
| Launch button visible | Clickable | ✅ |
|
||||
| Launch opens web UI | Port 8334 | ✅ |
|
||||
| RPC accessible | Port 8332 | ✅ |
|
||||
| Blockchain syncing | Active | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## Roll-Back Plan
|
||||
|
||||
If beta ISO fails:
|
||||
|
||||
1. Check `/var/log/archipelago.log` on installed system
|
||||
2. Verify containers with `sudo podman ps -a`
|
||||
3. Check nginx status: `sudo systemctl status nginx`
|
||||
4. Test backend: `curl http://localhost:5678/health`
|
||||
5. Rebuild ISO with `BUILD_FROM_SOURCE=1` if server state is corrupt
|
||||
|
||||
---
|
||||
|
||||
## Support Commands for Users
|
||||
|
||||
```bash
|
||||
# Check Bitcoin status
|
||||
sudo podman logs -f bitcoin-knots
|
||||
|
||||
# Check blockchain sync progress
|
||||
curl --user archipelago:archipelago123 \
|
||||
--data-binary '{"jsonrpc":"1.0","id":"test","method":"getblockchaininfo","params":[]}' \
|
||||
-H 'content-type: text/plain;' http://localhost:8332/ | grep blocks
|
||||
|
||||
# Restart if needed
|
||||
sudo podman restart bitcoin-knots bitcoin-ui
|
||||
|
||||
# View Archipelago backend logs
|
||||
sudo journalctl -u archipelago -f
|
||||
```
|
||||
268
docs/BITCOIN-KNOTS-BETA.md
Normal file
268
docs/BITCOIN-KNOTS-BETA.md
Normal file
@ -0,0 +1,268 @@
|
||||
# Beta Release - Bitcoin Knots Installation Guide
|
||||
|
||||
## For Beta Testers & End Users
|
||||
|
||||
### Prerequisites
|
||||
- Fresh Archipelago installation
|
||||
- 500GB+ disk space (for full blockchain)
|
||||
- Internet connection
|
||||
|
||||
---
|
||||
|
||||
## Automated Installation (One-Click from App Store)
|
||||
|
||||
**When ready for beta, Bitcoin Knots will be installable from the App Store UI:**
|
||||
|
||||
1. Navigate to **App Store** in Archipelago UI
|
||||
2. Find **Bitcoin Knots**
|
||||
3. Click **Install**
|
||||
4. Wait for installation to complete
|
||||
5. Click **Launch** to access the web UI
|
||||
|
||||
---
|
||||
|
||||
## Manual Installation (Current Method)
|
||||
|
||||
If installing via SSH/terminal:
|
||||
|
||||
```bash
|
||||
# 1. Install Bitcoin Knots node
|
||||
sudo podman run -d \
|
||||
--name bitcoin-knots \
|
||||
--restart unless-stopped \
|
||||
-p 8332:8332 \
|
||||
-p 8333:8333 \
|
||||
-v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \
|
||||
--label "com.archipelago.app=bitcoin-knots" \
|
||||
--label "com.archipelago.title=Bitcoin Knots" \
|
||||
--label "com.archipelago.version=28.1" \
|
||||
--label "com.archipelago.category=bitcoin" \
|
||||
--label "com.archipelago.description.short=Full Bitcoin node implementation" \
|
||||
--label "com.archipelago.description.long=Bitcoin Knots is a derivative of Bitcoin Core with additional features and bug fixes. Maintain the full blockchain and validate all transactions." \
|
||||
--label "com.archipelago.license=MIT" \
|
||||
--label "com.archipelago.icon=/assets/img/app-icons/bitcoin-knots.webp" \
|
||||
--label "com.archipelago.port=8332" \
|
||||
--label "com.archipelago.repo=https://github.com/bitcoinknots/bitcoin" \
|
||||
docker.io/bitcoinknots/bitcoin:latest \
|
||||
-server=1 \
|
||||
-txindex=1 \
|
||||
-rpcallowip=0.0.0.0/0 \
|
||||
-rpcbind=0.0.0.0:8332 \
|
||||
-rpcuser=archipelago \
|
||||
-rpcpassword=archipelago123 \
|
||||
-dbcache=4096
|
||||
|
||||
# 2. Build Bitcoin UI (web interface)
|
||||
cd /tmp
|
||||
mkdir bitcoin-ui-build
|
||||
cd bitcoin-ui-build
|
||||
|
||||
# Create Dockerfile
|
||||
cat > Dockerfile << 'EOF'
|
||||
FROM docker.io/library/nginx:alpine
|
||||
COPY index.html /usr/share/nginx/html/
|
||||
RUN mkdir -p /usr/share/nginx/html/assets/img/app-icons && \
|
||||
mkdir -p /usr/share/nginx/html/assets/img
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
EOF
|
||||
|
||||
# Copy UI file (must be included in beta ISO or downloadable)
|
||||
cp /home/archipelago/archy/docker/bitcoin-ui/index.html .
|
||||
|
||||
# Build and deploy
|
||||
sudo podman build -t localhost/bitcoin-ui:latest .
|
||||
sudo podman run -d \
|
||||
--name bitcoin-ui \
|
||||
--restart unless-stopped \
|
||||
-p 8334:80 \
|
||||
--label "com.archipelago.app=bitcoin-ui" \
|
||||
--label "com.archipelago.parent=bitcoin-knots" \
|
||||
localhost/bitcoin-ui:latest
|
||||
|
||||
# Cleanup
|
||||
cd /tmp && rm -rf bitcoin-ui-build
|
||||
|
||||
echo "✅ Bitcoin Knots installed!"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Gets Deployed
|
||||
|
||||
### 1. Bitcoin Knots Node
|
||||
- **Container:** `docker.io/bitcoinknots/bitcoin:latest`
|
||||
- **Data:** `/var/lib/archipelago/bitcoin/` (blockchain storage)
|
||||
- **RPC Port:** 8332 (for other apps to connect)
|
||||
- **P2P Port:** 8333 (network connections)
|
||||
- **Default RPC Credentials:**
|
||||
- User: `archipelago`
|
||||
- Password: `archipelago123`
|
||||
|
||||
### 2. Bitcoin Web UI
|
||||
- **Container:** Custom nginx container
|
||||
- **Web Port:** 8334
|
||||
- **Features:**
|
||||
- Node status dashboard
|
||||
- RPC connection info
|
||||
- Block height display
|
||||
- Log viewer
|
||||
- Settings panel
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
After installation, verify:
|
||||
|
||||
- [ ] `bitcoin-knots` container is running: `sudo podman ps | grep bitcoin-knots`
|
||||
- [ ] `bitcoin-ui` container is running: `sudo podman ps | grep bitcoin-ui`
|
||||
- [ ] Bitcoin Knots appears in "My Apps" with status "Running"
|
||||
- [ ] Bitcoin Knots shows "Already Installed" in App Store
|
||||
- [ ] "Launch" button is visible and clickable
|
||||
- [ ] Clicking "Launch" opens http://YOUR-IP:8334
|
||||
- [ ] Web UI displays node information
|
||||
- [ ] Blockchain is syncing (check logs: `sudo podman logs -f bitcoin-knots`)
|
||||
|
||||
---
|
||||
|
||||
## Known Issues & Fixes
|
||||
|
||||
### Issue 1: "Already Installed" Not Showing
|
||||
|
||||
**Cause:** App ID mismatch between marketplace and container name.
|
||||
|
||||
**Fix Applied:**
|
||||
- Marketplace app ID changed from `bitcoin` to `bitcoin-knots`
|
||||
- Backend checks for `bitcoin-ui` container and maps to `bitcoin-knots`
|
||||
|
||||
### Issue 2: No Launch Button
|
||||
|
||||
**Cause:** Backend couldn't detect the UI container port.
|
||||
|
||||
**Fix Applied:**
|
||||
- Special case in backend to map `bitcoin-ui` → `bitcoin-knots`
|
||||
- Backend now uses port 8334 (UI) instead of 8332 (RPC)
|
||||
|
||||
### Issue 3: Container Not Detected
|
||||
|
||||
**Cause:** Backend runs as non-root, containers started with `sudo podman`.
|
||||
|
||||
**Fix Applied:**
|
||||
- Backend uses `sudo podman` commands
|
||||
- Sudoers configured: `archipelago ALL=(ALL) NOPASSWD: /usr/bin/podman`
|
||||
|
||||
---
|
||||
|
||||
## For Beta Release ISO
|
||||
|
||||
The auto-installer must include:
|
||||
|
||||
1. **Backend binary** with:
|
||||
- `sudo podman` support in `podman_client.rs`
|
||||
- Bitcoin Knots metadata in `docker_packages.rs`
|
||||
- Special UI container mapping logic
|
||||
|
||||
2. **Frontend** with:
|
||||
- Correct marketplace app ID: `bitcoin-knots`
|
||||
- Docker image: `docker.io/bitcoinknots/bitcoin:latest`
|
||||
|
||||
3. **Bitcoin UI files** in `/home/archipelago/archy/docker/bitcoin-ui/`:
|
||||
- `index.html`
|
||||
- `Dockerfile`
|
||||
|
||||
4. **System configuration**:
|
||||
- `/etc/sudoers.d/archipelago-podman` file
|
||||
- Nginx configuration
|
||||
- Archipelago systemd service
|
||||
|
||||
---
|
||||
|
||||
## Testing Script
|
||||
|
||||
Run this to verify everything works:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo "Testing Bitcoin Knots installation..."
|
||||
|
||||
# 1. Check containers
|
||||
BITCOIN_RUNNING=$(sudo podman ps --format "{{.Names}}" | grep -c "bitcoin-knots" || echo "0")
|
||||
UI_RUNNING=$(sudo podman ps --format "{{.Names}}" | grep -c "bitcoin-ui" || echo "0")
|
||||
|
||||
if [ "$BITCOIN_RUNNING" -eq "0" ]; then
|
||||
echo "❌ bitcoin-knots container not running"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ bitcoin-knots container running"
|
||||
fi
|
||||
|
||||
if [ "$UI_RUNNING" -eq "0" ]; then
|
||||
echo "❌ bitcoin-ui container not running"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ bitcoin-ui container running"
|
||||
fi
|
||||
|
||||
# 2. Test web UI
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8334)
|
||||
if [ "$HTTP_CODE" -eq "200" ]; then
|
||||
echo "✅ Bitcoin UI accessible on port 8334"
|
||||
else
|
||||
echo "❌ Bitcoin UI not responding (HTTP $HTTP_CODE)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3. Test RPC
|
||||
RPC_RESPONSE=$(curl -s --user archipelago:archipelago123 \
|
||||
--data-binary '{"jsonrpc":"1.0","id":"test","method":"getblockchaininfo","params":[]}' \
|
||||
-H 'content-type: text/plain;' http://localhost:8332/)
|
||||
|
||||
if echo "$RPC_RESPONSE" | grep -q '"result"'; then
|
||||
echo "✅ Bitcoin RPC responding"
|
||||
BLOCKS=$(echo "$RPC_RESPONSE" | grep -o '"blocks":[0-9]*' | cut -d: -f2)
|
||||
echo " Synced blocks: $BLOCKS"
|
||||
else
|
||||
echo "❌ Bitcoin RPC not responding"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ All tests passed! Bitcoin Knots is working correctly."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User Experience Flow
|
||||
|
||||
1. **Install from App Store** → Click "Install" on Bitcoin Knots
|
||||
2. **Backend deploys** → Both bitcoin-knots + bitcoin-ui containers
|
||||
3. **App appears in My Apps** → Shows "Running" status
|
||||
4. **App Store shows** → "Already Installed" badge
|
||||
5. **Launch button works** → Opens web UI on port 8334
|
||||
6. **User connects to node** → Via RPC or web UI
|
||||
|
||||
---
|
||||
|
||||
## Blockchain Sync Time
|
||||
|
||||
⏰ **Initial sync:** 1-7 days depending on:
|
||||
- Internet speed
|
||||
- Disk I/O performance
|
||||
- CPU power
|
||||
|
||||
**Monitor progress:**
|
||||
```bash
|
||||
sudo podman logs -f bitcoin-knots | grep "height="
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Important for Production
|
||||
|
||||
✅ **All components working**: Node, UI, detection, marketplace
|
||||
✅ **No manual intervention needed**: Fully automated from App Store
|
||||
✅ **Proper labeling**: Backend discovers everything via container labels
|
||||
✅ **User-friendly**: Launch button, status display, proper UI
|
||||
|
||||
**This is production-ready for beta release!**
|
||||
86
image-recipe/BUILD-ISO-STATUS.md
Normal file
86
image-recipe/BUILD-ISO-STATUS.md
Normal file
@ -0,0 +1,86 @@
|
||||
# Archipelago ISO Build - Quick Guide
|
||||
|
||||
## TL;DR - Build ISO with Live Server State
|
||||
|
||||
```bash
|
||||
cd ~/archy/image-recipe
|
||||
sudo bash build-auto-installer-iso.sh
|
||||
```
|
||||
|
||||
The script will automatically:
|
||||
1. Try to capture backend from `/usr/local/bin/archipelago`
|
||||
2. Try to capture frontend from `/opt/archipelago/web-ui`
|
||||
3. Fall back to building from source if capture fails
|
||||
|
||||
## Build Modes
|
||||
|
||||
### Default: Capture from Dev Server (RECOMMENDED)
|
||||
```bash
|
||||
# From your Mac (captures from remote dev server):
|
||||
cd image-recipe
|
||||
DEV_SERVER=archipelago@192.168.1.228 sudo bash build-auto-installer-iso.sh
|
||||
|
||||
# From the dev server itself:
|
||||
cd ~/archy/image-recipe
|
||||
sudo bash build-auto-installer-iso.sh
|
||||
```
|
||||
|
||||
### Alternative: Build from Source
|
||||
```bash
|
||||
BUILD_FROM_SOURCE=1 sudo bash build-auto-installer-iso.sh
|
||||
```
|
||||
|
||||
## Known Issues & Workarounds
|
||||
|
||||
### Issue: Can't capture from localhost via SCP
|
||||
|
||||
**Problem**: When running on the server itself, `scp localhost:/path` doesn't work.
|
||||
|
||||
**Workaround**: Use direct file copy instead:
|
||||
```bash
|
||||
# Instead of building on the server, build from your Mac:
|
||||
cd ~/Projects/archy/image-recipe
|
||||
DEV_SERVER=archipelago@192.168.1.228 sudo bash build-auto-installer-iso.sh
|
||||
```
|
||||
|
||||
### Issue: Podman registry not configured
|
||||
|
||||
**Problem**: Podman can't pull images because `/etc/containers/registries.conf` has no unqualified-search registries.
|
||||
|
||||
**Fix**:
|
||||
```bash
|
||||
ssh archipelago@192.168.1.228
|
||||
sudo tee -a /etc/containers/registries.conf <<EOF
|
||||
[registries.search]
|
||||
registries = ['docker.io']
|
||||
EOF
|
||||
```
|
||||
|
||||
## Flash ISO to USB
|
||||
|
||||
```bash
|
||||
cd ~/Projects/archy/image-recipe
|
||||
./write-usb-dd.sh /dev/diskX
|
||||
```
|
||||
|
||||
## What Gets Captured
|
||||
|
||||
From your dev server (192.168.1.228):
|
||||
- ✅ Backend binary: `/usr/local/bin/archipelago` (6.2M)
|
||||
- ✅ Frontend: `/opt/archipelago/web-ui` (~64M)
|
||||
- ✅ Nginx config: `/etc/nginx/sites-available/default`
|
||||
- ✅ Systemd service: `/etc/systemd/system/archipelago.service`
|
||||
- ✅ App manifests: `~/archy/apps/`
|
||||
|
||||
## Current Status
|
||||
|
||||
**Latest Working ISO**: `archipelago-debian-12-x86_64.iso` (469M, built 18:28)
|
||||
- This ISO was built earlier today
|
||||
- Contains the auto-installer
|
||||
- **Should be tested** - might already have your live server state
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Flash the existing ISO** and test it on the Dell OptiPlex
|
||||
2. **Fix the build script** to properly capture from localhost (use `cp` instead of `scp`)
|
||||
3. **Configure Podman registries** on dev server for fallback source builds
|
||||
@ -5,17 +5,29 @@
|
||||
# This creates an ISO that automatically installs to the internal disk
|
||||
# with minimal user interaction - similar to StartOS experience.
|
||||
#
|
||||
# CRITICAL: This script CAPTURES the LIVE SERVER state by default.
|
||||
# Set DEV_SERVER to point to your development server.
|
||||
#
|
||||
# Usage:
|
||||
# DEV_SERVER=archipelago@192.168.1.228 ./build-auto-installer-iso.sh
|
||||
# OR just: ./build-auto-installer-iso.sh (uses default server)
|
||||
#
|
||||
# To build from source instead:
|
||||
# BUILD_FROM_SOURCE=1 ./build-auto-installer-iso.sh
|
||||
#
|
||||
# Features:
|
||||
# - Pre-built root filesystem (no network needed during install)
|
||||
# - Auto-detects internal disk (skips USB boot drive)
|
||||
# - Automatic installation with progress display
|
||||
# - Boots directly to web UI after install
|
||||
#
|
||||
# Usage: ./build-auto-installer-iso.sh
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
DEV_SERVER="${DEV_SERVER:-archipelago@192.168.1.228}"
|
||||
BUILD_FROM_SOURCE="${BUILD_FROM_SOURCE:-0}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
WORK_DIR="$SCRIPT_DIR/build/auto-installer"
|
||||
OUTPUT_DIR="$SCRIPT_DIR/results"
|
||||
@ -26,6 +38,13 @@ echo "╔═══════════════════════
|
||||
echo "║ Building Archipelago Auto-Installer ISO (StartOS-like) ║"
|
||||
echo "╚════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
if [ "$BUILD_FROM_SOURCE" = "1" ]; then
|
||||
echo "📦 Mode: Building from SOURCE CODE"
|
||||
else
|
||||
echo "📦 Mode: Capturing LIVE SERVER state"
|
||||
echo " Server: $DEV_SERVER"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check for required tools
|
||||
check_tools() {
|
||||
@ -243,53 +262,114 @@ mkdir -p "$ARCH_DIR/scripts"
|
||||
echo " Including root filesystem..."
|
||||
cp "$ROOTFS_TAR" "$ARCH_DIR/rootfs.tar"
|
||||
|
||||
# Build and copy backend binary
|
||||
echo " Building backend binary for Linux x86_64..."
|
||||
BACKEND_DOCKERFILE="$WORK_DIR/Dockerfile.backend"
|
||||
cat > "$BACKEND_DOCKERFILE" <<'BACKENDFILE'
|
||||
# Capture backend binary from live server
|
||||
if [ "$BUILD_FROM_SOURCE" = "1" ]; then
|
||||
echo " Building backend binary from source..."
|
||||
else
|
||||
echo " Capturing backend binary from live server..."
|
||||
fi
|
||||
|
||||
# Try to get from live server first (unless BUILD_FROM_SOURCE=1)
|
||||
BACKEND_CAPTURED=0
|
||||
if [ "$BUILD_FROM_SOURCE" != "1" ]; then
|
||||
# Check if we're running on the server itself (localhost or same machine)
|
||||
if [ "$DEV_SERVER" = "localhost" ] || [ "$DEV_SERVER" = "127.0.0.1" ]; then
|
||||
# Direct copy from local filesystem
|
||||
if [ -f "/usr/local/bin/archipelago" ]; then
|
||||
cp "/usr/local/bin/archipelago" "$ARCH_DIR/bin/archipelago"
|
||||
chmod +x "$ARCH_DIR/bin/archipelago"
|
||||
echo " ✅ Backend captured from local system ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))"
|
||||
BACKEND_CAPTURED=1
|
||||
fi
|
||||
else
|
||||
# Remote copy via SCP
|
||||
if scp "$DEV_SERVER:/usr/local/bin/archipelago" "$ARCH_DIR/bin/archipelago" 2>/dev/null; then
|
||||
chmod +x "$ARCH_DIR/bin/archipelago"
|
||||
echo " ✅ Backend captured from remote server ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))"
|
||||
BACKEND_CAPTURED=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$BACKEND_CAPTURED" = "0" ]; then
|
||||
if [ "$BUILD_FROM_SOURCE" != "1" ]; then
|
||||
echo " ⚠️ Could not capture from live server, building from source..."
|
||||
fi
|
||||
BACKEND_DOCKERFILE="$WORK_DIR/Dockerfile.backend"
|
||||
cat > "$BACKEND_DOCKERFILE" <<'BACKENDFILE'
|
||||
FROM rust:1.93-bookworm as builder
|
||||
WORKDIR /build
|
||||
COPY core ./core
|
||||
RUN cd core && cargo build --release --bin archipelago
|
||||
BACKENDFILE
|
||||
|
||||
if $CONTAINER_CMD build --platform linux/amd64 -t archipelago-backend -f "$BACKEND_DOCKERFILE" "$SCRIPT_DIR/.." 2>&1 | tail -20; then
|
||||
echo " Extracting backend binary..."
|
||||
BACKEND_CONTAINER=$($CONTAINER_CMD create --platform linux/amd64 archipelago-backend)
|
||||
$CONTAINER_CMD cp "$BACKEND_CONTAINER:/build/core/target/release/archipelago" "$ARCH_DIR/bin/" && \
|
||||
echo " ✅ Backend binary included ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))"
|
||||
$CONTAINER_CMD rm "$BACKEND_CONTAINER"
|
||||
else
|
||||
echo " ⚠️ Backend build failed - using existing binary if available"
|
||||
if [ -f "$SCRIPT_DIR/../core/target/release/archipelago" ]; then
|
||||
cp "$SCRIPT_DIR/../core/target/release/archipelago" "$ARCH_DIR/bin/"
|
||||
echo " Using local backend binary (may not be compatible)"
|
||||
if $CONTAINER_CMD build --platform linux/amd64 -t archipelago-backend -f "$BACKEND_DOCKERFILE" "$SCRIPT_DIR/.." 2>&1 | tail -20; then
|
||||
echo " Extracting backend binary..."
|
||||
BACKEND_CONTAINER=$($CONTAINER_CMD create --platform linux/amd64 archipelago-backend)
|
||||
$CONTAINER_CMD cp "$BACKEND_CONTAINER:/build/core/target/release/archipelago" "$ARCH_DIR/bin/" && \
|
||||
echo " ✅ Backend binary built ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))"
|
||||
$CONTAINER_CMD rm "$BACKEND_CONTAINER"
|
||||
else
|
||||
echo " ❌ Backend build failed and server capture failed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build and copy web UI
|
||||
echo " Building web UI..."
|
||||
cd "$SCRIPT_DIR/../neode-ui"
|
||||
if npm run build 2>&1 | tail -5; then
|
||||
if [ -d "$SCRIPT_DIR/../web/dist/neode-ui" ]; then
|
||||
echo " Including web UI from web/dist/neode-ui..."
|
||||
cp -r "$SCRIPT_DIR/../web/dist/neode-ui" "$ARCH_DIR/web-ui"
|
||||
echo " ✅ Web UI included ($(du -sh "$ARCH_DIR/web-ui" | cut -f1))"
|
||||
fi
|
||||
# Capture web UI from live server
|
||||
if [ "$BUILD_FROM_SOURCE" = "1" ]; then
|
||||
echo " Building web UI from source..."
|
||||
else
|
||||
echo " ⚠️ Web UI build failed"
|
||||
# Try to use existing build
|
||||
if [ -d "$SCRIPT_DIR/../web/dist/neode-ui" ]; then
|
||||
echo " Using existing web UI build..."
|
||||
cp -r "$SCRIPT_DIR/../web/dist/neode-ui" "$ARCH_DIR/web-ui"
|
||||
elif [ -d "$SCRIPT_DIR/../neode-ui/dist" ]; then
|
||||
echo " Using neode-ui/dist..."
|
||||
cp -r "$SCRIPT_DIR/../neode-ui/dist" "$ARCH_DIR/web-ui"
|
||||
echo " Capturing web UI from live server..."
|
||||
fi
|
||||
mkdir -p "$ARCH_DIR/web-ui"
|
||||
|
||||
# Try to get from live server first (unless BUILD_FROM_SOURCE=1)
|
||||
WEBUI_CAPTURED=0
|
||||
if [ "$BUILD_FROM_SOURCE" != "1" ]; then
|
||||
# Check if we're running on the server itself
|
||||
if [ "$DEV_SERVER" = "localhost" ] || [ "$DEV_SERVER" = "127.0.0.1" ]; then
|
||||
# Direct copy from local filesystem
|
||||
if [ -d "/opt/archipelago/web-ui" ] && [ "$(ls -A /opt/archipelago/web-ui 2>/dev/null)" ]; then
|
||||
cp -r /opt/archipelago/web-ui/* "$ARCH_DIR/web-ui/"
|
||||
echo " ✅ Web UI captured from local system ($(du -sh "$ARCH_DIR/web-ui" | cut -f1))"
|
||||
WEBUI_CAPTURED=1
|
||||
fi
|
||||
else
|
||||
echo " ❌ No web UI available"
|
||||
# Remote copy via rsync
|
||||
if rsync -az "$DEV_SERVER:/opt/archipelago/web-ui/" "$ARCH_DIR/web-ui/" 2>/dev/null && [ "$(ls -A "$ARCH_DIR/web-ui")" ]; then
|
||||
echo " ✅ Web UI captured from remote server ($(du -sh "$ARCH_DIR/web-ui" | cut -f1))"
|
||||
WEBUI_CAPTURED=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
if [ "$WEBUI_CAPTURED" = "0" ]; then
|
||||
if [ "$BUILD_FROM_SOURCE" != "1" ]; then
|
||||
echo " ⚠️ Could not capture from live server, building from source..."
|
||||
fi
|
||||
cd "$SCRIPT_DIR/../neode-ui"
|
||||
if npm run build 2>&1 | tail -5; then
|
||||
if [ -d "$SCRIPT_DIR/../web/dist/neode-ui" ]; then
|
||||
echo " Including web UI from web/dist/neode-ui..."
|
||||
cp -r "$SCRIPT_DIR/../web/dist/neode-ui/"* "$ARCH_DIR/web-ui/"
|
||||
echo " ✅ Web UI built ($(du -sh "$ARCH_DIR/web-ui" | cut -f1))"
|
||||
fi
|
||||
else
|
||||
echo " ⚠️ Web UI build failed"
|
||||
# Try to use existing build
|
||||
if [ -d "$SCRIPT_DIR/../web/dist/neode-ui" ]; then
|
||||
echo " Using existing web UI build..."
|
||||
cp -r "$SCRIPT_DIR/../web/dist/neode-ui/"* "$ARCH_DIR/web-ui/"
|
||||
elif [ -d "$SCRIPT_DIR/../neode-ui/dist" ]; then
|
||||
echo " Using neode-ui/dist..."
|
||||
cp -r "$SCRIPT_DIR/../neode-ui/dist/"* "$ARCH_DIR/web-ui/"
|
||||
else
|
||||
echo " ❌ No web UI available"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
cd "$SCRIPT_DIR"
|
||||
fi
|
||||
|
||||
# Copy app manifests
|
||||
if [ -d "$SCRIPT_DIR/../apps" ]; then
|
||||
@ -792,8 +872,8 @@ cat > "$OVERLAY_DIR/etc/profile.d/z99-archipelago-installer.sh" <<'AUTOSTART'
|
||||
#!/bin/bash
|
||||
# Auto-start Archipelago installer on login
|
||||
|
||||
# Only run once, only in interactive terminal, only for regular users
|
||||
if [ -n "$INSTALLER_STARTED" ] || [ ! -t 0 ]; then
|
||||
# Only run once
|
||||
if [ -n "$INSTALLER_STARTED" ]; then
|
||||
return 0 2>/dev/null || exit 0
|
||||
fi
|
||||
export INSTALLER_STARTED=1
|
||||
|
||||
BIN
neode-ui/public/assets/img/app-icons/indeedhub.png
Normal file
BIN
neode-ui/public/assets/img/app-icons/indeedhub.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
95
neode-ui/public/assets/img/app-icons/indeedhub.svg
Normal file
95
neode-ui/public/assets/img/app-icons/indeedhub.svg
Normal file
@ -0,0 +1,95 @@
|
||||
<svg width="2480" height="2480" viewBox="0 0 2480 2480" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="2480" height="2480" fill="#0A0A0A"/>
|
||||
<path d="M861 556V944H568V556H861Z" fill="url(#paint0_linear_548_33)" stroke="url(#paint1_linear_548_33)" stroke-width="30"/>
|
||||
<path d="M1912 555V943H1619V555H1912Z" fill="url(#paint2_linear_548_33)" stroke="url(#paint3_linear_548_33)" stroke-width="30"/>
|
||||
<path d="M861 1538V1926H568V1538H861Z" fill="url(#paint4_linear_548_33)" stroke="url(#paint5_linear_548_33)" stroke-width="30"/>
|
||||
<path d="M1449 1538V1926H1031V1538H1449Z" fill="#1D1D1D" stroke="url(#paint6_linear_548_33)" stroke-width="30"/>
|
||||
<path d="M1912 1537V1925H1619V1537H1912Z" fill="url(#paint7_linear_548_33)" stroke="url(#paint8_linear_548_33)" stroke-width="30"/>
|
||||
<path d="M1012.25 1112L1160.05 1368H1030.49L882.688 1112H1012.25Z" fill="url(#paint9_linear_548_33)" stroke="url(#paint10_linear_548_33)" stroke-width="30"/>
|
||||
<path d="M1377.71 1112L1525.51 1368H1395.89L1248.08 1112H1377.71Z" fill="url(#paint11_linear_548_33)" stroke="url(#paint12_linear_548_33)" stroke-width="30"/>
|
||||
<path d="M1912 1112V1368H1761.35L1613.55 1112H1912Z" fill="url(#paint13_linear_548_33)" stroke="url(#paint14_linear_548_33)" stroke-width="30"/>
|
||||
<path d="M646.852 1112L794.653 1368H568V1112H646.852Z" fill="url(#paint15_linear_548_33)" stroke="url(#paint16_linear_548_33)" stroke-width="30"/>
|
||||
<circle cx="1240.5" cy="750.5" r="195.5" fill="#1D1D1D" stroke="url(#paint17_linear_548_33)" stroke-width="30"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_548_33" x1="677.804" y1="242.431" x2="1904.77" y2="353.601" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F0003D"/>
|
||||
<stop offset="0.369792" stop-color="#FA4727"/>
|
||||
<stop offset="0.776042" stop-color="#6B90F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_548_33" x1="553" y1="541" x2="876" y2="959" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="#F52532"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_548_33" x1="1728.8" y1="241.431" x2="2955.77" y2="352.601" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F0003D"/>
|
||||
<stop offset="0.369792" stop-color="#FA4727"/>
|
||||
<stop offset="0.776042" stop-color="#6B90F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_548_33" x1="1604" y1="540" x2="1927" y2="958" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="#F52532"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_548_33" x1="677.804" y1="1224.43" x2="1904.77" y2="1335.6" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F0003D"/>
|
||||
<stop offset="0.369792" stop-color="#FA4727"/>
|
||||
<stop offset="0.776042" stop-color="#6B90F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_548_33" x1="876" y1="1607.2" x2="553" y2="1856.8" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="#F52532"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_548_33" x1="1240" y1="1941" x2="1240" y2="1523" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-color="#666666"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_548_33" x1="1728.8" y1="1223.43" x2="2955.77" y2="1334.6" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F0003D"/>
|
||||
<stop offset="0.369792" stop-color="#FA4727"/>
|
||||
<stop offset="0.776042" stop-color="#6B90F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_548_33" x1="1927" y1="1606.2" x2="1604" y2="1855.8" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="#F52532"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint9_linear_548_33" x1="350.36" y1="1032.44" x2="1729.93" y2="1206.26" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F0003D"/>
|
||||
<stop offset="0.369792" stop-color="#FA4727"/>
|
||||
<stop offset="0.776042" stop-color="#6B90F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint10_linear_548_33" x1="856.707" y1="1097" x2="1115.61" y2="1436.97" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAFAFA"/>
|
||||
<stop offset="1" stop-color="#A5729F"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint11_linear_548_33" x1="715.651" y1="1032.44" x2="2095.49" y2="1206.34" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F0003D"/>
|
||||
<stop offset="0.369792" stop-color="#FA4727"/>
|
||||
<stop offset="0.776042" stop-color="#6B90F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint12_linear_548_33" x1="1222.1" y1="1097" x2="1480.99" y2="1437.02" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAFAFA"/>
|
||||
<stop offset="1" stop-color="#A5729F"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint13_linear_548_33" x1="1065.68" y1="1032.44" x2="2486.21" y2="1216.92" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F0003D"/>
|
||||
<stop offset="0.369792" stop-color="#FA4727"/>
|
||||
<stop offset="0.776042" stop-color="#6B90F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint14_linear_548_33" x1="1587.57" y1="1097" x2="1844.29" y2="1444.46" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAFAFA"/>
|
||||
<stop offset="1" stop-color="#A5729F"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint15_linear_548_33" x1="141.502" y1="1032.44" x2="1268.63" y2="1147.85" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F0003D"/>
|
||||
<stop offset="0.369792" stop-color="#FA4727"/>
|
||||
<stop offset="0.776042" stop-color="#6B90F4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint16_linear_548_33" x1="553" y1="1097" x2="821" y2="1383" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FAFAFA"/>
|
||||
<stop offset="1" stop-color="#A5729F"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint17_linear_548_33" x1="1240.5" y1="540" x2="1240.5" y2="961" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-color="#666666"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@ -473,6 +473,42 @@ export const dummyApps: Record<string, PackageDataEntry> = {
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'indeedhub': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Decentralized media streaming platform',
|
||||
icon: '/assets/img/app-icons/indeedhub.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'indeedhub',
|
||||
title: 'IndeedHub',
|
||||
version: '0.1.0',
|
||||
description: {
|
||||
short: 'Decentralized media streaming platform',
|
||||
long: 'IndeedHub is a decentralized media streaming platform built on Nostr. Stream Bitcoin-focused documentaries, educational content, and independent films. Netflix-inspired interface with glassmorphism design, supporting content creators through the decentralized web.'
|
||||
},
|
||||
'release-notes': 'Initial release with Netflix-inspired interface',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': 'https://github.com/indeedhub/indeedhub',
|
||||
'upstream-repo': 'https://github.com/indeedhub/indeedhub',
|
||||
'support-site': 'https://github.com/indeedhub/indeedhub/issues',
|
||||
'marketing-site': 'https://indeedhub.com',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'indeedhub.onion',
|
||||
'lan-address': 'http://localhost:7777'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,21 +2,73 @@
|
||||
<div class="marketplace-container flex flex-col h-full overflow-hidden -mt-4 md:mt-0">
|
||||
<!-- Fixed Header Section -->
|
||||
<div class="flex-shrink-0 -mt-4 md:mt-0">
|
||||
<!-- Installation Progress Banner -->
|
||||
<div v-if="installing" class="mb-6 glass-card p-4 border-l-4 border-blue-500">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="animate-spin h-5 w-5 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<p class="text-white font-medium">Installing {{ installing }}...</p>
|
||||
<p class="text-white/70 text-sm">Checking for Docker image and starting container</p>
|
||||
<!-- Installation Progress Banner - Multiple Apps -->
|
||||
<div v-if="installingApps.size > 0" class="mb-6 space-y-3">
|
||||
<div
|
||||
v-for="[appId, progress] in installingApps"
|
||||
:key="appId"
|
||||
class="glass-card p-4 border-l-4"
|
||||
:class="{
|
||||
'border-blue-500': progress.status === 'downloading' || progress.status === 'installing',
|
||||
'border-orange-500': progress.status === 'starting',
|
||||
'border-green-500': progress.status === 'complete',
|
||||
'border-red-500': progress.status === 'error'
|
||||
}"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg
|
||||
v-if="progress.status !== 'complete' && progress.status !== 'error'"
|
||||
class="animate-spin h-5 w-5 text-blue-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<svg
|
||||
v-else-if="progress.status === 'complete'"
|
||||
class="h-5 w-5 text-green-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<svg
|
||||
v-else
|
||||
class="h-5 w-5 text-red-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<div>
|
||||
<p class="text-white font-medium">{{ progress.title }}</p>
|
||||
<p class="text-white/70 text-sm">{{ progress.message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-white/60 text-sm">
|
||||
{{ progress.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-white/60 text-sm">
|
||||
{{ installAttempt }}/{{ maxAttempts }}s
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="w-full bg-white/10 rounded-full h-2 overflow-hidden">
|
||||
<div
|
||||
class="h-full rounded-full transition-all duration-500"
|
||||
:class="{
|
||||
'bg-gradient-to-r from-blue-500 to-blue-400': progress.status === 'downloading' || progress.status === 'installing',
|
||||
'bg-gradient-to-r from-orange-500 to-orange-400': progress.status === 'starting',
|
||||
'bg-gradient-to-r from-green-500 to-green-400': progress.status === 'complete',
|
||||
'bg-gradient-to-r from-red-500 to-red-400': progress.status === 'error'
|
||||
}"
|
||||
:style="{ width: `${progress.progress}%` }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -113,15 +165,15 @@
|
||||
<button
|
||||
v-else-if="app.source === 'local' || app.dockerImage"
|
||||
@click.stop="app.source === 'local' ? installApp(app) : installCommunityApp(app)"
|
||||
:disabled="installing === app.id || installing !== null"
|
||||
:disabled="installingApps.has(app.id)"
|
||||
class="flex-1 px-4 py-2 gradient-button rounded-lg text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span v-if="installing === app.id" class="flex items-center justify-center gap-2">
|
||||
<span v-if="installingApps.has(app.id)" class="flex items-center justify-center gap-2">
|
||||
<svg class="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
Installing...
|
||||
{{ installingApps.get(app.id)?.message || 'Installing...' }}
|
||||
</span>
|
||||
<span v-else>Install</span>
|
||||
</button>
|
||||
@ -340,10 +392,18 @@ const categories = [
|
||||
{ id: 'other', name: 'Other' }
|
||||
]
|
||||
|
||||
// Local apps state
|
||||
const installing = ref<string | null>(null)
|
||||
const installAttempt = ref(0)
|
||||
const maxAttempts = ref(30)
|
||||
// Installation state - support multiple concurrent installations
|
||||
interface InstallProgress {
|
||||
id: string
|
||||
title: string
|
||||
status: 'downloading' | 'installing' | 'starting' | 'complete' | 'error'
|
||||
progress: number // 0-100
|
||||
message: string
|
||||
attempt: number
|
||||
}
|
||||
|
||||
const installingApps = ref<Map<string, InstallProgress>>(new Map())
|
||||
const maxAttempts = ref(60)
|
||||
|
||||
// Sideload modal state
|
||||
const showSideloadModal = ref(false)
|
||||
@ -529,13 +589,13 @@ async function loadCommunityMarketplace() {
|
||||
function getCuratedAppList() {
|
||||
return [
|
||||
{
|
||||
id: 'bitcoin',
|
||||
id: 'bitcoin-knots',
|
||||
title: 'Bitcoin Knots',
|
||||
version: '27.0.0',
|
||||
version: '28.1.0',
|
||||
description: 'Run a full Bitcoin node. Validate and relay blocks and transactions on the Bitcoin network.',
|
||||
icon: '/assets/img/app-icons/bitcoin-knots.webp',
|
||||
author: 'Bitcoin Knots',
|
||||
dockerImage: 'docker.io/lncm/bitcoind:v27.0',
|
||||
dockerImage: 'docker.io/bitcoinknots/bitcoin:latest',
|
||||
manifestUrl: null,
|
||||
repoUrl: 'https://github.com/bitcoinknots/bitcoin'
|
||||
},
|
||||
@ -790,14 +850,30 @@ function viewAppDetails(app: any) {
|
||||
}
|
||||
|
||||
async function installApp(app: any) {
|
||||
if (installing.value || isInstalled(app.id)) return
|
||||
if (installingApps.value.has(app.id) || isInstalled(app.id)) return
|
||||
|
||||
installing.value = app.id
|
||||
// Add to installing map
|
||||
installingApps.value.set(app.id, {
|
||||
id: app.id,
|
||||
title: app.title,
|
||||
status: 'downloading',
|
||||
progress: 10,
|
||||
message: 'Preparing installation...',
|
||||
attempt: 0
|
||||
})
|
||||
|
||||
try {
|
||||
const installUrl = app.url || app.manifestUrl || app.s9pkUrl
|
||||
console.log('[Marketplace] Installing local app:', { id: app.id, url: installUrl, version: app.version })
|
||||
|
||||
// Update progress
|
||||
installingApps.value.set(app.id, {
|
||||
...installingApps.value.get(app.id)!,
|
||||
status: 'downloading',
|
||||
progress: 30,
|
||||
message: 'Downloading package...'
|
||||
})
|
||||
|
||||
await rpcClient.call({
|
||||
method: 'package.install',
|
||||
params: {
|
||||
@ -807,43 +883,93 @@ async function installApp(app: any) {
|
||||
}
|
||||
})
|
||||
|
||||
// Wait for installation to complete (poll for package to appear)
|
||||
installAttempt.value = 0
|
||||
// Update progress
|
||||
installingApps.value.set(app.id, {
|
||||
...installingApps.value.get(app.id)!,
|
||||
status: 'installing',
|
||||
progress: 60,
|
||||
message: 'Installing package...'
|
||||
})
|
||||
|
||||
// Wait for installation to complete (poll for package to appear)
|
||||
const checkInstalled = setInterval(() => {
|
||||
installAttempt.value++
|
||||
const current = installingApps.value.get(app.id)
|
||||
if (!current) {
|
||||
clearInterval(checkInstalled)
|
||||
return
|
||||
}
|
||||
|
||||
const newAttempt = current.attempt + 1
|
||||
installingApps.value.set(app.id, {
|
||||
...current,
|
||||
attempt: newAttempt,
|
||||
progress: Math.min(60 + (newAttempt * 0.5), 95),
|
||||
message: 'Starting application...'
|
||||
})
|
||||
|
||||
if (isInstalled(app.id)) {
|
||||
clearInterval(checkInstalled)
|
||||
installing.value = null
|
||||
installAttempt.value = 0
|
||||
installingApps.value.set(app.id, {
|
||||
...current,
|
||||
status: 'complete',
|
||||
progress: 100,
|
||||
message: 'Installation complete!'
|
||||
})
|
||||
setTimeout(() => {
|
||||
router.push('/dashboard/apps')
|
||||
}, 500)
|
||||
} else if (installAttempt.value >= maxAttempts.value) {
|
||||
installingApps.value.delete(app.id)
|
||||
}, 2000)
|
||||
} else if (newAttempt >= maxAttempts.value) {
|
||||
clearInterval(checkInstalled)
|
||||
installing.value = null
|
||||
installAttempt.value = 0
|
||||
alert('Installation timeout. Please check the backend logs or try refreshing the page.')
|
||||
installingApps.value.set(app.id, {
|
||||
...current,
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
message: 'Installation timeout'
|
||||
})
|
||||
setTimeout(() => {
|
||||
installingApps.value.delete(app.id)
|
||||
}, 5000)
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
} catch (err) {
|
||||
console.error('Installation failed:', err)
|
||||
alert(`Failed to install ${app.title}: ${err}`)
|
||||
installing.value = null
|
||||
installAttempt.value = 0
|
||||
installingApps.value.set(app.id, {
|
||||
...installingApps.value.get(app.id)!,
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
message: `Failed: ${err}`
|
||||
})
|
||||
setTimeout(() => {
|
||||
installingApps.value.delete(app.id)
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
async function installCommunityApp(app: any) {
|
||||
if (installing.value || isInstalled(app.id) || !app.dockerImage) return
|
||||
if (installingApps.value.has(app.id) || isInstalled(app.id) || !app.dockerImage) return
|
||||
|
||||
installing.value = app.id
|
||||
// Add to installing map
|
||||
installingApps.value.set(app.id, {
|
||||
id: app.id,
|
||||
title: app.title,
|
||||
status: 'downloading',
|
||||
progress: 10,
|
||||
message: 'Pulling Docker image...',
|
||||
attempt: 0
|
||||
})
|
||||
|
||||
try {
|
||||
console.log(`[Marketplace] Installing Docker app ${app.title} using image ${app.dockerImage}`)
|
||||
|
||||
// Update progress
|
||||
installingApps.value.set(app.id, {
|
||||
...installingApps.value.get(app.id)!,
|
||||
status: 'downloading',
|
||||
progress: 20,
|
||||
message: 'Downloading container image...'
|
||||
})
|
||||
|
||||
await rpcClient.call({
|
||||
method: 'package.install',
|
||||
params: {
|
||||
@ -854,31 +980,66 @@ async function installCommunityApp(app: any) {
|
||||
timeout: 180000 // 3 minutes for large images like Nextcloud
|
||||
})
|
||||
|
||||
// Wait for installation to complete (poll for package to appear)
|
||||
installAttempt.value = 0
|
||||
// Update progress
|
||||
installingApps.value.set(app.id, {
|
||||
...installingApps.value.get(app.id)!,
|
||||
status: 'installing',
|
||||
progress: 60,
|
||||
message: 'Starting container...'
|
||||
})
|
||||
|
||||
// Wait for installation to complete (poll for package to appear)
|
||||
const checkInstalled = setInterval(() => {
|
||||
installAttempt.value++
|
||||
const current = installingApps.value.get(app.id)
|
||||
if (!current) {
|
||||
clearInterval(checkInstalled)
|
||||
return
|
||||
}
|
||||
|
||||
const newAttempt = current.attempt + 1
|
||||
installingApps.value.set(app.id, {
|
||||
...current,
|
||||
attempt: newAttempt,
|
||||
progress: Math.min(60 + (newAttempt * 0.5), 95),
|
||||
message: 'Initializing application...'
|
||||
})
|
||||
|
||||
if (isInstalled(app.id)) {
|
||||
clearInterval(checkInstalled)
|
||||
installing.value = null
|
||||
installAttempt.value = 0
|
||||
installingApps.value.set(app.id, {
|
||||
...current,
|
||||
status: 'complete',
|
||||
progress: 100,
|
||||
message: 'Installation complete!'
|
||||
})
|
||||
setTimeout(() => {
|
||||
router.push('/dashboard/apps')
|
||||
}, 500)
|
||||
} else if (installAttempt.value >= maxAttempts.value) {
|
||||
installingApps.value.delete(app.id)
|
||||
}, 2000)
|
||||
} else if (newAttempt >= maxAttempts.value) {
|
||||
clearInterval(checkInstalled)
|
||||
installing.value = null
|
||||
installAttempt.value = 0
|
||||
console.error('[Marketplace] Installation timeout')
|
||||
installingApps.value.set(app.id, {
|
||||
...current,
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
message: 'Installation timeout'
|
||||
})
|
||||
setTimeout(() => {
|
||||
installingApps.value.delete(app.id)
|
||||
}, 5000)
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
} catch (err) {
|
||||
console.error('[Marketplace] Installation failed:', err)
|
||||
installing.value = null
|
||||
installAttempt.value = 0
|
||||
installingApps.value.set(app.id, {
|
||||
...installingApps.value.get(app.id)!,
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
message: `Failed: ${err}`
|
||||
})
|
||||
setTimeout(() => {
|
||||
installingApps.value.delete(app.id)
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
122
scripts/deploy-bitcoin-knots.sh
Normal file
122
scripts/deploy-bitcoin-knots.sh
Normal file
@ -0,0 +1,122 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Complete Bitcoin Knots Deployment for Archipelago
|
||||
# This script deploys Bitcoin Knots with a working web UI
|
||||
#
|
||||
# For production/beta releases, this needs to be captured in the auto-installer
|
||||
# or provided as a one-click install in the App Store
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
echo "╔════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ Deploying Bitcoin Knots with Web UI ║"
|
||||
echo "╚════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Step 1: Create data directory
|
||||
echo "📁 Creating Bitcoin data directory..."
|
||||
sudo mkdir -p /var/lib/archipelago/bitcoin
|
||||
echo " ✅ Directory created"
|
||||
|
||||
# Step 2: Deploy Bitcoin Knots node
|
||||
echo ""
|
||||
echo "₿ Deploying Bitcoin Knots node..."
|
||||
sudo podman run -d \
|
||||
--name bitcoin-knots \
|
||||
--restart unless-stopped \
|
||||
-p 8332:8332 \
|
||||
-p 8333:8333 \
|
||||
-v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \
|
||||
--label "com.archipelago.app=bitcoin-knots" \
|
||||
--label "com.archipelago.title=Bitcoin Knots" \
|
||||
--label "com.archipelago.version=28.1" \
|
||||
--label "com.archipelago.category=bitcoin" \
|
||||
--label "com.archipelago.description.short=Full Bitcoin node implementation" \
|
||||
--label "com.archipelago.description.long=Bitcoin Knots is a derivative of Bitcoin Core with additional features and bug fixes. Maintain the full blockchain and validate all transactions." \
|
||||
--label "com.archipelago.license=MIT" \
|
||||
--label "com.archipelago.icon=/assets/img/app-icons/bitcoin-knots.webp" \
|
||||
--label "com.archipelago.port=8332" \
|
||||
--label "com.archipelago.repo=https://github.com/bitcoinknots/bitcoin" \
|
||||
docker.io/bitcoinknots/bitcoin:latest \
|
||||
-server=1 \
|
||||
-txindex=1 \
|
||||
-rpcallowip=0.0.0.0/0 \
|
||||
-rpcbind=0.0.0.0:8332 \
|
||||
-rpcuser=archipelago \
|
||||
-rpcpassword=archipelago123 \
|
||||
-dbcache=4096
|
||||
|
||||
echo " ✅ Bitcoin Knots node starting"
|
||||
|
||||
# Step 3: Build and deploy web UI
|
||||
echo ""
|
||||
echo "🌐 Building Bitcoin Knots web UI..."
|
||||
|
||||
# Create temporary build directory
|
||||
BUILD_DIR="/tmp/bitcoin-ui-build"
|
||||
rm -rf "$BUILD_DIR"
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# Create Dockerfile
|
||||
cat > "$BUILD_DIR/Dockerfile" << 'EOF'
|
||||
FROM docker.io/library/nginx:alpine
|
||||
|
||||
# Copy the static UI
|
||||
COPY index.html /usr/share/nginx/html/
|
||||
|
||||
# Create assets directories
|
||||
RUN mkdir -p /usr/share/nginx/html/assets/img/app-icons && \
|
||||
mkdir -p /usr/share/nginx/html/assets/img
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
EOF
|
||||
|
||||
# Copy UI file from the project
|
||||
# For beta: this needs to be included in the ISO or downloadable
|
||||
cp /home/archipelago/archy/docker/bitcoin-ui/index.html "$BUILD_DIR/"
|
||||
|
||||
# Build the image
|
||||
sudo podman build -t localhost/bitcoin-ui:latest "$BUILD_DIR"
|
||||
|
||||
# Deploy UI container
|
||||
sudo podman run -d \
|
||||
--name bitcoin-ui \
|
||||
--restart unless-stopped \
|
||||
-p 8334:80 \
|
||||
--label "com.archipelago.app=bitcoin-ui" \
|
||||
--label "com.archipelago.parent=bitcoin-knots" \
|
||||
localhost/bitcoin-ui:latest
|
||||
|
||||
echo " ✅ Bitcoin UI deployed on port 8334"
|
||||
|
||||
# Cleanup
|
||||
rm -rf "$BUILD_DIR"
|
||||
|
||||
# Step 4: Wait for backend to detect
|
||||
echo ""
|
||||
echo "⏳ Waiting for backend to detect containers..."
|
||||
sleep 5
|
||||
|
||||
echo ""
|
||||
echo "╔════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ ✅ BITCOIN KNOTS DEPLOYED! ║"
|
||||
echo "╚════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo "📊 Status:"
|
||||
sudo podman ps | grep bitcoin
|
||||
echo ""
|
||||
echo "🌐 Access:"
|
||||
echo " • Web UI: http://YOUR-SERVER-IP:8334"
|
||||
echo " • RPC: http://localhost:8332"
|
||||
echo " • Network: Port 8333 (Bitcoin P2P)"
|
||||
echo ""
|
||||
echo "📝 RPC Credentials:"
|
||||
echo " • User: archipelago"
|
||||
echo " • Pass: archipelago123"
|
||||
echo ""
|
||||
echo "⏰ Blockchain sync will take several hours to days."
|
||||
echo " Check progress: sudo podman logs -f bitcoin-knots"
|
||||
echo ""
|
||||
Loading…
x
Reference in New Issue
Block a user