# Single-VM deployment The deployment is relocatable. Compose paths and management commands are resolved relative to the repository checkout, so it can live anywhere on the VM. `/opt/idssys/TA-Deployment-Access` is only one possible example. One Compose project manages: - TAPM broker, with an embedded SQLite database - Gitea, with its own embedded SQLite database and repository storage - Nginx, terminating TLS for the broker and Gitea - Certbot, as an on-demand utility for certificate issuance and renewal The same deployment supports an explicitly insecure HTTP testing phase before public DNS and port forwarding are ready. HTTP mode changes only routing, URL schemes, and cookie security; it does not create separate databases or move runtime data. In production, only the configured Nginx HTTP and HTTPS ports are published. They default to 8680 and 8643. Inside Docker, Nginx listens on ports 80 and 443, the broker on 8080, and Gitea is explicitly pinned to `0.0.0.0:3000`. Gitea SSH is disabled. Deployment-level variables such as `HTTP_PORT` are not passed into the Gitea container, preventing them from overriding its internal listener. ## 1. VM and network Install Docker Engine with Compose v2. Permit the selected HTTP and HTTPS ports and forward public TCP 80 and 443 from the datacenter edge to them. Create public DNS A/AAAA records for both application names before requesting the certificate. Public port 80 must reach the container's port 80 for HTTP-01 certificate renewal. For example, if `HTTP_PORT=8080`, the edge must forward public port 80 to VM port 8080. If IPv6 is published, it must reach this same VM. The company firewall should provide volumetric DDoS protection and permit only the required forwarded ports. Nginx supplies the application-edge controls: unknown HTTP hostnames are dropped, unknown TLS SNI names are rejected, TLS is restricted to 1.2/1.3, session tickets and version disclosure are disabled, broker requests and per-address connections are limited, slow-client timeouts are bounded, Gitea login bursts are throttled, and HSTS plus browser security headers are returned. Container PID ceilings and Docker log rotation reduce the impact of local resource exhaustion. Nginx also runs with a read-only container filesystem and narrowly scoped temporary filesystems. The broker trusts proxy headers because Nginx is its only production ingress; Nginx replaces rather than appends client-supplied forwarding headers. ## 2. Install the repository ```sh sudo mkdir -p /YOUR/INSTALL/PARENT sudo git clone GITEA-REPOSITORY-URL /YOUR/INSTALL/PARENT/TA-Deployment-Access sudo chown -R 1000:1000 /YOUR/INSTALL/PARENT/TA-Deployment-Access cd /YOUR/INSTALL/PARENT/TA-Deployment-Access ./install.sh ``` The guided installer validates both hostnames and Docker ports, generates the broker cookie secret, writes `.env` with mode `0600`, prepares persistent storage, and starts only Gitea and Nginx. It defaults to `SSL_MODE=none`, so no certificate or public port forwarding is required. With the default `HTTP_PORT=8680`, test URLs include that port: ```sh http://BROKER_DOMAIN:8680 http://GITEA_DOMAIN:8680 ``` Both names must resolve to the Docker VM from the test workstation. Temporary hosts-file records are sufficient. Do not forward this HTTP test listener to the public Internet. For a manual installation instead, copy `.env.example` to `.env`, replace its example hostnames, generate `TAPM_COOKIE_SECRET` with `openssl rand -base64 48`, and run `./manage.sh bootstrap`. Runtime state is deliberately visible below the checkout: ```text config/ ├── broker/ # tapm.db and SQLite WAL files ├── gitea/ │ ├── config/ # app.ini and Gitea configuration │ └── data/ # repositories, packages, and gitea.db ├── letsencrypt/ # account data, certificate, and private key ├── ssl/ # optional administrator-provided certificate ├── certbot-webroot/ # HTTP-01 challenge files └── backups/ # local offline snapshots ``` The contents are ignored by Git. They must never be committed. ## 3. Configure Gitea and the broker The installer has already bootstrapped Gitea. Create the initial administrator: ```sh ./manage.sh create-admin ``` Sign in at the Gitea URL printed by the command and change the temporary password. Create the `TAI` organization, then create: 1. `tapm-packages`, with a read-only package token. 2. `tapm-publisher`, with a write package token. 3. An OAuth2 application. In HTTP testing mode its callback is `http://BROKER_DOMAIN:HTTP_PORT/auth/callback`. Save the resulting values without echoing secrets to the terminal: ```sh ./manage.sh configure-broker ./manage.sh start ``` The broker is deliberately not started during the first bootstrap. Blank OAuth/package values are valid for Gitea-only setup, while `start` refuses to launch the broker until all required credentials are present. `TAPM_GITEA_URL` is the public browser-facing Gitea URL. `TAPM_GITEA_INTERNAL_URL` defaults to `http://gitea:3000` in installations so OAuth token exchange and package traffic stay on the private Compose network. Do not publish Gitea's internal port to make broker authentication work. Deployment codes default to seven days: ```dotenv TAPM_DEFAULT_DURATION=7d ``` The setting accepts day values such as `7d` and legacy Go duration values such as `168h`. Existing deployments retain their current `.env`; change a previous `TAPM_DEFAULT_DURATION=3h` value to `7d` and run `./manage.sh start` to adopt the new default. Individual active codes can be extended from the Codes page. The same editor can change the maximum host count and add or remove protected packages and installer actions without issuing a replacement code. For troubleshooting, use `./manage.sh diagnostics`. Do not share unredacted `docker compose config` output: Compose resolves the broker's `.env` file and can print cookie, OAuth, and package secrets. If configuration output is accidentally disclosed, rotate the cookie secret with `./manage.sh rotate-cookie-secret` and replace any configured Gitea tokens or OAuth secret that appeared. ## 4. Enable TLS when forwarding is ready After public DNS resolves and the datacenter edge forwards public TCP 80 and 443 to the configured VM ports, run: ```sh ./manage.sh set-ssl letsencrypt ./manage.sh bootstrap ./manage.sh start ``` `set-ssl` changes all public URLs to HTTPS and removes the temporary `:8680` test port. `bootstrap` obtains the certificate and switches Nginx to TLS. `start` recreates Gitea and the broker with secure cookies. Existing SQLite databases, repositories, packages, installation identities, and secrets remain in place. For an administrator-provided certificate instead, place the certificate and key in `config/ssl/` and set: ```dotenv SSL_MODE=custom SSL_CERTIFICATE_DIR=./config/ssl SSL_CERTIFICATE_FILE=fullchain.pem SSL_CERTIFICATE_KEY_FILE=privkey.pem ``` The certificate must cover both `BROKER_DOMAIN` and `GITEA_DOMAIN`. Run `./manage.sh set-ssl custom` before bootstrap. With custom mode, `bootstrap` skips Certbot and `renew` is intentionally unavailable; replace the files through the certificate provider's process and restart or reload Nginx. `SSL_CERTIFICATE_DIR` can point at another directory relative to the repository or at an absolute host path. ## 5. Start and inspect the complete deployment ```sh ./manage.sh start ./manage.sh status curl --fail "http://BROKER_DOMAIN:HTTP_PORT/health/ready" # SSL_MODE=none curl --fail "http://GITEA_DOMAIN:HTTP_PORT/api/healthz" # SSL_MODE=none ``` The broker automatically applies SQLite schema migrations before starting. Gitea uses its own SQLite database at `config/gitea/data/data/gitea.db`. After TLS is enabled, use the equivalent `https://` URLs without the temporary HTTP test port. ### Final identity hardening After public DNS, WAN TCP 80/443 forwarding, and the Let's Encrypt certificate are working, configure Microsoft Entra ID as Gitea's OpenID Connect authentication source. Register the final callback URL: ```text https://GITEA_DOMAIN/user/oauth2/entra/callback ``` Require assignment to the Entra enterprise application, apply the company's Duo-backed Conditional Access policy, and map the Entra `Gitea.User` and `Gitea.Admin` application roles into Gitea. Verify an Entra administrator can sign in and reach the broker before disabling Gitea's local password sign-in form. Keep a documented VM-console recovery procedure and retain the package reader and publisher as non-admin service accounts with narrowly scoped tokens. For local testing that bypasses Nginx, set `BROKER_DIRECT_PORT` and `GITEA_DIRECT_PORT` in `.env`. Set `GITEA_DIRECT_ROOT_URL` to the exact URL the test browser will use, including the VM hostname or IP address and port: ```dotenv BROKER_DIRECT_PORT=8080 GITEA_DIRECT_PORT=3000 GITEA_DIRECT_ROOT_URL=http://192.0.2.10:3000/ ``` Then run: ```sh ./manage.sh start-direct ``` This adds host mappings to the broker's port 8080 and Gitea's port 3000. It also changes Gitea's root URL and session cookie policy to permit HTTP login. The direct broker port is suitable for health and API testing; technician OAuth login remains designed for the HTTPS public URL. Do not use the direct-port override on an Internet-facing production VM. Return to the production URL and secure-cookie policy with: ```sh ./manage.sh start ``` ## 6. Move repositories For each existing repository, create an empty matching repository in the new Gitea and mirror all refs: ```sh git clone --mirror OLD-REPOSITORY-URL cd REPOSITORY.git git push --mirror https://GITEA_DOMAIN/TAI/REPOSITORY.git ``` Update developer remotes, CI credentials, submodules, documentation, and the Go module path only if the hostname embedded in the module path is also changing. Move the deployment repository last so this checkout remains updateable during the transition. ## 7. Renewal and backups When `SSL_MODE=letsencrypt`, run renewal twice daily from root's crontab: ```cron 17 3,15 * * * cd /YOUR/INSTALL/PARENT/TA-Deployment-Access && ./manage.sh renew ``` Create an application-consistent local snapshot with: ```sh ./manage.sh backup ``` The backup briefly stops both SQLite writers. Copy `config/backups/` to storage outside the VM. A backup left only on this VM does not protect against VM or datacenter loss. Also back up `.env` through a secrets-aware system. ## 8. Updates ```sh cd /YOUR/INSTALL/PARENT/TA-Deployment-Access ./update.sh ``` The updater operates only on the current VM. It fast-forwards the checked-out branch, pulls the pinned Gitea and Nginx images, rebuilds and restarts the local stack, and waits for the local broker to become healthy. Certbot is not pulled or run during a routine update; it is used only by `bootstrap` and `renew`. The updater has no peer discovery, SSH, or multi-node update behavior. Pin image versions as supplied and review release notes before changing them. Never change Gitea between rootless and rootful image families in place.