Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Shinobi NVR on GCP

Deploys Shinobi NVR to Google Cloud, mirroring the existing reference Terraform route: a single Container-Optimized OS (COS) Compute Engine VM (shinobi-vm) running a docker-compose stack (Shinobi + Caddy for TLS), delivered via the instance startup-script. It attaches to the existing IT-managed your-vpc / your-subnet by reference only, keeps SQLite, and sources all secrets from Secret Manager.

  • Terraform module: terraform/infra/
  • Migration helpers: migration/
  • Target: project your-project, region us-central1, zone us-central1-a

The shared network (your-vpc/your-subnet) is referenced via Terraform data sources and is never created or modified by this module.


1. Prerequisites

  1. gcloud CLI installed and authenticated with Application Default Credentials:
    gcloud auth login
    gcloud auth application-default login
    gcloud config set project your-project
  2. Terraform ≥ 1.5 (terraform version).
  3. Billing enabled on your-project, and permission to create Compute, IAM, and Secret Manager resources.

2. Create the secrets (out-of-band, never committed)

Secret values live only in Secret Manager — never in Terraform, the compose file, or git (NFR-2). Create the three secrets with distinct, strong values. Do not reuse the local hardcoded admin password (FR-4.2):

printf '%s' 'A-NEW-STRONG-ADMIN-PASSWORD' | gcloud secrets create shinobi-admin-password --data-file=- --project your-project
printf '%s' "$(openssl rand -hex 24)"      | gcloud secrets create shinobi-cron-key       --data-file=- --project your-project
printf '%s' "$(openssl rand -hex 24)"      | gcloud secrets create shinobi-plugins-key     --data-file=- --project your-project

The VM service account is granted roles/secretmanager.secretAccessor on each of these secrets individually (least privilege) by Terraform.

3. Pin the Shinobi image (reproducibility — R4)

Resolve a digest for the Shinobi image and pin it, rather than deploying a floating tag:

docker buildx imagetools inspect shinobisystems/shinobi:dev
# copy the "Digest: sha256:..." value

Set it in terraform.tfvars:

shinobi_image_digest = "sha256:<resolved-digest>"

If shinobi_image_digest is left empty, the stack falls back to shinobi_image_tag (default dev) — acceptable for a first bring-up, but pin the digest for any real deploy.

4. Deploy

cd terraform/infra
cp terraform.tfvars.example terraform.tfvars   # set project_id (+ image digest)
terraform init
terraform plan      # review: NO create/modify/destroy on your-vpc or your-subnet
terraform apply

Read the outputs:

terraform output external_ip                  # for the DNS A-record
terraform output app_url                       # https://shinobi.example.org
terraform output ssh_command                   # IAP SSH into the VM
terraform output startup_script_log_command    # tail the boot log
terraform output camera_check_command          # RTSP reachability probe

5. DNS A-record (manual — automation out of scope for v1)

Create an A record pointing the hostname at the reserved IP:

A   shinobi.example.org   ->   <external_ip from terraform output>

Once DNS resolves, Caddy completes the Let's Encrypt HTTP-01 challenge (port 80) and serves HTTPS (port 443), proxying to Shinobi on :8080.

6. Access + logs (IAP)

# SSH via IAP (no public port 22):
gcloud compute ssh shinobi-vm --zone us-central1-a --project your-project --tunnel-through-iap

# Tail the startup-script log (secret fetch, stack launch, camera probe):
gcloud compute ssh shinobi-vm --zone us-central1-a --project your-project --tunnel-through-iap \
  --command='sudo journalctl -u google-startup-scripts.service --no-pager'

7. Camera reachability verification (FR-7) — confirm, do not assume

The camera (10.0.0.10) is outside your-subnet (10.1.0.0/24), so a route to it is not implied by subnet membership. Verify both stages before relying on live camera streams.

(a) Route existence — blocking

gcloud compute routes list --project your-project --filter="network:your-vpc"

Confirm a route whose destRange covers 10.0.0.0/24 with a next hop that is the IT-managed gateway (VPN tunnel / interconnect / next-hop-ip). If no such route exists, halt and request IT add it — this is the primary (and preferred) path to the lab network.

(b) RTSP TCP probe — blocking

The startup-script probes this at every boot (look for CAMERA REACHABLE / CAMERA UNREACHABLE in the startup log). To probe manually:

# via the terraform output helper:
$(terraform -chdir=terraform/infra output -raw camera_check_command)

# or directly from the VM:
gcloud compute ssh shinobi-vm --zone us-central1-a --project your-project --tunnel-through-iap \
  --command="timeout 5 bash -c '</dev/tcp/10.0.0.10/554' && echo REACHABLE || echo UNREACHABLE"

A failure of either check is a blocking issue for camera use. The default stack contains no Tailscale/VPN sidecar (FR-7.3) — reachability relies on the VPC route above. If the IT-managed gateway path cannot be made to work, the contingent fallback below is authorized.

Contingent fallback — Tailscale sidecar (only if the probe fails)

Per the amended FR-7.3 / design decision D-G, if the route/RTSP probe cannot be made to pass via the IT gateway, a Tailscale sidecar may be added to the compose stack (mirroring the reference pattern) so shinobi-vm reaches the camera over the tailnet. This is Task 10 in the spec and is not built by default; it is implemented only if the probe fails at deploy time (auth key via a new shinobi-ts-authkey Secret Manager secret, per NFR-2/NFR-6).


8. Migrate the existing local Shinobi config (FR-8)

Migrates the live monitors/DB from the local deployment, preserving group <group-id> and monitors <monitor-id-1>, <monitor-id-2>, <monitor-id-3>, <monitor-id-4>. The live config lives inside the local container (the host ~/shinobi/config mount is empty), so the export starts the container and copies from inside it.

# 1. Export + sanitize from the local machine (Docker must be running):
LOCAL_SHINOBI_DIR="$HOME/shinobi" ./migration/export-local-config.sh
#    -> writes sanitized super.json/conf.json + SQLite DB to migration/export/
#       (local hardcoded password and 'changeme' keys stripped — FR-8.4)

# 2. Import into shinobi-vm (via IAP), preserve IDs, reset the admin password:
PROJECT_ID=your-project ./migration/import-to-gcp.sh

The import script:

  • copies the sanitized artifacts into /var/shinobi/config on shinobi-vm,
  • restarts Shinobi,
  • verifies the group + 4 monitor IDs are present (FR-8.2),
  • resets the admin account password to the shinobi-admin-password Secret Manager value (Risk R2), and runs a login smoke check.

Confirm every ID line reports present and that you can log in with the Secret Manager password.

Note on the password reset: Shinobi stores account passwords as an md5 hash in the SQLite Users table; the script recomputes the hash from the secret. Confirm this matches your Shinobi version, or reset via the Shinobi UI.


9. Teardown

cd terraform/infra
terraform destroy

shinobi-ip is protected by prevent_destroy; to release it, remove the lifecycle block (or terraform state rm) first. The three Secret Manager secrets are not managed by Terraform — delete them explicitly if desired:

for s in shinobi-admin-password shinobi-cron-key shinobi-plugins-key; do
  gcloud secrets delete "$s" --project your-project --quiet
done

To pause billing without destroying, set vm_desired_status = "TERMINATED" and terraform apply.

About

Terraform module + runbook to deploy Shinobi NVR (COS + docker-compose + Caddy) on GCP

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages