GuestOps/docs/deployment.md

5.9 KiB

Debian sandbox deployment

Target: https://sandbox-guestops.futuresens.co.uk. These are reviewable deployment instructions; creating these files does not deploy or change the server.

1. Prepare the host

Check the existing Nginx, Docker, MongoDB and firewall configuration before installation. Preserve existing services. The provided Compose file creates a dedicated MongoDB instance for GuestOps with no host port; if MongoDB already exists on the host, use a reviewed private connection and application-scoped user instead of starting a conflicting instance.

Install Docker Engine/Compose and Nginx using their official Debian instructions. Keep SSH access unchanged. Only HTTPS/HTTP for this hostname need public access; port 8080 is loopback-only and MongoDB has no published port.

Clone the private repository using an authorized GitHub account. Place the checkout in a dedicated application directory. Copy .env.example to .env, set permissions to 600, and fill two different MongoDB passwords generated with openssl rand -hex 32. Use hex values so they are safe in the MongoDB URI. Store real values only on the server or in its secret-management system.

The MongoDB initialization script runs only on a new volume. Changing .env later does not rotate existing database users. Rotate those credentials through MongoDB administration and update application configuration together.

2. Load reviewed application images

CI builds API and worker images and packages them in a guestops-linux-* artifact. Download the successful artifact for the desired commit and transfer it to the sandbox through your normal authorized deployment process.

docker load -i guestops-images.tar.gz

Set GUESTOPS_API_IMAGE=guestops-api:<commit-sha> and GUESTOPS_WORKER_IMAGE=guestops-worker:<commit-sha> in .env using that exact build's SHA. Then run:

docker compose config --quiet
docker compose up -d --no-build
curl --fail http://127.0.0.1:8080/health

Do not run docker compose config without --quiet in shared logs: expanded configuration contains secrets. Local image builds are available with Compose for development, but CI builds avoid consuming sandbox resources.

3. Provision the first hotel owner

The API has an administrative bootstrap command. It creates one hotel and one owner, refuses an existing email address, and does not reset passwords. Run it with environment variables passed from a private terminal; do not put the password directly in shell history or a repository file.

read -r -p 'Owner email: ' BOOTSTRAP_EMAIL
read -r -p 'Hotel name: ' BOOTSTRAP_HOTEL
read -r -s -p 'Owner password (14+ characters): ' BOOTSTRAP_PASSWORD
printf '\n'
export BOOTSTRAP_EMAIL BOOTSTRAP_HOTEL BOOTSTRAP_PASSWORD
docker compose run --rm --no-deps -e BOOTSTRAP_EMAIL -e BOOTSTRAP_HOTEL -e BOOTSTRAP_PASSWORD api --bootstrap
unset BOOTSTRAP_EMAIL BOOTSTRAP_HOTEL BOOTSTRAP_PASSWORD

There is no development/demo account in production. Staff invitation and password recovery UI are follow-on work; do not treat this as a public self-service service yet.

4. Configure HTTPS

Verify the hostname's DNS resolves to this server. Obtain a valid certificate for it using your existing ACME/Certbot process. The Nginx example expects a Let's Encrypt certificate. For first issuance, configure the port-80 ACME location before enabling the port-443 block; do not point Nginx at missing certificate files.

Merge deploy/nginx.conf into the existing host configuration, check with nginx -t, then reload Nginx. Confirm HTTPS serves the login page. API cookies are always Secure outside preview mode; logging in through plain HTTP is intentionally unsupported.

The initial rate limiter keys off the direct peer address. Behind this loopback reverse proxy it is shared across users (10 login attempts/minute), which is conservative for a pilot. Before scaling, configure explicitly trusted forwarded headers and per-account/IP rate limits; never trust arbitrary client-supplied forwarding headers.

5. Configure Google

Create or select your Google Cloud project, enable Gmail API, and configure a Web application OAuth client. Register this exact redirect URI:

https://sandbox-guestops.futuresens.co.uk/api/integrations/google/callback

Configure GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in the server's .env and recreate the API/worker services. Sign in as hotel owner, open Settings and connect a dedicated test mailbox. The only requested Gmail scope is gmail.readonly.

OAuth requests expire after ten minutes, are bound to the signed-in user and hotel, and can be consumed once. Refresh tokens are protected with ASP.NET Data Protection. API and worker share the private persistent key volume; back it up securely with the database. Losing it prevents existing mailbox tokens and sessions from being decrypted. Filesystem protection for the key volume is required; it is separate from MongoDB and must not be publicly served or committed.

A multi-hotel production launch using Gmail restricted scopes requires planning for Google's verification/security-assessment requirements. This implementation does not bypass those requirements. See Google restricted-scope verification.

6. Acceptance and rollback

Verify separate hotels cannot read or edit each other's records; save and reload settings; restart services and confirm persistence; import test messages twice without duplicates; check the worker resumes a paginated import; confirm no mail is sent. Review the activity log and Google account used by the connection.

Keep reviewed image tags for rollback and backups of both MongoDB and the key volume. Do not remove named volumes to fix application errors. The initial release has no automatic schema migration that destroys data. Establish retention, off-server backup and a restore drill before importing real guest data.