From a2dc9f43e3a481ad570bb9bbc6d3b0b5f7adaffc Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Fri, 29 Aug 2025 22:40:30 +1000 Subject: [PATCH] feat: Add docker support for inbuilt HSD --- .dockerignore | 32 ++++++++++++++++++++ .gitea/workflows/build.yml | 39 +++++++++++++++++++++++-- Dockerfile | 15 +++++++++- Dockerfile.hsd | 58 +++++++++++++++++++++++++++++++++++++ account.py | 40 ++++++++++++++++--------- docker-compose-internal.yml | 14 +++++++++ docker-compose.yml | 26 +++++++++++++++++ main.py | 2 +- 8 files changed, 208 insertions(+), 18 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile.hsd create mode 100644 docker-compose-internal.yml create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..981916b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,32 @@ + +.env +.env* +__pycache__/ + +templates/assets/css/styles.min.css + +ignore/ + +plugins/signatures.json + +.venv/ + +user_data/ +customPlugins/ +cache/ +build/ +dist/ +hsd/ +hsd-data/ +hsd.lock +hsdconfig.json + +Dockerfile +Dockerfile.hsd +FireWalletBrowser.bsdesign +LICENSE.md +README.md +docker-compose.yml +example.env +plugins.md + diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index d598885..481c448 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -34,8 +34,43 @@ jobs: fi - docker build -t firewallet:$tag_num . + docker build --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') --build-arg VCS_REF=$GITEA_SHA --build-arg VERSION=$GITEA_TAG \ -t firewallet:$tag_num . docker tag firewallet:$tag_num git.woodburn.au/nathanwoodburn/firewallet:$tag_num docker push git.woodburn.au/nathanwoodburn/firewallet:$tag_num docker tag firewallet:$tag_num git.woodburn.au/nathanwoodburn/firewallet:$tag - docker push git.woodburn.au/nathanwoodburn/firewallet:$tag \ No newline at end of file + docker push git.woodburn.au/nathanwoodburn/firewallet:$tag + Build Image with HSD: + runs-on: [ubuntu-latest, amd] + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install Docker + run : | + apt-get install ca-certificates curl gnupg + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg + chmod a+r /etc/apt/keyrings/docker.gpg + echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + apt-get update + apt-get install docker-ce-cli -y + - name: Build Docker image + run : | + echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin + echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" + tag=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} + tag=${tag//\//-} + tag_num=${GITHUB_RUN_NUMBER} + echo "tag_num=$tag_num" + + if [[ "$tag" == "main" ]]; then + tag="latest" + else + tag_num="${tag}-${tag_num}" + fi + + + docker build --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') --build-arg VCS_REF=$GITEA_SHA --build-arg VERSION=$GITEA_TAG \ -t firewallet-hsd:$tag_num . + docker tag firewallet-hsd:$tag_num git.woodburn.au/nathanwoodburn/firewallet-hsd:$tag_num + docker push git.woodburn.au/nathanwoodburn/firewallet-hsd:$tag_num + docker tag firewallet-hsd:$tag_num git.woodburn.au/nathanwoodburn/firewallet-hsd:$tag + docker push git.woodburn.au/nathanwoodburn/firewallet-hsd:$tag \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 31dcc5f..5873738 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,20 @@ RUN --mount=type=cache,target=/root/.cache/pip \ COPY . /app # Add mount point for data volume -# VOLUME /data +VOLUME /app/user_data + + +ARG BUILD_DATE +ARG VCS_REF + +LABEL org.opencontainers.image.title="FireWallet" \ + org.opencontainers.image.description="The Handshake Wallet That is Fire" \ + org.opencontainers.image.url="https://firewallet.au" \ + org.opencontainers.image.source="https://git.woodburn.au/nathanwoodburn/firewalletbrowser" \ + org.opencontainers.image.version="2.0.0" \ + org.opencontainers.image.created=$BUILD_DATE \ + org.opencontainers.image.revision=$VCS_REF \ + org.opencontainers.image.licenses="AGPL-3.0-only" ENTRYPOINT ["python3"] CMD ["server.py"] diff --git a/Dockerfile.hsd b/Dockerfile.hsd new file mode 100644 index 0000000..c3ed27e --- /dev/null +++ b/Dockerfile.hsd @@ -0,0 +1,58 @@ +# ---- HSD build stage ---- +FROM node:22-alpine AS hsd-build +WORKDIR /opt/hsd +RUN apk add --no-cache git bash unbound-dev gmp-dev g++ gcc make python3 +RUN git clone --depth=1 --branch v8.0.0 https://github.com/handshake-org/hsd.git . +RUN npm install --omit=dev + +# ---- Final stage ---- +FROM python:3.13-alpine +WORKDIR /app + +# Install runtime deps only +RUN apk add --no-cache unbound-dev gmp + + +# Copy node and npm from hsd-build stage +COPY --from=hsd-build /usr/local/bin/node /usr/local/bin/node +COPY --from=hsd-build /usr/local/lib/node_modules/npm /usr/local/lib/node +COPY --from=hsd-build /usr/local/bin/npm /usr/local/bin/npm + +# Copy FireWallet dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy HSD from build stage +COPY --from=hsd-build /opt/hsd /app/hsd + +# Copy FireWallet source +COPY . . + +# Expose ports +EXPOSE 5000 +# Optional HSD ports +# EXPOSE 12037 +# EXPOSE 12039 + +ENV INTERNAL_HSD=true +ENV HSD_DOCKER_CONTAINER=true + +ARG BUILD_DATE +ARG VCS_REF + +LABEL org.opencontainers.image.title="FireWallet (HSD)" \ + org.opencontainers.image.description="The Handshake Wallet That is Fire" \ + org.opencontainers.image.url="https://firewallet.au" \ + org.opencontainers.image.source="https://git.woodburn.au/nathanwoodburn/firewalletbrowser" \ + org.opencontainers.image.version="2.0.0" \ + org.opencontainers.image.created=$BUILD_DATE \ + org.opencontainers.image.revision=$VCS_REF \ + org.opencontainers.image.licenses="AGPL-3.0-only" + + + +VOLUME ["/app/hsd-data", "/app/user_data"] + + +ENTRYPOINT ["python3"] +CMD ["server.py"] diff --git a/account.py b/account.py index a9612e5..4ba99bd 100644 --- a/account.py +++ b/account.py @@ -1787,38 +1787,36 @@ def checkPreRequisites() -> dict[str, bool]: try: # Check if node is installed and get version - nodeSubprocess = subprocess.run(["node", "-v"], capture_output=True, text=True) + nodeSubprocess = subprocess.run(["node", "-v"], capture_output=True, text=True,timeout=2) if nodeSubprocess.returncode == 0: major_version = int(nodeSubprocess.stdout.strip().lstrip('v').split('.')[0]) if major_version >= HSD_CONFIG.get("minNodeVersion", 20): prerequisites["node"] = True - except FileNotFoundError: + except Exception: pass - + try: # Check if npm is installed - npmSubprocess = subprocess.run(["npm", "-v"], capture_output=True, text=True) + npmSubprocess = subprocess.run(["npm", "-v"], capture_output=True, text=True,timeout=2) if npmSubprocess.returncode == 0: major_version = int(npmSubprocess.stdout.strip().split('.')[0]) if major_version >= HSD_CONFIG.get("minNPMVersion", 8): prerequisites["npm"] = True - except FileNotFoundError: + except Exception: pass try: # Check if git is installed - gitSubprocess = subprocess.run(["git", "-v"], capture_output=True, text=True) + gitSubprocess = subprocess.run(["git", "-v"], capture_output=True, text=True,timeout=2) if gitSubprocess.returncode == 0: prerequisites["git"] = True - except FileNotFoundError: + except Exception: pass + # Check if hsd is installed if os.path.exists("./hsd/bin/hsd"): prerequisites["hsd"] = True - - - return prerequisites @@ -1827,11 +1825,23 @@ def checkPreRequisites() -> dict[str, bool]: def hsdInit(): if not HSD_INTERNAL_NODE: return - prerequisites = checkPreRequisites() + # Don't check prerequisites if HSD is included in a docker container + if os.getenv("HSD_DOCKER_CONTAINER", "false").lower() == "true": + prerequisites = { + "node": True, + "npm": True, + "git": True, + "hsd": True + } + else: + prerequisites = checkPreRequisites() + + minNodeVersion = HSD_CONFIG.get("minNodeVersion", 20) + minNPMVersion = HSD_CONFIG.get("minNPMVersion", 8) PREREQ_MESSAGES = { - "node": "Install Node.js from https://nodejs.org/en/download (Version >= {minNodeVersion})", - "npm": "Install npm (version >= {minNPMVersion}) - usually comes with Node.js", + "node": f"Install Node.js from https://nodejs.org/en/download (Version >= {minNodeVersion})", + "npm": f"Install npm (version >= {minNPMVersion}) - usually comes with Node.js", "git": "Install Git from https://git-scm.com/downloads"} @@ -1840,7 +1850,9 @@ def hsdInit(): print("HSD Internal Node prerequisites not met:") for key, value in prerequisites.items(): if not value: - print(f" - {key} is missing or does not meet the version requirement.") + print(f" - {key} is missing or does not meet the version requirement.",flush=True) + if key in PREREQ_MESSAGES: + print(PREREQ_MESSAGES[key],flush=True) exit(1) return diff --git a/docker-compose-internal.yml b/docker-compose-internal.yml new file mode 100644 index 0000000..68b186f --- /dev/null +++ b/docker-compose-internal.yml @@ -0,0 +1,14 @@ +services: + firewallet: + image: firewallet-hsd:latest + ports: + - "5000:5000" + volumes: + - hsd_data:/app/hsd-data + - user_data:/app/user_data + environment: + - INTERNAL_HSD=true + +volumes: + hsd_data: + user_data: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b565d57 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +services: + hsd: + image: ghcr.io/handshake-org/hsd:8 + volumes: + - hsd_data:/root/.hsd + environment: + - HSD_HTTP_HOST=0.0.0.0 + - HSD_WALLET_HTTP_HOST=0.0.0.0 + - HSD_LOG_LEVEL=error + - HSD_API_KEY=changeme + + + firewallet: + image: git.woodburn.au/nathanwoodburn/firewallet:latest + ports: + - "5000:5000" + environment: + - HSD_IP=hsd + - HSD_API=changeme + volumes: + - user_data:/app/user_data + + +volumes: + hsd_data: + user_data: diff --git a/main.py b/main.py index 2076780..e374506 100644 --- a/main.py +++ b/main.py @@ -30,7 +30,7 @@ fees = 0.02 revokeCheck = random.randint(100000,999999) -THEME = os.getenv("THEME") +THEME = os.getenv("THEME", "black") def blocks_to_time(blocks: int) -> str: