Operations¶
This page covers day-to-day operations for a running ObsidianPalace instance. For initial setup, see the Setup Guide.
CI/CD Pipeline¶
ObsidianPalace uses two GitHub Actions workflows that run in sequence:
CI (ci.yml)¶
Triggers on every push to main and on pull requests.
| Job | What it does |
|---|---|
| Lint | Runs ruff check and ruff format --check |
| Test | Runs pytest tests/ -v |
| Build & Push | Builds the Docker image and pushes to Artifact Registry (main only) |
The image is tagged with both the short SHA (e.g., a1b2c3d) and latest.
Deploy (deploy.yml)¶
Triggers after CI completes on main, or on direct pushes to terraform/.
- Updates the
container_imagevariable in Terraform Cloud to the new SHA tag - Uploads the Terraform configuration
- Creates and applies a Terraform run
- Resets the GCE instance to pick up the new image
Required GitHub secrets¶
| Secret | Description |
|---|---|
GCP_WORKLOAD_IDENTITY_PROVIDER |
Workload Identity Federation provider resource name |
GCP_SERVICE_ACCOUNT |
GCP service account email for GitHub Actions |
TFC_API_TOKEN |
Terraform Cloud API token |
TFC_WORKSPACE_ID |
Terraform Cloud workspace ID |
Setting up Workload Identity Federation¶
Follow the GCP guide for GitHub Actions to create a Workload Identity Pool and Provider. The service account needs:
roles/artifactregistry.writer(push images)roles/compute.instanceAdmin.v1(reset instance)
Manual Deployment¶
If you prefer not to use CI/CD, or need to deploy a hotfix:
# Build and push
docker build -t us-central1-docker.pkg.dev/YOUR_PROJECT_ID/obsidian-palace/obsidian-palace:latest .
gcloud auth configure-docker us-central1-docker.pkg.dev
docker push us-central1-docker.pkg.dev/YOUR_PROJECT_ID/obsidian-palace/obsidian-palace:latest
# Reset the instance to pull the new image
gcloud compute instances reset obsidian-palace \
--zone=us-central1-a \
--project=YOUR_PROJECT_ID
The startup script on the VM automatically pulls the configured image and restarts the container.
Monitoring¶
Health check¶
Container logs¶
# SSH into the instance
gcloud compute ssh obsidian-palace --zone=us-central1-a --project=YOUR_PROJECT_ID
# View all container logs
docker logs obsidian-palace --tail 100 -f
# View per-process logs via supervisord
docker exec obsidian-palace supervisorctl tail -f mcp-server
docker exec obsidian-palace supervisorctl tail -f obsidian-sync
docker exec obsidian-palace supervisorctl tail -f nginx
Process status¶
docker exec obsidian-palace supervisorctl status
# Expected output:
# mcp-server RUNNING pid 42, uptime 1:23:45
# nginx RUNNING pid 12, uptime 1:23:45
# obsidian-sync RUNNING pid 37, uptime 1:23:45
SSL Certificate Renewal¶
Let's Encrypt certificates expire every 90 days. The GCE startup script automatically schedules renewal via a persistent systemd timer that runs on the 1st and 15th of each month at 3 AM UTC (with up to 1 hour of randomized delay).
The timer survives reboots because /etc/systemd/system/ on COS lives on the writable stateful partition overlay.
Verify the timer is active¶
gcloud compute ssh obsidian-palace --zone=us-central1-a --project=YOUR_PROJECT_ID \
-- sudo systemctl list-timers certbot-renew.timer
Manual renewal¶
If you need to renew immediately:
gcloud compute ssh obsidian-palace --zone=us-central1-a --project=YOUR_PROJECT_ID \
-- sudo systemctl start certbot-renew.service
Check the result:
gcloud compute ssh obsidian-palace --zone=us-central1-a --project=YOUR_PROJECT_ID \
-- sudo journalctl -t certbot-renew --since today
Updating Obsidian Sync Credentials¶
The ob CLI stores two pieces of state, both persisted on the data disk via symlinks created by entrypoint.sh:
| Credential | Written by | Persistent disk path |
|---|---|---|
| Auth token | ob login |
/data/obsidian-config/headless/auth_token |
| Sync config | ob sync-setup |
/data/obsidian-config/config/sync/<vault-id>/config.json |
If your Obsidian session expires or you change your account password, re-run the appropriate command:
# SSH into the instance
gcloud compute ssh obsidian-palace --zone=us-central1-a --project=YOUR_PROJECT_ID
# Re-authenticate (if auth token expired)
docker exec -it obsidian-palace ob login
# Re-configure sync (if sync config is missing — rare)
docker exec -it obsidian-palace ob sync-setup
# Restart the container to pick up new credentials
docker restart obsidian-palace
Both credentials survive container rebuilds and redeploys because they live on the persistent disk. sync-guard.sh verifies both exist before allowing ob sync to start.
Terraform State¶
State is stored in Terraform Cloud (free tier). To inspect or modify:
cd terraform/environments/prod
# Show current state
terraform show
# Import an existing resource
terraform import 'module.obsidian_palace.google_compute_instance.obsidian_palace' \
'projects/YOUR_PROJECT_ID/zones/us-central1-a/instances/obsidian-palace'
Switching to GCS backend¶
If you prefer storing state in a GCS bucket instead of Terraform Cloud, uncomment the GCS backend in backend.tf and remove the cloud {} block from versions.tf:
terraform {
backend "gcs" {
bucket = "your-tfstate-bucket"
prefix = "obsidian_palace/state"
}
}
Backup and Recovery¶
What's on the persistent disk¶
| Path | Contents | Recreatable? |
|---|---|---|
/mnt/disks/data/vault/ |
Your Obsidian vault files | Yes -- re-synced from Obsidian Sync |
/mnt/disks/data/chromadb/ |
Semantic search index | Yes -- rebuilt on startup |
/mnt/disks/data/chroma-cache/ |
ONNX embedding model (~79MB) | Yes -- re-downloaded on first use |
/mnt/disks/data/obsidian-config/headless/ |
ob login auth token |
No -- requires interactive ob login |
/mnt/disks/data/obsidian-config/config/sync/<vault-id>/ |
ob sync-setup vault config |
No -- requires interactive ob sync-setup |
/mnt/disks/data/letsencrypt/ |
SSL certificates | Yes -- re-issued by certbot |
/mnt/disks/data/state/ |
OAuth session state | Yes -- regenerated on use |
Creating a disk snapshot¶
gcloud compute disks snapshot obsidian-palace-data \
--zone=us-central1-a \
--project=YOUR_PROJECT_ID \
--snapshot-names=obsidian-palace-backup-$(date +%Y%m%d)
Recovery from snapshot¶
If the data disk is lost, create a new disk from the snapshot and attach it:
gcloud compute disks create obsidian-palace-data \
--zone=us-central1-a \
--source-snapshot=obsidian-palace-backup-YYYYMMDD \
--project=YOUR_PROJECT_ID
Then run terraform apply to reconcile state.
Cost Estimate¶
| Resource | Monthly Cost |
|---|---|
| GCE e2-small (always-on) | ~$13.00 |
| Persistent disk (20 GB pd-standard) | ~$0.80 |
| Static IP (in use) | ~$0.00 |
| Secret Manager (4 secrets) | ~$0.00 |
| Artifact Registry (storage) | ~$0.10 |
| Total | ~$14.00 |
Cost optimization
If you don't need the server running 24/7, you can stop the instance when not in use. A stopped e2-small costs ~$0 for compute (you still pay for the disk and static IP).