auto: 일일 백업 2026-10-02 02:00
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+6
-2
@@ -89,9 +89,13 @@ LLM-LLM 자연어 통신은 프롬프트 인젝션·할루시네이션 증폭
|
||||
|
||||
## 운영 자산 (자주 쓰는 것들)
|
||||
|
||||
- **브리핑:** `scripts/briefing_mail.py {morning|evening}` (cron 07:00 / 19:00)
|
||||
- **브리핑:** `scripts/briefing_mail.py {morning|evening}` (cron 오전 **월~토 07:30** + 폴백 07:55·08:30 / 오후 **평일 19:00**)
|
||||
- **클로드코드 세션:** `skills/claude-code-session/scripts/ensure_session.sh {open|close|status}` ("클로드코드 열어줘"/"닫아줘"로 코디 호출)
|
||||
- **cron 정의:** `~/.openclaw/cron/jobs.json`
|
||||
- **테일스케일 켜기·끄기:** `python3 scripts/tailscale_ctl.py {on|off|status}` (관리자님이 "테일스케일 꺼줘/켜줘/상태" 라고 할 때. sudo 불필요, 출력은 그대로 전달해도 된다)
|
||||
- ⚠️ `off` 하면 밖에서 맥으로 들어오는 길이 전부 막힌다 (`stock.hyowons.net`·`sim.hyowons.net`·폰에서 맥 직접 접근·오픈클로 웹 화면). **텔레그램은 영향 없으니** 끈 뒤에도 "켜줘"는 받을 수 있다 — 끄기 전 이 사실을 한 줄로 알려드려라.
|
||||
- 되돌리기는 `on` 하나면 끝. 재로그인·설정 복구 불필요(실측 왕복 1초).
|
||||
- 자동 트리거(cron/launchd)에 걸지 마라. 관리자님이 말할 때만 부른다.
|
||||
- **cron 정의:** `state/openclaw.sqlite` 의 `cron_jobs` 테이블. 조회·수정은 `openclaw cron {list|show|edit}` (⚠️ `cron/jobs.json` 은 2026-07-20 sqlite 이관으로 **없어졌다** — `.migrated` 잔재만 남아 있다)
|
||||
- **메인 설정:** `~/.openclaw/openclaw.json`
|
||||
- **스킬 메타:** 각 스킬의 `SKILL.md`, 로컬 메모는 `TOOLS.md`
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
|
||||
클로가 책임지는 자동 실행 목록. 새로 추가·제거·이관 시 이 섹션 즉시 갱신.
|
||||
|
||||
### OpenClaw cron (LLM 세션 기반, `cron/jobs.json`)
|
||||
### OpenClaw cron (LLM 세션 기반 — `state/openclaw.sqlite` 의 `cron_jobs` 테이블)
|
||||
|
||||
| 이름 | 일정 (Asia/Seoul) | 스크립트 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Session: 2026-10-01 23:42:44 Asia/Seoul
|
||||
|
||||
- **Session Key**: agent:main:telegram:direct:8443122995
|
||||
- **Session ID**: ce15e0ea-32d4-4945-bd02-78cc13d4675e
|
||||
- **Source**: telegram
|
||||
|
||||
## Conversation Summary
|
||||
|
||||
assistant: "관리자님, 캘린더에 등록했습니다.\n\n- 2026-09-12 토요일 09:30-10:30\n- 제목: 산부인과\n- 캘린더: mini.snowoyh@gmail.com\n\n[답변완료]"
|
||||
user: "9/9 플투회식(호박꽃) 도 추가해줘"
|
||||
assistant: "관리자님, 캘린더에 추가했습니다.\n\n- 2026-09-09 수요일 19:00-21:00\n- 제목: 플투회식(호박꽃)\n- 캘린더: mini.snowoyh@gmail.com\n\n시간이 따로 정해져 있으면 알려주세요. 바로 수정하겠습니다.\n\n[답변완료]"
|
||||
user: "클로 안녕?"
|
||||
assistant: "관리자님, 안녕하세요. 필요하신 일 말씀해주시면 바로 도와드리겠습니다.\n\n[답변완료]"
|
||||
user: "10/2일 오후 4시20분 산부인과 예약 등록해줘"
|
||||
assistant: "관리자님, Google 캘린더 등록을 위해 `gog` 스킬로 일정을 추가하고 결과까지 확인하겠습니다."
|
||||
assistant: "관리자님, 캘린더에 등록했습니다.\n\n- 2026-10-02 금요일 16:20-17:20\n- 제목: 산부인과\n- 캘린더: mini.snowoyh@gmail.com\n\n[답변완료]"
|
||||
user: "다음주 수요일 오후7시 그 다음주 월요일 오후 9시 수영 등록해줘"
|
||||
assistant: "관리자님, `gog` 스킬로 날짜를 계산해 두 건 모두 Google 캘린더에 등록하고 확인하겠습니다. “그 다음주 월요일”은 2026년 9월 28일로 등록하겠습니다."
|
||||
assistant: "관리자님, 수영 일정 2건을 등록했습니다.\n\n- 2026-09-23 수요일 19:00-20:00\n- 2026-09-28 월요일 21:00-22:00\n- 캘린더: mini.snowoyh@gmail.com\n\n[답변완료]"
|
||||
user: "연휴였어 다시 점검해봐"
|
||||
assistant: "관리자님, 지난 등록 기준일과 2026년 추석 연휴 날짜를 다시 대조하겠습니다. 캘린더의 실제 등록 내역도 함께 확인하겠습니다."
|
||||
assistant: "대화 기록 검색 기능은 현재 비활성화되어 있어, 이 대화에 남아 있는 등록 기록과 실제 캘린더를 기준으로 점검하겠습니다. 당시 기준일은 9월 18일이었습니다."
|
||||
assistant: "관리자님, 다시 확인했습니다.\n\n- 요청일: 2026-09-18 금요일\n- “다음주 수요일”: 9월 23일 19:00\n- “그 다음주 월요일”: 9월 28일 21:00\n- 추석 연휴: 9월 24일~26일\n\n따라서 등록한 9월 23일과 9월 28일은 공식 공휴일 자체는 아니며, 날짜 계산은 맞습니다. 다만 수영장이 연휴 일정으로 휴관했다면 9월 23일 일정은 별도 조정이 필요합니다. 현재 캘린더에는 두 일정이 그대로 등록되어 있습니다.\n\n참고로 대화 기록 검색 기능은 비활성화되어 있어, 현재 대화 기록과 실제 캘린더를 직접 대조했습니다.\n\n[답변완료]"
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""테일스케일 켜기·끄기·상태 조회.
|
||||
|
||||
관리자님이 "테일스케일 꺼줘 / 켜줘 / 상태" 라고 하면 호출한다.
|
||||
sudo 불필요 — CLI가 localhost localapi + sameuserproof 토큰으로 인증한다.
|
||||
|
||||
사용법:
|
||||
python3 scripts/tailscale_ctl.py {on|off|status}
|
||||
|
||||
출력은 그대로 관리자님께 전달해도 되는 한국어다.
|
||||
끄면 밖에서 맥으로 들어오는 길(stock.hyowons.net 등)이 전부 막히지만,
|
||||
텔레그램은 아웃바운드라 영향 없다 — 다시 켜라고 시킬 길은 항상 남는다.
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
TS = "/usr/local/bin/tailscale"
|
||||
WAIT_SEC = 20 # up 이후 연결 복구를 기다리는 상한
|
||||
POLL_SEC = 1
|
||||
|
||||
|
||||
def _run(args, timeout=30):
|
||||
return subprocess.run([TS, *args], capture_output=True, text=True, timeout=timeout)
|
||||
|
||||
|
||||
def _status():
|
||||
"""(켜짐여부, 내IPv4, [(기기이름, 온라인)]) 반환."""
|
||||
r = _run(["status", "--json"], timeout=15)
|
||||
if r.returncode != 0:
|
||||
raise RuntimeError(f"상태 조회 실패: {r.stderr.strip() or f'rc={r.returncode}'}")
|
||||
d = json.loads(r.stdout)
|
||||
running = d.get("BackendState") == "Running"
|
||||
ip = next((x for x in (d.get("TailscaleIPs") or []) if ":" not in x), None)
|
||||
peers = [((p.get("DNSName") or "?").split(".")[0], bool(p.get("Online")))
|
||||
for p in (d.get("Peer") or {}).values()]
|
||||
peers.sort(key=lambda x: (not x[1], x[0]))
|
||||
return running, ip, peers
|
||||
|
||||
|
||||
def _status_lines(running, ip, peers):
|
||||
lines = [f"테일스케일: {'켜짐' if running else '꺼짐'}"]
|
||||
if not running:
|
||||
# 꺼진 상태에선 다른 기기 상태를 알 수 없다 (캐시된 옛 값이라 보여주면 거짓말).
|
||||
return lines
|
||||
if ip:
|
||||
lines.append(f"내 주소: {ip}")
|
||||
if peers:
|
||||
lines.append("기기: " + " / ".join(
|
||||
f"{n}({'온라인' if on else '오프라인'})" for n, on in peers))
|
||||
return lines
|
||||
|
||||
|
||||
def cmd_status():
|
||||
print("\n".join(_status_lines(*_status())))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_on():
|
||||
running, ip, peers = _status()
|
||||
if running:
|
||||
print("테일스케일은 이미 켜져 있습니다.")
|
||||
print("\n".join(_status_lines(running, ip, peers)[1:]))
|
||||
return 0
|
||||
|
||||
r = _run(["up"])
|
||||
if r.returncode != 0:
|
||||
print(f"켜기 실패: {r.stderr.strip() or f'rc={r.returncode}'}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
for waited in range(WAIT_SEC):
|
||||
time.sleep(POLL_SEC)
|
||||
running, ip, peers = _status()
|
||||
if running and ip:
|
||||
print(f"테일스케일을 켰습니다. ({waited + 1}초 만에 연결)")
|
||||
print("\n".join(_status_lines(running, ip, peers)[1:]))
|
||||
return 0
|
||||
|
||||
print(f"up 명령은 받았지만 {WAIT_SEC}초 안에 연결되지 않았습니다. "
|
||||
"테일스케일 앱을 직접 확인해 주세요.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
def cmd_off():
|
||||
running, _, _ = _status()
|
||||
if not running:
|
||||
print("테일스케일은 이미 꺼져 있습니다.")
|
||||
return 0
|
||||
|
||||
r = _run(["down"])
|
||||
if r.returncode != 0:
|
||||
print(f"끄기 실패: {r.stderr.strip() or f'rc={r.returncode}'}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
time.sleep(2)
|
||||
running, _, _ = _status()
|
||||
if running:
|
||||
print("down 명령은 받았는데 아직 켜져 있습니다. 다시 시도해 주세요.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print("테일스케일을 껐습니다.")
|
||||
print("이제 밖에서 맥으로 들어오는 길이 막힙니다 (stock.hyowons.net 등).")
|
||||
print('다시 켜려면 텔레그램으로 "테일스케일 켜줘" 하시면 됩니다.')
|
||||
return 0
|
||||
|
||||
|
||||
CMDS = {"on": cmd_on, "off": cmd_off, "status": cmd_status}
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2 or sys.argv[1] not in CMDS:
|
||||
print(f"사용법: {sys.argv[0]} {{on|off|status}}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
try:
|
||||
sys.exit(CMDS[sys.argv[1]]())
|
||||
except Exception as e:
|
||||
print(f"오류: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user