Spaces:
Running
Running
Upload 12 files
Browse files- CHANGELOG.md +27 -0
- CODE_OF_CONDUCT.md +27 -0
- CONTRIBUTING.md +46 -0
- Dockerfile +40 -0
- LICENSE +21 -0
- README.md +275 -7
- SECURITY.md +28 -0
- dns-fix.js +19 -0
- health-server.js +27 -0
- keep-alive.sh +34 -0
- start.sh +237 -0
- workspace-sync.sh +40 -0
CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Changelog
|
| 2 |
+
|
| 3 |
+
All notable changes to this project will be documented in this file.
|
| 4 |
+
|
| 5 |
+
## [1.0.0] - 2026-03-30
|
| 6 |
+
|
| 7 |
+
### π Initial Release
|
| 8 |
+
|
| 9 |
+
#### Features
|
| 10 |
+
- **Any LLM provider** β Anthropic (Claude), OpenAI (GPT-4), Google (Gemini)
|
| 11 |
+
- **Telegram integration** β connect via @BotFather, supports multiple users
|
| 12 |
+
- **Built-in keep-alive** β self-pings to prevent HF Spaces 48h sleep
|
| 13 |
+
- **Auto-sync workspace** β commits + pushes to HF Dataset every 10 min
|
| 14 |
+
- **Auto-create backup** β creates HF Dataset automatically on first run
|
| 15 |
+
- **Graceful shutdown** β saves workspace before container stops
|
| 16 |
+
- **Health endpoint** β `/health` on port 7861 for monitoring
|
| 17 |
+
- **DNS fix** β bypasses HF Spaces internal DNS restrictions
|
| 18 |
+
- **Version pinning** β lock OpenClaw to a specific version
|
| 19 |
+
- **Startup banner** β clean summary of all running services
|
| 20 |
+
- **Zero-config defaults** β just 2 secrets to get started
|
| 21 |
+
|
| 22 |
+
#### Architecture
|
| 23 |
+
- `start.sh` β config generator + validation + orchestrator
|
| 24 |
+
- `keep-alive.sh` β self-ping background service
|
| 25 |
+
- `workspace-sync.sh` β periodic workspace backup
|
| 26 |
+
- `health-server.js` β lightweight health endpoint
|
| 27 |
+
- `dns-fix.js` β DNS override for HF network restrictions
|
CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Code of Conduct
|
| 2 |
+
|
| 3 |
+
## Our Pledge
|
| 4 |
+
|
| 5 |
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
| 6 |
+
|
| 7 |
+
## Our Standards
|
| 8 |
+
|
| 9 |
+
**Positive behavior includes:**
|
| 10 |
+
- Using welcoming and inclusive language
|
| 11 |
+
- Being respectful of differing viewpoints
|
| 12 |
+
- Gracefully accepting constructive criticism
|
| 13 |
+
- Focusing on what is best for the community
|
| 14 |
+
|
| 15 |
+
**Unacceptable behavior includes:**
|
| 16 |
+
- Trolling, insulting, or derogatory comments
|
| 17 |
+
- Public or private harassment
|
| 18 |
+
- Publishing others' private information without permission
|
| 19 |
+
- Other conduct which could reasonably be considered inappropriate
|
| 20 |
+
|
| 21 |
+
## Enforcement
|
| 22 |
+
|
| 23 |
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting the maintainer. All complaints will be reviewed and investigated.
|
| 24 |
+
|
| 25 |
+
## Attribution
|
| 26 |
+
|
| 27 |
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.0.
|
CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing to HuggingClaw
|
| 2 |
+
|
| 3 |
+
Thanks for your interest in contributing! π¦
|
| 4 |
+
|
| 5 |
+
## How to Contribute
|
| 6 |
+
|
| 7 |
+
### Bug Reports
|
| 8 |
+
- Open an issue with a clear description
|
| 9 |
+
- Include your HF Space logs if possible
|
| 10 |
+
- Mention which LLM provider you're using
|
| 11 |
+
|
| 12 |
+
### Feature Requests
|
| 13 |
+
- Open an issue with the `enhancement` label
|
| 14 |
+
- Describe the use case β why is this needed?
|
| 15 |
+
|
| 16 |
+
### Pull Requests
|
| 17 |
+
1. Fork the repo
|
| 18 |
+
2. Create a feature branch: `git checkout -b feature/my-feature`
|
| 19 |
+
3. Make your changes
|
| 20 |
+
4. Test locally with Docker: `docker build -t huggingclaw . && docker run -p 7860:7860 --env-file .env huggingclaw`
|
| 21 |
+
5. Commit with a clear message
|
| 22 |
+
6. Push and open a PR
|
| 23 |
+
|
| 24 |
+
### Code Style
|
| 25 |
+
- Shell scripts: use `set -e`, quote variables, comment non-obvious logic
|
| 26 |
+
- Keep it simple β this project should stay easy to understand
|
| 27 |
+
- No unnecessary dependencies
|
| 28 |
+
|
| 29 |
+
### Testing
|
| 30 |
+
- Test with at least one LLM provider (Anthropic, OpenAI, or Google)
|
| 31 |
+
- Test with and without Telegram enabled
|
| 32 |
+
- Test with and without workspace backup enabled
|
| 33 |
+
- Verify keep-alive and auto-sync work
|
| 34 |
+
|
| 35 |
+
## Development Setup
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
cp .env.example .env
|
| 39 |
+
# Fill in your values
|
| 40 |
+
docker build -t huggingclaw .
|
| 41 |
+
docker run -p 7860:7860 --env-file .env huggingclaw
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
## Questions?
|
| 45 |
+
|
| 46 |
+
Open an issue or start a discussion. We're friendly! π€
|
Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:22-slim
|
| 2 |
+
|
| 3 |
+
# Version pinning (default: latest)
|
| 4 |
+
ARG OPENCLAW_VERSION=latest
|
| 5 |
+
|
| 6 |
+
# Install git, ca-certificates, jq, and curl
|
| 7 |
+
RUN apt-get update && apt-get install -y \
|
| 8 |
+
git \
|
| 9 |
+
ca-certificates \
|
| 10 |
+
jq \
|
| 11 |
+
curl \
|
| 12 |
+
--no-install-recommends && \
|
| 13 |
+
rm -rf /var/lib/apt/lists/*
|
| 14 |
+
|
| 15 |
+
# Reuse existing node user (UID 1000)
|
| 16 |
+
RUN mkdir -p /home/node/app /home/node/.openclaw && \
|
| 17 |
+
chown -R 1000:1000 /home/node
|
| 18 |
+
|
| 19 |
+
# Install OpenClaw (version configurable via build arg)
|
| 20 |
+
RUN npm install -g openclaw@${OPENCLAW_VERSION}
|
| 21 |
+
|
| 22 |
+
# Copy files
|
| 23 |
+
COPY --chown=1000:1000 dns-fix.js /opt/dns-fix.js
|
| 24 |
+
COPY --chown=1000:1000 health-server.js /home/node/app/health-server.js
|
| 25 |
+
COPY --chown=1000:1000 start.sh /home/node/app/start.sh
|
| 26 |
+
COPY --chown=1000:1000 keep-alive.sh /home/node/app/keep-alive.sh
|
| 27 |
+
COPY --chown=1000:1000 workspace-sync.sh /home/node/app/workspace-sync.sh
|
| 28 |
+
RUN chmod +x /home/node/app/start.sh /home/node/app/keep-alive.sh /home/node/app/workspace-sync.sh
|
| 29 |
+
|
| 30 |
+
USER node
|
| 31 |
+
|
| 32 |
+
ENV HOME=/home/node \
|
| 33 |
+
PATH=/home/node/.local/bin:/usr/local/bin:$PATH \
|
| 34 |
+
NODE_OPTIONS="--require /opt/dns-fix.js"
|
| 35 |
+
|
| 36 |
+
WORKDIR /home/node/app
|
| 37 |
+
|
| 38 |
+
EXPOSE 7860
|
| 39 |
+
|
| 40 |
+
CMD ["/home/node/app/start.sh"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Somrat Sorkar (@somratpro)
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -1,12 +1,280 @@
|
|
| 1 |
---
|
| 2 |
title: HuggingClaw
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
short_description: π¦ Run your own always-on AI assistant on HuggingFace Spaces
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: HuggingClaw
|
| 3 |
+
emoji: π¦
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: true
|
|
|
|
| 9 |
---
|
| 10 |
|
| 11 |
+
<!-- Badges -->
|
| 12 |
+
[](https://github.com/somratpro/huggingclaw)
|
| 13 |
+
[](https://opensource.org/licenses/MIT)
|
| 14 |
+
[](https://huggingface.co/spaces)
|
| 15 |
+
[](https://github.com/openclaw/openclaw)
|
| 16 |
+
|
| 17 |
+
# π¦ HuggingClaw
|
| 18 |
+
|
| 19 |
+
Run your own **always-on AI assistant** on HuggingFace Spaces β for free.
|
| 20 |
+
|
| 21 |
+
Works with **any LLM** (Anthropic, OpenAI, Google), connects via **Telegram**, and persists your workspace to **HF Datasets** automatically.
|
| 22 |
+
|
| 23 |
+
### β¨ Features
|
| 24 |
+
|
| 25 |
+
- **Zero-config** β just add 2 secrets and deploy
|
| 26 |
+
- **Any LLM provider** β Claude, GPT-4, Gemini, etc.
|
| 27 |
+
- **Built-in keep-alive** β self-pings to prevent HF sleep (no external cron needed)
|
| 28 |
+
- **Auto-sync workspace** β commits + pushes changes every 10 min
|
| 29 |
+
- **Auto-create backup** β creates the HF Dataset for you if it doesn't exist
|
| 30 |
+
- **Graceful shutdown** β saves workspace before container dies
|
| 31 |
+
- **Multi-user Telegram** β supports comma-separated user IDs for teams
|
| 32 |
+
- **Health endpoint** β `/health` for monitoring
|
| 33 |
+
- **Version pinning** β lock OpenClaw to a specific version
|
| 34 |
+
- **100% HF-native** β runs entirely on HuggingFace infrastructure
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
## π Quick Start
|
| 39 |
+
|
| 40 |
+
### 1. Duplicate this Space
|
| 41 |
+
Click **"Duplicate this Space"** β name it β set to **Private**
|
| 42 |
+
|
| 43 |
+
### 2. Add Required Secrets
|
| 44 |
+
Go to **Settings β Secrets**:
|
| 45 |
+
|
| 46 |
+
| Secret | Value |
|
| 47 |
+
|--------|-------|
|
| 48 |
+
| `LLM_API_KEY` | Your API key ([Anthropic](https://console.anthropic.com/) / [OpenAI](https://platform.openai.com/) / [Google](https://ai.google.dev/)) |
|
| 49 |
+
| `GATEWAY_TOKEN` | Run `openssl rand -hex 32` to generate |
|
| 50 |
+
|
| 51 |
+
### 3. Deploy
|
| 52 |
+
That's it! The Space builds and starts automatically.
|
| 53 |
+
|
| 54 |
+
### 4. (Optional) Add Telegram
|
| 55 |
+
| Secret | Value |
|
| 56 |
+
|--------|-------|
|
| 57 |
+
| `TELEGRAM_BOT_TOKEN` | From [@BotFather](https://t.me/BotFather) |
|
| 58 |
+
| `TELEGRAM_USER_ID` | Your user ID ([how to find](https://t.me/userinfobot)) |
|
| 59 |
+
|
| 60 |
+
### 5. (Optional) Enable Workspace Backup
|
| 61 |
+
| Secret | Value |
|
| 62 |
+
|--------|-------|
|
| 63 |
+
| `HF_USERNAME` | Your HuggingFace username |
|
| 64 |
+
| `HF_TOKEN` | [HF token](https://huggingface.co/settings/tokens) with write access |
|
| 65 |
+
|
| 66 |
+
The backup dataset (`huggingclaw-backup`) is **created automatically** β no manual setup needed.
|
| 67 |
+
|
| 68 |
+
---
|
| 69 |
+
|
| 70 |
+
## π All Configuration Options
|
| 71 |
+
|
| 72 |
+
See **`.env.example`** for the complete reference with examples.
|
| 73 |
+
|
| 74 |
+
#### Required
|
| 75 |
+
|
| 76 |
+
| Variable | Purpose |
|
| 77 |
+
|----------|---------|
|
| 78 |
+
| `LLM_API_KEY` | LLM provider API key |
|
| 79 |
+
| `GATEWAY_TOKEN` | Gateway auth token |
|
| 80 |
+
|
| 81 |
+
#### LLM
|
| 82 |
+
|
| 83 |
+
| Variable | Default | Purpose |
|
| 84 |
+
|----------|---------|---------|
|
| 85 |
+
| `LLM_PROVIDER` | `anthropic` | Provider: `anthropic`, `openai`, `google` |
|
| 86 |
+
| `LLM_MODEL` | `anthropic/claude-haiku-4-5` | Model to use |
|
| 87 |
+
|
| 88 |
+
#### Telegram
|
| 89 |
+
|
| 90 |
+
| Variable | Purpose |
|
| 91 |
+
|----------|---------|
|
| 92 |
+
| `TELEGRAM_BOT_TOKEN` | Bot token from @BotFather |
|
| 93 |
+
| `TELEGRAM_USER_ID` | Single user allowlist |
|
| 94 |
+
| `TELEGRAM_USER_IDS` | Multiple users (comma-separated): `123,456,789` |
|
| 95 |
+
|
| 96 |
+
#### Workspace Backup
|
| 97 |
+
|
| 98 |
+
| Variable | Default | Purpose |
|
| 99 |
+
|----------|---------|---------|
|
| 100 |
+
| `HF_USERNAME` | β | Your HF username |
|
| 101 |
+
| `HF_TOKEN` | β | HF token (write access) |
|
| 102 |
+
| `BACKUP_DATASET_NAME` | `huggingclaw-backup` | Dataset name (auto-created!) |
|
| 103 |
+
| `WORKSPACE_GIT_USER` | `openclaw@example.com` | Git commit email |
|
| 104 |
+
| `WORKSPACE_GIT_NAME` | `OpenClaw Bot` | Git commit name |
|
| 105 |
+
|
| 106 |
+
#### Background Services
|
| 107 |
+
|
| 108 |
+
| Variable | Default | Purpose |
|
| 109 |
+
|----------|---------|---------|
|
| 110 |
+
| `KEEP_ALIVE_INTERVAL` | `300` (5 min) | Self-ping interval. `0` = disable |
|
| 111 |
+
| `SYNC_INTERVAL` | `600` (10 min) | Auto-sync interval |
|
| 112 |
+
|
| 113 |
+
#### Advanced
|
| 114 |
+
|
| 115 |
+
| Variable | Default | Purpose |
|
| 116 |
+
|----------|---------|---------|
|
| 117 |
+
| `OPENCLAW_VERSION` | `latest` | Pin OpenClaw version |
|
| 118 |
+
| `HEALTH_PORT` | `7861` | Health endpoint port |
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
## π€ LLM Provider Setup
|
| 123 |
+
|
| 124 |
+
### Anthropic (Claude)
|
| 125 |
+
```
|
| 126 |
+
LLM_PROVIDER=anthropic
|
| 127 |
+
LLM_API_KEY=sk-ant-v0-...
|
| 128 |
+
LLM_MODEL=anthropic/claude-haiku-4-5
|
| 129 |
+
```
|
| 130 |
+
Models: `claude-opus-4-6` Β· `claude-sonnet-4-5` Β· `claude-haiku-4-5`
|
| 131 |
+
|
| 132 |
+
### OpenAI
|
| 133 |
+
```
|
| 134 |
+
LLM_PROVIDER=openai
|
| 135 |
+
LLM_API_KEY=sk-...
|
| 136 |
+
LLM_MODEL=gpt-4
|
| 137 |
+
```
|
| 138 |
+
Models: `gpt-4-turbo` Β· `gpt-4` Β· `gpt-3.5-turbo`
|
| 139 |
+
|
| 140 |
+
### Google (Gemini)
|
| 141 |
+
```
|
| 142 |
+
LLM_PROVIDER=google
|
| 143 |
+
LLM_API_KEY=AIzaSy...
|
| 144 |
+
LLM_MODEL=gemini-pro
|
| 145 |
+
```
|
| 146 |
+
Models: `gemini-pro` Β· `gemini-pro-vision`
|
| 147 |
+
|
| 148 |
+
---
|
| 149 |
+
|
| 150 |
+
## π± Telegram Setup
|
| 151 |
+
|
| 152 |
+
1. Message [@BotFather](https://t.me/BotFather) β `/newbot` β copy the token
|
| 153 |
+
2. Message [@userinfobot](https://t.me/userinfobot) to get your user ID
|
| 154 |
+
3. Add secrets: `TELEGRAM_BOT_TOKEN` and `TELEGRAM_USER_ID`
|
| 155 |
+
4. Restart the Space β DM your bot π
|
| 156 |
+
|
| 157 |
+
**Multiple users?** Use `TELEGRAM_USER_IDS=123,456,789` (comma-separated)
|
| 158 |
+
|
| 159 |
+
---
|
| 160 |
+
|
| 161 |
+
## πΎ Workspace Backup
|
| 162 |
+
|
| 163 |
+
Set `HF_USERNAME` + `HF_TOKEN` and HuggingClaw handles everything:
|
| 164 |
+
|
| 165 |
+
1. **Auto-creates** the dataset if it doesn't exist
|
| 166 |
+
2. **Restores** workspace on every startup
|
| 167 |
+
3. **Auto-syncs** changes every 10 minutes (configurable)
|
| 168 |
+
4. **Saves** on shutdown (graceful SIGTERM handling)
|
| 169 |
+
|
| 170 |
+
Custom dataset name: `BACKUP_DATASET_NAME=my-custom-backup`
|
| 171 |
+
|
| 172 |
+
---
|
| 173 |
+
|
| 174 |
+
## π How It Stays Alive
|
| 175 |
+
|
| 176 |
+
HF Spaces sleeps after 48h of no HTTP requests. HuggingClaw prevents this with:
|
| 177 |
+
|
| 178 |
+
- **Self-ping** β pings its own URL every 5 min (uses HF's `SPACE_HOST` env var)
|
| 179 |
+
- **Health endpoint** β returns `200 OK` with uptime info
|
| 180 |
+
- **Zero dependencies** β no external cron, no third-party pinger
|
| 181 |
+
|
| 182 |
+
Your Space runs forever, powered entirely by HF. π―
|
| 183 |
+
|
| 184 |
+
---
|
| 185 |
+
|
| 186 |
+
## π» Local Development
|
| 187 |
+
|
| 188 |
+
```bash
|
| 189 |
+
git clone https://github.com/somratpro/huggingclaw.git
|
| 190 |
+
cd huggingclaw
|
| 191 |
+
cp .env.example .env
|
| 192 |
+
nano .env # fill in your values
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
**Docker:**
|
| 196 |
+
```bash
|
| 197 |
+
docker build -t huggingclaw .
|
| 198 |
+
docker run -p 7860:7860 --env-file .env huggingclaw
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
**Without Docker:**
|
| 202 |
+
```bash
|
| 203 |
+
npm install -g openclaw@latest
|
| 204 |
+
export $(cat .env | xargs)
|
| 205 |
+
bash start.sh
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
---
|
| 209 |
+
|
| 210 |
+
## π Connect via CLI
|
| 211 |
+
|
| 212 |
+
```bash
|
| 213 |
+
npm install -g openclaw@latest
|
| 214 |
+
openclaw channels login --gateway https://YOUR-SPACE-URL.hf.space
|
| 215 |
+
# Enter your GATEWAY_TOKEN when prompted
|
| 216 |
+
```
|
| 217 |
+
|
| 218 |
+
---
|
| 219 |
+
|
| 220 |
+
## ποΈ Architecture
|
| 221 |
+
|
| 222 |
+
```
|
| 223 |
+
HuggingClaw/
|
| 224 |
+
βββ Dockerfile # Runtime: Node.js + OpenClaw + curl + jq
|
| 225 |
+
βββ start.sh # Config generator + validation + orchestrator
|
| 226 |
+
βββ keep-alive.sh # Self-ping to prevent HF sleep
|
| 227 |
+
βββ workspace-sync.sh # Periodic workspace commit + push
|
| 228 |
+
βββ health-server.js # Health endpoint (/health)
|
| 229 |
+
βββ dns-fix.js # DNS override for HF network restrictions
|
| 230 |
+
βββ .env.example # Complete configuration reference
|
| 231 |
+
βββ .gitignore # Keeps secrets out of version control
|
| 232 |
+
βββ README.md # You are here
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
**Startup flow:**
|
| 236 |
+
1. Validate secrets β fail fast with clear errors
|
| 237 |
+
2. Validate HF token β warn if expired
|
| 238 |
+
3. Auto-create backup dataset if missing
|
| 239 |
+
4. Restore workspace from HF Dataset
|
| 240 |
+
5. Generate `openclaw.json` config from env vars
|
| 241 |
+
6. Print startup summary
|
| 242 |
+
7. Start background services (keep-alive, auto-sync)
|
| 243 |
+
8. Launch OpenClaw gateway
|
| 244 |
+
9. On SIGTERM β save workspace β exit cleanly
|
| 245 |
+
|
| 246 |
+
---
|
| 247 |
+
|
| 248 |
+
## π Troubleshooting
|
| 249 |
+
|
| 250 |
+
**Missing secrets** β Check **Settings β Secrets** for `LLM_API_KEY` and `GATEWAY_TOKEN`
|
| 251 |
+
|
| 252 |
+
**Telegram not working** β Verify bot token is valid, check logs for `π± Enabling Telegram`
|
| 253 |
+
|
| 254 |
+
**Workspace not restoring** β Check `HF_USERNAME` and `HF_TOKEN` are set, token has write access
|
| 255 |
+
|
| 256 |
+
**Space sleeping** β Check logs for `π Keep-alive started`. If missing, `SPACE_HOST` might not be set
|
| 257 |
+
|
| 258 |
+
**Control UI blocked** β The Space URL is auto-allowlisted. Check logs for origin errors
|
| 259 |
+
|
| 260 |
+
**Version issues** β Pin with `OPENCLAW_VERSION=2026.3.24` in secrets
|
| 261 |
+
|
| 262 |
+
---
|
| 263 |
+
|
| 264 |
+
## π Links
|
| 265 |
+
|
| 266 |
+
- [OpenClaw Docs](https://docs.openclaw.ai) Β· [OpenClaw GitHub](https://github.com/openclaw/openclaw) Β· [HF Spaces Docs](https://huggingface.co/docs/hub/spaces)
|
| 267 |
+
|
| 268 |
+
---
|
| 269 |
+
|
| 270 |
+
## π€ Contributing
|
| 271 |
+
|
| 272 |
+
Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
| 273 |
+
|
| 274 |
+
## π License
|
| 275 |
+
|
| 276 |
+
MIT β see [LICENSE](LICENSE) for details.
|
| 277 |
+
|
| 278 |
+
---
|
| 279 |
+
|
| 280 |
+
Made with β€οΈ by [@somratpro](https://github.com/somratpro) for the [OpenClaw](https://github.com/openclaw/openclaw) community
|
SECURITY.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Security Policy
|
| 2 |
+
|
| 3 |
+
## Reporting a Vulnerability
|
| 4 |
+
|
| 5 |
+
If you discover a security vulnerability, please report it responsibly:
|
| 6 |
+
|
| 7 |
+
1. **Do NOT open a public issue**
|
| 8 |
+
2. Email the maintainer or open a private security advisory on GitHub
|
| 9 |
+
3. Include steps to reproduce if possible
|
| 10 |
+
|
| 11 |
+
We'll respond within 48 hours and work on a fix.
|
| 12 |
+
|
| 13 |
+
## Security Best Practices
|
| 14 |
+
|
| 15 |
+
When deploying HuggingClaw:
|
| 16 |
+
|
| 17 |
+
- **Set your Space to Private** β prevents unauthorized access to your gateway
|
| 18 |
+
- **Use a strong `GATEWAY_TOKEN`** β generate with `openssl rand -hex 32`
|
| 19 |
+
- **Keep your HF token scoped** β use fine-grained tokens with minimum permissions
|
| 20 |
+
- **Don't commit `.env` files** β the `.gitignore` already excludes them
|
| 21 |
+
- **Use `TELEGRAM_USER_ID`** β restricts bot access to your account only
|
| 22 |
+
- **Review logs regularly** β check for unauthorized access attempts
|
| 23 |
+
|
| 24 |
+
## Supported Versions
|
| 25 |
+
|
| 26 |
+
| Version | Supported |
|
| 27 |
+
|---------|-----------|
|
| 28 |
+
| 1.0.x | β
|
|
dns-fix.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Fix HF Spaces DNS: internal resolver can't resolve discord.com / api.telegram.org
|
| 2 |
+
// Override dns.lookup (used by http/https) to use Google/Cloudflare DNS
|
| 3 |
+
const dns = require('dns');
|
| 4 |
+
const { Resolver } = dns;
|
| 5 |
+
const resolver = new Resolver();
|
| 6 |
+
resolver.setServers(['8.8.8.8', '1.1.1.1']);
|
| 7 |
+
|
| 8 |
+
const origLookup = dns.lookup;
|
| 9 |
+
dns.lookup = function(hostname, options, callback) {
|
| 10 |
+
if (typeof options === 'function') { callback = options; options = { family: 0 }; }
|
| 11 |
+
resolver.resolve4(hostname, (err, addresses) => {
|
| 12 |
+
if (err || !addresses || !addresses.length) return origLookup.call(dns, hostname, options, callback);
|
| 13 |
+
if (options && options.all) {
|
| 14 |
+
callback(null, addresses.map(a => ({ address: a, family: 4 })));
|
| 15 |
+
} else {
|
| 16 |
+
callback(null, addresses[0], 4);
|
| 17 |
+
}
|
| 18 |
+
});
|
| 19 |
+
};
|
health-server.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Lightweight health endpoint on port 7861
|
| 2 |
+
// OpenClaw runs on 7860, this runs alongside it
|
| 3 |
+
// Returns 200 OK for keep-alive pings and external monitoring
|
| 4 |
+
const http = require('http');
|
| 5 |
+
|
| 6 |
+
const PORT = process.env.HEALTH_PORT || 7861;
|
| 7 |
+
const startTime = Date.now();
|
| 8 |
+
|
| 9 |
+
const server = http.createServer((req, res) => {
|
| 10 |
+
if (req.url === '/health' || req.url === '/') {
|
| 11 |
+
const uptime = Math.floor((Date.now() - startTime) / 1000);
|
| 12 |
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
| 13 |
+
res.end(JSON.stringify({
|
| 14 |
+
status: 'ok',
|
| 15 |
+
uptime: uptime,
|
| 16 |
+
uptimeHuman: `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m`,
|
| 17 |
+
timestamp: new Date().toISOString()
|
| 18 |
+
}));
|
| 19 |
+
} else {
|
| 20 |
+
res.writeHead(404);
|
| 21 |
+
res.end();
|
| 22 |
+
}
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
server.listen(PORT, '0.0.0.0', () => {
|
| 26 |
+
console.log(`π₯ Health server listening on port ${PORT}`);
|
| 27 |
+
});
|
keep-alive.sh
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Self-ping keep-alive for HF Spaces
|
| 3 |
+
# HF Spaces sleeps after 48h of inactivity (no HTTP requests)
|
| 4 |
+
# This script pings the Space's own URL to prevent that
|
| 5 |
+
#
|
| 6 |
+
# HF provides SPACE_HOST env var automatically (e.g., "username-spacename.hf.space")
|
| 7 |
+
# Runs as a background process alongside the gateway
|
| 8 |
+
|
| 9 |
+
INTERVAL="${KEEP_ALIVE_INTERVAL:-300}" # Default: every 5 minutes
|
| 10 |
+
|
| 11 |
+
if [ "$INTERVAL" = "0" ]; then
|
| 12 |
+
echo "βΈοΈ Keep-alive: disabled (KEEP_ALIVE_INTERVAL=0)"
|
| 13 |
+
exit 0
|
| 14 |
+
fi
|
| 15 |
+
|
| 16 |
+
if [ -z "$SPACE_HOST" ]; then
|
| 17 |
+
echo "βΈοΈ Keep-alive: SPACE_HOST not set (not on HF Spaces?), skipping."
|
| 18 |
+
exit 0
|
| 19 |
+
fi
|
| 20 |
+
|
| 21 |
+
# Ping the Space URL β any HTTP response (even 404) counts as activity
|
| 22 |
+
PING_URL="https://${SPACE_HOST}"
|
| 23 |
+
|
| 24 |
+
echo "π Keep-alive started: pinging ${PING_URL} every ${INTERVAL}s"
|
| 25 |
+
|
| 26 |
+
while true; do
|
| 27 |
+
sleep "$INTERVAL"
|
| 28 |
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$PING_URL" 2>/dev/null)
|
| 29 |
+
if [ "$HTTP_CODE" = "000" ]; then
|
| 30 |
+
echo "π Keep-alive: ping failed (network error), retrying next cycle..."
|
| 31 |
+
else
|
| 32 |
+
echo "π Keep-alive: OK"
|
| 33 |
+
fi
|
| 34 |
+
done
|
start.sh
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -e
|
| 3 |
+
|
| 4 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 5 |
+
# HuggingClaw β OpenClaw Gateway for HF Spaces
|
| 6 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 7 |
+
|
| 8 |
+
# ββ Startup Banner ββ
|
| 9 |
+
OPENCLAW_VERSION="${OPENCLAW_VERSION:-latest}"
|
| 10 |
+
echo ""
|
| 11 |
+
echo " ββββββββββββββββββββββββββββββββββββββββββββ"
|
| 12 |
+
echo " β π¦ HuggingClaw Gateway β"
|
| 13 |
+
echo " ββββββββββββββββββββββββββββββββββββββββββββ"
|
| 14 |
+
echo ""
|
| 15 |
+
|
| 16 |
+
# ββ Validate required secrets ββ
|
| 17 |
+
ERRORS=""
|
| 18 |
+
if [ -z "$LLM_API_KEY" ]; then
|
| 19 |
+
ERRORS="${ERRORS} β LLM_API_KEY is not set\n"
|
| 20 |
+
fi
|
| 21 |
+
if [ -z "$GATEWAY_TOKEN" ]; then
|
| 22 |
+
ERRORS="${ERRORS} β GATEWAY_TOKEN is not set (generate: openssl rand -hex 32)\n"
|
| 23 |
+
fi
|
| 24 |
+
if [ -n "$ERRORS" ]; then
|
| 25 |
+
echo "Missing required secrets:"
|
| 26 |
+
echo -e "$ERRORS"
|
| 27 |
+
echo "Add them in HF Spaces β Settings β Secrets"
|
| 28 |
+
exit 1
|
| 29 |
+
fi
|
| 30 |
+
|
| 31 |
+
# ββ Set LLM env based on provider ββ
|
| 32 |
+
LLM_PROVIDER="${LLM_PROVIDER:-anthropic}"
|
| 33 |
+
LLM_MODEL="${LLM_MODEL:-anthropic/claude-haiku-4-5}"
|
| 34 |
+
|
| 35 |
+
case "$LLM_PROVIDER" in
|
| 36 |
+
anthropic) export ANTHROPIC_API_KEY="$LLM_API_KEY" ;;
|
| 37 |
+
openai) export OPENAI_API_KEY="$LLM_API_KEY" ;;
|
| 38 |
+
google) export GOOGLE_API_KEY="$LLM_API_KEY" ;;
|
| 39 |
+
*) export LLM_API_KEY="$LLM_API_KEY" ;;
|
| 40 |
+
esac
|
| 41 |
+
|
| 42 |
+
# ββ Setup directories ββ
|
| 43 |
+
mkdir -p /home/node/.openclaw/agents/main/sessions
|
| 44 |
+
mkdir -p /home/node/.openclaw/credentials
|
| 45 |
+
mkdir -p /home/node/.openclaw/workspace
|
| 46 |
+
chmod 700 /home/node/.openclaw
|
| 47 |
+
|
| 48 |
+
# ββ Validate HF token (if provided) ββ
|
| 49 |
+
if [ -n "$HF_TOKEN" ]; then
|
| 50 |
+
echo "π Validating HF token..."
|
| 51 |
+
HF_AUTH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $HF_TOKEN" https://huggingface.co/api/repos/create --max-time 10 2>/dev/null || echo "000")
|
| 52 |
+
if [ "$HF_AUTH_STATUS" = "401" ]; then
|
| 53 |
+
echo " β οΈ HF token is invalid or expired! Workspace backup will not work."
|
| 54 |
+
echo " Get a new token: https://huggingface.co/settings/tokens"
|
| 55 |
+
else
|
| 56 |
+
echo " β
HF token is valid"
|
| 57 |
+
fi
|
| 58 |
+
fi
|
| 59 |
+
|
| 60 |
+
# ββ Auto-create + Restore workspace from HF Dataset ββ
|
| 61 |
+
if [ -n "$HF_USERNAME" ] && [ -n "$HF_TOKEN" ]; then
|
| 62 |
+
BACKUP_DATASET="${BACKUP_DATASET_NAME:-huggingclaw-backup}"
|
| 63 |
+
BACKUP_URL="https://${HF_USERNAME}:${HF_TOKEN}@huggingface.co/datasets/${HF_USERNAME}/${BACKUP_DATASET}"
|
| 64 |
+
|
| 65 |
+
# Auto-create the dataset if it doesn't exist
|
| 66 |
+
echo "π¦ Checking HF Dataset: ${HF_USERNAME}/${BACKUP_DATASET}..."
|
| 67 |
+
DATASET_CHECK=$(curl -s -o /dev/null -w "%{http_code}" \
|
| 68 |
+
-H "Authorization: Bearer $HF_TOKEN" \
|
| 69 |
+
"https://huggingface.co/api/datasets/${HF_USERNAME}/${BACKUP_DATASET}" \
|
| 70 |
+
--max-time 10 2>/dev/null || echo "000")
|
| 71 |
+
|
| 72 |
+
if [ "$DATASET_CHECK" = "404" ]; then
|
| 73 |
+
echo " π Dataset not found, creating ${HF_USERNAME}/${BACKUP_DATASET}..."
|
| 74 |
+
CREATE_RESULT=$(curl -s -w "\n%{http_code}" \
|
| 75 |
+
-X POST "https://huggingface.co/api/repos/create" \
|
| 76 |
+
-H "Authorization: Bearer $HF_TOKEN" \
|
| 77 |
+
-H "Content-Type: application/json" \
|
| 78 |
+
-d "{\"type\":\"dataset\",\"name\":\"${BACKUP_DATASET}\",\"private\":true}" \
|
| 79 |
+
--max-time 15 2>/dev/null || echo "error")
|
| 80 |
+
CREATE_STATUS=$(echo "$CREATE_RESULT" | tail -1)
|
| 81 |
+
if [ "$CREATE_STATUS" = "200" ] || [ "$CREATE_STATUS" = "201" ]; then
|
| 82 |
+
echo " β
Dataset created: ${HF_USERNAME}/${BACKUP_DATASET} (private)"
|
| 83 |
+
else
|
| 84 |
+
echo " β οΈ Could not create dataset (HTTP $CREATE_STATUS). Create it manually:"
|
| 85 |
+
echo " https://huggingface.co/datasets/create"
|
| 86 |
+
fi
|
| 87 |
+
elif [ "$DATASET_CHECK" = "200" ]; then
|
| 88 |
+
echo " β
Dataset exists"
|
| 89 |
+
else
|
| 90 |
+
echo " β οΈ Could not check dataset (HTTP $DATASET_CHECK)"
|
| 91 |
+
fi
|
| 92 |
+
|
| 93 |
+
# Restore workspace
|
| 94 |
+
echo "π¦ Restoring workspace..."
|
| 95 |
+
WORKSPACE="/home/node/.openclaw/workspace"
|
| 96 |
+
GIT_USER_EMAIL="${WORKSPACE_GIT_USER:-openclaw@example.com}"
|
| 97 |
+
GIT_USER_NAME="${WORKSPACE_GIT_NAME:-OpenClaw Bot}"
|
| 98 |
+
|
| 99 |
+
cd "$WORKSPACE"
|
| 100 |
+
if [ ! -d ".git" ]; then
|
| 101 |
+
git init -q
|
| 102 |
+
git remote add origin "$BACKUP_URL"
|
| 103 |
+
else
|
| 104 |
+
git remote set-url origin "$BACKUP_URL"
|
| 105 |
+
fi
|
| 106 |
+
|
| 107 |
+
git config user.email "$GIT_USER_EMAIL"
|
| 108 |
+
git config user.name "$GIT_USER_NAME"
|
| 109 |
+
|
| 110 |
+
if git fetch origin main 2>/dev/null; then
|
| 111 |
+
git reset --hard origin/main 2>/dev/null && echo " β
Workspace restored!"
|
| 112 |
+
else
|
| 113 |
+
echo " β οΈ No remote data yet, starting fresh."
|
| 114 |
+
fi
|
| 115 |
+
cd /
|
| 116 |
+
fi
|
| 117 |
+
|
| 118 |
+
# ββ Build config ββ
|
| 119 |
+
CONFIG_JSON=$(cat <<'CONFIGEOF'
|
| 120 |
+
{
|
| 121 |
+
"gateway": {
|
| 122 |
+
"mode": "local",
|
| 123 |
+
"port": 7860,
|
| 124 |
+
"bind": "lan",
|
| 125 |
+
"auth": {
|
| 126 |
+
"token": ""
|
| 127 |
+
},
|
| 128 |
+
"controlUi": {
|
| 129 |
+
"allowInsecureAuth": true
|
| 130 |
+
},
|
| 131 |
+
"trustedProxies": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
|
| 132 |
+
},
|
| 133 |
+
"channels": {},
|
| 134 |
+
"plugins": {
|
| 135 |
+
"entries": {}
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
CONFIGEOF
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
# Gateway token + model
|
| 142 |
+
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".gateway.auth.token = \"$GATEWAY_TOKEN\"")
|
| 143 |
+
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".agents.defaults.model = \"$LLM_MODEL\"")
|
| 144 |
+
|
| 145 |
+
# Control UI origin (allow HF Space URL for web UI access)
|
| 146 |
+
if [ -n "$SPACE_HOST" ]; then
|
| 147 |
+
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".gateway.controlUi.allowedOrigins = [\"https://${SPACE_HOST}\"]")
|
| 148 |
+
fi
|
| 149 |
+
|
| 150 |
+
# Telegram (supports multiple user IDs, comma-separated)
|
| 151 |
+
if [ -n "$TELEGRAM_BOT_TOKEN" ]; then
|
| 152 |
+
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq '.plugins.entries.telegram = {"enabled": true}')
|
| 153 |
+
export TELEGRAM_BOT_TOKEN="$TELEGRAM_BOT_TOKEN"
|
| 154 |
+
|
| 155 |
+
if [ -n "$TELEGRAM_USER_IDS" ]; then
|
| 156 |
+
# Convert comma-separated IDs to JSON array
|
| 157 |
+
IDS_JSON=$(echo "$TELEGRAM_USER_IDS" | tr ',' '\n' | sed 's/^ *//;s/ *$//' | jq -R . | jq -s .)
|
| 158 |
+
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".channels.telegram = {\"dmPolicy\": \"allowlist\", \"allowFrom\": $IDS_JSON}")
|
| 159 |
+
elif [ -n "$TELEGRAM_USER_ID" ]; then
|
| 160 |
+
# Single user (backward compatible)
|
| 161 |
+
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".channels.telegram = {\"dmPolicy\": \"allowlist\", \"allowFrom\": [\"$TELEGRAM_USER_ID\"]}")
|
| 162 |
+
fi
|
| 163 |
+
fi
|
| 164 |
+
|
| 165 |
+
# Write config
|
| 166 |
+
echo "$CONFIG_JSON" > "/home/node/.openclaw/openclaw.json"
|
| 167 |
+
|
| 168 |
+
# ββ Startup Summary ββ
|
| 169 |
+
echo ""
|
| 170 |
+
echo " ββββββββββββββββββββββββββββββββββββββββββββ"
|
| 171 |
+
echo " β π Configuration Summary β"
|
| 172 |
+
echo " ββββββββββββββββββββββββββββββββββββββββββββ€"
|
| 173 |
+
printf " β %-40s β\n" "LLM: $LLM_PROVIDER"
|
| 174 |
+
printf " β %-40s β\n" "Model: $LLM_MODEL"
|
| 175 |
+
if [ -n "$TELEGRAM_BOT_TOKEN" ]; then
|
| 176 |
+
printf " β %-40s β\n" "Telegram: β
enabled"
|
| 177 |
+
else
|
| 178 |
+
printf " β %-40s β\n" "Telegram: β not configured"
|
| 179 |
+
fi
|
| 180 |
+
if [ -n "$HF_USERNAME" ] && [ -n "$HF_TOKEN" ]; then
|
| 181 |
+
printf " β %-40s β\n" "Backup: β
${HF_USERNAME}/${BACKUP_DATASET:-huggingclaw-backup}"
|
| 182 |
+
else
|
| 183 |
+
printf " β %-40s β\n" "Backup: β not configured"
|
| 184 |
+
fi
|
| 185 |
+
if [ -n "$SPACE_HOST" ]; then
|
| 186 |
+
printf " β %-40s β\n" "Keep-alive: β
every ${KEEP_ALIVE_INTERVAL:-300}s"
|
| 187 |
+
printf " β %-40s β\n" "Control UI: https://${SPACE_HOST}"
|
| 188 |
+
else
|
| 189 |
+
printf " β %-40s β\n" "Keep-alive: βΈοΈ local mode"
|
| 190 |
+
fi
|
| 191 |
+
SYNC_STATUS="β disabled"
|
| 192 |
+
if [ -n "$HF_USERNAME" ] && [ -n "$HF_TOKEN" ]; then
|
| 193 |
+
SYNC_STATUS="β
every ${SYNC_INTERVAL:-600}s"
|
| 194 |
+
fi
|
| 195 |
+
printf " β %-40s β\n" "Auto-sync: $SYNC_STATUS"
|
| 196 |
+
echo " ββββββββββββββββββββββββββββββββββββββββββββ"
|
| 197 |
+
echo ""
|
| 198 |
+
|
| 199 |
+
# ββ Trap SIGTERM for graceful shutdown ββ
|
| 200 |
+
graceful_shutdown() {
|
| 201 |
+
echo ""
|
| 202 |
+
echo "π Shutting down gracefully..."
|
| 203 |
+
|
| 204 |
+
# Commit any unsaved workspace changes
|
| 205 |
+
if [ -d "/home/node/.openclaw/workspace/.git" ]; then
|
| 206 |
+
echo "πΎ Saving workspace before exit..."
|
| 207 |
+
cd /home/node/.openclaw/workspace
|
| 208 |
+
git add -A 2>/dev/null
|
| 209 |
+
if ! git diff --cached --quiet 2>/dev/null; then
|
| 210 |
+
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
| 211 |
+
git commit -m "Shutdown sync ${TIMESTAMP}" 2>/dev/null
|
| 212 |
+
git push origin main 2>/dev/null && echo " β
Workspace saved!" || echo " β οΈ Push failed"
|
| 213 |
+
else
|
| 214 |
+
echo " β
No unsaved changes"
|
| 215 |
+
fi
|
| 216 |
+
fi
|
| 217 |
+
|
| 218 |
+
# Kill background processes
|
| 219 |
+
kill $(jobs -p) 2>/dev/null
|
| 220 |
+
echo "π Goodbye!"
|
| 221 |
+
exit 0
|
| 222 |
+
}
|
| 223 |
+
trap graceful_shutdown SIGTERM SIGINT
|
| 224 |
+
|
| 225 |
+
# ββ Start background services ββ
|
| 226 |
+
node /home/node/app/health-server.js &
|
| 227 |
+
/home/node/app/keep-alive.sh &
|
| 228 |
+
/home/node/app/workspace-sync.sh &
|
| 229 |
+
|
| 230 |
+
# ββ Launch gateway ββ
|
| 231 |
+
echo "π Launching OpenClaw gateway on port 7860..."
|
| 232 |
+
echo ""
|
| 233 |
+
openclaw gateway run --port 7860 --bind lan --verbose &
|
| 234 |
+
GATEWAY_PID=$!
|
| 235 |
+
|
| 236 |
+
# Wait for gateway (allows trap to fire)
|
| 237 |
+
wait $GATEWAY_PID
|
workspace-sync.sh
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Periodic workspace sync to HF Dataset
|
| 3 |
+
# Commits and pushes workspace changes every SYNC_INTERVAL seconds
|
| 4 |
+
# Runs as a background process alongside the gateway
|
| 5 |
+
|
| 6 |
+
INTERVAL="${SYNC_INTERVAL:-600}" # Default: every 10 minutes
|
| 7 |
+
WORKSPACE="/home/node/.openclaw/workspace"
|
| 8 |
+
|
| 9 |
+
# Wait for workspace to be initialized
|
| 10 |
+
sleep 30
|
| 11 |
+
|
| 12 |
+
if [ ! -d "$WORKSPACE/.git" ]; then
|
| 13 |
+
echo "π Workspace sync: no git repo found, skipping."
|
| 14 |
+
exit 0
|
| 15 |
+
fi
|
| 16 |
+
|
| 17 |
+
echo "π Workspace sync started: syncing every ${INTERVAL}s"
|
| 18 |
+
|
| 19 |
+
while true; do
|
| 20 |
+
sleep "$INTERVAL"
|
| 21 |
+
|
| 22 |
+
cd "$WORKSPACE" || continue
|
| 23 |
+
|
| 24 |
+
# Check if there are any changes
|
| 25 |
+
git add -A 2>/dev/null
|
| 26 |
+
if git diff --cached --quiet 2>/dev/null; then
|
| 27 |
+
# No changes
|
| 28 |
+
continue
|
| 29 |
+
fi
|
| 30 |
+
|
| 31 |
+
# Commit and push
|
| 32 |
+
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
| 33 |
+
if git commit -m "Auto-sync ${TIMESTAMP}" 2>/dev/null; then
|
| 34 |
+
if git push origin main 2>/dev/null; then
|
| 35 |
+
echo "π Workspace sync: pushed changes (${TIMESTAMP})"
|
| 36 |
+
else
|
| 37 |
+
echo "π Workspace sync: commit ok, push failed (will retry)"
|
| 38 |
+
fi
|
| 39 |
+
fi
|
| 40 |
+
done
|