feat: auth allowlist, memoraiz project, git push-to-deploy, group chat support, bot menu commands
This commit is contained in:
parent
92ae862c2d
commit
bd5b041149
5 changed files with 213 additions and 47 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
FROM python:3.12-slim
|
FROM python:3.12-slim
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
COPY main.py .
|
COPY main.py .
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@ TG_BOT_TOKEN=CHANGE_ME
|
||||||
VERCEL_AI_GATEWAY_KEY=CHANGE_ME
|
VERCEL_AI_GATEWAY_KEY=CHANGE_ME
|
||||||
OPENAI_BASE_URL=https://ai-gateway.vercel.sh/v1
|
OPENAI_BASE_URL=https://ai-gateway.vercel.sh/v1
|
||||||
MODEL=anthropic/claude-sonnet-4
|
MODEL=anthropic/claude-sonnet-4
|
||||||
|
ALLOWED_USERS=876499264,417471802
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ services:
|
||||||
container_name: betterbot
|
container_name: betterbot
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- /opt/betterlifesg/site:/site:rw
|
- /opt/src/betterlifesg:/repo/betterlifesg:rw
|
||||||
|
- /opt/src/hk_memoraiz:/repo/memoraiz:rw
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- TZ=${TZ:-Asia/Singapore}
|
- TZ=${TZ:-Asia/Singapore}
|
||||||
- SITE_DIR=/site
|
- SITE_DIR=/repo/betterlifesg/site
|
||||||
|
- MEMORAIZ_DIR=/repo/memoraiz/frontend
|
||||||
|
|
|
||||||
241
main.py
241
main.py
|
|
@ -3,12 +3,14 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
from telegram import Update
|
from telegram import BotCommand, Update
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
ApplicationBuilder,
|
ApplicationBuilder,
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
|
|
@ -29,36 +31,86 @@ TG_BOT_TOKEN = os.environ["TG_BOT_TOKEN"]
|
||||||
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") or os.environ["VERCEL_AI_GATEWAY_KEY"]
|
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") or os.environ["VERCEL_AI_GATEWAY_KEY"]
|
||||||
OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1")
|
OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1")
|
||||||
MODEL = os.environ.get("MODEL", "gpt-4.1")
|
MODEL = os.environ.get("MODEL", "gpt-4.1")
|
||||||
|
|
||||||
|
# Site directories
|
||||||
SITE_DIR = pathlib.Path(os.environ.get("SITE_DIR", "/site"))
|
SITE_DIR = pathlib.Path(os.environ.get("SITE_DIR", "/site"))
|
||||||
|
MEMORAIZ_DIR = pathlib.Path(os.environ.get("MEMORAIZ_DIR", "/memoraiz"))
|
||||||
|
|
||||||
|
# Authorized users (Telegram user IDs)
|
||||||
|
ALLOWED_USERS: set[int] = set()
|
||||||
|
_raw = os.environ.get("ALLOWED_USERS", "876499264,417471802")
|
||||||
|
for _id in _raw.split(","):
|
||||||
|
_id = _id.strip()
|
||||||
|
if _id:
|
||||||
|
ALLOWED_USERS.add(int(_id))
|
||||||
|
log.info("Authorized users: %s", ALLOWED_USERS)
|
||||||
|
|
||||||
client = OpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_BASE_URL)
|
client = OpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_BASE_URL)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Tools — read / write / list site files
|
# Project definitions
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
PROJECTS = {
|
||||||
|
"betterlifesg": {
|
||||||
|
"dir": SITE_DIR,
|
||||||
|
"label": "Better Life SG website",
|
||||||
|
"git_repo": SITE_DIR.parent, # /repo/betterlifesg/site -> /repo/betterlifesg
|
||||||
|
"deploy_cmd": None, # static files served directly by Caddy
|
||||||
|
},
|
||||||
|
"memoraiz": {
|
||||||
|
"dir": MEMORAIZ_DIR,
|
||||||
|
"label": "Memoraiz app (React frontend)",
|
||||||
|
"git_repo": MEMORAIZ_DIR.parent, # /repo/memoraiz/frontend -> /repo/memoraiz
|
||||||
|
"deploy_cmd": None, # deploy triggered externally after push
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Tools — read / write / list site files + deploy
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
TOOLS = [
|
TOOLS = [
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "list_files",
|
"name": "list_files",
|
||||||
"description": "List all files in the website directory.",
|
"description": "List all files in a project directory.",
|
||||||
"parameters": {"type": "object", "properties": {}, "required": []},
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": list(PROJECTS.keys()),
|
||||||
|
"description": "Which project to list files for.",
|
||||||
|
},
|
||||||
|
"subdirectory": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional subdirectory to list, e.g. 'src/pages'. Defaults to root.",
|
||||||
|
"default": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["project"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "read_file",
|
"name": "read_file",
|
||||||
"description": "Read the full contents of a website file.",
|
"description": "Read the full contents of a project file.",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": list(PROJECTS.keys()),
|
||||||
|
"description": "Which project the file belongs to.",
|
||||||
|
},
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Relative path inside the site directory, e.g. 'index.html' or 'images/logo.png'.",
|
"description": "Relative path inside the project directory.",
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"required": ["path"],
|
},
|
||||||
|
"required": ["project", "path"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -66,67 +118,122 @@ TOOLS = [
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "write_file",
|
"name": "write_file",
|
||||||
"description": "Write (create or overwrite) a text file in the website directory. Use for HTML, CSS, JS files.",
|
"description": "Write (create or overwrite) a text file in a project directory.",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": list(PROJECTS.keys()),
|
||||||
|
"description": "Which project the file belongs to.",
|
||||||
|
},
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Relative path inside the site directory.",
|
"description": "Relative path inside the project directory.",
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The full file content to write.",
|
"description": "The full file content to write.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": ["path", "content"],
|
"required": ["project", "path", "content"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
SYSTEM_PROMPT = """\
|
SYSTEM_PROMPT = """\
|
||||||
You are BetterBot, a helpful assistant that manages the Better Life SG website.
|
You are BetterBot, a helpful assistant that manages two projects:
|
||||||
The website is a static HTML site using Tailwind CSS (loaded via CDN).
|
|
||||||
|
|
||||||
The site files are in the /site directory. Key files:
|
1. **Better Life SG website** (project: "betterlifesg")
|
||||||
- index.html (home page)
|
- Static HTML site using Tailwind CSS (loaded via CDN)
|
||||||
- fresh-grads.html, prenatal.html, retirement.html, legacy.html (service pages)
|
- Key files: index.html, fresh-grads.html, prenatal.html, retirement.html, \
|
||||||
- team.html, contact.html
|
legacy.html, team.html, contact.html, images/ folder
|
||||||
- images/ folder with photos and backgrounds
|
- Brand color: teal (#00b49a)
|
||||||
|
- Changes go live immediately after writing
|
||||||
|
|
||||||
When the user asks you to change something on the website:
|
2. **Memoraiz app** (project: "memoraiz")
|
||||||
1. First read the relevant file(s) to understand the current state
|
- React 19 + Vite 6 + Tailwind CSS 4 frontend
|
||||||
2. Make the requested changes
|
- Source code is under frontend/src/ (pages in frontend/src/pages/, \
|
||||||
3. Write the updated file back
|
components in frontend/src/components/)
|
||||||
4. Confirm what you changed
|
- Changes require a rebuild to go live (handled automatically after you write)
|
||||||
|
|
||||||
The brand color is teal/green (#00b49a). The site uses Tailwind CSS classes.
|
When the user asks you to change something:
|
||||||
|
1. Ask which project if unclear (default to betterlifesg for website questions)
|
||||||
|
2. First read the relevant file(s) to understand the current state
|
||||||
|
3. Make the requested changes
|
||||||
|
4. Write the updated file back
|
||||||
|
5. Confirm what you changed
|
||||||
|
|
||||||
|
When writing a file, always write the COMPLETE file content — never partial.
|
||||||
Keep your responses concise and friendly. Always confirm changes after making them.
|
Keep your responses concise and friendly. Always confirm changes after making them.
|
||||||
Do NOT change the overall page structure unless explicitly asked.
|
Do NOT change the overall page structure unless explicitly asked.
|
||||||
When writing a file, always write the COMPLETE file content — never partial.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _resolve(path: str) -> pathlib.Path:
|
def _resolve(base: pathlib.Path, path: str) -> pathlib.Path:
|
||||||
"""Resolve a relative path inside SITE_DIR, preventing path traversal."""
|
"""Resolve a relative path inside a base dir, preventing path traversal."""
|
||||||
resolved = (SITE_DIR / path).resolve()
|
resolved = (base / path).resolve()
|
||||||
if not str(resolved).startswith(str(SITE_DIR.resolve())):
|
if not str(resolved).startswith(str(base.resolve())):
|
||||||
raise ValueError(f"Path traversal blocked: {path}")
|
raise ValueError(f"Path traversal blocked: {path}")
|
||||||
return resolved
|
return resolved
|
||||||
|
|
||||||
|
|
||||||
|
def _git_push(project_key: str, changed_file: str) -> str:
|
||||||
|
"""Commit and push changes in the project's git repo."""
|
||||||
|
proj = PROJECTS[project_key]
|
||||||
|
repo = proj["git_repo"]
|
||||||
|
if not (repo / ".git").exists():
|
||||||
|
return "(no git repo — skipped push)"
|
||||||
|
try:
|
||||||
|
subprocess.run(["git", "add", "-A"], cwd=repo, check=True, capture_output=True)
|
||||||
|
subprocess.run(
|
||||||
|
["git", "commit", "-m", f"betterbot: update {changed_file}"],
|
||||||
|
cwd=repo,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "push", "origin", "HEAD"],
|
||||||
|
cwd=repo,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
log.info("Git push for %s: %s", project_key, result.stderr.strip())
|
||||||
|
|
||||||
|
# Trigger deploy if needed
|
||||||
|
deploy_cmd = proj.get("deploy_cmd")
|
||||||
|
if deploy_cmd:
|
||||||
|
log.info("Running deploy: %s", deploy_cmd)
|
||||||
|
subprocess.run(deploy_cmd, shell=True, check=True, capture_output=True)
|
||||||
|
return f"Pushed and deployed {project_key}"
|
||||||
|
return f"Pushed {project_key} to git"
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
log.error("Git/deploy error: %s\nstdout: %s\nstderr: %s", e, e.stdout, e.stderr)
|
||||||
|
return f"Push failed: {e.stderr or e.stdout or str(e)}"
|
||||||
|
|
||||||
|
|
||||||
def handle_tool_call(name: str, args: dict) -> str:
|
def handle_tool_call(name: str, args: dict) -> str:
|
||||||
"""Execute a tool call and return the result as a string."""
|
"""Execute a tool call and return the result as a string."""
|
||||||
|
project_key = args.get("project", "betterlifesg")
|
||||||
|
if project_key not in PROJECTS:
|
||||||
|
return f"Unknown project: {project_key}"
|
||||||
|
base = PROJECTS[project_key]["dir"]
|
||||||
|
|
||||||
if name == "list_files":
|
if name == "list_files":
|
||||||
|
subdir = args.get("subdirectory", "")
|
||||||
|
target = _resolve(base, subdir) if subdir else base
|
||||||
files = []
|
files = []
|
||||||
for p in sorted(SITE_DIR.rglob("*")):
|
for p in sorted(target.rglob("*")):
|
||||||
if p.is_file():
|
if p.is_file() and not any(
|
||||||
files.append(str(p.relative_to(SITE_DIR)))
|
part in (".git", "node_modules", "__pycache__") for part in p.parts
|
||||||
return "\n".join(files) if files else "(no files found)"
|
):
|
||||||
|
files.append(str(p.relative_to(base)))
|
||||||
|
return "\n".join(files[:200]) if files else "(no files found)"
|
||||||
|
|
||||||
if name == "read_file":
|
if name == "read_file":
|
||||||
path = _resolve(args["path"])
|
path = _resolve(base, args["path"])
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return f"Error: {args['path']} does not exist."
|
return f"Error: {args['path']} does not exist."
|
||||||
if path.suffix in (".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico"):
|
if path.suffix in (".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico"):
|
||||||
|
|
@ -134,10 +241,13 @@ def handle_tool_call(name: str, args: dict) -> str:
|
||||||
return path.read_text(encoding="utf-8")
|
return path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
if name == "write_file":
|
if name == "write_file":
|
||||||
path = _resolve(args["path"])
|
path = _resolve(base, args["path"])
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
path.write_text(args["content"], encoding="utf-8")
|
path.write_text(args["content"], encoding="utf-8")
|
||||||
return f"OK — wrote {len(args['content'])} chars to {args['path']}"
|
push_result = _git_push(project_key, args["path"])
|
||||||
|
return (
|
||||||
|
f"OK — wrote {len(args['content'])} chars to {args['path']}. {push_result}"
|
||||||
|
)
|
||||||
|
|
||||||
return f"Unknown tool: {name}"
|
return f"Unknown tool: {name}"
|
||||||
|
|
||||||
|
|
@ -154,32 +264,69 @@ def get_messages(chat_id: int) -> list[dict]:
|
||||||
return conversations[chat_id]
|
return conversations[chat_id]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Auth check
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def _is_authorized(user_id: int | None) -> bool:
|
||||||
|
if not ALLOWED_USERS:
|
||||||
|
return True
|
||||||
|
return user_id in ALLOWED_USERS
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Telegram handlers
|
# Telegram handlers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
|
async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
if not _is_authorized(update.effective_user.id):
|
||||||
|
await update.message.reply_text("Sorry, you're not authorized to use this bot.")
|
||||||
|
return
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
"Hi! I'm BetterBot 🤖\n\n"
|
"Hi! I'm BetterBot 🤖\n\n"
|
||||||
"I manage the Better Life SG website. Just tell me what you'd like to change!\n\n"
|
"I manage two projects:\n"
|
||||||
|
"• **Better Life SG** website\n"
|
||||||
|
"• **Memoraiz** app\n\n"
|
||||||
|
"Just tell me what you'd like to change!\n\n"
|
||||||
"Examples:\n"
|
"Examples:\n"
|
||||||
'• "Change the WhatsApp number to 91234567"\n'
|
'• "Change the WhatsApp number to 91234567"\n'
|
||||||
'• "Update Hendri\'s title to Senior Consultant"\n'
|
'• "Update Hendri\'s title to Senior Consultant"\n'
|
||||||
'• "Add a new section about critical illness"\n\n'
|
'• "Update the login page text in Memoraiz"\n\n'
|
||||||
"Type /reset to start a fresh conversation."
|
"Type /reset to start a fresh conversation.",
|
||||||
|
parse_mode="Markdown",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def cmd_reset(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
|
async def cmd_reset(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
if not _is_authorized(update.effective_user.id):
|
||||||
|
return
|
||||||
conversations.pop(update.effective_chat.id, None)
|
conversations.pop(update.effective_chat.id, None)
|
||||||
await update.message.reply_text("Conversation reset! Send me a new request.")
|
await update.message.reply_text("Conversation reset! Send me a new request.")
|
||||||
|
|
||||||
|
|
||||||
async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
|
async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
if not _is_authorized(update.effective_user.id):
|
||||||
|
return
|
||||||
chat_id = update.effective_chat.id
|
chat_id = update.effective_chat.id
|
||||||
user_text = update.message.text
|
user_text = update.message.text
|
||||||
if not user_text:
|
if not user_text:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# In group chats, only respond if bot is mentioned or replied to
|
||||||
|
if update.effective_chat.type in ("group", "supergroup"):
|
||||||
|
bot_username = ctx.bot.username
|
||||||
|
is_mentioned = bot_username and f"@{bot_username}" in user_text
|
||||||
|
is_reply = (
|
||||||
|
update.message.reply_to_message
|
||||||
|
and update.message.reply_to_message.from_user
|
||||||
|
and update.message.reply_to_message.from_user.id == ctx.bot.id
|
||||||
|
)
|
||||||
|
if not is_mentioned and not is_reply:
|
||||||
|
return
|
||||||
|
# Strip the @mention from the text
|
||||||
|
if bot_username:
|
||||||
|
user_text = user_text.replace(f"@{bot_username}", "").strip()
|
||||||
|
if not user_text:
|
||||||
|
return
|
||||||
|
|
||||||
messages = get_messages(chat_id)
|
messages = get_messages(chat_id)
|
||||||
messages.append({"role": "user", "content": user_text})
|
messages.append({"role": "user", "content": user_text})
|
||||||
|
|
||||||
|
|
@ -209,8 +356,6 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None
|
||||||
|
|
||||||
if msg.tool_calls:
|
if msg.tool_calls:
|
||||||
for tc in msg.tool_calls:
|
for tc in msg.tool_calls:
|
||||||
import json
|
|
||||||
|
|
||||||
args = (
|
args = (
|
||||||
json.loads(tc.function.arguments) if tc.function.arguments else {}
|
json.loads(tc.function.arguments) if tc.function.arguments else {}
|
||||||
)
|
)
|
||||||
|
|
@ -252,8 +397,18 @@ def main() -> None:
|
||||||
app.add_handler(CommandHandler("start", cmd_start))
|
app.add_handler(CommandHandler("start", cmd_start))
|
||||||
app.add_handler(CommandHandler("reset", cmd_reset))
|
app.add_handler(CommandHandler("reset", cmd_reset))
|
||||||
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
|
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
|
||||||
log.info("BetterBot starting (model=%s, site_dir=%s)", MODEL, SITE_DIR)
|
|
||||||
app.run_polling(drop_pending_updates=True)
|
# Register bot commands for the / menu
|
||||||
|
async def post_init(application) -> None:
|
||||||
|
await application.bot.set_my_commands([
|
||||||
|
BotCommand("start", "Show welcome message"),
|
||||||
|
BotCommand("reset", "Reset conversation"),
|
||||||
|
])
|
||||||
|
log.info("Bot commands registered")
|
||||||
|
|
||||||
|
app.post_init = post_init
|
||||||
|
log.info("BetterBot starting (model=%s, sites=%s)", MODEL, list(PROJECTS.keys()))
|
||||||
|
app.run_polling(drop_pending_updates=True, allowed_updates=Update.ALL_TYPES)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,11 @@ set -euo pipefail
|
||||||
|
|
||||||
REPO_DIR="${REPO_DIR:-/opt/src/betterbot}"
|
REPO_DIR="${REPO_DIR:-/opt/src/betterbot}"
|
||||||
STACK_DIR="${STACK_DIR:-/opt/betterbot}"
|
STACK_DIR="${STACK_DIR:-/opt/betterbot}"
|
||||||
SITE_DIR="${SITE_DIR:-/opt/betterlifesg/site}"
|
|
||||||
|
|
||||||
cd "$REPO_DIR"
|
cd "$REPO_DIR"
|
||||||
git pull --ff-only origin master
|
git pull --ff-only origin master
|
||||||
|
|
||||||
mkdir -p "$STACK_DIR" "$SITE_DIR"
|
mkdir -p "$STACK_DIR"
|
||||||
|
|
||||||
cp compose/docker-compose.yml "$STACK_DIR/docker-compose.yml"
|
cp compose/docker-compose.yml "$STACK_DIR/docker-compose.yml"
|
||||||
|
|
||||||
|
|
@ -26,6 +25,14 @@ if [ -f /opt/config/infisical-agent.env ] && [ -f /opt/src/self_hosting/infra/sc
|
||||||
infisical_fetch racknerd-betterbot "$STACK_DIR/.env"
|
infisical_fetch racknerd-betterbot "$STACK_DIR/.env"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Configure git identity for the bot's commits in mounted repos
|
||||||
|
for repo_dir in /opt/src/betterlifesg /opt/src/hk_memoraiz; do
|
||||||
|
if [ -d "$repo_dir/.git" ]; then
|
||||||
|
git -C "$repo_dir" config user.email "betterbot@bytesizeprotip.com"
|
||||||
|
git -C "$repo_dir" config user.name "BetterBot"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
cd "$STACK_DIR"
|
cd "$STACK_DIR"
|
||||||
docker compose build --pull
|
docker compose build --pull
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue