Explorar el Código

ci: isolate deployment from existing IIS sites

ci-iis-release-pipeline
Clawdbot hace 4 horas
padre
commit
203b7d76ff
Se han modificado 1 ficheros con 75 adiciones y 176 borrados
  1. +75
    -176
      docs/deployment-guide.md

+ 75
- 176
docs/deployment-guide.md Ver fichero

@@ -1,235 +1,134 @@
# IIS Deployment Guide # IIS Deployment Guide


## Deployment model
## Isolation boundary


Production uses immutable release directories on the Windows host:
This pipeline owns one dedicated IIS site and one dedicated application pool. It never discovers, copies configuration from, adopts, or changes another IIS site. In particular, no existing Schedulicious site, app pool, binding, path, or configuration is a deployment source or target.

Defaults (each may be overridden explicitly):

```text
SiteName AspClassicUnifiedFramework
AppPoolName AspClassicUnifiedFramework
DeployRoot D:\Deployments\AspClassicUnifiedFramework
Binding http://100.97.39.23:8085/ (empty host header)
ExpectedBranch master
```

An existing target is accepted only when its name, app pool, sole HTTP binding, and physical path exactly match this pipeline's expected dedicated state. The physical path must be a `public` directory under `<DeployRoot>\releases`. A partial or mismatched target fails closed; there is no adoption mode. Unrelated sites, pools, and bindings are left unchanged.

## Release and web-root model


```text ```text
C:\inetpub\deployments\<site>\
D:\Deployments\AspClassicUnifiedFramework\
releases\ releases\
<release-id>\ <release-id>\
app\ app\
core\ core\
db\ db\
public\ <- IIS physicalPath points here
public\ <- only this directory is served by IIS
scripts\ scripts\
tests\
... ...
shared\ shared\
public.web.config <- machine-owned configuration
public.web.config
deployment-state.json deployment-state.json
``` ```


The complete repository is copied into each release. This is required because
`public/Default.asp` includes files above `public/`, including `../core` and
`../app`. IIS exposes only `<release>\public`; sibling source, scripts, tests,
and database files are not web-root content.

Switching `physicalPath` from one release's `public` directory to another is the
atomic-ish cutover. The app pool is recycled after the switch. If the smoke test
fails, the installer restores the previous physical path and recycles again.
The script does not stop the site for the normal copy/staging phase.

## Why Gitea is not the runner

The repository server is Gitea 1.11.4, which predates Gitea Actions. Treat
Gitea as the Git source only. Run `scripts/deploy-iis-git.ps1` from one of:

- a trusted external CI worker that checks out this repository;
- a controlled operator workstation;
- a separately managed scheduled task.

The worker must have Tailscale reachability and OpenSSH key-based access to
`webserver-1`. No password, private key, database path, or connection string
belongs in this repository.

## Files

- `scripts/deploy-iis-git.ps1`: controller/CI entry point. Validates branch and
worktree state, packages the full source tree, hashes it, transfers it with
`scp`, and invokes the host installer with `ssh`.
- `scripts/install-iis-release.ps1`: elevated host-side installer. Performs
preflight, preserves machine configuration, stages/releases, configures
Classic ASP parent paths, switches IIS, smoke tests, and rolls back.

Both scripts target Windows PowerShell 5.1 syntax.

## One-time host preparation

Complete these steps on `webserver-1` before the first live deployment:

1. Join the host and CI worker to the intended Tailscale tailnet. Confirm ACLs
allow SSH only from the intended deploy identity.
2. Install and configure Windows OpenSSH Server for key-based authentication.
The deploy identity must be able to run an elevated, non-interactive
PowerShell process; choose and audit the elevation mechanism locally.
3. Install IIS, Classic ASP (`IIS-ASP` / `Web-ASP`), and IIS URL Rewrite.
4. Create the IIS site and application pool. The scripts require an existing
site and do not change its bindings, authentication, app-pool identity, or
managed runtime.
5. Ensure the site currently points at a valid `public` directory containing
its machine-specific `web.config`. On first deployment this file is copied
to `shared\public.web.config` and becomes the deployment-owned preserved
copy.
6. Put the production Access database and logs outside release directories.
Grant the existing app-pool identity only the required permissions. The
deployment scripts deliberately do not change app-pool identity or ACLs.
7. Grant the deploy identity modify permission on the chosen deployment root
and permission to update this IIS site's configuration.
8. Back up IIS configuration and production data using the host's normal backup
system before enabling unattended deployment.

Default deployment root:

```text
C:\inetpub\deployments\<IIS site name>
```
The complete repository is packaged because `public/Default.asp` uses parent includes from sibling directories. Releases are extracted into `<release-id>.staging`, checked for required files, safe paths/reparse points, and valid `public\web.config` XML, overlaid with shared configuration, and renamed to an immutable release directory. IIS serves only `<release>\public`.


Classic ASP parent paths are set explicitly at the site's location in
`applicationHost.config` by `install-iis-release.ps1`:
On the first deployment, shared configuration is initialized from the packaged release's `public\web.config`. Supply `-InitialWebConfigPath` only when an explicitly managed host-local configuration file should seed the first deployment. The script validates that file as XML. It never copies configuration from any IIS site. Once created, the shared file is reused and is not automatically refreshed.


```text
system.webServer/asp enableParentPaths = true
```
Classic ASP parent paths are enabled with a location-scoped `system.webServer/asp` setting for this dedicated site only.


This is intentional. The ASP section is commonly locked against `web.config`
overrides, so the setting is applied at the host/site level rather than added to
repository `public/web.config`.
## Scripts


## Safe validation sequence
- `scripts/deploy-iis-git.ps1`: controller entry point; validates source, branch, XML, package structure, and safety; hashes and optionally transfers the package.
- `scripts/install-iis-release.ps1`: elevated Windows PowerShell 5.1 host installer; performs read-only host preflight, stages and validates releases, creates/manages only the dedicated target, switches releases, smoke tests, and rolls back target mutations on failure.


From a standalone checkout on a Windows worker, first validate without making a
network connection:
Gitea 1.11.4 predates Gitea Actions. Run the controller from a trusted external worker or operator workstation with approved Tailscale/OpenSSH access. No credentials or production configuration belong in this repository.


```powershell
.\scripts\deploy-iis-git.ps1 `
-SiteName 'ttasp' `
-RemoteTarget 'webserver-1' `
-ExpectedBranch 'master' `
-DryRun
```
## Preflight sequence


`-DryRun` validates the local source, branch, dirty state, XML, package creation,
and argument construction. It prints remote operations but does not run `ssh`
or `scp`.
### 1. Local/controller preflight (no network)


Next run a remote read-only preflight. This does connect to `webserver-1`, copies
only the installer to a temporary directory, and checks the existing site,
app pool, Classic ASP feature (when its feature cmdlet is available), URL
Rewrite module, and availability of a preservable `web.config`:
Run from a clean standalone `master` checkout:


```powershell ```powershell
.\scripts\deploy-iis-git.ps1 `
-SiteName 'ttasp' `
-RemoteTarget 'webserver-1' `
-RemotePreflightOnly
.\scripts\deploy-iis-git.ps1 -LocalPreflightOnly
``` ```


For a normal deployment:

```powershell
.\scripts\deploy-iis-git.ps1 `
-SiteName 'ttasp' `
-RemoteTarget 'webserver-1' `
-ExpectedBranch 'master' `
-BaseUrl 'http://127.0.0.1/'
```
This validates the dedicated names/binding/root, source provenance, required source files, repository `public\web.config` XML, package creation, extracted package layout, and reparse-point/symlink safety. It does not call SSH/SCP and makes no host change. `-AllowAnyBranch` is still required when the source is not a standalone Git checkout.


Use a host header or HTTPS URL in `-BaseUrl` when the site's binding requires
one. If no URL is supplied, the installer uses the first HTTP binding's port on
`127.0.0.1`. `-SkipSmokeTest` is an explicit exception and should not be used in
unattended production CI.
`-DryRun` also avoids network access but prints the remote operation that would be performed.


## Branch and artifact policy
### 2. Full preflight (connects, read-only)


The controller defaults to `-ExpectedBranch master`, matching the repository's
actual default branch. It refuses a different branch or dirty worktree. External CI should:
```powershell
.\scripts\deploy-iis-git.ps1 -PreflightOnly -RemoteTarget 'webserver-1'
```


1. fetch from Gitea over its configured authenticated channel;
2. check out the exact approved commit on `master`;
3. run repository tests/static checks;
4. call the deployment script from that standalone checkout;
5. retain the commit ID, release ID, package SHA-256, and deployment output.
After the local/package checks, the controller streams the installer over SSH and runs it in memory; it does not create a remote transport directory or copy a remote file. The host phase checks elevation, WebAdministration, Classic ASP feature/module, URL Rewrite module, deployment drive/path and resource names, binding conflicts (including wildcard IP overlap), shared-pool conflicts, and existing dedicated target consistency. The dedicated site and pool do not need to exist. No IIS or deployment-root change is made. `-HostPreflightOnly` and `-RemotePreflightOnly` remain aliases.


Use `-AllowAnyBranch` or `-AllowDirty` only for an intentional, reviewed manual
exception. If `SourcePath` is not itself a Git root, `-AllowAnyBranch` is
required because branch provenance cannot be proved.
## First deployment


A webhook receiver is not included. Gitea 1.11.4 can emit webhooks, but accepting
and authenticating them safely is infrastructure-specific. Polling or a manually
approved CI job is simpler unless an existing secured webhook runner is
available.
The installer creates the dedicated app pool and site only after the staged release and selected initial `web.config` validate. Example using all defaults:


## Configuration preservation
```powershell
.\scripts\deploy-iis-git.ps1 -RemoteTarget 'webserver-1'
```


The installer copies the current site's entire `public\web.config` to:
Example with explicit overrides:


```text
<DeployRoot>\shared\public.web.config
```powershell
.\scripts\deploy-iis-git.ps1 `
-SiteName 'AspClassicUnifiedFramework-Test' `
-AppPoolName 'AspClassicUnifiedFramework-Test' `
-DeployRoot 'E:\Deployments\AspClassicUnifiedFramework-Test' `
-BindingIpAddress '100.97.39.23' `
-BindingPort 8185 `
-HostHeader 'framework-test.internal' `
-InitialWebConfigPath 'E:\ManagedConfig\framework-test.web.config' `
-RemoteTarget 'webserver-1' `
-ExpectedBranch 'master'
``` ```


It then overwrites each staged release's repository version with that preserved
file before cutover. This preserves all machine-specific values, especially:
The new app pool uses no managed CLR. Configure any required app-pool identity and ACL policy through separately reviewed host preparation; the deployment does not borrow another pool's identity or permissions.


- `ConnectionString` and its Access `Data Source` path;
- `Environment`;
- `ErrorLogPath` and logging flags;
- site-specific rewrite or app settings already present on the host.
## Existing target and cutover


The shared file is never automatically refreshed from repository content. To
change production settings, edit the shared file under the host's normal change
control, validate its XML, and deploy or roll back. Keep a protected backup.
Do not commit production values.
For later deployments, the host preflight requires the dedicated site to use:


## Database migration policy
- exactly `AppPoolName`;
- exactly one HTTP binding matching IP, port, and host header;
- a physical path ending in `\public` under `<DeployRoot>\releases`.


Deployments do **not** run migrations by default. The sample `db` directory is
part of the complete release but the production connection string should point
to data outside the release tree.
Any mismatch fails without mutation. After the new release validates, the installer changes only the dedicated site's physical path, applies the site-location parent-path setting, recycles/starts the dedicated pool, and smoke tests the configured binding or explicit `-BaseUrl`.


`-RunMigrations` is available only as an explicit operator choice. Before using
it:
## Failure recovery


1. take and verify a database backup;
2. review every pending migration against the target schema;
3. schedule downtime if the Access database requires exclusive access;
4. understand that switching IIS back does not undo a database migration.
The installer records whether this invocation created the target site or pool and snapshots the prior state of an existing valid target.


The old script's automatic legacy `migrate_isbusiness_to_households.vbs` path has
been removed from deployment. Run one-off data repair scripts only under a
separate reviewed procedure.
- If target creation or smoke testing fails, it removes only the site/app pool created by this invocation.
- If an existing target fails during cutover or smoke testing, it restores its previous physical path and previous site-location parent-path value, then restarts/recycles its pool.
- It never removes or edits an unrelated site, pool, or binding.
- Staging/release files may remain for diagnosis; releases are reported as retention candidates and are never deleted automatically.
- Explicit database migrations cannot be undone by IIS rollback.


## Rollback ## Rollback


List release IDs on the host under `<DeployRoot>\releases`, or use the value in
`deployment-state.json`. Then run from the worker:

```powershell ```powershell
.\scripts\deploy-iis-git.ps1 ` .\scripts\deploy-iis-git.ps1 `
-SiteName 'ttasp' `
-RemoteTarget 'webserver-1' `
-Rollback ` -Rollback `
-RollbackTo '20260914-171500-a1b2c3d4e5f6' ` -RollbackTo '20260914-171500-a1b2c3d4e5f6' `
-BaseUrl 'http://127.0.0.1/'
-RemoteTarget 'webserver-1'
``` ```


Rollback refreshes the target release's `public\web.config` from the preserved
shared copy, switches `physicalPath`, recycles the app pool, and smoke tests. If
that smoke test fails, it restores the pre-rollback path.
Rollback requires an already valid dedicated target and an existing immutable release under the configured `DeployRoot`. It switches only that target's physical path and smoke tests; it does not rewrite the prior release. Failure restores the prior target state.


The installer reports releases older than `-KeepReleases` as retention
candidates but does not delete them automatically. Delete only after confirming
they are neither current nor required for rollback and that backups exist.
## Database and writable data


## Failure and recovery behavior
Migrations are disabled by default. `-RunMigrations` is an explicit exception and requires a verified backup; path rollback cannot undo database changes. Keep production databases, logs, and other writable data outside immutable release directories and grant the dedicated app-pool identity only necessary permissions.


- Package extraction occurs in `<release-id>.staging`.
- Layout and `web.config` XML are validated before promotion.
- Promotion is a directory rename within the release volume.
- IIS changes only after the final release directory exists.
- Failed smoke tests restore the prior `physicalPath`.
- Incomplete staging directories are retained for diagnosis.
- Database changes, external file writes, and machine configuration outside the
IIS path switch cannot be undone by release rollback.
## Windows verification gates


Review Windows Event Log, IIS logs, app logs, the script transcript, and
`deployment-state.json` after any failure.
Before production use, validate both scripts with the Windows PowerShell 5.1 parser, run host preflight on the intended Windows/IIS host, deploy to an isolated test name/binding, verify HTTP and parent includes, induce a smoke failure to prove invocation-scoped cleanup/restoration, then prove rollback. Linux checks cannot validate WebAdministration behavior, installed IIS features/modules, binding semantics on the live host, elevation, ACLs, app-pool identity, Access/ACE, or an actual HTTP response.

Cargando…
Cancelar
Guardar

Powered by TurnKey Linux.