diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..1d473b7
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+buy_me_a_coffee:
+ displayName: "Buy Me a Coffee"
+ account: dockhand
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f32e31a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.idea/
+.DS_Store
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..1e2e0c7
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,183 @@
+# syntax=docker/dockerfile:1.4
+# =============================================================================
+# Dockhand Docker Image - Security-Hardened Build
+# =============================================================================
+# This Dockerfile builds a custom Wolfi OS from scratch using apko, ensuring:
+# - Full transparency (no dependency on pre-built Chainguard images)
+# - Reproducible builds from open-source Wolfi packages
+# - Minimal attack surface with only required packages
+#
+# Bun is copied from the official oven/bun image (app-builder stage).
+# For CPUs without AVX support (Celeron, Atom, pre-Haswell), build with:
+# docker build --build-arg BUN_VARIANT=baseline -t dockhand:baseline .
+# =============================================================================
+
+# -----------------------------------------------------------------------------
+# Stage 1: OS Generator (Alpine + apko tool)
+# -----------------------------------------------------------------------------
+# We use Alpine because it has a shell. This lets us download and run apko
+# to build our custom Wolfi OS from scratch using open-source packages.
+FROM alpine:3.21 AS os-builder
+
+ARG TARGETARCH
+
+WORKDIR /work
+
+# Install apko tool (latest stable release)
+# apko is the tool Chainguard uses to build their images - we use it directly
+ARG APKO_VERSION=0.30.34
+RUN apk add --no-cache curl unzip \
+ && ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "arm64" || echo "amd64") \
+ && curl -sL "https://github.com/chainguard-dev/apko/releases/download/v${APKO_VERSION}/apko_${APKO_VERSION}_linux_${ARCH}.tar.gz" \
+ | tar -xz --strip-components=1 -C /usr/local/bin \
+ && chmod +x /usr/local/bin/apko
+
+# Generate apko.yaml for current target architecture only
+# We build single-arch to avoid multi-arch layer confusion in extraction
+# Note: Bun is NOT included here - it's copied from app-builder stage for CPU compatibility
+RUN APKO_ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "x86_64") \
+ && printf '%s\n' \
+ "contents:" \
+ " repositories:" \
+ " - https://packages.wolfi.dev/os" \
+ " keyring:" \
+ " - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub" \
+ " packages:" \
+ " - wolfi-base" \
+ " - ca-certificates" \
+ " - busybox" \
+ " - tzdata" \
+ " - docker-cli" \
+ " - docker-compose" \
+ " - sqlite" \
+ " - git" \
+ " - openssh-client" \
+ " - curl" \
+ " - tini" \
+ " - su-exec" \
+ "entrypoint:" \
+ " command: /bin/sh -l" \
+ "archs:" \
+ " - ${APKO_ARCH}" \
+ > apko.yaml
+
+# Build the OS tarball and extract rootfs
+# apko creates an OCI tarball - we need to extract the actual filesystem layer
+RUN apko build apko.yaml dockhand-base:latest output.tar \
+ && mkdir -p rootfs \
+ && tar -xf output.tar \
+ && LAYER=$(tar -tf output.tar | grep '.tar.gz$' | head -1) \
+ && tar -xzf "$LAYER" -C rootfs
+
+# -----------------------------------------------------------------------------
+# Stage 2: Application Builder
+# -----------------------------------------------------------------------------
+# Using Debian to avoid Alpine musl thread creation issues
+# Alpine's musl libc causes rayon/tokio thread pool panics during svelte-adapter-bun build
+FROM oven/bun:1.3.5-debian AS app-builder
+
+# Build argument for Bun variant (regular or baseline)
+# baseline is for CPUs without AVX support (Celeron, Atom, pre-Haswell)
+ARG BUN_VARIANT=regular
+ARG TARGETARCH
+
+WORKDIR /app
+
+# Install build dependencies
+RUN apt-get update && apt-get install -y --no-install-recommends jq git curl unzip ca-certificates && rm -rf /var/lib/apt/lists/*
+
+# Copy package files and install ALL dependencies (needed for build)
+COPY package.json bun.lock* bunfig.toml ./
+RUN bun install --frozen-lockfile
+
+# Copy source code and build
+COPY . .
+
+# Build with parallelism - dedicated build VM has 16 CPUs and 32GB RAM
+RUN NODE_OPTIONS="--max-old-space-size=8192 --max-semi-space-size=128" bun run build
+
+# Prepare production node_modules (do this in builder where we have compilers)
+# This ensures native addons compile correctly before copying to hardened runtime
+RUN rm -rf node_modules && bun install --production --frozen-lockfile \
+ && rm -rf node_modules/@types node_modules/bun-types
+
+# Download baseline Bun binary if BUN_VARIANT=baseline (for CPUs without AVX)
+# Only applies to amd64 - ARM64 doesn't have AVX concept
+ARG BUN_VERSION=1.3.5
+RUN if [ "$BUN_VARIANT" = "baseline" ] && [ "$TARGETARCH" = "amd64" ]; then \
+ echo "Downloading Bun baseline binary for CPUs without AVX support..." && \
+ curl -fsSL "https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/bun-linux-x64-baseline.zip" -o /tmp/bun.zip && \
+ unzip -o /tmp/bun.zip -d /tmp && \
+ cp /tmp/bun-linux-x64-baseline/bun /usr/local/bin/bun && \
+ chmod +x /usr/local/bin/bun && \
+ rm -rf /tmp/bun.zip /tmp/bun-linux-x64-baseline && \
+ echo "Bun baseline binary installed successfully"; \
+ fi
+
+# -----------------------------------------------------------------------------
+# Stage 3: Final Image (Scratch + Custom Wolfi OS)
+# -----------------------------------------------------------------------------
+FROM scratch
+
+# Install our custom-built Wolfi OS (now we have /bin/sh!)
+COPY --from=os-builder /work/rootfs/ /
+
+# Copy Bun from official image - ensures compatibility with all x86_64 CPUs (no AVX2 requirement)
+# Wolfi's bun package requires AVX2 which breaks on Celeron/Atom CPUs
+# For baseline builds (BUN_VARIANT=baseline), this contains the baseline binary (no AVX requirement)
+# For regular builds, this contains the standard oven/bun binary
+COPY --from=app-builder /usr/local/bin/bun /usr/bin/bun
+
+WORKDIR /app
+
+# Set up environment variables
+ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
+ SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \
+ NODE_ENV=production \
+ PORT=3000 \
+ HOST=0.0.0.0 \
+ DATA_DIR=/app/data \
+ HOME=/home/dockhand \
+ PUID=1001 \
+ PGID=1001
+
+# Create docker compose plugin symlink (we use `docker compose` syntax, Wolfi has standalone binary)
+RUN mkdir -p /usr/libexec/docker/cli-plugins \
+ && ln -s /usr/bin/docker-compose /usr/libexec/docker/cli-plugins/docker-compose
+
+# Create dockhand user and group (using busybox commands)
+RUN addgroup -g 1001 dockhand \
+ && adduser -u 1001 -G dockhand -h /home/dockhand -D dockhand
+
+# Copy application files with correct ownership (avoids layer duplication from chown -R)
+COPY --from=app-builder --chown=dockhand:dockhand /app/node_modules ./node_modules
+COPY --from=app-builder --chown=dockhand:dockhand /app/package.json ./
+COPY --from=app-builder --chown=dockhand:dockhand /app/build ./build
+COPY --from=app-builder --chown=dockhand:dockhand /app/build/subprocesses/ ./subprocesses/
+
+# Copy database migrations
+COPY --chown=dockhand:dockhand drizzle/ ./drizzle/
+COPY --chown=dockhand:dockhand drizzle-pg/ ./drizzle-pg/
+
+# Copy legal documents
+COPY --chown=dockhand:dockhand LICENSE.txt PRIVACY.txt ./
+
+# Copy entrypoint script (root-owned, executable)
+COPY docker-entrypoint.sh /usr/local/bin/
+RUN chmod +x /usr/local/bin/docker-entrypoint.sh
+
+# Copy emergency scripts
+COPY --chown=dockhand:dockhand scripts/emergency/ ./scripts/
+RUN chmod +x ./scripts/*.sh ./scripts/**/*.sh 2>/dev/null || true
+
+# Create data directories with correct ownership
+RUN mkdir -p /home/dockhand/.dockhand/stacks /app/data \
+ && chown dockhand:dockhand /app/data /home/dockhand /home/dockhand/.dockhand /home/dockhand/.dockhand/stacks
+
+EXPOSE 3000
+
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD curl -f http://localhost:3000/ || exit 1
+
+ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
+CMD ["bun", "run", "./build/index.js"]
diff --git a/LICENSE.txt b/LICENSE.txt
index 86472ee..148c985 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -123,6 +123,6 @@ under an Open Source License, as stated in this License.
For licensing inquiries, commercial licensing, or enterprise features:
- Website: https://dockhand.io
+ Website: https://dockhand.pro
-----------------------------------------------------------------------------
diff --git a/PRIVACY.txt b/PRIVACY.txt
new file mode 100644
index 0000000..fb5bc78
--- /dev/null
+++ b/PRIVACY.txt
@@ -0,0 +1,425 @@
+DOCKHAND PRIVACY POLICY
+
+Last Updated: December 14, 2025
+Effective Date: December 14, 2025
+
+================================================================================
+
+1. INTRODUCTION
+
+This Privacy Policy describes how Finsys Jaroslaw Krochmalski ("Finsys," "we,"
+"us," or "our") handles data in connection with the Dockhand software
+application ("Software"). This Policy applies to all users of the Software.
+
+Finsys is committed to protecting your privacy and ensuring transparency
+about our data practices. This Policy explains that the Software operates
+entirely locally on your infrastructure with no data transmitted to Finsys.
+
+
+2. DATA CONTROLLER INFORMATION
+
+Finsys Jaroslaw Krochmalski
+ul. Borki 6
+05-119 Jozefow
+Poland
+
+VAT ID: PL7121835977
+REGON: 061576391
+
+Email: enterprise@dockhand.pro
+Website: https://dockhand.pro
+
+For the purpose of the General Data Protection Regulation (GDPR) and other
+applicable data protection laws, Finsys is NOT the data controller for any
+personal data processed through your installation of the Software. You (the
+user or your organization) are the data controller for all data stored in
+your Software installation.
+
+
+3. OUR FUNDAMENTAL PRINCIPLE: LOCAL-ONLY DATA
+
+The Software is designed with privacy as a core principle:
+
+- ALL DATA STAYS LOCAL: The Software stores all data exclusively on your
+ infrastructure (your servers, your databases, your storage).
+
+- NO DATA TRANSMISSION: The Software does not transmit any data to Finsys
+ servers, third-party servers, or any external services.
+
+- NO TELEMETRY: The Software contains no telemetry, analytics, usage
+ tracking, crash reporting, or any other data collection mechanisms.
+
+- FULLY SELF-CONTAINED: The Software operates entirely within your
+ infrastructure without requiring any connection to Finsys systems.
+
+- FINSYS HAS NO ACCESS: Finsys cannot access, view, retrieve, or process
+ any data stored in your Software installation.
+
+
+4. DATA PROCESSED BY THE SOFTWARE
+
+When you use the Software, the following types of data may be stored
+LOCALLY on your infrastructure:
+
+4.1 User Account Data
+- Usernames and email addresses
+- Password hashes (never stored in plain text)
+- Multi-factor authentication (MFA) secrets (Enterprise Edition)
+- User profile information and avatars
+- Role assignments and permissions (Enterprise Edition)
+
+4.2 Authentication Data
+- Session tokens and cookies
+- OIDC/SSO tokens and provider configurations
+- LDAP/Active Directory connection settings (Enterprise Edition)
+- API tokens for remote access
+
+4.3 Docker Environment Data
+- Docker host connection details (URLs, ports, socket paths)
+- Docker container information (names, IDs, configurations)
+- Container logs and metrics
+- Image and volume data
+- Network configurations
+- Compose stack definitions
+
+4.4 Git Integration Data
+- Git repository URLs and credentials
+- SSH keys and access tokens
+- Deployment webhooks
+
+4.5 Registry Data
+- Docker registry URLs and credentials
+- Image pull/push history
+
+4.6 Activity and Audit Data
+- User activity logs
+- Container events and operations
+- Audit trails (Enterprise Edition)
+
+4.7 Application Settings
+- General configuration preferences
+- Notification channel settings (SMTP, webhooks)
+- Scheduled task configurations
+
+All of the above data is stored exclusively in your local database
+(SQLite or PostgreSQL) and on your local filesystem. None of this data
+is transmitted to or accessible by Finsys.
+
+
+5. HOW DATA IS STORED
+
+5.1 Database Storage
+
+The Software uses either SQLite or PostgreSQL as configured by you:
+- SQLite: Data stored in a local file on your server
+- PostgreSQL: Data stored in your PostgreSQL database instance
+
+5.2 File Storage
+
+Certain data is stored in the local filesystem:
+- Compose stack files
+- Uploaded files (e.g., user avatars)
+- Temporary files during operations
+
+5.3 Encryption
+
+- Passwords are hashed using secure algorithms (Argon2id)
+- Sensitive credentials may be encrypted at rest depending on your
+ database configuration
+- You are responsible for implementing disk encryption, database
+ encryption, and network security for your infrastructure
+
+
+6. YOUR RESPONSIBILITIES AS DATA CONTROLLER
+
+Since all data is stored locally on your infrastructure, YOU are the
+data controller for purposes of GDPR and other data protection laws.
+As data controller, you are responsible for:
+
+6.1 Legal Basis for Processing
+Ensuring you have a valid legal basis for processing personal data of
+your users (e.g., consent, legitimate interest, contractual necessity).
+
+6.2 Data Subject Rights
+Responding to data subject requests including:
+- Right of access (Article 15 GDPR)
+- Right to rectification (Article 16 GDPR)
+- Right to erasure (Article 17 GDPR)
+- Right to restriction of processing (Article 18 GDPR)
+- Right to data portability (Article 20 GDPR)
+- Right to object (Article 21 GDPR)
+
+6.3 Security Measures
+Implementing appropriate technical and organizational measures to
+protect personal data, including:
+- Access controls and authentication
+- Encryption of data at rest and in transit
+- Regular security updates and patches
+- Backup and disaster recovery procedures
+- Network security (firewalls, VPNs, etc.)
+
+6.4 Data Retention
+Establishing and implementing appropriate data retention policies.
+
+6.5 Breach Notification
+Notifying supervisory authorities and affected individuals in case
+of a personal data breach, as required by applicable law.
+
+6.6 Privacy Notices
+Providing appropriate privacy notices to your users regarding how
+their data is processed within the Software.
+
+
+7. DATA WE DO NOT COLLECT
+
+To be absolutely clear, Finsys does NOT collect, receive, access, or
+process ANY of the following:
+
+- Your identity or contact information (unless you contact us directly)
+- Your Docker infrastructure information
+- Your container configurations or data
+- Your user accounts or credentials
+- Your activity logs or audit trails
+- Your git repositories or deployment data
+- Usage statistics or analytics
+- Error reports or crash data
+- Any telemetry or diagnostic data
+- Any data whatsoever from your Software installation
+
+
+8. WHEN FINSYS MAY RECEIVE DATA
+
+The only circumstances in which Finsys may receive data from you are:
+
+8.1 Direct Communication
+When you voluntarily contact us via email (enterprise@dockhand.pro),
+we receive and process the information you provide (name, email address,
+message content). This data is processed for the purpose of responding
+to your inquiry based on our legitimate interest in providing customer
+support.
+
+8.2 License Purchase
+
+When you purchase an Enterprise Edition license, we collect and process:
+
+Data Collected:
+- Name and/or company name
+- Email address
+- Billing address
+- Payment information (processed by payment provider)
+- Licensed hostname/identifier
+
+Legal Basis (GDPR Article 6):
+- Contract performance (Art. 6(1)(b)) - to fulfill the license agreement
+- Legal obligation (Art. 6(1)(c)) - for invoicing and tax records
+
+How We Use This Data:
+- To issue and deliver your License Key
+- To send license renewal reminders
+- To provide support related to your license
+- To comply with tax and accounting obligations
+
+Data Retention:
+- License and invoice records: 7 years (Polish tax law requirement)
+- Email correspondence: 3 years after last contact
+
+Data Sharing:
+- Payment processor (for payment transactions only)
+- No other third parties
+- No marketing or advertising use
+
+8.3 Website Visits
+If you visit our website (https://dockhand.pro), standard web server
+logs may be collected. See our website privacy policy for details.
+
+
+9. LICENSE KEY DATA
+
+Enterprise Edition License Keys contain:
+- Customer name (as registered)
+- Licensed hostname or identifier
+- Expiration date
+- Cryptographic signature
+
+This information is embedded in the License Key itself and stored
+locally in your Software installation. Finsys retains a record of
+issued licenses for license management purposes.
+
+
+10. INTERNATIONAL DATA TRANSFERS
+
+Since all Software data is stored locally on your infrastructure, no
+international data transfers occur through the Software itself.
+
+If your infrastructure is located outside the European Economic Area
+(EEA), you are responsible for ensuring appropriate safeguards for
+any personal data stored therein.
+
+
+11. DATA RETENTION
+
+11.1 Software Data
+You control the retention of all data in your Software installation.
+The Software does not automatically delete data unless you configure
+retention policies or manually delete data.
+
+11.2 Communication Data
+If you contact us directly, we retain correspondence for as long as
+necessary to respond to your inquiry and for our records, typically
+not exceeding 3 years unless required for legal purposes.
+
+11.3 License Records
+We retain license purchase and activation records for the duration
+required by tax and accounting regulations (typically 5-7 years).
+
+
+12. CHILDREN'S PRIVACY
+
+The Software is not intended for use by children under 16 years of age.
+We do not knowingly collect personal data from children. If you are a
+parent or guardian and believe your child has provided personal data
+to us through direct communication, please contact us.
+
+
+13. THIRD-PARTY SERVICES
+
+13.1 Software Integrations
+
+The Software may connect to third-party services as configured by you:
+- Docker registries
+- Git repositories (GitHub, GitLab, etc.)
+- OIDC/SSO providers
+- LDAP/Active Directory servers
+- Notification services (SMTP, Discord, Slack, etc.)
+
+These connections are initiated by you, configured by you, and occur
+between your infrastructure and these third-party services. Finsys is
+not involved in these connections and has no access to the data
+exchanged. The privacy policies of these third-party services apply
+to your use of them.
+
+13.2 No Hidden Third-Party Data Sharing
+
+The Software does not share any data with third parties on our behalf.
+There are no embedded analytics services, advertising networks, or
+data brokers within the Software.
+
+
+14. SECURITY
+
+14.1 Software Security
+
+We implement security measures in the Software design:
+- Secure password hashing (Argon2id)
+- Session management with secure tokens
+- Input validation and sanitization
+- Protection against common web vulnerabilities
+
+14.2 Your Security Responsibilities
+
+Since all data is stored on your infrastructure, you are responsible
+for:
+- Keeping the Software updated
+- Securing your server and database
+- Implementing network security measures
+- Managing user access and authentication
+- Creating and securing backups
+
+
+15. CHANGES TO THIS PRIVACY POLICY
+
+We may update this Privacy Policy from time to time. Material changes
+will be communicated through:
+- Updated "Last Updated" date at the top of this Policy
+- Notice on our website
+- Notice within the Software (for significant changes)
+
+We encourage you to review this Privacy Policy periodically.
+
+
+16. GDPR COMPLIANCE
+
+Finsys complies with the General Data Protection Regulation (EU) 2016/679.
+
+Summary of Our Data Processing:
+- We only collect personal data (email, name) when you purchase a license
+- Legal basis: Contract performance and legal obligation
+- Data is stored securely in the EU (Poland)
+- Retention: 7 years for tax records, 3 years for correspondence
+- No automated decision-making or profiling
+- No data sold or shared for marketing purposes
+
+Your GDPR Rights (Articles 15-22):
+You have the right to access, rectify, erase, restrict processing,
+data portability, and object to processing of your personal data.
+
+To exercise any of these rights, contact: enterprise@dockhand.pro
+We will respond within 30 days as required by GDPR.
+
+
+17. YOUR RIGHTS
+
+If you are located in the European Economic Area (EEA), United Kingdom,
+or other jurisdiction with data protection laws, you have rights
+regarding personal data we hold about you (from direct communications
+or license purchases):
+
+- Access: Request access to personal data we hold about you
+- Rectification: Request correction of inaccurate data
+- Erasure: Request deletion of your data
+- Restriction: Request restriction of processing
+- Portability: Request a copy of your data in portable format
+- Objection: Object to processing based on legitimate interests
+- Complaint: Lodge a complaint with a supervisory authority
+
+To exercise these rights, contact us at enterprise@dockhand.pro.
+
+Note: These rights apply to data WE hold (from direct communication or
+license purchases), not to data in YOUR Software installation. For data
+in your installation, YOU are the data controller and responsible for
+handling such requests from your users.
+
+
+18. SUPERVISORY AUTHORITY
+
+If you are located in Poland, the relevant supervisory authority is:
+
+Urzad Ochrony Danych Osobowych (UODO)
+ul. Stawki 2
+00-193 Warszawa
+Poland
+https://uodo.gov.pl
+
+If you are located in another EEA country, you may contact your local
+data protection authority.
+
+
+19. CONTACT US
+
+For any privacy-related questions, concerns, or requests:
+
+Finsys Jaroslaw Krochmalski
+ul. Borki 6
+05-119 Jozefow
+Poland
+
+Email: enterprise@dockhand.pro
+Website: https://dockhand.pro
+
+
+================================================================================
+SUMMARY
+
+Dockhand is a privacy-respecting application:
+- All data stays on YOUR infrastructure
+- NO data is sent to Finsys servers
+- NO telemetry or analytics
+- YOU are the data controller for your installation
+- Finsys has NO access to your data
+
+We believe privacy is a fundamental right, and we have designed Dockhand
+to respect that right by ensuring you maintain complete control over your
+data at all times.
+================================================================================
+
+Copyright (c) 2025-2026 Finsys Jaroslaw Krochmalski. All rights reserved.
diff --git a/README.md b/README.md
index 4d77500..e687248 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
@@ -7,8 +7,8 @@
- Website •
- Documentation •
+ Website •
+ Documentation •
License
@@ -16,7 +16,7 @@
## About
-Dockhand is a modern, efficient Docker management application providing real-time container management, Compose stack orchestration, and multi-environment support.
+Dockhand is a modern, efficient Docker management application providing real-time container management, Compose stack orchestration, and multi-environment support. All in a lightweight, secure and privacy-focused package.
### Features
@@ -30,10 +30,11 @@ Dockhand is a modern, efficient Docker management application providing real-tim
## Tech Stack
+- **Base**: own OS layer built from scratch using Wolfi packages via apko. Every package is explicitly declared in the Dockerfile.
- **Frontend**: SvelteKit 2, Svelte 5, shadcn-svelte, TailwindCSS
- **Backend**: Bun runtime with SvelteKit API routes
- **Database**: SQLite or PostgreSQL via Drizzle ORM
-- **Docker**: Dockerode library
+- **Docker**: direct docker API calls.
## License
@@ -47,10 +48,18 @@ Dockhand is licensed under the [Business Source License 1.1](LICENSE.txt) (BSL 1
See [LICENSE.txt](LICENSE.txt) for full terms.
+
+
+
+
+
+
## Links
-- **Website**: [https://dockhand.io](https://dockhand.io)
-- **Documentation**: [https://dockhand.io/docs](https://dockhand.io/docs)
+- **Website**: [https://dockhand.pro](https://dockhand.pro)
+- **Documentation**: [https://dockhand.pro/manual](https://dockhand.pro/manual)
---
diff --git a/bunfig.toml b/bunfig.toml
new file mode 100644
index 0000000..51bc0ff
--- /dev/null
+++ b/bunfig.toml
@@ -0,0 +1,9 @@
+# Bun configuration for Dockhand
+
+[install]
+# Use exact versions for reproducible builds
+exact = true
+
+[run]
+# Enable source maps for better error messages
+sourcemap = "external"
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..c5d91b4
--- /dev/null
+++ b/components.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "https://shadcn-svelte.com/schema.json",
+ "tailwind": {
+ "css": "src/app.css",
+ "baseColor": "slate"
+ },
+ "aliases": {
+ "components": "$lib/components",
+ "utils": "$lib/utils",
+ "ui": "$lib/components/ui",
+ "hooks": "$lib/hooks",
+ "lib": "$lib"
+ },
+ "typescript": true,
+ "registry": "https://shadcn-svelte.com/registry"
+}
diff --git a/docker-compose-postgresql.yaml b/docker-compose-postgresql.yaml
new file mode 100644
index 0000000..a1fa941
--- /dev/null
+++ b/docker-compose-postgresql.yaml
@@ -0,0 +1,25 @@
+services:
+ postgres:
+ image: postgres:16-alpine
+ environment:
+ POSTGRES_USER: dockhand
+ POSTGRES_PASSWORD: changeme
+ POSTGRES_DB: dockhand
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+
+ dockhand:
+ image: fnsys/dockhand:latest
+ ports:
+ - 3000:3000
+ environment:
+ DATABASE_URL: postgres://dockhand:changeme@postgres:5432/dockhand
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - dockhand_data:/app/data
+ depends_on:
+ - postgres
+
+volumes:
+ postgres_data:
+ dockhand_data:
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000..b83f7c4
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,13 @@
+services:
+ dockhand:
+ image: fnsys/dockhand:latest
+ container_name: dockhand
+ restart: unless-stopped
+ ports:
+ - 3000:3000
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - dockhand_data:/app/data
+
+volumes:
+ dockhand_data:
\ No newline at end of file
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
new file mode 100644
index 0000000..82ab463
--- /dev/null
+++ b/docker-entrypoint.sh
@@ -0,0 +1,191 @@
+#!/bin/sh
+set -e
+
+# Dockhand Docker Entrypoint
+# === Configuration ===
+PUID=${PUID:-1001}
+PGID=${PGID:-1001}
+
+# === Detect if running as root ===
+RUNNING_AS_ROOT=false
+if [ "$(id -u)" = "0" ]; then
+ RUNNING_AS_ROOT=true
+fi
+
+# === Non-root mode (user: directive in compose) ===
+# If container started as non-root, skip all user management and run directly
+if [ "$RUNNING_AS_ROOT" = "false" ]; then
+ echo "Running as user $(id -u):$(id -g) (set via container user directive)"
+
+ # Ensure data directories exist (user must have write access to DATA_DIR via volume mount)
+ DATA_DIR="${DATA_DIR:-/app/data}"
+ if [ ! -d "$DATA_DIR/db" ]; then
+ echo "Creating database directory at $DATA_DIR/db"
+ mkdir -p "$DATA_DIR/db" 2>/dev/null || {
+ echo "ERROR: Cannot create $DATA_DIR/db directory"
+ echo "Ensure the data volume is mounted with correct permissions for user $(id -u):$(id -g)"
+ echo ""
+ echo "Example docker-compose.yml:"
+ echo " volumes:"
+ echo " - ./data:/app/data # This directory must be writable by user $(id -u)"
+ exit 1
+ }
+ fi
+ if [ ! -d "$DATA_DIR/stacks" ]; then
+ mkdir -p "$DATA_DIR/stacks" 2>/dev/null || true
+ fi
+
+ # Check Docker socket access if mounted
+ SOCKET_PATH="/var/run/docker.sock"
+ if [ -S "$SOCKET_PATH" ]; then
+ if test -r "$SOCKET_PATH" 2>/dev/null; then
+ echo "Docker socket accessible at $SOCKET_PATH"
+ # Detect hostname from Docker if not set
+ if [ -z "$DOCKHAND_HOSTNAME" ]; then
+ DETECTED_HOSTNAME=$(curl -s --unix-socket "$SOCKET_PATH" http://localhost/info 2>/dev/null | sed -n 's/.*"Name":"\([^"]*\)".*/\1/p')
+ if [ -n "$DETECTED_HOSTNAME" ]; then
+ export DOCKHAND_HOSTNAME="$DETECTED_HOSTNAME"
+ echo "Detected Docker host hostname: $DOCKHAND_HOSTNAME"
+ fi
+ fi
+ else
+ SOCKET_GID=$(stat -c '%g' "$SOCKET_PATH" 2>/dev/null || echo "unknown")
+ echo "WARNING: Docker socket not readable by user $(id -u)"
+ echo "Add --group-add $SOCKET_GID to your docker run command"
+ fi
+ else
+ echo "No Docker socket found at $SOCKET_PATH"
+ echo "Configure Docker environments via the web UI (Settings > Environments)"
+ fi
+
+ # Run directly as current user (no su-exec needed)
+ if [ "$1" = "" ]; then
+ exec bun run ./build/index.js
+ else
+ exec "$@"
+ fi
+fi
+
+# === User Setup ===
+# Root mode: PUID=0 requested OR already running as root with default PUID/PGID
+if [ "$PUID" = "0" ]; then
+ echo "Running as root user (PUID=0)"
+ RUN_USER="root"
+elif [ "$RUNNING_AS_ROOT" = "true" ] && [ "$PUID" = "1001" ] && [ "$PGID" = "1001" ]; then
+ echo "Running as root user"
+ RUN_USER="root"
+else
+ RUN_USER="dockhand"
+ # Only modify if PUID/PGID differ from image defaults (1001:1001)
+ if [ "$PUID" != "1001" ] || [ "$PGID" != "1001" ]; then
+ echo "Configuring user with PUID=$PUID PGID=$PGID"
+
+ # Remove existing dockhand user/group (using busybox commands)
+ deluser dockhand 2>/dev/null || true
+ delgroup dockhand 2>/dev/null || true
+
+ # Check for UID conflicts - warn but don't delete other users
+ SKIP_USER_CREATE=false
+ if getent passwd "$PUID" >/dev/null 2>&1; then
+ EXISTING=$(getent passwd "$PUID" | cut -d: -f1)
+ if [ "$EXISTING" = "bun" ]; then
+ echo "Note: UID $PUID is used by the 'bun' runtime user - reusing it for dockhand"
+ echo "If upgrading from a previous version, you may need to fix data permissions:"
+ echo " chown -R $PUID:$PGID /path/to/your/data"
+ RUN_USER="bun"
+ SKIP_USER_CREATE=true
+ else
+ echo "WARNING: UID $PUID already in use by '$EXISTING'. Using default UID 1001."
+ PUID=1001
+ fi
+ fi
+
+ # Handle GID - reuse existing group or create new
+ if getent group "$PGID" >/dev/null 2>&1; then
+ TARGET_GROUP=$(getent group "$PGID" | cut -d: -f1)
+ else
+ addgroup -g "$PGID" dockhand
+ TARGET_GROUP="dockhand"
+ fi
+
+ if [ "$SKIP_USER_CREATE" = "false" ]; then
+ adduser -u "$PUID" -G "$TARGET_GROUP" -h /home/dockhand -D dockhand
+ fi
+ fi
+
+ # === Directory Ownership ===
+ chown -R "$RUN_USER":"$RUN_USER" /app/data 2>/dev/null || true
+ if [ "$RUN_USER" = "dockhand" ]; then
+ chown -R dockhand:dockhand /home/dockhand 2>/dev/null || true
+ fi
+
+ if [ -n "$DATA_DIR" ] && [ "$DATA_DIR" != "/app/data" ] && [ "$DATA_DIR" != "./data" ]; then
+ mkdir -p "$DATA_DIR"
+ chown -R "$RUN_USER":"$RUN_USER" "$DATA_DIR" 2>/dev/null || true
+ fi
+fi
+
+# === Docker Socket Access (Optional) ===
+# Check if Docker socket is mounted and accessible
+# Note: DOCKER_HOST with tcp:// requires configuring an environment via the web UI
+SOCKET_PATH="/var/run/docker.sock"
+
+if [ -S "$SOCKET_PATH" ]; then
+ # Socket exists - check if readable
+ if [ "$RUN_USER" != "root" ]; then
+ if ! su-exec "$RUN_USER" test -r "$SOCKET_PATH" 2>/dev/null; then
+ SOCKET_GID=$(stat -c '%g' "$SOCKET_PATH" 2>/dev/null || echo "unknown")
+ echo "WARNING: Docker socket at $SOCKET_PATH is not readable by $RUN_USER user"
+ echo ""
+ echo "To use local Docker, fix with one of these options:"
+ echo ""
+ echo " 1. Add container to docker group (GID: $SOCKET_GID):"
+ echo " docker run --group-add $SOCKET_GID ..."
+ echo ""
+ echo " 2. Use a socket proxy:"
+ echo " Configure a 'direct' environment pointing to tcp://socket-proxy:2375"
+ echo ""
+ echo " 3. Make socket world-readable (less secure):"
+ echo " chmod 666 /var/run/docker.sock"
+ echo ""
+ echo "Continuing startup - configure environments via the web UI..."
+ else
+ echo "Docker socket accessible at $SOCKET_PATH"
+ fi
+ else
+ echo "Docker socket accessible at $SOCKET_PATH"
+ fi
+
+ # === Detect Docker Host Hostname (for license validation) ===
+ # Query Docker API to get the real host hostname (not container ID)
+ if [ -z "$DOCKHAND_HOSTNAME" ]; then
+ DETECTED_HOSTNAME=$(curl -s --unix-socket "$SOCKET_PATH" http://localhost/info 2>/dev/null | sed -n 's/.*"Name":"\([^"]*\)".*/\1/p')
+ if [ -n "$DETECTED_HOSTNAME" ]; then
+ export DOCKHAND_HOSTNAME="$DETECTED_HOSTNAME"
+ echo "Detected Docker host hostname: $DOCKHAND_HOSTNAME"
+ fi
+ else
+ echo "Using configured hostname: $DOCKHAND_HOSTNAME"
+ fi
+else
+ echo "No local Docker socket mounted (this is normal when using socket-proxy or remote Docker)"
+ echo "Configure your Docker environment via the web UI: Settings > Environments"
+fi
+
+# === Run Application ===
+if [ "$RUN_USER" = "root" ]; then
+ # Running as root - execute directly
+ if [ "$1" = "" ]; then
+ exec bun run ./build/index.js
+ else
+ exec "$@"
+ fi
+else
+ # Running as non-root user
+ echo "Running as user: $RUN_USER"
+ if [ "$1" = "" ]; then
+ exec su-exec "$RUN_USER" bun run ./build/index.js
+ else
+ exec su-exec "$RUN_USER" "$@"
+ fi
+fi
diff --git a/drizzle-pg/0000_initial_schema.sql b/drizzle-pg/0000_initial_schema.sql
new file mode 100644
index 0000000..15c71a7
--- /dev/null
+++ b/drizzle-pg/0000_initial_schema.sql
@@ -0,0 +1,401 @@
+CREATE TABLE "audit_logs" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" integer,
+ "username" text NOT NULL,
+ "action" text NOT NULL,
+ "entity_type" text NOT NULL,
+ "entity_id" text,
+ "entity_name" text,
+ "environment_id" integer,
+ "description" text,
+ "details" text,
+ "ip_address" text,
+ "user_agent" text,
+ "created_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "auth_settings" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "auth_enabled" boolean DEFAULT false,
+ "default_provider" text DEFAULT 'local',
+ "session_timeout" integer DEFAULT 86400,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "auto_update_settings" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "environment_id" integer,
+ "container_name" text NOT NULL,
+ "enabled" boolean DEFAULT false,
+ "schedule_type" text DEFAULT 'daily',
+ "cron_expression" text,
+ "vulnerability_criteria" text DEFAULT 'never',
+ "last_checked" timestamp,
+ "last_updated" timestamp,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "auto_update_settings_environment_id_container_name_unique" UNIQUE("environment_id","container_name")
+);
+--> statement-breakpoint
+CREATE TABLE "config_sets" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" text NOT NULL,
+ "description" text,
+ "env_vars" text,
+ "labels" text,
+ "ports" text,
+ "volumes" text,
+ "network_mode" text DEFAULT 'bridge',
+ "restart_policy" text DEFAULT 'no',
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "config_sets_name_unique" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE "container_events" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "environment_id" integer,
+ "container_id" text NOT NULL,
+ "container_name" text,
+ "image" text,
+ "action" text NOT NULL,
+ "actor_attributes" text,
+ "timestamp" timestamp NOT NULL,
+ "created_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "environment_notifications" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "environment_id" integer NOT NULL,
+ "notification_id" integer NOT NULL,
+ "enabled" boolean DEFAULT true,
+ "event_types" text,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "environment_notifications_environment_id_notification_id_unique" UNIQUE("environment_id","notification_id")
+);
+--> statement-breakpoint
+CREATE TABLE "environments" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" text NOT NULL,
+ "host" text,
+ "port" integer DEFAULT 2375,
+ "protocol" text DEFAULT 'http',
+ "tls_ca" text,
+ "tls_cert" text,
+ "tls_key" text,
+ "tls_skip_verify" boolean DEFAULT false,
+ "icon" text DEFAULT 'globe',
+ "collect_activity" boolean DEFAULT true,
+ "collect_metrics" boolean DEFAULT true,
+ "highlight_changes" boolean DEFAULT true,
+ "labels" text,
+ "connection_type" text DEFAULT 'socket',
+ "socket_path" text DEFAULT '/var/run/docker.sock',
+ "hawser_token" text,
+ "hawser_last_seen" timestamp,
+ "hawser_agent_id" text,
+ "hawser_agent_name" text,
+ "hawser_version" text,
+ "hawser_capabilities" text,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "environments_name_unique" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE "git_credentials" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" text NOT NULL,
+ "auth_type" text DEFAULT 'none' NOT NULL,
+ "username" text,
+ "password" text,
+ "ssh_private_key" text,
+ "ssh_passphrase" text,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "git_credentials_name_unique" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE "git_repositories" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" text NOT NULL,
+ "url" text NOT NULL,
+ "branch" text DEFAULT 'main',
+ "credential_id" integer,
+ "compose_path" text DEFAULT 'docker-compose.yml',
+ "environment_id" integer,
+ "auto_update" boolean DEFAULT false,
+ "auto_update_schedule" text DEFAULT 'daily',
+ "auto_update_cron" text DEFAULT '0 3 * * *',
+ "webhook_enabled" boolean DEFAULT false,
+ "webhook_secret" text,
+ "last_sync" timestamp,
+ "last_commit" text,
+ "sync_status" text DEFAULT 'pending',
+ "sync_error" text,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "git_repositories_name_unique" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE "git_stacks" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "stack_name" text NOT NULL,
+ "environment_id" integer,
+ "repository_id" integer NOT NULL,
+ "compose_path" text DEFAULT 'docker-compose.yml',
+ "auto_update" boolean DEFAULT false,
+ "auto_update_schedule" text DEFAULT 'daily',
+ "auto_update_cron" text DEFAULT '0 3 * * *',
+ "webhook_enabled" boolean DEFAULT false,
+ "webhook_secret" text,
+ "last_sync" timestamp,
+ "last_commit" text,
+ "sync_status" text DEFAULT 'pending',
+ "sync_error" text,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "git_stacks_stack_name_environment_id_unique" UNIQUE("stack_name","environment_id")
+);
+--> statement-breakpoint
+CREATE TABLE "hawser_tokens" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "token" text NOT NULL,
+ "token_prefix" text NOT NULL,
+ "name" text NOT NULL,
+ "environment_id" integer,
+ "is_active" boolean DEFAULT true,
+ "last_used" timestamp,
+ "created_at" timestamp DEFAULT now(),
+ "expires_at" timestamp,
+ CONSTRAINT "hawser_tokens_token_unique" UNIQUE("token")
+);
+--> statement-breakpoint
+CREATE TABLE "host_metrics" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "environment_id" integer,
+ "cpu_percent" double precision NOT NULL,
+ "memory_percent" double precision NOT NULL,
+ "memory_used" bigint,
+ "memory_total" bigint,
+ "timestamp" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "ldap_config" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" text NOT NULL,
+ "enabled" boolean DEFAULT false,
+ "server_url" text NOT NULL,
+ "bind_dn" text,
+ "bind_password" text,
+ "base_dn" text NOT NULL,
+ "user_filter" text DEFAULT '(uid={{username}})',
+ "username_attribute" text DEFAULT 'uid',
+ "email_attribute" text DEFAULT 'mail',
+ "display_name_attribute" text DEFAULT 'cn',
+ "group_base_dn" text,
+ "group_filter" text,
+ "admin_group" text,
+ "role_mappings" text,
+ "tls_enabled" boolean DEFAULT false,
+ "tls_ca" text,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "notification_settings" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "type" text NOT NULL,
+ "name" text NOT NULL,
+ "enabled" boolean DEFAULT true,
+ "config" text NOT NULL,
+ "event_types" text,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "oidc_config" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" text NOT NULL,
+ "enabled" boolean DEFAULT false,
+ "issuer_url" text NOT NULL,
+ "client_id" text NOT NULL,
+ "client_secret" text NOT NULL,
+ "redirect_uri" text NOT NULL,
+ "scopes" text DEFAULT 'openid profile email',
+ "username_claim" text DEFAULT 'preferred_username',
+ "email_claim" text DEFAULT 'email',
+ "display_name_claim" text DEFAULT 'name',
+ "admin_claim" text,
+ "admin_value" text,
+ "role_mappings_claim" text DEFAULT 'groups',
+ "role_mappings" text,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "registries" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" text NOT NULL,
+ "url" text NOT NULL,
+ "username" text,
+ "password" text,
+ "is_default" boolean DEFAULT false,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "registries_name_unique" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE "roles" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "name" text NOT NULL,
+ "description" text,
+ "is_system" boolean DEFAULT false,
+ "permissions" text NOT NULL,
+ "environment_ids" text,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "roles_name_unique" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE "schedule_executions" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "schedule_type" text NOT NULL,
+ "schedule_id" integer NOT NULL,
+ "environment_id" integer,
+ "entity_name" text NOT NULL,
+ "triggered_by" text NOT NULL,
+ "triggered_at" timestamp NOT NULL,
+ "started_at" timestamp,
+ "completed_at" timestamp,
+ "duration" integer,
+ "status" text NOT NULL,
+ "error_message" text,
+ "details" text,
+ "logs" text,
+ "created_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "sessions" (
+ "id" text PRIMARY KEY NOT NULL,
+ "user_id" integer NOT NULL,
+ "provider" text NOT NULL,
+ "expires_at" timestamp NOT NULL,
+ "created_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "settings" (
+ "key" text PRIMARY KEY NOT NULL,
+ "value" text NOT NULL,
+ "updated_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+CREATE TABLE "stack_events" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "environment_id" integer,
+ "stack_name" text NOT NULL,
+ "event_type" text NOT NULL,
+ "timestamp" timestamp DEFAULT now(),
+ "metadata" text
+);
+--> statement-breakpoint
+CREATE TABLE "stack_sources" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "stack_name" text NOT NULL,
+ "environment_id" integer,
+ "source_type" text DEFAULT 'internal' NOT NULL,
+ "git_repository_id" integer,
+ "git_stack_id" integer,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "stack_sources_stack_name_environment_id_unique" UNIQUE("stack_name","environment_id")
+);
+--> statement-breakpoint
+CREATE TABLE "user_preferences" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" integer,
+ "environment_id" integer,
+ "key" text NOT NULL,
+ "value" text NOT NULL,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "user_preferences_user_id_environment_id_key_unique" UNIQUE("user_id","environment_id","key")
+);
+--> statement-breakpoint
+CREATE TABLE "user_roles" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "user_id" integer NOT NULL,
+ "role_id" integer NOT NULL,
+ "environment_id" integer,
+ "created_at" timestamp DEFAULT now(),
+ CONSTRAINT "user_roles_user_id_role_id_environment_id_unique" UNIQUE("user_id","role_id","environment_id")
+);
+--> statement-breakpoint
+CREATE TABLE "users" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "username" text NOT NULL,
+ "email" text,
+ "password_hash" text NOT NULL,
+ "display_name" text,
+ "avatar" text,
+ "auth_provider" text DEFAULT 'local',
+ "mfa_enabled" boolean DEFAULT false,
+ "mfa_secret" text,
+ "is_active" boolean DEFAULT true,
+ "last_login" timestamp,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "users_username_unique" UNIQUE("username")
+);
+--> statement-breakpoint
+CREATE TABLE "vulnerability_scans" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "environment_id" integer,
+ "image_id" text NOT NULL,
+ "image_name" text NOT NULL,
+ "scanner" text NOT NULL,
+ "scanned_at" timestamp NOT NULL,
+ "scan_duration" integer,
+ "critical_count" integer DEFAULT 0,
+ "high_count" integer DEFAULT 0,
+ "medium_count" integer DEFAULT 0,
+ "low_count" integer DEFAULT 0,
+ "negligible_count" integer DEFAULT 0,
+ "unknown_count" integer DEFAULT 0,
+ "vulnerabilities" text,
+ "error" text,
+ "created_at" timestamp DEFAULT now()
+);
+--> statement-breakpoint
+ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "auto_update_settings" ADD CONSTRAINT "auto_update_settings_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "container_events" ADD CONSTRAINT "container_events_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "environment_notifications" ADD CONSTRAINT "environment_notifications_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "environment_notifications" ADD CONSTRAINT "environment_notifications_notification_id_notification_settings_id_fk" FOREIGN KEY ("notification_id") REFERENCES "public"."notification_settings"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "git_repositories" ADD CONSTRAINT "git_repositories_credential_id_git_credentials_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."git_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "git_stacks" ADD CONSTRAINT "git_stacks_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "git_stacks" ADD CONSTRAINT "git_stacks_repository_id_git_repositories_id_fk" FOREIGN KEY ("repository_id") REFERENCES "public"."git_repositories"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "hawser_tokens" ADD CONSTRAINT "hawser_tokens_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "host_metrics" ADD CONSTRAINT "host_metrics_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "schedule_executions" ADD CONSTRAINT "schedule_executions_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "stack_events" ADD CONSTRAINT "stack_events_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "stack_sources" ADD CONSTRAINT "stack_sources_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "stack_sources" ADD CONSTRAINT "stack_sources_git_repository_id_git_repositories_id_fk" FOREIGN KEY ("git_repository_id") REFERENCES "public"."git_repositories"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "stack_sources" ADD CONSTRAINT "stack_sources_git_stack_id_git_stacks_id_fk" FOREIGN KEY ("git_stack_id") REFERENCES "public"."git_stacks"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_preferences" ADD CONSTRAINT "user_preferences_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_preferences" ADD CONSTRAINT "user_preferences_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "vulnerability_scans" ADD CONSTRAINT "vulnerability_scans_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+CREATE INDEX "audit_logs_user_id_idx" ON "audit_logs" USING btree ("user_id");--> statement-breakpoint
+CREATE INDEX "audit_logs_created_at_idx" ON "audit_logs" USING btree ("created_at");--> statement-breakpoint
+CREATE INDEX "container_events_env_timestamp_idx" ON "container_events" USING btree ("environment_id","timestamp");--> statement-breakpoint
+CREATE INDEX "host_metrics_env_timestamp_idx" ON "host_metrics" USING btree ("environment_id","timestamp");--> statement-breakpoint
+CREATE INDEX "schedule_executions_type_id_idx" ON "schedule_executions" USING btree ("schedule_type","schedule_id");--> statement-breakpoint
+CREATE INDEX "sessions_user_id_idx" ON "sessions" USING btree ("user_id");--> statement-breakpoint
+CREATE INDEX "sessions_expires_at_idx" ON "sessions" USING btree ("expires_at");--> statement-breakpoint
+CREATE INDEX "vulnerability_scans_env_image_idx" ON "vulnerability_scans" USING btree ("environment_id","image_id");
\ No newline at end of file
diff --git a/drizzle-pg/0001_add_stack_env_vars.sql b/drizzle-pg/0001_add_stack_env_vars.sql
new file mode 100644
index 0000000..8ee2010
--- /dev/null
+++ b/drizzle-pg/0001_add_stack_env_vars.sql
@@ -0,0 +1,14 @@
+CREATE TABLE "stack_environment_variables" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "stack_name" text NOT NULL,
+ "environment_id" integer,
+ "key" text NOT NULL,
+ "value" text NOT NULL,
+ "is_secret" boolean DEFAULT false,
+ "created_at" timestamp DEFAULT now(),
+ "updated_at" timestamp DEFAULT now(),
+ CONSTRAINT "stack_environment_variables_stack_name_environment_id_key_unique" UNIQUE("stack_name","environment_id","key")
+);
+--> statement-breakpoint
+ALTER TABLE "git_stacks" ADD COLUMN "env_file_path" text;--> statement-breakpoint
+ALTER TABLE "stack_environment_variables" ADD CONSTRAINT "stack_environment_variables_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;
\ No newline at end of file
diff --git a/drizzle-pg/0002_add_pending_container_updates.sql b/drizzle-pg/0002_add_pending_container_updates.sql
new file mode 100644
index 0000000..ac712d1
--- /dev/null
+++ b/drizzle-pg/0002_add_pending_container_updates.sql
@@ -0,0 +1,12 @@
+CREATE TABLE "pending_container_updates" (
+ "id" serial PRIMARY KEY NOT NULL,
+ "environment_id" integer NOT NULL,
+ "container_id" text NOT NULL,
+ "container_name" text NOT NULL,
+ "current_image" text NOT NULL,
+ "checked_at" timestamp DEFAULT now(),
+ "created_at" timestamp DEFAULT now(),
+ CONSTRAINT "pending_container_updates_environment_id_container_id_unique" UNIQUE("environment_id","container_id")
+);
+--> statement-breakpoint
+ALTER TABLE "pending_container_updates" ADD CONSTRAINT "pending_container_updates_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;
\ No newline at end of file
diff --git a/drizzle-pg/0003_add_stack_paths.sql b/drizzle-pg/0003_add_stack_paths.sql
new file mode 100644
index 0000000..8102648
--- /dev/null
+++ b/drizzle-pg/0003_add_stack_paths.sql
@@ -0,0 +1,2 @@
+ALTER TABLE "stack_sources" ADD COLUMN "compose_path" text;--> statement-breakpoint
+ALTER TABLE "stack_sources" ADD COLUMN "env_path" text;
\ No newline at end of file
diff --git a/drizzle-pg/meta/0000_snapshot.json b/drizzle-pg/meta/0000_snapshot.json
new file mode 100644
index 0000000..c2004b5
--- /dev/null
+++ b/drizzle-pg/meta/0000_snapshot.json
@@ -0,0 +1,2709 @@
+{
+ "id": "50905243-3288-41de-8cef-87b4e546d7cd",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.audit_logs": {
+ "name": "audit_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_logs_user_id_idx": {
+ "name": "audit_logs_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_logs_created_at_idx": {
+ "name": "audit_logs_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "audit_logs_user_id_users_id_fk": {
+ "name": "audit_logs_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_logs_environment_id_environments_id_fk": {
+ "name": "audit_logs_environment_id_environments_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.auth_settings": {
+ "name": "auth_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "auth_enabled": {
+ "name": "auth_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "default_provider": {
+ "name": "default_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'local'"
+ },
+ "session_timeout": {
+ "name": "session_timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 86400
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.auto_update_settings": {
+ "name": "auto_update_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "cron_expression": {
+ "name": "cron_expression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vulnerability_criteria": {
+ "name": "vulnerability_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'never'"
+ },
+ "last_checked": {
+ "name": "last_checked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_updated": {
+ "name": "last_updated",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "auto_update_settings_environment_id_environments_id_fk": {
+ "name": "auto_update_settings_environment_id_environments_id_fk",
+ "tableFrom": "auto_update_settings",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "auto_update_settings_environment_id_container_name_unique": {
+ "name": "auto_update_settings_environment_id_container_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "environment_id",
+ "container_name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.config_sets": {
+ "name": "config_sets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env_vars": {
+ "name": "env_vars",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ports": {
+ "name": "ports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumes": {
+ "name": "volumes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "network_mode": {
+ "name": "network_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'bridge'"
+ },
+ "restart_policy": {
+ "name": "restart_policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'no'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "config_sets_name_unique": {
+ "name": "config_sets_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.container_events": {
+ "name": "container_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "actor_attributes": {
+ "name": "actor_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "container_events_env_timestamp_idx": {
+ "name": "container_events_env_timestamp_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "timestamp",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "container_events_environment_id_environments_id_fk": {
+ "name": "container_events_environment_id_environments_id_fk",
+ "tableFrom": "container_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environment_notifications": {
+ "name": "environment_notifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "notification_id": {
+ "name": "notification_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "environment_notifications_environment_id_environments_id_fk": {
+ "name": "environment_notifications_environment_id_environments_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "environment_notifications_notification_id_notification_settings_id_fk": {
+ "name": "environment_notifications_notification_id_notification_settings_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "notification_settings",
+ "columnsFrom": [
+ "notification_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "environment_notifications_environment_id_notification_id_unique": {
+ "name": "environment_notifications_environment_id_notification_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "environment_id",
+ "notification_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environments": {
+ "name": "environments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 2375
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'http'"
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_cert": {
+ "name": "tls_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_key": {
+ "name": "tls_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_skip_verify": {
+ "name": "tls_skip_verify",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'globe'"
+ },
+ "collect_activity": {
+ "name": "collect_activity",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "collect_metrics": {
+ "name": "collect_metrics",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "highlight_changes": {
+ "name": "highlight_changes",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connection_type": {
+ "name": "connection_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'socket'"
+ },
+ "socket_path": {
+ "name": "socket_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/var/run/docker.sock'"
+ },
+ "hawser_token": {
+ "name": "hawser_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_last_seen": {
+ "name": "hawser_last_seen",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_agent_id": {
+ "name": "hawser_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_agent_name": {
+ "name": "hawser_agent_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_version": {
+ "name": "hawser_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_capabilities": {
+ "name": "hawser_capabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "environments_name_unique": {
+ "name": "environments_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_credentials": {
+ "name": "git_credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ssh_private_key": {
+ "name": "ssh_private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ssh_passphrase": {
+ "name": "ssh_passphrase",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_credentials_name_unique": {
+ "name": "git_credentials_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_repositories": {
+ "name": "git_repositories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'main'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'docker-compose.yml'"
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_repositories_credential_id_git_credentials_id_fk": {
+ "name": "git_repositories_credential_id_git_credentials_id_fk",
+ "tableFrom": "git_repositories",
+ "tableTo": "git_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_repositories_name_unique": {
+ "name": "git_repositories_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_stacks": {
+ "name": "git_stacks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "repository_id": {
+ "name": "repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'docker-compose.yml'"
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_stacks_environment_id_environments_id_fk": {
+ "name": "git_stacks_environment_id_environments_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_stacks_repository_id_git_repositories_id_fk": {
+ "name": "git_stacks_repository_id_git_repositories_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_stacks_stack_name_environment_id_unique": {
+ "name": "git_stacks_stack_name_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hawser_tokens": {
+ "name": "hawser_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_prefix": {
+ "name": "token_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "hawser_tokens_environment_id_environments_id_fk": {
+ "name": "hawser_tokens_environment_id_environments_id_fk",
+ "tableFrom": "hawser_tokens",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "hawser_tokens_token_unique": {
+ "name": "hawser_tokens_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.host_metrics": {
+ "name": "host_metrics",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpu_percent": {
+ "name": "cpu_percent",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "memory_percent": {
+ "name": "memory_percent",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "memory_used": {
+ "name": "memory_used",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memory_total": {
+ "name": "memory_total",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "host_metrics_env_timestamp_idx": {
+ "name": "host_metrics_env_timestamp_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "timestamp",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "host_metrics_environment_id_environments_id_fk": {
+ "name": "host_metrics_environment_id_environments_id_fk",
+ "tableFrom": "host_metrics",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ldap_config": {
+ "name": "ldap_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "server_url": {
+ "name": "server_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bind_dn": {
+ "name": "bind_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bind_password": {
+ "name": "bind_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "base_dn": {
+ "name": "base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_filter": {
+ "name": "user_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'(uid={{username}})'"
+ },
+ "username_attribute": {
+ "name": "username_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'uid'"
+ },
+ "email_attribute": {
+ "name": "email_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'mail'"
+ },
+ "display_name_attribute": {
+ "name": "display_name_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'cn'"
+ },
+ "group_base_dn": {
+ "name": "group_base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_filter": {
+ "name": "group_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "admin_group": {
+ "name": "admin_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_enabled": {
+ "name": "tls_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notification_settings": {
+ "name": "notification_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oidc_config": {
+ "name": "oidc_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "issuer_url": {
+ "name": "issuer_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'openid profile email'"
+ },
+ "username_claim": {
+ "name": "username_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'preferred_username'"
+ },
+ "email_claim": {
+ "name": "email_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'email'"
+ },
+ "display_name_claim": {
+ "name": "display_name_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'name'"
+ },
+ "admin_claim": {
+ "name": "admin_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "admin_value": {
+ "name": "admin_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role_mappings_claim": {
+ "name": "role_mappings_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'groups'"
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.registries": {
+ "name": "registries",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "registries_name_unique": {
+ "name": "registries_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.roles": {
+ "name": "roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_system": {
+ "name": "is_system",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_ids": {
+ "name": "environment_ids",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "roles_name_unique": {
+ "name": "roles_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.schedule_executions": {
+ "name": "schedule_executions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "triggered_by": {
+ "name": "triggered_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "triggered_at": {
+ "name": "triggered_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "logs": {
+ "name": "logs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "schedule_executions_type_id_idx": {
+ "name": "schedule_executions_type_id_idx",
+ "columns": [
+ {
+ "expression": "schedule_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "schedule_executions_environment_id_environments_id_fk": {
+ "name": "schedule_executions_environment_id_environments_id_fk",
+ "tableFrom": "schedule_executions",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "sessions_user_id_idx": {
+ "name": "sessions_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sessions_expires_at_idx": {
+ "name": "sessions_expires_at_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.settings": {
+ "name": "settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.stack_events": {
+ "name": "stack_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_events_environment_id_environments_id_fk": {
+ "name": "stack_events_environment_id_environments_id_fk",
+ "tableFrom": "stack_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.stack_sources": {
+ "name": "stack_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'internal'"
+ },
+ "git_repository_id": {
+ "name": "git_repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "git_stack_id": {
+ "name": "git_stack_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_sources_environment_id_environments_id_fk": {
+ "name": "stack_sources_environment_id_environments_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_repository_id_git_repositories_id_fk": {
+ "name": "stack_sources_git_repository_id_git_repositories_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "git_repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_stack_id_git_stacks_id_fk": {
+ "name": "stack_sources_git_stack_id_git_stacks_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_stacks",
+ "columnsFrom": [
+ "git_stack_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "stack_sources_stack_name_environment_id_unique": {
+ "name": "stack_sources_stack_name_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_preferences": {
+ "name": "user_preferences",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_preferences_user_id_users_id_fk": {
+ "name": "user_preferences_user_id_users_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_preferences_environment_id_environments_id_fk": {
+ "name": "user_preferences_environment_id_environments_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_preferences_user_id_environment_id_key_unique": {
+ "name": "user_preferences_user_id_environment_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id",
+ "environment_id",
+ "key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_role_id_roles_id_fk": {
+ "name": "user_roles_role_id_roles_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_environment_id_environments_id_fk": {
+ "name": "user_roles_environment_id_environments_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_roles_user_id_role_id_environment_id_unique": {
+ "name": "user_roles_user_id_role_id_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id",
+ "role_id",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar": {
+ "name": "avatar",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_provider": {
+ "name": "auth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'local'"
+ },
+ "mfa_enabled": {
+ "name": "mfa_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "mfa_secret": {
+ "name": "mfa_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.vulnerability_scans": {
+ "name": "vulnerability_scans",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_id": {
+ "name": "image_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image_name": {
+ "name": "image_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scanner": {
+ "name": "scanner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scanned_at": {
+ "name": "scanned_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scan_duration": {
+ "name": "scan_duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "critical_count": {
+ "name": "critical_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "high_count": {
+ "name": "high_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "medium_count": {
+ "name": "medium_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "low_count": {
+ "name": "low_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "negligible_count": {
+ "name": "negligible_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "unknown_count": {
+ "name": "unknown_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "vulnerabilities": {
+ "name": "vulnerabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "vulnerability_scans_env_image_idx": {
+ "name": "vulnerability_scans_env_image_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "image_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "vulnerability_scans_environment_id_environments_id_fk": {
+ "name": "vulnerability_scans_environment_id_environments_id_fk",
+ "tableFrom": "vulnerability_scans",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle-pg/meta/0001_snapshot.json b/drizzle-pg/meta/0001_snapshot.json
new file mode 100644
index 0000000..c972fe5
--- /dev/null
+++ b/drizzle-pg/meta/0001_snapshot.json
@@ -0,0 +1,2803 @@
+{
+ "id": "31d336d0-689e-4403-b49e-308e13df0014",
+ "prevId": "50905243-3288-41de-8cef-87b4e546d7cd",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.audit_logs": {
+ "name": "audit_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_logs_user_id_idx": {
+ "name": "audit_logs_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_logs_created_at_idx": {
+ "name": "audit_logs_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "audit_logs_user_id_users_id_fk": {
+ "name": "audit_logs_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_logs_environment_id_environments_id_fk": {
+ "name": "audit_logs_environment_id_environments_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.auth_settings": {
+ "name": "auth_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "auth_enabled": {
+ "name": "auth_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "default_provider": {
+ "name": "default_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'local'"
+ },
+ "session_timeout": {
+ "name": "session_timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 86400
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.auto_update_settings": {
+ "name": "auto_update_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "cron_expression": {
+ "name": "cron_expression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vulnerability_criteria": {
+ "name": "vulnerability_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'never'"
+ },
+ "last_checked": {
+ "name": "last_checked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_updated": {
+ "name": "last_updated",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "auto_update_settings_environment_id_environments_id_fk": {
+ "name": "auto_update_settings_environment_id_environments_id_fk",
+ "tableFrom": "auto_update_settings",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "auto_update_settings_environment_id_container_name_unique": {
+ "name": "auto_update_settings_environment_id_container_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "environment_id",
+ "container_name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.config_sets": {
+ "name": "config_sets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env_vars": {
+ "name": "env_vars",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ports": {
+ "name": "ports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumes": {
+ "name": "volumes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "network_mode": {
+ "name": "network_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'bridge'"
+ },
+ "restart_policy": {
+ "name": "restart_policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'no'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "config_sets_name_unique": {
+ "name": "config_sets_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.container_events": {
+ "name": "container_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "actor_attributes": {
+ "name": "actor_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "container_events_env_timestamp_idx": {
+ "name": "container_events_env_timestamp_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "timestamp",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "container_events_environment_id_environments_id_fk": {
+ "name": "container_events_environment_id_environments_id_fk",
+ "tableFrom": "container_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environment_notifications": {
+ "name": "environment_notifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "notification_id": {
+ "name": "notification_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "environment_notifications_environment_id_environments_id_fk": {
+ "name": "environment_notifications_environment_id_environments_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "environment_notifications_notification_id_notification_settings_id_fk": {
+ "name": "environment_notifications_notification_id_notification_settings_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "notification_settings",
+ "columnsFrom": [
+ "notification_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "environment_notifications_environment_id_notification_id_unique": {
+ "name": "environment_notifications_environment_id_notification_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "environment_id",
+ "notification_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environments": {
+ "name": "environments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 2375
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'http'"
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_cert": {
+ "name": "tls_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_key": {
+ "name": "tls_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_skip_verify": {
+ "name": "tls_skip_verify",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'globe'"
+ },
+ "collect_activity": {
+ "name": "collect_activity",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "collect_metrics": {
+ "name": "collect_metrics",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "highlight_changes": {
+ "name": "highlight_changes",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connection_type": {
+ "name": "connection_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'socket'"
+ },
+ "socket_path": {
+ "name": "socket_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/var/run/docker.sock'"
+ },
+ "hawser_token": {
+ "name": "hawser_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_last_seen": {
+ "name": "hawser_last_seen",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_agent_id": {
+ "name": "hawser_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_agent_name": {
+ "name": "hawser_agent_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_version": {
+ "name": "hawser_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_capabilities": {
+ "name": "hawser_capabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "environments_name_unique": {
+ "name": "environments_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_credentials": {
+ "name": "git_credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ssh_private_key": {
+ "name": "ssh_private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ssh_passphrase": {
+ "name": "ssh_passphrase",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_credentials_name_unique": {
+ "name": "git_credentials_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_repositories": {
+ "name": "git_repositories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'main'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'docker-compose.yml'"
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_repositories_credential_id_git_credentials_id_fk": {
+ "name": "git_repositories_credential_id_git_credentials_id_fk",
+ "tableFrom": "git_repositories",
+ "tableTo": "git_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_repositories_name_unique": {
+ "name": "git_repositories_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_stacks": {
+ "name": "git_stacks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "repository_id": {
+ "name": "repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'docker-compose.yml'"
+ },
+ "env_file_path": {
+ "name": "env_file_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_stacks_environment_id_environments_id_fk": {
+ "name": "git_stacks_environment_id_environments_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_stacks_repository_id_git_repositories_id_fk": {
+ "name": "git_stacks_repository_id_git_repositories_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_stacks_stack_name_environment_id_unique": {
+ "name": "git_stacks_stack_name_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hawser_tokens": {
+ "name": "hawser_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_prefix": {
+ "name": "token_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "hawser_tokens_environment_id_environments_id_fk": {
+ "name": "hawser_tokens_environment_id_environments_id_fk",
+ "tableFrom": "hawser_tokens",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "hawser_tokens_token_unique": {
+ "name": "hawser_tokens_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.host_metrics": {
+ "name": "host_metrics",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpu_percent": {
+ "name": "cpu_percent",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "memory_percent": {
+ "name": "memory_percent",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "memory_used": {
+ "name": "memory_used",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memory_total": {
+ "name": "memory_total",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "host_metrics_env_timestamp_idx": {
+ "name": "host_metrics_env_timestamp_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "timestamp",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "host_metrics_environment_id_environments_id_fk": {
+ "name": "host_metrics_environment_id_environments_id_fk",
+ "tableFrom": "host_metrics",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ldap_config": {
+ "name": "ldap_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "server_url": {
+ "name": "server_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bind_dn": {
+ "name": "bind_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bind_password": {
+ "name": "bind_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "base_dn": {
+ "name": "base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_filter": {
+ "name": "user_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'(uid={{username}})'"
+ },
+ "username_attribute": {
+ "name": "username_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'uid'"
+ },
+ "email_attribute": {
+ "name": "email_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'mail'"
+ },
+ "display_name_attribute": {
+ "name": "display_name_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'cn'"
+ },
+ "group_base_dn": {
+ "name": "group_base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_filter": {
+ "name": "group_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "admin_group": {
+ "name": "admin_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_enabled": {
+ "name": "tls_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notification_settings": {
+ "name": "notification_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oidc_config": {
+ "name": "oidc_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "issuer_url": {
+ "name": "issuer_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'openid profile email'"
+ },
+ "username_claim": {
+ "name": "username_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'preferred_username'"
+ },
+ "email_claim": {
+ "name": "email_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'email'"
+ },
+ "display_name_claim": {
+ "name": "display_name_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'name'"
+ },
+ "admin_claim": {
+ "name": "admin_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "admin_value": {
+ "name": "admin_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role_mappings_claim": {
+ "name": "role_mappings_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'groups'"
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.registries": {
+ "name": "registries",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "registries_name_unique": {
+ "name": "registries_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.roles": {
+ "name": "roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_system": {
+ "name": "is_system",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_ids": {
+ "name": "environment_ids",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "roles_name_unique": {
+ "name": "roles_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.schedule_executions": {
+ "name": "schedule_executions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "triggered_by": {
+ "name": "triggered_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "triggered_at": {
+ "name": "triggered_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "logs": {
+ "name": "logs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "schedule_executions_type_id_idx": {
+ "name": "schedule_executions_type_id_idx",
+ "columns": [
+ {
+ "expression": "schedule_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "schedule_executions_environment_id_environments_id_fk": {
+ "name": "schedule_executions_environment_id_environments_id_fk",
+ "tableFrom": "schedule_executions",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "sessions_user_id_idx": {
+ "name": "sessions_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sessions_expires_at_idx": {
+ "name": "sessions_expires_at_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.settings": {
+ "name": "settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.stack_environment_variables": {
+ "name": "stack_environment_variables",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_secret": {
+ "name": "is_secret",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_environment_variables_environment_id_environments_id_fk": {
+ "name": "stack_environment_variables_environment_id_environments_id_fk",
+ "tableFrom": "stack_environment_variables",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "stack_environment_variables_stack_name_environment_id_key_unique": {
+ "name": "stack_environment_variables_stack_name_environment_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "stack_name",
+ "environment_id",
+ "key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.stack_events": {
+ "name": "stack_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_events_environment_id_environments_id_fk": {
+ "name": "stack_events_environment_id_environments_id_fk",
+ "tableFrom": "stack_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.stack_sources": {
+ "name": "stack_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'internal'"
+ },
+ "git_repository_id": {
+ "name": "git_repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "git_stack_id": {
+ "name": "git_stack_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_sources_environment_id_environments_id_fk": {
+ "name": "stack_sources_environment_id_environments_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_repository_id_git_repositories_id_fk": {
+ "name": "stack_sources_git_repository_id_git_repositories_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "git_repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_stack_id_git_stacks_id_fk": {
+ "name": "stack_sources_git_stack_id_git_stacks_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_stacks",
+ "columnsFrom": [
+ "git_stack_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "stack_sources_stack_name_environment_id_unique": {
+ "name": "stack_sources_stack_name_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_preferences": {
+ "name": "user_preferences",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_preferences_user_id_users_id_fk": {
+ "name": "user_preferences_user_id_users_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_preferences_environment_id_environments_id_fk": {
+ "name": "user_preferences_environment_id_environments_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_preferences_user_id_environment_id_key_unique": {
+ "name": "user_preferences_user_id_environment_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id",
+ "environment_id",
+ "key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_role_id_roles_id_fk": {
+ "name": "user_roles_role_id_roles_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_environment_id_environments_id_fk": {
+ "name": "user_roles_environment_id_environments_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_roles_user_id_role_id_environment_id_unique": {
+ "name": "user_roles_user_id_role_id_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id",
+ "role_id",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar": {
+ "name": "avatar",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_provider": {
+ "name": "auth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'local'"
+ },
+ "mfa_enabled": {
+ "name": "mfa_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "mfa_secret": {
+ "name": "mfa_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.vulnerability_scans": {
+ "name": "vulnerability_scans",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_id": {
+ "name": "image_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image_name": {
+ "name": "image_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scanner": {
+ "name": "scanner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scanned_at": {
+ "name": "scanned_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scan_duration": {
+ "name": "scan_duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "critical_count": {
+ "name": "critical_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "high_count": {
+ "name": "high_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "medium_count": {
+ "name": "medium_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "low_count": {
+ "name": "low_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "negligible_count": {
+ "name": "negligible_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "unknown_count": {
+ "name": "unknown_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "vulnerabilities": {
+ "name": "vulnerabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "vulnerability_scans_env_image_idx": {
+ "name": "vulnerability_scans_env_image_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "image_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "vulnerability_scans_environment_id_environments_id_fk": {
+ "name": "vulnerability_scans_environment_id_environments_id_fk",
+ "tableFrom": "vulnerability_scans",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle-pg/meta/0002_snapshot.json b/drizzle-pg/meta/0002_snapshot.json
new file mode 100644
index 0000000..209f367
--- /dev/null
+++ b/drizzle-pg/meta/0002_snapshot.json
@@ -0,0 +1,2883 @@
+{
+ "id": "eef8322a-0ccc-418c-b0f6-f51972a1850e",
+ "prevId": "31d336d0-689e-4403-b49e-308e13df0014",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.audit_logs": {
+ "name": "audit_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_logs_user_id_idx": {
+ "name": "audit_logs_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_logs_created_at_idx": {
+ "name": "audit_logs_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "audit_logs_user_id_users_id_fk": {
+ "name": "audit_logs_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_logs_environment_id_environments_id_fk": {
+ "name": "audit_logs_environment_id_environments_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.auth_settings": {
+ "name": "auth_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "auth_enabled": {
+ "name": "auth_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "default_provider": {
+ "name": "default_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'local'"
+ },
+ "session_timeout": {
+ "name": "session_timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 86400
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.auto_update_settings": {
+ "name": "auto_update_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "cron_expression": {
+ "name": "cron_expression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vulnerability_criteria": {
+ "name": "vulnerability_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'never'"
+ },
+ "last_checked": {
+ "name": "last_checked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_updated": {
+ "name": "last_updated",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "auto_update_settings_environment_id_environments_id_fk": {
+ "name": "auto_update_settings_environment_id_environments_id_fk",
+ "tableFrom": "auto_update_settings",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "auto_update_settings_environment_id_container_name_unique": {
+ "name": "auto_update_settings_environment_id_container_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "environment_id",
+ "container_name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.config_sets": {
+ "name": "config_sets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env_vars": {
+ "name": "env_vars",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ports": {
+ "name": "ports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumes": {
+ "name": "volumes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "network_mode": {
+ "name": "network_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'bridge'"
+ },
+ "restart_policy": {
+ "name": "restart_policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'no'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "config_sets_name_unique": {
+ "name": "config_sets_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.container_events": {
+ "name": "container_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "actor_attributes": {
+ "name": "actor_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "container_events_env_timestamp_idx": {
+ "name": "container_events_env_timestamp_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "timestamp",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "container_events_environment_id_environments_id_fk": {
+ "name": "container_events_environment_id_environments_id_fk",
+ "tableFrom": "container_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environment_notifications": {
+ "name": "environment_notifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "notification_id": {
+ "name": "notification_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "environment_notifications_environment_id_environments_id_fk": {
+ "name": "environment_notifications_environment_id_environments_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "environment_notifications_notification_id_notification_settings_id_fk": {
+ "name": "environment_notifications_notification_id_notification_settings_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "notification_settings",
+ "columnsFrom": [
+ "notification_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "environment_notifications_environment_id_notification_id_unique": {
+ "name": "environment_notifications_environment_id_notification_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "environment_id",
+ "notification_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environments": {
+ "name": "environments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 2375
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'http'"
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_cert": {
+ "name": "tls_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_key": {
+ "name": "tls_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_skip_verify": {
+ "name": "tls_skip_verify",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'globe'"
+ },
+ "collect_activity": {
+ "name": "collect_activity",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "collect_metrics": {
+ "name": "collect_metrics",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "highlight_changes": {
+ "name": "highlight_changes",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connection_type": {
+ "name": "connection_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'socket'"
+ },
+ "socket_path": {
+ "name": "socket_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/var/run/docker.sock'"
+ },
+ "hawser_token": {
+ "name": "hawser_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_last_seen": {
+ "name": "hawser_last_seen",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_agent_id": {
+ "name": "hawser_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_agent_name": {
+ "name": "hawser_agent_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_version": {
+ "name": "hawser_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_capabilities": {
+ "name": "hawser_capabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "environments_name_unique": {
+ "name": "environments_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_credentials": {
+ "name": "git_credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ssh_private_key": {
+ "name": "ssh_private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ssh_passphrase": {
+ "name": "ssh_passphrase",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_credentials_name_unique": {
+ "name": "git_credentials_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_repositories": {
+ "name": "git_repositories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'main'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'docker-compose.yml'"
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_repositories_credential_id_git_credentials_id_fk": {
+ "name": "git_repositories_credential_id_git_credentials_id_fk",
+ "tableFrom": "git_repositories",
+ "tableTo": "git_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_repositories_name_unique": {
+ "name": "git_repositories_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_stacks": {
+ "name": "git_stacks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "repository_id": {
+ "name": "repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'docker-compose.yml'"
+ },
+ "env_file_path": {
+ "name": "env_file_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_stacks_environment_id_environments_id_fk": {
+ "name": "git_stacks_environment_id_environments_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_stacks_repository_id_git_repositories_id_fk": {
+ "name": "git_stacks_repository_id_git_repositories_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_stacks_stack_name_environment_id_unique": {
+ "name": "git_stacks_stack_name_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hawser_tokens": {
+ "name": "hawser_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_prefix": {
+ "name": "token_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "hawser_tokens_environment_id_environments_id_fk": {
+ "name": "hawser_tokens_environment_id_environments_id_fk",
+ "tableFrom": "hawser_tokens",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "hawser_tokens_token_unique": {
+ "name": "hawser_tokens_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.host_metrics": {
+ "name": "host_metrics",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpu_percent": {
+ "name": "cpu_percent",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "memory_percent": {
+ "name": "memory_percent",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "memory_used": {
+ "name": "memory_used",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memory_total": {
+ "name": "memory_total",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "host_metrics_env_timestamp_idx": {
+ "name": "host_metrics_env_timestamp_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "timestamp",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "host_metrics_environment_id_environments_id_fk": {
+ "name": "host_metrics_environment_id_environments_id_fk",
+ "tableFrom": "host_metrics",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ldap_config": {
+ "name": "ldap_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "server_url": {
+ "name": "server_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bind_dn": {
+ "name": "bind_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bind_password": {
+ "name": "bind_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "base_dn": {
+ "name": "base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_filter": {
+ "name": "user_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'(uid={{username}})'"
+ },
+ "username_attribute": {
+ "name": "username_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'uid'"
+ },
+ "email_attribute": {
+ "name": "email_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'mail'"
+ },
+ "display_name_attribute": {
+ "name": "display_name_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'cn'"
+ },
+ "group_base_dn": {
+ "name": "group_base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_filter": {
+ "name": "group_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "admin_group": {
+ "name": "admin_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_enabled": {
+ "name": "tls_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notification_settings": {
+ "name": "notification_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oidc_config": {
+ "name": "oidc_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "issuer_url": {
+ "name": "issuer_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'openid profile email'"
+ },
+ "username_claim": {
+ "name": "username_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'preferred_username'"
+ },
+ "email_claim": {
+ "name": "email_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'email'"
+ },
+ "display_name_claim": {
+ "name": "display_name_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'name'"
+ },
+ "admin_claim": {
+ "name": "admin_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "admin_value": {
+ "name": "admin_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role_mappings_claim": {
+ "name": "role_mappings_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'groups'"
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pending_container_updates": {
+ "name": "pending_container_updates",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "current_image": {
+ "name": "current_image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checked_at": {
+ "name": "checked_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "pending_container_updates_environment_id_environments_id_fk": {
+ "name": "pending_container_updates_environment_id_environments_id_fk",
+ "tableFrom": "pending_container_updates",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "pending_container_updates_environment_id_container_id_unique": {
+ "name": "pending_container_updates_environment_id_container_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "environment_id",
+ "container_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.registries": {
+ "name": "registries",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "registries_name_unique": {
+ "name": "registries_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.roles": {
+ "name": "roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_system": {
+ "name": "is_system",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_ids": {
+ "name": "environment_ids",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "roles_name_unique": {
+ "name": "roles_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.schedule_executions": {
+ "name": "schedule_executions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "triggered_by": {
+ "name": "triggered_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "triggered_at": {
+ "name": "triggered_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "logs": {
+ "name": "logs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "schedule_executions_type_id_idx": {
+ "name": "schedule_executions_type_id_idx",
+ "columns": [
+ {
+ "expression": "schedule_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "schedule_executions_environment_id_environments_id_fk": {
+ "name": "schedule_executions_environment_id_environments_id_fk",
+ "tableFrom": "schedule_executions",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "sessions_user_id_idx": {
+ "name": "sessions_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sessions_expires_at_idx": {
+ "name": "sessions_expires_at_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.settings": {
+ "name": "settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.stack_environment_variables": {
+ "name": "stack_environment_variables",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_secret": {
+ "name": "is_secret",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_environment_variables_environment_id_environments_id_fk": {
+ "name": "stack_environment_variables_environment_id_environments_id_fk",
+ "tableFrom": "stack_environment_variables",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "stack_environment_variables_stack_name_environment_id_key_unique": {
+ "name": "stack_environment_variables_stack_name_environment_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "stack_name",
+ "environment_id",
+ "key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.stack_events": {
+ "name": "stack_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_events_environment_id_environments_id_fk": {
+ "name": "stack_events_environment_id_environments_id_fk",
+ "tableFrom": "stack_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.stack_sources": {
+ "name": "stack_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'internal'"
+ },
+ "git_repository_id": {
+ "name": "git_repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "git_stack_id": {
+ "name": "git_stack_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_sources_environment_id_environments_id_fk": {
+ "name": "stack_sources_environment_id_environments_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_repository_id_git_repositories_id_fk": {
+ "name": "stack_sources_git_repository_id_git_repositories_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "git_repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_stack_id_git_stacks_id_fk": {
+ "name": "stack_sources_git_stack_id_git_stacks_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_stacks",
+ "columnsFrom": [
+ "git_stack_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "stack_sources_stack_name_environment_id_unique": {
+ "name": "stack_sources_stack_name_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_preferences": {
+ "name": "user_preferences",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_preferences_user_id_users_id_fk": {
+ "name": "user_preferences_user_id_users_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_preferences_environment_id_environments_id_fk": {
+ "name": "user_preferences_environment_id_environments_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_preferences_user_id_environment_id_key_unique": {
+ "name": "user_preferences_user_id_environment_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id",
+ "environment_id",
+ "key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_role_id_roles_id_fk": {
+ "name": "user_roles_role_id_roles_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_environment_id_environments_id_fk": {
+ "name": "user_roles_environment_id_environments_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_roles_user_id_role_id_environment_id_unique": {
+ "name": "user_roles_user_id_role_id_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id",
+ "role_id",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar": {
+ "name": "avatar",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_provider": {
+ "name": "auth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'local'"
+ },
+ "mfa_enabled": {
+ "name": "mfa_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "mfa_secret": {
+ "name": "mfa_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.vulnerability_scans": {
+ "name": "vulnerability_scans",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_id": {
+ "name": "image_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image_name": {
+ "name": "image_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scanner": {
+ "name": "scanner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scanned_at": {
+ "name": "scanned_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scan_duration": {
+ "name": "scan_duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "critical_count": {
+ "name": "critical_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "high_count": {
+ "name": "high_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "medium_count": {
+ "name": "medium_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "low_count": {
+ "name": "low_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "negligible_count": {
+ "name": "negligible_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "unknown_count": {
+ "name": "unknown_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "vulnerabilities": {
+ "name": "vulnerabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "vulnerability_scans_env_image_idx": {
+ "name": "vulnerability_scans_env_image_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "image_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "vulnerability_scans_environment_id_environments_id_fk": {
+ "name": "vulnerability_scans_environment_id_environments_id_fk",
+ "tableFrom": "vulnerability_scans",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle-pg/meta/0003_snapshot.json b/drizzle-pg/meta/0003_snapshot.json
new file mode 100644
index 0000000..565117e
--- /dev/null
+++ b/drizzle-pg/meta/0003_snapshot.json
@@ -0,0 +1,2895 @@
+{
+ "id": "b10cba96-4947-484f-84a2-efb65205381f",
+ "prevId": "eef8322a-0ccc-418c-b0f6-f51972a1850e",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.audit_logs": {
+ "name": "audit_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_logs_user_id_idx": {
+ "name": "audit_logs_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_logs_created_at_idx": {
+ "name": "audit_logs_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "audit_logs_user_id_users_id_fk": {
+ "name": "audit_logs_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_logs_environment_id_environments_id_fk": {
+ "name": "audit_logs_environment_id_environments_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.auth_settings": {
+ "name": "auth_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "auth_enabled": {
+ "name": "auth_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "default_provider": {
+ "name": "default_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'local'"
+ },
+ "session_timeout": {
+ "name": "session_timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 86400
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.auto_update_settings": {
+ "name": "auto_update_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "cron_expression": {
+ "name": "cron_expression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vulnerability_criteria": {
+ "name": "vulnerability_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'never'"
+ },
+ "last_checked": {
+ "name": "last_checked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_updated": {
+ "name": "last_updated",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "auto_update_settings_environment_id_environments_id_fk": {
+ "name": "auto_update_settings_environment_id_environments_id_fk",
+ "tableFrom": "auto_update_settings",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "auto_update_settings_environment_id_container_name_unique": {
+ "name": "auto_update_settings_environment_id_container_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "environment_id",
+ "container_name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.config_sets": {
+ "name": "config_sets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env_vars": {
+ "name": "env_vars",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ports": {
+ "name": "ports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumes": {
+ "name": "volumes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "network_mode": {
+ "name": "network_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'bridge'"
+ },
+ "restart_policy": {
+ "name": "restart_policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'no'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "config_sets_name_unique": {
+ "name": "config_sets_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.container_events": {
+ "name": "container_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "actor_attributes": {
+ "name": "actor_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "container_events_env_timestamp_idx": {
+ "name": "container_events_env_timestamp_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "timestamp",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "container_events_environment_id_environments_id_fk": {
+ "name": "container_events_environment_id_environments_id_fk",
+ "tableFrom": "container_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environment_notifications": {
+ "name": "environment_notifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "notification_id": {
+ "name": "notification_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "environment_notifications_environment_id_environments_id_fk": {
+ "name": "environment_notifications_environment_id_environments_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "environment_notifications_notification_id_notification_settings_id_fk": {
+ "name": "environment_notifications_notification_id_notification_settings_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "notification_settings",
+ "columnsFrom": [
+ "notification_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "environment_notifications_environment_id_notification_id_unique": {
+ "name": "environment_notifications_environment_id_notification_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "environment_id",
+ "notification_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environments": {
+ "name": "environments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 2375
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'http'"
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_cert": {
+ "name": "tls_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_key": {
+ "name": "tls_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_skip_verify": {
+ "name": "tls_skip_verify",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'globe'"
+ },
+ "collect_activity": {
+ "name": "collect_activity",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "collect_metrics": {
+ "name": "collect_metrics",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "highlight_changes": {
+ "name": "highlight_changes",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connection_type": {
+ "name": "connection_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'socket'"
+ },
+ "socket_path": {
+ "name": "socket_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/var/run/docker.sock'"
+ },
+ "hawser_token": {
+ "name": "hawser_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_last_seen": {
+ "name": "hawser_last_seen",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_agent_id": {
+ "name": "hawser_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_agent_name": {
+ "name": "hawser_agent_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_version": {
+ "name": "hawser_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hawser_capabilities": {
+ "name": "hawser_capabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "environments_name_unique": {
+ "name": "environments_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_credentials": {
+ "name": "git_credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ssh_private_key": {
+ "name": "ssh_private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ssh_passphrase": {
+ "name": "ssh_passphrase",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_credentials_name_unique": {
+ "name": "git_credentials_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_repositories": {
+ "name": "git_repositories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'main'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'docker-compose.yml'"
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_repositories_credential_id_git_credentials_id_fk": {
+ "name": "git_repositories_credential_id_git_credentials_id_fk",
+ "tableFrom": "git_repositories",
+ "tableTo": "git_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_repositories_name_unique": {
+ "name": "git_repositories_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_stacks": {
+ "name": "git_stacks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "repository_id": {
+ "name": "repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'docker-compose.yml'"
+ },
+ "env_file_path": {
+ "name": "env_file_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_stacks_environment_id_environments_id_fk": {
+ "name": "git_stacks_environment_id_environments_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_stacks_repository_id_git_repositories_id_fk": {
+ "name": "git_stacks_repository_id_git_repositories_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "git_stacks_stack_name_environment_id_unique": {
+ "name": "git_stacks_stack_name_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hawser_tokens": {
+ "name": "hawser_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_prefix": {
+ "name": "token_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "hawser_tokens_environment_id_environments_id_fk": {
+ "name": "hawser_tokens_environment_id_environments_id_fk",
+ "tableFrom": "hawser_tokens",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "hawser_tokens_token_unique": {
+ "name": "hawser_tokens_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.host_metrics": {
+ "name": "host_metrics",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpu_percent": {
+ "name": "cpu_percent",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "memory_percent": {
+ "name": "memory_percent",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "memory_used": {
+ "name": "memory_used",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memory_total": {
+ "name": "memory_total",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "host_metrics_env_timestamp_idx": {
+ "name": "host_metrics_env_timestamp_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "timestamp",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "host_metrics_environment_id_environments_id_fk": {
+ "name": "host_metrics_environment_id_environments_id_fk",
+ "tableFrom": "host_metrics",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ldap_config": {
+ "name": "ldap_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "server_url": {
+ "name": "server_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bind_dn": {
+ "name": "bind_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bind_password": {
+ "name": "bind_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "base_dn": {
+ "name": "base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_filter": {
+ "name": "user_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'(uid={{username}})'"
+ },
+ "username_attribute": {
+ "name": "username_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'uid'"
+ },
+ "email_attribute": {
+ "name": "email_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'mail'"
+ },
+ "display_name_attribute": {
+ "name": "display_name_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'cn'"
+ },
+ "group_base_dn": {
+ "name": "group_base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_filter": {
+ "name": "group_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "admin_group": {
+ "name": "admin_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tls_enabled": {
+ "name": "tls_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notification_settings": {
+ "name": "notification_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oidc_config": {
+ "name": "oidc_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "issuer_url": {
+ "name": "issuer_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'openid profile email'"
+ },
+ "username_claim": {
+ "name": "username_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'preferred_username'"
+ },
+ "email_claim": {
+ "name": "email_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'email'"
+ },
+ "display_name_claim": {
+ "name": "display_name_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'name'"
+ },
+ "admin_claim": {
+ "name": "admin_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "admin_value": {
+ "name": "admin_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role_mappings_claim": {
+ "name": "role_mappings_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'groups'"
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pending_container_updates": {
+ "name": "pending_container_updates",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "current_image": {
+ "name": "current_image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checked_at": {
+ "name": "checked_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "pending_container_updates_environment_id_environments_id_fk": {
+ "name": "pending_container_updates_environment_id_environments_id_fk",
+ "tableFrom": "pending_container_updates",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "pending_container_updates_environment_id_container_id_unique": {
+ "name": "pending_container_updates_environment_id_container_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "environment_id",
+ "container_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.registries": {
+ "name": "registries",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "registries_name_unique": {
+ "name": "registries_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.roles": {
+ "name": "roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_system": {
+ "name": "is_system",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_ids": {
+ "name": "environment_ids",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "roles_name_unique": {
+ "name": "roles_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.schedule_executions": {
+ "name": "schedule_executions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "triggered_by": {
+ "name": "triggered_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "triggered_at": {
+ "name": "triggered_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "logs": {
+ "name": "logs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "schedule_executions_type_id_idx": {
+ "name": "schedule_executions_type_id_idx",
+ "columns": [
+ {
+ "expression": "schedule_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "schedule_executions_environment_id_environments_id_fk": {
+ "name": "schedule_executions_environment_id_environments_id_fk",
+ "tableFrom": "schedule_executions",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "sessions_user_id_idx": {
+ "name": "sessions_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sessions_expires_at_idx": {
+ "name": "sessions_expires_at_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.settings": {
+ "name": "settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.stack_environment_variables": {
+ "name": "stack_environment_variables",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_secret": {
+ "name": "is_secret",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_environment_variables_environment_id_environments_id_fk": {
+ "name": "stack_environment_variables_environment_id_environments_id_fk",
+ "tableFrom": "stack_environment_variables",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "stack_environment_variables_stack_name_environment_id_key_unique": {
+ "name": "stack_environment_variables_stack_name_environment_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "stack_name",
+ "environment_id",
+ "key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.stack_events": {
+ "name": "stack_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_events_environment_id_environments_id_fk": {
+ "name": "stack_events_environment_id_environments_id_fk",
+ "tableFrom": "stack_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.stack_sources": {
+ "name": "stack_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'internal'"
+ },
+ "git_repository_id": {
+ "name": "git_repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "git_stack_id": {
+ "name": "git_stack_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_compose_path": {
+ "name": "external_compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_env_path": {
+ "name": "external_env_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_sources_environment_id_environments_id_fk": {
+ "name": "stack_sources_environment_id_environments_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_repository_id_git_repositories_id_fk": {
+ "name": "stack_sources_git_repository_id_git_repositories_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "git_repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_stack_id_git_stacks_id_fk": {
+ "name": "stack_sources_git_stack_id_git_stacks_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_stacks",
+ "columnsFrom": [
+ "git_stack_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "stack_sources_stack_name_environment_id_unique": {
+ "name": "stack_sources_stack_name_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_preferences": {
+ "name": "user_preferences",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_preferences_user_id_users_id_fk": {
+ "name": "user_preferences_user_id_users_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_preferences_environment_id_environments_id_fk": {
+ "name": "user_preferences_environment_id_environments_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_preferences_user_id_environment_id_key_unique": {
+ "name": "user_preferences_user_id_environment_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id",
+ "environment_id",
+ "key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_role_id_roles_id_fk": {
+ "name": "user_roles_role_id_roles_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_environment_id_environments_id_fk": {
+ "name": "user_roles_environment_id_environments_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_roles_user_id_role_id_environment_id_unique": {
+ "name": "user_roles_user_id_role_id_environment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id",
+ "role_id",
+ "environment_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar": {
+ "name": "avatar",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_provider": {
+ "name": "auth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'local'"
+ },
+ "mfa_enabled": {
+ "name": "mfa_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "mfa_secret": {
+ "name": "mfa_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.vulnerability_scans": {
+ "name": "vulnerability_scans",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_id": {
+ "name": "image_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image_name": {
+ "name": "image_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scanner": {
+ "name": "scanner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scanned_at": {
+ "name": "scanned_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scan_duration": {
+ "name": "scan_duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "critical_count": {
+ "name": "critical_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "high_count": {
+ "name": "high_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "medium_count": {
+ "name": "medium_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "low_count": {
+ "name": "low_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "negligible_count": {
+ "name": "negligible_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "unknown_count": {
+ "name": "unknown_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "vulnerabilities": {
+ "name": "vulnerabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "vulnerability_scans_env_image_idx": {
+ "name": "vulnerability_scans_env_image_idx",
+ "columns": [
+ {
+ "expression": "environment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "image_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "vulnerability_scans_environment_id_environments_id_fk": {
+ "name": "vulnerability_scans_environment_id_environments_id_fk",
+ "tableFrom": "vulnerability_scans",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle-pg/meta/_journal.json b/drizzle-pg/meta/_journal.json
new file mode 100644
index 0000000..590bb1f
--- /dev/null
+++ b/drizzle-pg/meta/_journal.json
@@ -0,0 +1,34 @@
+{
+ "version": "7",
+ "dialect": "postgresql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "7",
+ "when": 1765804022462,
+ "tag": "0000_initial_schema",
+ "breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1766378770502,
+ "tag": "0001_add_stack_env_vars",
+ "breakpoints": true
+ },
+ {
+ "idx": 2,
+ "version": "7",
+ "when": 1766763867484,
+ "tag": "0002_add_pending_container_updates",
+ "breakpoints": true
+ },
+ {
+ "idx": 3,
+ "version": "7",
+ "when": 1767687362730,
+ "tag": "0003_add_stack_paths",
+ "breakpoints": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/drizzle.config.ts b/drizzle.config.ts
new file mode 100644
index 0000000..44f2d39
--- /dev/null
+++ b/drizzle.config.ts
@@ -0,0 +1,16 @@
+import { defineConfig } from 'drizzle-kit';
+
+const databaseUrl = process.env.DATABASE_URL;
+const isPostgres = databaseUrl && (databaseUrl.startsWith('postgres://') || databaseUrl.startsWith('postgresql://'));
+
+export default defineConfig({
+ // Use different schema files for SQLite vs PostgreSQL
+ schema: isPostgres
+ ? './src/lib/server/db/schema/pg-schema.ts'
+ : './src/lib/server/db/schema/index.ts',
+ out: isPostgres ? './drizzle-pg' : './drizzle',
+ dialect: isPostgres ? 'postgresql' : 'sqlite',
+ dbCredentials: isPostgres
+ ? { url: databaseUrl! }
+ : { url: `file:${process.env.DATA_DIR || './data'}/dockhand.db` }
+});
diff --git a/drizzle/0000_initial_schema.sql b/drizzle/0000_initial_schema.sql
new file mode 100644
index 0000000..b04383a
--- /dev/null
+++ b/drizzle/0000_initial_schema.sql
@@ -0,0 +1,401 @@
+CREATE TABLE `audit_logs` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` integer,
+ `username` text NOT NULL,
+ `action` text NOT NULL,
+ `entity_type` text NOT NULL,
+ `entity_id` text,
+ `entity_name` text,
+ `environment_id` integer,
+ `description` text,
+ `details` text,
+ `ip_address` text,
+ `user_agent` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE set null,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE set null
+);
+--> statement-breakpoint
+CREATE INDEX `audit_logs_user_id_idx` ON `audit_logs` (`user_id`);--> statement-breakpoint
+CREATE INDEX `audit_logs_created_at_idx` ON `audit_logs` (`created_at`);--> statement-breakpoint
+CREATE TABLE `auth_settings` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `auth_enabled` integer DEFAULT false,
+ `default_provider` text DEFAULT 'local',
+ `session_timeout` integer DEFAULT 86400,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP
+);
+--> statement-breakpoint
+CREATE TABLE `auto_update_settings` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `environment_id` integer,
+ `container_name` text NOT NULL,
+ `enabled` integer DEFAULT false,
+ `schedule_type` text DEFAULT 'daily',
+ `cron_expression` text,
+ `vulnerability_criteria` text DEFAULT 'never',
+ `last_checked` text,
+ `last_updated` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE no action
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `auto_update_settings_environment_id_container_name_unique` ON `auto_update_settings` (`environment_id`,`container_name`);--> statement-breakpoint
+CREATE TABLE `config_sets` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` text NOT NULL,
+ `description` text,
+ `env_vars` text,
+ `labels` text,
+ `ports` text,
+ `volumes` text,
+ `network_mode` text DEFAULT 'bridge',
+ `restart_policy` text DEFAULT 'no',
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `config_sets_name_unique` ON `config_sets` (`name`);--> statement-breakpoint
+CREATE TABLE `container_events` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `environment_id` integer,
+ `container_id` text NOT NULL,
+ `container_name` text,
+ `image` text,
+ `action` text NOT NULL,
+ `actor_attributes` text,
+ `timestamp` text NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE INDEX `container_events_env_timestamp_idx` ON `container_events` (`environment_id`,`timestamp`);--> statement-breakpoint
+CREATE TABLE `environment_notifications` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `environment_id` integer NOT NULL,
+ `notification_id` integer NOT NULL,
+ `enabled` integer DEFAULT true,
+ `event_types` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`notification_id`) REFERENCES `notification_settings`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `environment_notifications_environment_id_notification_id_unique` ON `environment_notifications` (`environment_id`,`notification_id`);--> statement-breakpoint
+CREATE TABLE `environments` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` text NOT NULL,
+ `host` text,
+ `port` integer DEFAULT 2375,
+ `protocol` text DEFAULT 'http',
+ `tls_ca` text,
+ `tls_cert` text,
+ `tls_key` text,
+ `tls_skip_verify` integer DEFAULT false,
+ `icon` text DEFAULT 'globe',
+ `collect_activity` integer DEFAULT true,
+ `collect_metrics` integer DEFAULT true,
+ `highlight_changes` integer DEFAULT true,
+ `labels` text,
+ `connection_type` text DEFAULT 'socket',
+ `socket_path` text DEFAULT '/var/run/docker.sock',
+ `hawser_token` text,
+ `hawser_last_seen` text,
+ `hawser_agent_id` text,
+ `hawser_agent_name` text,
+ `hawser_version` text,
+ `hawser_capabilities` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `environments_name_unique` ON `environments` (`name`);--> statement-breakpoint
+CREATE TABLE `git_credentials` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` text NOT NULL,
+ `auth_type` text DEFAULT 'none' NOT NULL,
+ `username` text,
+ `password` text,
+ `ssh_private_key` text,
+ `ssh_passphrase` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `git_credentials_name_unique` ON `git_credentials` (`name`);--> statement-breakpoint
+CREATE TABLE `git_repositories` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` text NOT NULL,
+ `url` text NOT NULL,
+ `branch` text DEFAULT 'main',
+ `credential_id` integer,
+ `compose_path` text DEFAULT 'docker-compose.yml',
+ `environment_id` integer,
+ `auto_update` integer DEFAULT false,
+ `auto_update_schedule` text DEFAULT 'daily',
+ `auto_update_cron` text DEFAULT '0 3 * * *',
+ `webhook_enabled` integer DEFAULT false,
+ `webhook_secret` text,
+ `last_sync` text,
+ `last_commit` text,
+ `sync_status` text DEFAULT 'pending',
+ `sync_error` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`credential_id`) REFERENCES `git_credentials`(`id`) ON UPDATE no action ON DELETE set null
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `git_repositories_name_unique` ON `git_repositories` (`name`);--> statement-breakpoint
+CREATE TABLE `git_stacks` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `stack_name` text NOT NULL,
+ `environment_id` integer,
+ `repository_id` integer NOT NULL,
+ `compose_path` text DEFAULT 'docker-compose.yml',
+ `auto_update` integer DEFAULT false,
+ `auto_update_schedule` text DEFAULT 'daily',
+ `auto_update_cron` text DEFAULT '0 3 * * *',
+ `webhook_enabled` integer DEFAULT false,
+ `webhook_secret` text,
+ `last_sync` text,
+ `last_commit` text,
+ `sync_status` text DEFAULT 'pending',
+ `sync_error` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`repository_id`) REFERENCES `git_repositories`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `git_stacks_stack_name_environment_id_unique` ON `git_stacks` (`stack_name`,`environment_id`);--> statement-breakpoint
+CREATE TABLE `hawser_tokens` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `token` text NOT NULL,
+ `token_prefix` text NOT NULL,
+ `name` text NOT NULL,
+ `environment_id` integer,
+ `is_active` integer DEFAULT true,
+ `last_used` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `expires_at` text,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `hawser_tokens_token_unique` ON `hawser_tokens` (`token`);--> statement-breakpoint
+CREATE TABLE `host_metrics` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `environment_id` integer,
+ `cpu_percent` real NOT NULL,
+ `memory_percent` real NOT NULL,
+ `memory_used` integer,
+ `memory_total` integer,
+ `timestamp` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE INDEX `host_metrics_env_timestamp_idx` ON `host_metrics` (`environment_id`,`timestamp`);--> statement-breakpoint
+CREATE TABLE `ldap_config` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` text NOT NULL,
+ `enabled` integer DEFAULT false,
+ `server_url` text NOT NULL,
+ `bind_dn` text,
+ `bind_password` text,
+ `base_dn` text NOT NULL,
+ `user_filter` text DEFAULT '(uid={{username}})',
+ `username_attribute` text DEFAULT 'uid',
+ `email_attribute` text DEFAULT 'mail',
+ `display_name_attribute` text DEFAULT 'cn',
+ `group_base_dn` text,
+ `group_filter` text,
+ `admin_group` text,
+ `role_mappings` text,
+ `tls_enabled` integer DEFAULT false,
+ `tls_ca` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP
+);
+--> statement-breakpoint
+CREATE TABLE `notification_settings` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `type` text NOT NULL,
+ `name` text NOT NULL,
+ `enabled` integer DEFAULT true,
+ `config` text NOT NULL,
+ `event_types` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP
+);
+--> statement-breakpoint
+CREATE TABLE `oidc_config` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` text NOT NULL,
+ `enabled` integer DEFAULT false,
+ `issuer_url` text NOT NULL,
+ `client_id` text NOT NULL,
+ `client_secret` text NOT NULL,
+ `redirect_uri` text NOT NULL,
+ `scopes` text DEFAULT 'openid profile email',
+ `username_claim` text DEFAULT 'preferred_username',
+ `email_claim` text DEFAULT 'email',
+ `display_name_claim` text DEFAULT 'name',
+ `admin_claim` text,
+ `admin_value` text,
+ `role_mappings_claim` text DEFAULT 'groups',
+ `role_mappings` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP
+);
+--> statement-breakpoint
+CREATE TABLE `registries` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` text NOT NULL,
+ `url` text NOT NULL,
+ `username` text,
+ `password` text,
+ `is_default` integer DEFAULT false,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `registries_name_unique` ON `registries` (`name`);--> statement-breakpoint
+CREATE TABLE `roles` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` text NOT NULL,
+ `description` text,
+ `is_system` integer DEFAULT false,
+ `permissions` text NOT NULL,
+ `environment_ids` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `roles_name_unique` ON `roles` (`name`);--> statement-breakpoint
+CREATE TABLE `schedule_executions` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `schedule_type` text NOT NULL,
+ `schedule_id` integer NOT NULL,
+ `environment_id` integer,
+ `entity_name` text NOT NULL,
+ `triggered_by` text NOT NULL,
+ `triggered_at` text NOT NULL,
+ `started_at` text,
+ `completed_at` text,
+ `duration` integer,
+ `status` text NOT NULL,
+ `error_message` text,
+ `details` text,
+ `logs` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE INDEX `schedule_executions_type_id_idx` ON `schedule_executions` (`schedule_type`,`schedule_id`);--> statement-breakpoint
+CREATE TABLE `sessions` (
+ `id` text PRIMARY KEY NOT NULL,
+ `user_id` integer NOT NULL,
+ `provider` text NOT NULL,
+ `expires_at` text NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE INDEX `sessions_user_id_idx` ON `sessions` (`user_id`);--> statement-breakpoint
+CREATE INDEX `sessions_expires_at_idx` ON `sessions` (`expires_at`);--> statement-breakpoint
+CREATE TABLE `settings` (
+ `key` text PRIMARY KEY NOT NULL,
+ `value` text NOT NULL,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP
+);
+--> statement-breakpoint
+CREATE TABLE `stack_events` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `environment_id` integer,
+ `stack_name` text NOT NULL,
+ `event_type` text NOT NULL,
+ `timestamp` text DEFAULT CURRENT_TIMESTAMP,
+ `metadata` text,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `stack_sources` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `stack_name` text NOT NULL,
+ `environment_id` integer,
+ `source_type` text DEFAULT 'internal' NOT NULL,
+ `git_repository_id` integer,
+ `git_stack_id` integer,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`git_repository_id`) REFERENCES `git_repositories`(`id`) ON UPDATE no action ON DELETE set null,
+ FOREIGN KEY (`git_stack_id`) REFERENCES `git_stacks`(`id`) ON UPDATE no action ON DELETE set null
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `stack_sources_stack_name_environment_id_unique` ON `stack_sources` (`stack_name`,`environment_id`);--> statement-breakpoint
+CREATE TABLE `user_preferences` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` integer,
+ `environment_id` integer,
+ `key` text NOT NULL,
+ `value` text NOT NULL,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `user_preferences_user_id_environment_id_key_unique` ON `user_preferences` (`user_id`,`environment_id`,`key`);--> statement-breakpoint
+CREATE TABLE `user_roles` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `user_id` integer NOT NULL,
+ `role_id` integer NOT NULL,
+ `environment_id` integer,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `user_roles_user_id_role_id_environment_id_unique` ON `user_roles` (`user_id`,`role_id`,`environment_id`);--> statement-breakpoint
+CREATE TABLE `users` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `username` text NOT NULL,
+ `email` text,
+ `password_hash` text NOT NULL,
+ `display_name` text,
+ `avatar` text,
+ `auth_provider` text DEFAULT 'local',
+ `mfa_enabled` integer DEFAULT false,
+ `mfa_secret` text,
+ `is_active` integer DEFAULT true,
+ `last_login` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `users_username_unique` ON `users` (`username`);--> statement-breakpoint
+CREATE TABLE `vulnerability_scans` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `environment_id` integer,
+ `image_id` text NOT NULL,
+ `image_name` text NOT NULL,
+ `scanner` text NOT NULL,
+ `scanned_at` text NOT NULL,
+ `scan_duration` integer,
+ `critical_count` integer DEFAULT 0,
+ `high_count` integer DEFAULT 0,
+ `medium_count` integer DEFAULT 0,
+ `low_count` integer DEFAULT 0,
+ `negligible_count` integer DEFAULT 0,
+ `unknown_count` integer DEFAULT 0,
+ `vulnerabilities` text,
+ `error` text,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE INDEX `vulnerability_scans_env_image_idx` ON `vulnerability_scans` (`environment_id`,`image_id`);
\ No newline at end of file
diff --git a/drizzle/0001_add_stack_env_vars.sql b/drizzle/0001_add_stack_env_vars.sql
new file mode 100644
index 0000000..aa52b21
--- /dev/null
+++ b/drizzle/0001_add_stack_env_vars.sql
@@ -0,0 +1,14 @@
+CREATE TABLE `stack_environment_variables` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `stack_name` text NOT NULL,
+ `environment_id` integer,
+ `key` text NOT NULL,
+ `value` text NOT NULL,
+ `is_secret` integer DEFAULT false,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `stack_environment_variables_stack_name_environment_id_key_unique` ON `stack_environment_variables` (`stack_name`,`environment_id`,`key`);--> statement-breakpoint
+ALTER TABLE `git_stacks` ADD `env_file_path` text;
\ No newline at end of file
diff --git a/drizzle/0002_add_pending_container_updates.sql b/drizzle/0002_add_pending_container_updates.sql
new file mode 100644
index 0000000..f3c87a6
--- /dev/null
+++ b/drizzle/0002_add_pending_container_updates.sql
@@ -0,0 +1,12 @@
+CREATE TABLE `pending_container_updates` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `environment_id` integer NOT NULL,
+ `container_id` text NOT NULL,
+ `container_name` text NOT NULL,
+ `current_image` text NOT NULL,
+ `checked_at` text DEFAULT CURRENT_TIMESTAMP,
+ `created_at` text DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`environment_id`) REFERENCES `environments`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `pending_container_updates_environment_id_container_id_unique` ON `pending_container_updates` (`environment_id`,`container_id`);
\ No newline at end of file
diff --git a/drizzle/0003_add_stack_paths.sql b/drizzle/0003_add_stack_paths.sql
new file mode 100644
index 0000000..a9447ea
--- /dev/null
+++ b/drizzle/0003_add_stack_paths.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `stack_sources` ADD `compose_path` text;--> statement-breakpoint
+ALTER TABLE `stack_sources` ADD `env_path` text;
\ No newline at end of file
diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json
new file mode 100644
index 0000000..0aaf6ba
--- /dev/null
+++ b/drizzle/meta/0000_snapshot.json
@@ -0,0 +1,2824 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "d7d12244-ddb1-4246-844c-56f6c903ea29",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "tables": {
+ "audit_logs": {
+ "name": "audit_logs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "audit_logs_user_id_idx": {
+ "name": "audit_logs_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ },
+ "audit_logs_created_at_idx": {
+ "name": "audit_logs_created_at_idx",
+ "columns": [
+ "created_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "audit_logs_user_id_users_id_fk": {
+ "name": "audit_logs_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_logs_environment_id_environments_id_fk": {
+ "name": "audit_logs_environment_id_environments_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "auth_settings": {
+ "name": "auth_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "auth_enabled": {
+ "name": "auth_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "default_provider": {
+ "name": "default_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'local'"
+ },
+ "session_timeout": {
+ "name": "session_timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 86400
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "auto_update_settings": {
+ "name": "auto_update_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "cron_expression": {
+ "name": "cron_expression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vulnerability_criteria": {
+ "name": "vulnerability_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'never'"
+ },
+ "last_checked": {
+ "name": "last_checked",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_updated": {
+ "name": "last_updated",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "auto_update_settings_environment_id_container_name_unique": {
+ "name": "auto_update_settings_environment_id_container_name_unique",
+ "columns": [
+ "environment_id",
+ "container_name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "auto_update_settings_environment_id_environments_id_fk": {
+ "name": "auto_update_settings_environment_id_environments_id_fk",
+ "tableFrom": "auto_update_settings",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "config_sets": {
+ "name": "config_sets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "env_vars": {
+ "name": "env_vars",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ports": {
+ "name": "ports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "volumes": {
+ "name": "volumes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "network_mode": {
+ "name": "network_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'bridge'"
+ },
+ "restart_policy": {
+ "name": "restart_policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'no'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "config_sets_name_unique": {
+ "name": "config_sets_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "container_events": {
+ "name": "container_events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "actor_attributes": {
+ "name": "actor_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "container_events_env_timestamp_idx": {
+ "name": "container_events_env_timestamp_idx",
+ "columns": [
+ "environment_id",
+ "timestamp"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "container_events_environment_id_environments_id_fk": {
+ "name": "container_events_environment_id_environments_id_fk",
+ "tableFrom": "container_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "environment_notifications": {
+ "name": "environment_notifications",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "notification_id": {
+ "name": "notification_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "environment_notifications_environment_id_notification_id_unique": {
+ "name": "environment_notifications_environment_id_notification_id_unique",
+ "columns": [
+ "environment_id",
+ "notification_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "environment_notifications_environment_id_environments_id_fk": {
+ "name": "environment_notifications_environment_id_environments_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "environment_notifications_notification_id_notification_settings_id_fk": {
+ "name": "environment_notifications_notification_id_notification_settings_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "notification_settings",
+ "columnsFrom": [
+ "notification_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "environments": {
+ "name": "environments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 2375
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'http'"
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_cert": {
+ "name": "tls_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_key": {
+ "name": "tls_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_skip_verify": {
+ "name": "tls_skip_verify",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'globe'"
+ },
+ "collect_activity": {
+ "name": "collect_activity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "collect_metrics": {
+ "name": "collect_metrics",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "highlight_changes": {
+ "name": "highlight_changes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "connection_type": {
+ "name": "connection_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'socket'"
+ },
+ "socket_path": {
+ "name": "socket_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'/var/run/docker.sock'"
+ },
+ "hawser_token": {
+ "name": "hawser_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_last_seen": {
+ "name": "hawser_last_seen",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_agent_id": {
+ "name": "hawser_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_agent_name": {
+ "name": "hawser_agent_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_version": {
+ "name": "hawser_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_capabilities": {
+ "name": "hawser_capabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "environments_name_unique": {
+ "name": "environments_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_credentials": {
+ "name": "git_credentials",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'none'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_private_key": {
+ "name": "ssh_private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_passphrase": {
+ "name": "ssh_passphrase",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_credentials_name_unique": {
+ "name": "git_credentials_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_repositories": {
+ "name": "git_repositories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'main'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'docker-compose.yml'"
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_repositories_name_unique": {
+ "name": "git_repositories_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "git_repositories_credential_id_git_credentials_id_fk": {
+ "name": "git_repositories_credential_id_git_credentials_id_fk",
+ "tableFrom": "git_repositories",
+ "tableTo": "git_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_stacks": {
+ "name": "git_stacks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "repository_id": {
+ "name": "repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'docker-compose.yml'"
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_stacks_stack_name_environment_id_unique": {
+ "name": "git_stacks_stack_name_environment_id_unique",
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "git_stacks_environment_id_environments_id_fk": {
+ "name": "git_stacks_environment_id_environments_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_stacks_repository_id_git_repositories_id_fk": {
+ "name": "git_stacks_repository_id_git_repositories_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "hawser_tokens": {
+ "name": "hawser_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token_prefix": {
+ "name": "token_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "hawser_tokens_token_unique": {
+ "name": "hawser_tokens_token_unique",
+ "columns": [
+ "token"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "hawser_tokens_environment_id_environments_id_fk": {
+ "name": "hawser_tokens_environment_id_environments_id_fk",
+ "tableFrom": "hawser_tokens",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "host_metrics": {
+ "name": "host_metrics",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cpu_percent": {
+ "name": "cpu_percent",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "memory_percent": {
+ "name": "memory_percent",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "memory_used": {
+ "name": "memory_used",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "memory_total": {
+ "name": "memory_total",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "host_metrics_env_timestamp_idx": {
+ "name": "host_metrics_env_timestamp_idx",
+ "columns": [
+ "environment_id",
+ "timestamp"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "host_metrics_environment_id_environments_id_fk": {
+ "name": "host_metrics_environment_id_environments_id_fk",
+ "tableFrom": "host_metrics",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ldap_config": {
+ "name": "ldap_config",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "server_url": {
+ "name": "server_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bind_dn": {
+ "name": "bind_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bind_password": {
+ "name": "bind_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "base_dn": {
+ "name": "base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_filter": {
+ "name": "user_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'(uid={{username}})'"
+ },
+ "username_attribute": {
+ "name": "username_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'uid'"
+ },
+ "email_attribute": {
+ "name": "email_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'mail'"
+ },
+ "display_name_attribute": {
+ "name": "display_name_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'cn'"
+ },
+ "group_base_dn": {
+ "name": "group_base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "group_filter": {
+ "name": "group_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "admin_group": {
+ "name": "admin_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_enabled": {
+ "name": "tls_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_settings": {
+ "name": "notification_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "oidc_config": {
+ "name": "oidc_config",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "issuer_url": {
+ "name": "issuer_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'openid profile email'"
+ },
+ "username_claim": {
+ "name": "username_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'preferred_username'"
+ },
+ "email_claim": {
+ "name": "email_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'email'"
+ },
+ "display_name_claim": {
+ "name": "display_name_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'name'"
+ },
+ "admin_claim": {
+ "name": "admin_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "admin_value": {
+ "name": "admin_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_mappings_claim": {
+ "name": "role_mappings_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'groups'"
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "registries": {
+ "name": "registries",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "registries_name_unique": {
+ "name": "registries_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "roles": {
+ "name": "roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_system": {
+ "name": "is_system",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_ids": {
+ "name": "environment_ids",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "roles_name_unique": {
+ "name": "roles_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "schedule_executions": {
+ "name": "schedule_executions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "triggered_by": {
+ "name": "triggered_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "triggered_at": {
+ "name": "triggered_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "logs": {
+ "name": "logs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "schedule_executions_type_id_idx": {
+ "name": "schedule_executions_type_id_idx",
+ "columns": [
+ "schedule_type",
+ "schedule_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "schedule_executions_environment_id_environments_id_fk": {
+ "name": "schedule_executions_environment_id_environments_id_fk",
+ "tableFrom": "schedule_executions",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sessions": {
+ "name": "sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "sessions_user_id_idx": {
+ "name": "sessions_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ },
+ "sessions_expires_at_idx": {
+ "name": "sessions_expires_at_idx",
+ "columns": [
+ "expires_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "settings": {
+ "name": "settings",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stack_events": {
+ "name": "stack_events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_events_environment_id_environments_id_fk": {
+ "name": "stack_events_environment_id_environments_id_fk",
+ "tableFrom": "stack_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stack_sources": {
+ "name": "stack_sources",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'internal'"
+ },
+ "git_repository_id": {
+ "name": "git_repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "git_stack_id": {
+ "name": "git_stack_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "stack_sources_stack_name_environment_id_unique": {
+ "name": "stack_sources_stack_name_environment_id_unique",
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "stack_sources_environment_id_environments_id_fk": {
+ "name": "stack_sources_environment_id_environments_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_repository_id_git_repositories_id_fk": {
+ "name": "stack_sources_git_repository_id_git_repositories_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "git_repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_stack_id_git_stacks_id_fk": {
+ "name": "stack_sources_git_stack_id_git_stacks_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_stacks",
+ "columnsFrom": [
+ "git_stack_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_preferences": {
+ "name": "user_preferences",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "user_preferences_user_id_environment_id_key_unique": {
+ "name": "user_preferences_user_id_environment_id_key_unique",
+ "columns": [
+ "user_id",
+ "environment_id",
+ "key"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "user_preferences_user_id_users_id_fk": {
+ "name": "user_preferences_user_id_users_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_preferences_environment_id_environments_id_fk": {
+ "name": "user_preferences_environment_id_environments_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_roles": {
+ "name": "user_roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "user_roles_user_id_role_id_environment_id_unique": {
+ "name": "user_roles_user_id_role_id_environment_id_unique",
+ "columns": [
+ "user_id",
+ "role_id",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_role_id_roles_id_fk": {
+ "name": "user_roles_role_id_roles_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_environment_id_environments_id_fk": {
+ "name": "user_roles_environment_id_environments_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "avatar": {
+ "name": "avatar",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auth_provider": {
+ "name": "auth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'local'"
+ },
+ "mfa_enabled": {
+ "name": "mfa_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "mfa_secret": {
+ "name": "mfa_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "columns": [
+ "username"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "vulnerability_scans": {
+ "name": "vulnerability_scans",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image_id": {
+ "name": "image_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image_name": {
+ "name": "image_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scanner": {
+ "name": "scanner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scanned_at": {
+ "name": "scanned_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scan_duration": {
+ "name": "scan_duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "critical_count": {
+ "name": "critical_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "high_count": {
+ "name": "high_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "medium_count": {
+ "name": "medium_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "low_count": {
+ "name": "low_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "negligible_count": {
+ "name": "negligible_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "unknown_count": {
+ "name": "unknown_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "vulnerabilities": {
+ "name": "vulnerabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "vulnerability_scans_env_image_idx": {
+ "name": "vulnerability_scans_env_image_idx",
+ "columns": [
+ "environment_id",
+ "image_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "vulnerability_scans_environment_id_environments_id_fk": {
+ "name": "vulnerability_scans_environment_id_environments_id_fk",
+ "tableFrom": "vulnerability_scans",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json
new file mode 100644
index 0000000..4a5d606
--- /dev/null
+++ b/drizzle/meta/0001_snapshot.json
@@ -0,0 +1,2924 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "9dd11a39-a911-4c3f-9c2f-6920b14c2d96",
+ "prevId": "d7d12244-ddb1-4246-844c-56f6c903ea29",
+ "tables": {
+ "audit_logs": {
+ "name": "audit_logs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "audit_logs_user_id_idx": {
+ "name": "audit_logs_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ },
+ "audit_logs_created_at_idx": {
+ "name": "audit_logs_created_at_idx",
+ "columns": [
+ "created_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "audit_logs_user_id_users_id_fk": {
+ "name": "audit_logs_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_logs_environment_id_environments_id_fk": {
+ "name": "audit_logs_environment_id_environments_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "auth_settings": {
+ "name": "auth_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "auth_enabled": {
+ "name": "auth_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "default_provider": {
+ "name": "default_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'local'"
+ },
+ "session_timeout": {
+ "name": "session_timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 86400
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "auto_update_settings": {
+ "name": "auto_update_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "cron_expression": {
+ "name": "cron_expression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vulnerability_criteria": {
+ "name": "vulnerability_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'never'"
+ },
+ "last_checked": {
+ "name": "last_checked",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_updated": {
+ "name": "last_updated",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "auto_update_settings_environment_id_container_name_unique": {
+ "name": "auto_update_settings_environment_id_container_name_unique",
+ "columns": [
+ "environment_id",
+ "container_name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "auto_update_settings_environment_id_environments_id_fk": {
+ "name": "auto_update_settings_environment_id_environments_id_fk",
+ "tableFrom": "auto_update_settings",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "config_sets": {
+ "name": "config_sets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "env_vars": {
+ "name": "env_vars",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ports": {
+ "name": "ports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "volumes": {
+ "name": "volumes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "network_mode": {
+ "name": "network_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'bridge'"
+ },
+ "restart_policy": {
+ "name": "restart_policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'no'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "config_sets_name_unique": {
+ "name": "config_sets_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "container_events": {
+ "name": "container_events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "actor_attributes": {
+ "name": "actor_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "container_events_env_timestamp_idx": {
+ "name": "container_events_env_timestamp_idx",
+ "columns": [
+ "environment_id",
+ "timestamp"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "container_events_environment_id_environments_id_fk": {
+ "name": "container_events_environment_id_environments_id_fk",
+ "tableFrom": "container_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "environment_notifications": {
+ "name": "environment_notifications",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "notification_id": {
+ "name": "notification_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "environment_notifications_environment_id_notification_id_unique": {
+ "name": "environment_notifications_environment_id_notification_id_unique",
+ "columns": [
+ "environment_id",
+ "notification_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "environment_notifications_environment_id_environments_id_fk": {
+ "name": "environment_notifications_environment_id_environments_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "environment_notifications_notification_id_notification_settings_id_fk": {
+ "name": "environment_notifications_notification_id_notification_settings_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "notification_settings",
+ "columnsFrom": [
+ "notification_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "environments": {
+ "name": "environments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 2375
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'http'"
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_cert": {
+ "name": "tls_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_key": {
+ "name": "tls_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_skip_verify": {
+ "name": "tls_skip_verify",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'globe'"
+ },
+ "collect_activity": {
+ "name": "collect_activity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "collect_metrics": {
+ "name": "collect_metrics",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "highlight_changes": {
+ "name": "highlight_changes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "connection_type": {
+ "name": "connection_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'socket'"
+ },
+ "socket_path": {
+ "name": "socket_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'/var/run/docker.sock'"
+ },
+ "hawser_token": {
+ "name": "hawser_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_last_seen": {
+ "name": "hawser_last_seen",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_agent_id": {
+ "name": "hawser_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_agent_name": {
+ "name": "hawser_agent_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_version": {
+ "name": "hawser_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_capabilities": {
+ "name": "hawser_capabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "environments_name_unique": {
+ "name": "environments_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_credentials": {
+ "name": "git_credentials",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'none'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_private_key": {
+ "name": "ssh_private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_passphrase": {
+ "name": "ssh_passphrase",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_credentials_name_unique": {
+ "name": "git_credentials_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_repositories": {
+ "name": "git_repositories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'main'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'docker-compose.yml'"
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_repositories_name_unique": {
+ "name": "git_repositories_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "git_repositories_credential_id_git_credentials_id_fk": {
+ "name": "git_repositories_credential_id_git_credentials_id_fk",
+ "tableFrom": "git_repositories",
+ "tableTo": "git_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_stacks": {
+ "name": "git_stacks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "repository_id": {
+ "name": "repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'docker-compose.yml'"
+ },
+ "env_file_path": {
+ "name": "env_file_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_stacks_stack_name_environment_id_unique": {
+ "name": "git_stacks_stack_name_environment_id_unique",
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "git_stacks_environment_id_environments_id_fk": {
+ "name": "git_stacks_environment_id_environments_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_stacks_repository_id_git_repositories_id_fk": {
+ "name": "git_stacks_repository_id_git_repositories_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "hawser_tokens": {
+ "name": "hawser_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token_prefix": {
+ "name": "token_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "hawser_tokens_token_unique": {
+ "name": "hawser_tokens_token_unique",
+ "columns": [
+ "token"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "hawser_tokens_environment_id_environments_id_fk": {
+ "name": "hawser_tokens_environment_id_environments_id_fk",
+ "tableFrom": "hawser_tokens",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "host_metrics": {
+ "name": "host_metrics",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cpu_percent": {
+ "name": "cpu_percent",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "memory_percent": {
+ "name": "memory_percent",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "memory_used": {
+ "name": "memory_used",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "memory_total": {
+ "name": "memory_total",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "host_metrics_env_timestamp_idx": {
+ "name": "host_metrics_env_timestamp_idx",
+ "columns": [
+ "environment_id",
+ "timestamp"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "host_metrics_environment_id_environments_id_fk": {
+ "name": "host_metrics_environment_id_environments_id_fk",
+ "tableFrom": "host_metrics",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ldap_config": {
+ "name": "ldap_config",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "server_url": {
+ "name": "server_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bind_dn": {
+ "name": "bind_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bind_password": {
+ "name": "bind_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "base_dn": {
+ "name": "base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_filter": {
+ "name": "user_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'(uid={{username}})'"
+ },
+ "username_attribute": {
+ "name": "username_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'uid'"
+ },
+ "email_attribute": {
+ "name": "email_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'mail'"
+ },
+ "display_name_attribute": {
+ "name": "display_name_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'cn'"
+ },
+ "group_base_dn": {
+ "name": "group_base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "group_filter": {
+ "name": "group_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "admin_group": {
+ "name": "admin_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_enabled": {
+ "name": "tls_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_settings": {
+ "name": "notification_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "oidc_config": {
+ "name": "oidc_config",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "issuer_url": {
+ "name": "issuer_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'openid profile email'"
+ },
+ "username_claim": {
+ "name": "username_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'preferred_username'"
+ },
+ "email_claim": {
+ "name": "email_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'email'"
+ },
+ "display_name_claim": {
+ "name": "display_name_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'name'"
+ },
+ "admin_claim": {
+ "name": "admin_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "admin_value": {
+ "name": "admin_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_mappings_claim": {
+ "name": "role_mappings_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'groups'"
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "registries": {
+ "name": "registries",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "registries_name_unique": {
+ "name": "registries_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "roles": {
+ "name": "roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_system": {
+ "name": "is_system",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_ids": {
+ "name": "environment_ids",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "roles_name_unique": {
+ "name": "roles_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "schedule_executions": {
+ "name": "schedule_executions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "triggered_by": {
+ "name": "triggered_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "triggered_at": {
+ "name": "triggered_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "logs": {
+ "name": "logs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "schedule_executions_type_id_idx": {
+ "name": "schedule_executions_type_id_idx",
+ "columns": [
+ "schedule_type",
+ "schedule_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "schedule_executions_environment_id_environments_id_fk": {
+ "name": "schedule_executions_environment_id_environments_id_fk",
+ "tableFrom": "schedule_executions",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sessions": {
+ "name": "sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "sessions_user_id_idx": {
+ "name": "sessions_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ },
+ "sessions_expires_at_idx": {
+ "name": "sessions_expires_at_idx",
+ "columns": [
+ "expires_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "settings": {
+ "name": "settings",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stack_environment_variables": {
+ "name": "stack_environment_variables",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_secret": {
+ "name": "is_secret",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "stack_environment_variables_stack_name_environment_id_key_unique": {
+ "name": "stack_environment_variables_stack_name_environment_id_key_unique",
+ "columns": [
+ "stack_name",
+ "environment_id",
+ "key"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "stack_environment_variables_environment_id_environments_id_fk": {
+ "name": "stack_environment_variables_environment_id_environments_id_fk",
+ "tableFrom": "stack_environment_variables",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stack_events": {
+ "name": "stack_events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_events_environment_id_environments_id_fk": {
+ "name": "stack_events_environment_id_environments_id_fk",
+ "tableFrom": "stack_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stack_sources": {
+ "name": "stack_sources",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'internal'"
+ },
+ "git_repository_id": {
+ "name": "git_repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "git_stack_id": {
+ "name": "git_stack_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "stack_sources_stack_name_environment_id_unique": {
+ "name": "stack_sources_stack_name_environment_id_unique",
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "stack_sources_environment_id_environments_id_fk": {
+ "name": "stack_sources_environment_id_environments_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_repository_id_git_repositories_id_fk": {
+ "name": "stack_sources_git_repository_id_git_repositories_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "git_repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_stack_id_git_stacks_id_fk": {
+ "name": "stack_sources_git_stack_id_git_stacks_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_stacks",
+ "columnsFrom": [
+ "git_stack_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_preferences": {
+ "name": "user_preferences",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "user_preferences_user_id_environment_id_key_unique": {
+ "name": "user_preferences_user_id_environment_id_key_unique",
+ "columns": [
+ "user_id",
+ "environment_id",
+ "key"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "user_preferences_user_id_users_id_fk": {
+ "name": "user_preferences_user_id_users_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_preferences_environment_id_environments_id_fk": {
+ "name": "user_preferences_environment_id_environments_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_roles": {
+ "name": "user_roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "user_roles_user_id_role_id_environment_id_unique": {
+ "name": "user_roles_user_id_role_id_environment_id_unique",
+ "columns": [
+ "user_id",
+ "role_id",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_role_id_roles_id_fk": {
+ "name": "user_roles_role_id_roles_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_environment_id_environments_id_fk": {
+ "name": "user_roles_environment_id_environments_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "avatar": {
+ "name": "avatar",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auth_provider": {
+ "name": "auth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'local'"
+ },
+ "mfa_enabled": {
+ "name": "mfa_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "mfa_secret": {
+ "name": "mfa_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "columns": [
+ "username"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "vulnerability_scans": {
+ "name": "vulnerability_scans",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image_id": {
+ "name": "image_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image_name": {
+ "name": "image_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scanner": {
+ "name": "scanner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scanned_at": {
+ "name": "scanned_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scan_duration": {
+ "name": "scan_duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "critical_count": {
+ "name": "critical_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "high_count": {
+ "name": "high_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "medium_count": {
+ "name": "medium_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "low_count": {
+ "name": "low_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "negligible_count": {
+ "name": "negligible_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "unknown_count": {
+ "name": "unknown_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "vulnerabilities": {
+ "name": "vulnerabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "vulnerability_scans_env_image_idx": {
+ "name": "vulnerability_scans_env_image_idx",
+ "columns": [
+ "environment_id",
+ "image_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "vulnerability_scans_environment_id_environments_id_fk": {
+ "name": "vulnerability_scans_environment_id_environments_id_fk",
+ "tableFrom": "vulnerability_scans",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json
new file mode 100644
index 0000000..f99d580
--- /dev/null
+++ b/drizzle/meta/0002_snapshot.json
@@ -0,0 +1,3008 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "31bce98b-04c0-4e21-8cb0-49a67c345d87",
+ "prevId": "9dd11a39-a911-4c3f-9c2f-6920b14c2d96",
+ "tables": {
+ "audit_logs": {
+ "name": "audit_logs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "audit_logs_user_id_idx": {
+ "name": "audit_logs_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ },
+ "audit_logs_created_at_idx": {
+ "name": "audit_logs_created_at_idx",
+ "columns": [
+ "created_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "audit_logs_user_id_users_id_fk": {
+ "name": "audit_logs_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_logs_environment_id_environments_id_fk": {
+ "name": "audit_logs_environment_id_environments_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "auth_settings": {
+ "name": "auth_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "auth_enabled": {
+ "name": "auth_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "default_provider": {
+ "name": "default_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'local'"
+ },
+ "session_timeout": {
+ "name": "session_timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 86400
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "auto_update_settings": {
+ "name": "auto_update_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "cron_expression": {
+ "name": "cron_expression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vulnerability_criteria": {
+ "name": "vulnerability_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'never'"
+ },
+ "last_checked": {
+ "name": "last_checked",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_updated": {
+ "name": "last_updated",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "auto_update_settings_environment_id_container_name_unique": {
+ "name": "auto_update_settings_environment_id_container_name_unique",
+ "columns": [
+ "environment_id",
+ "container_name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "auto_update_settings_environment_id_environments_id_fk": {
+ "name": "auto_update_settings_environment_id_environments_id_fk",
+ "tableFrom": "auto_update_settings",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "config_sets": {
+ "name": "config_sets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "env_vars": {
+ "name": "env_vars",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ports": {
+ "name": "ports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "volumes": {
+ "name": "volumes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "network_mode": {
+ "name": "network_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'bridge'"
+ },
+ "restart_policy": {
+ "name": "restart_policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'no'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "config_sets_name_unique": {
+ "name": "config_sets_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "container_events": {
+ "name": "container_events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "actor_attributes": {
+ "name": "actor_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "container_events_env_timestamp_idx": {
+ "name": "container_events_env_timestamp_idx",
+ "columns": [
+ "environment_id",
+ "timestamp"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "container_events_environment_id_environments_id_fk": {
+ "name": "container_events_environment_id_environments_id_fk",
+ "tableFrom": "container_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "environment_notifications": {
+ "name": "environment_notifications",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "notification_id": {
+ "name": "notification_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "environment_notifications_environment_id_notification_id_unique": {
+ "name": "environment_notifications_environment_id_notification_id_unique",
+ "columns": [
+ "environment_id",
+ "notification_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "environment_notifications_environment_id_environments_id_fk": {
+ "name": "environment_notifications_environment_id_environments_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "environment_notifications_notification_id_notification_settings_id_fk": {
+ "name": "environment_notifications_notification_id_notification_settings_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "notification_settings",
+ "columnsFrom": [
+ "notification_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "environments": {
+ "name": "environments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 2375
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'http'"
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_cert": {
+ "name": "tls_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_key": {
+ "name": "tls_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_skip_verify": {
+ "name": "tls_skip_verify",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'globe'"
+ },
+ "collect_activity": {
+ "name": "collect_activity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "collect_metrics": {
+ "name": "collect_metrics",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "highlight_changes": {
+ "name": "highlight_changes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "connection_type": {
+ "name": "connection_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'socket'"
+ },
+ "socket_path": {
+ "name": "socket_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'/var/run/docker.sock'"
+ },
+ "hawser_token": {
+ "name": "hawser_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_last_seen": {
+ "name": "hawser_last_seen",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_agent_id": {
+ "name": "hawser_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_agent_name": {
+ "name": "hawser_agent_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_version": {
+ "name": "hawser_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_capabilities": {
+ "name": "hawser_capabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "environments_name_unique": {
+ "name": "environments_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_credentials": {
+ "name": "git_credentials",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'none'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_private_key": {
+ "name": "ssh_private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_passphrase": {
+ "name": "ssh_passphrase",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_credentials_name_unique": {
+ "name": "git_credentials_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_repositories": {
+ "name": "git_repositories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'main'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'docker-compose.yml'"
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_repositories_name_unique": {
+ "name": "git_repositories_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "git_repositories_credential_id_git_credentials_id_fk": {
+ "name": "git_repositories_credential_id_git_credentials_id_fk",
+ "tableFrom": "git_repositories",
+ "tableTo": "git_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_stacks": {
+ "name": "git_stacks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "repository_id": {
+ "name": "repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'docker-compose.yml'"
+ },
+ "env_file_path": {
+ "name": "env_file_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_stacks_stack_name_environment_id_unique": {
+ "name": "git_stacks_stack_name_environment_id_unique",
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "git_stacks_environment_id_environments_id_fk": {
+ "name": "git_stacks_environment_id_environments_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_stacks_repository_id_git_repositories_id_fk": {
+ "name": "git_stacks_repository_id_git_repositories_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "hawser_tokens": {
+ "name": "hawser_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token_prefix": {
+ "name": "token_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "hawser_tokens_token_unique": {
+ "name": "hawser_tokens_token_unique",
+ "columns": [
+ "token"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "hawser_tokens_environment_id_environments_id_fk": {
+ "name": "hawser_tokens_environment_id_environments_id_fk",
+ "tableFrom": "hawser_tokens",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "host_metrics": {
+ "name": "host_metrics",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cpu_percent": {
+ "name": "cpu_percent",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "memory_percent": {
+ "name": "memory_percent",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "memory_used": {
+ "name": "memory_used",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "memory_total": {
+ "name": "memory_total",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "host_metrics_env_timestamp_idx": {
+ "name": "host_metrics_env_timestamp_idx",
+ "columns": [
+ "environment_id",
+ "timestamp"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "host_metrics_environment_id_environments_id_fk": {
+ "name": "host_metrics_environment_id_environments_id_fk",
+ "tableFrom": "host_metrics",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ldap_config": {
+ "name": "ldap_config",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "server_url": {
+ "name": "server_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bind_dn": {
+ "name": "bind_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bind_password": {
+ "name": "bind_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "base_dn": {
+ "name": "base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_filter": {
+ "name": "user_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'(uid={{username}})'"
+ },
+ "username_attribute": {
+ "name": "username_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'uid'"
+ },
+ "email_attribute": {
+ "name": "email_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'mail'"
+ },
+ "display_name_attribute": {
+ "name": "display_name_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'cn'"
+ },
+ "group_base_dn": {
+ "name": "group_base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "group_filter": {
+ "name": "group_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "admin_group": {
+ "name": "admin_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_enabled": {
+ "name": "tls_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_settings": {
+ "name": "notification_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "oidc_config": {
+ "name": "oidc_config",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "issuer_url": {
+ "name": "issuer_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'openid profile email'"
+ },
+ "username_claim": {
+ "name": "username_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'preferred_username'"
+ },
+ "email_claim": {
+ "name": "email_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'email'"
+ },
+ "display_name_claim": {
+ "name": "display_name_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'name'"
+ },
+ "admin_claim": {
+ "name": "admin_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "admin_value": {
+ "name": "admin_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_mappings_claim": {
+ "name": "role_mappings_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'groups'"
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "pending_container_updates": {
+ "name": "pending_container_updates",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "current_image": {
+ "name": "current_image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "checked_at": {
+ "name": "checked_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "pending_container_updates_environment_id_container_id_unique": {
+ "name": "pending_container_updates_environment_id_container_id_unique",
+ "columns": [
+ "environment_id",
+ "container_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "pending_container_updates_environment_id_environments_id_fk": {
+ "name": "pending_container_updates_environment_id_environments_id_fk",
+ "tableFrom": "pending_container_updates",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "registries": {
+ "name": "registries",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "registries_name_unique": {
+ "name": "registries_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "roles": {
+ "name": "roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_system": {
+ "name": "is_system",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_ids": {
+ "name": "environment_ids",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "roles_name_unique": {
+ "name": "roles_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "schedule_executions": {
+ "name": "schedule_executions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "triggered_by": {
+ "name": "triggered_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "triggered_at": {
+ "name": "triggered_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "logs": {
+ "name": "logs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "schedule_executions_type_id_idx": {
+ "name": "schedule_executions_type_id_idx",
+ "columns": [
+ "schedule_type",
+ "schedule_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "schedule_executions_environment_id_environments_id_fk": {
+ "name": "schedule_executions_environment_id_environments_id_fk",
+ "tableFrom": "schedule_executions",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sessions": {
+ "name": "sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "sessions_user_id_idx": {
+ "name": "sessions_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ },
+ "sessions_expires_at_idx": {
+ "name": "sessions_expires_at_idx",
+ "columns": [
+ "expires_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "settings": {
+ "name": "settings",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stack_environment_variables": {
+ "name": "stack_environment_variables",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_secret": {
+ "name": "is_secret",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "stack_environment_variables_stack_name_environment_id_key_unique": {
+ "name": "stack_environment_variables_stack_name_environment_id_key_unique",
+ "columns": [
+ "stack_name",
+ "environment_id",
+ "key"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "stack_environment_variables_environment_id_environments_id_fk": {
+ "name": "stack_environment_variables_environment_id_environments_id_fk",
+ "tableFrom": "stack_environment_variables",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stack_events": {
+ "name": "stack_events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_events_environment_id_environments_id_fk": {
+ "name": "stack_events_environment_id_environments_id_fk",
+ "tableFrom": "stack_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stack_sources": {
+ "name": "stack_sources",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'internal'"
+ },
+ "git_repository_id": {
+ "name": "git_repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "git_stack_id": {
+ "name": "git_stack_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "stack_sources_stack_name_environment_id_unique": {
+ "name": "stack_sources_stack_name_environment_id_unique",
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "stack_sources_environment_id_environments_id_fk": {
+ "name": "stack_sources_environment_id_environments_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_repository_id_git_repositories_id_fk": {
+ "name": "stack_sources_git_repository_id_git_repositories_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "git_repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_stack_id_git_stacks_id_fk": {
+ "name": "stack_sources_git_stack_id_git_stacks_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_stacks",
+ "columnsFrom": [
+ "git_stack_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_preferences": {
+ "name": "user_preferences",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "user_preferences_user_id_environment_id_key_unique": {
+ "name": "user_preferences_user_id_environment_id_key_unique",
+ "columns": [
+ "user_id",
+ "environment_id",
+ "key"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "user_preferences_user_id_users_id_fk": {
+ "name": "user_preferences_user_id_users_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_preferences_environment_id_environments_id_fk": {
+ "name": "user_preferences_environment_id_environments_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_roles": {
+ "name": "user_roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "user_roles_user_id_role_id_environment_id_unique": {
+ "name": "user_roles_user_id_role_id_environment_id_unique",
+ "columns": [
+ "user_id",
+ "role_id",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_role_id_roles_id_fk": {
+ "name": "user_roles_role_id_roles_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_environment_id_environments_id_fk": {
+ "name": "user_roles_environment_id_environments_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "avatar": {
+ "name": "avatar",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auth_provider": {
+ "name": "auth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'local'"
+ },
+ "mfa_enabled": {
+ "name": "mfa_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "mfa_secret": {
+ "name": "mfa_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "columns": [
+ "username"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "vulnerability_scans": {
+ "name": "vulnerability_scans",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image_id": {
+ "name": "image_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image_name": {
+ "name": "image_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scanner": {
+ "name": "scanner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scanned_at": {
+ "name": "scanned_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scan_duration": {
+ "name": "scan_duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "critical_count": {
+ "name": "critical_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "high_count": {
+ "name": "high_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "medium_count": {
+ "name": "medium_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "low_count": {
+ "name": "low_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "negligible_count": {
+ "name": "negligible_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "unknown_count": {
+ "name": "unknown_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "vulnerabilities": {
+ "name": "vulnerabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "vulnerability_scans_env_image_idx": {
+ "name": "vulnerability_scans_env_image_idx",
+ "columns": [
+ "environment_id",
+ "image_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "vulnerability_scans_environment_id_environments_id_fk": {
+ "name": "vulnerability_scans_environment_id_environments_id_fk",
+ "tableFrom": "vulnerability_scans",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json
new file mode 100644
index 0000000..5a24de1
--- /dev/null
+++ b/drizzle/meta/0003_snapshot.json
@@ -0,0 +1,3022 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "6414712d-d1a8-437b-9d1c-e339b4829a85",
+ "prevId": "31bce98b-04c0-4e21-8cb0-49a67c345d87",
+ "tables": {
+ "audit_logs": {
+ "name": "audit_logs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "audit_logs_user_id_idx": {
+ "name": "audit_logs_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ },
+ "audit_logs_created_at_idx": {
+ "name": "audit_logs_created_at_idx",
+ "columns": [
+ "created_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "audit_logs_user_id_users_id_fk": {
+ "name": "audit_logs_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_logs_environment_id_environments_id_fk": {
+ "name": "audit_logs_environment_id_environments_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "auth_settings": {
+ "name": "auth_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "auth_enabled": {
+ "name": "auth_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "default_provider": {
+ "name": "default_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'local'"
+ },
+ "session_timeout": {
+ "name": "session_timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 86400
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "auto_update_settings": {
+ "name": "auto_update_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "cron_expression": {
+ "name": "cron_expression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vulnerability_criteria": {
+ "name": "vulnerability_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'never'"
+ },
+ "last_checked": {
+ "name": "last_checked",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_updated": {
+ "name": "last_updated",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "auto_update_settings_environment_id_container_name_unique": {
+ "name": "auto_update_settings_environment_id_container_name_unique",
+ "columns": [
+ "environment_id",
+ "container_name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "auto_update_settings_environment_id_environments_id_fk": {
+ "name": "auto_update_settings_environment_id_environments_id_fk",
+ "tableFrom": "auto_update_settings",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "config_sets": {
+ "name": "config_sets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "env_vars": {
+ "name": "env_vars",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ports": {
+ "name": "ports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "volumes": {
+ "name": "volumes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "network_mode": {
+ "name": "network_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'bridge'"
+ },
+ "restart_policy": {
+ "name": "restart_policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'no'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "config_sets_name_unique": {
+ "name": "config_sets_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "container_events": {
+ "name": "container_events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "actor_attributes": {
+ "name": "actor_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "container_events_env_timestamp_idx": {
+ "name": "container_events_env_timestamp_idx",
+ "columns": [
+ "environment_id",
+ "timestamp"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "container_events_environment_id_environments_id_fk": {
+ "name": "container_events_environment_id_environments_id_fk",
+ "tableFrom": "container_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "environment_notifications": {
+ "name": "environment_notifications",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "notification_id": {
+ "name": "notification_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "environment_notifications_environment_id_notification_id_unique": {
+ "name": "environment_notifications_environment_id_notification_id_unique",
+ "columns": [
+ "environment_id",
+ "notification_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "environment_notifications_environment_id_environments_id_fk": {
+ "name": "environment_notifications_environment_id_environments_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "environment_notifications_notification_id_notification_settings_id_fk": {
+ "name": "environment_notifications_notification_id_notification_settings_id_fk",
+ "tableFrom": "environment_notifications",
+ "tableTo": "notification_settings",
+ "columnsFrom": [
+ "notification_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "environments": {
+ "name": "environments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 2375
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'http'"
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_cert": {
+ "name": "tls_cert",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_key": {
+ "name": "tls_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_skip_verify": {
+ "name": "tls_skip_verify",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'globe'"
+ },
+ "collect_activity": {
+ "name": "collect_activity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "collect_metrics": {
+ "name": "collect_metrics",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "highlight_changes": {
+ "name": "highlight_changes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "labels": {
+ "name": "labels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "connection_type": {
+ "name": "connection_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'socket'"
+ },
+ "socket_path": {
+ "name": "socket_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'/var/run/docker.sock'"
+ },
+ "hawser_token": {
+ "name": "hawser_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_last_seen": {
+ "name": "hawser_last_seen",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_agent_id": {
+ "name": "hawser_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_agent_name": {
+ "name": "hawser_agent_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_version": {
+ "name": "hawser_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "hawser_capabilities": {
+ "name": "hawser_capabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "environments_name_unique": {
+ "name": "environments_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_credentials": {
+ "name": "git_credentials",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'none'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_private_key": {
+ "name": "ssh_private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ssh_passphrase": {
+ "name": "ssh_passphrase",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_credentials_name_unique": {
+ "name": "git_credentials_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_repositories": {
+ "name": "git_repositories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'main'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'docker-compose.yml'"
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_repositories_name_unique": {
+ "name": "git_repositories_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "git_repositories_credential_id_git_credentials_id_fk": {
+ "name": "git_repositories_credential_id_git_credentials_id_fk",
+ "tableFrom": "git_repositories",
+ "tableTo": "git_credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "git_stacks": {
+ "name": "git_stacks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "repository_id": {
+ "name": "repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'docker-compose.yml'"
+ },
+ "env_file_path": {
+ "name": "env_file_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auto_update": {
+ "name": "auto_update",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "auto_update_schedule": {
+ "name": "auto_update_schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'daily'"
+ },
+ "auto_update_cron": {
+ "name": "auto_update_cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'0 3 * * *'"
+ },
+ "webhook_enabled": {
+ "name": "webhook_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "webhook_secret": {
+ "name": "webhook_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_sync": {
+ "name": "last_sync",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_commit": {
+ "name": "last_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "git_stacks_stack_name_environment_id_unique": {
+ "name": "git_stacks_stack_name_environment_id_unique",
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "git_stacks_environment_id_environments_id_fk": {
+ "name": "git_stacks_environment_id_environments_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_stacks_repository_id_git_repositories_id_fk": {
+ "name": "git_stacks_repository_id_git_repositories_id_fk",
+ "tableFrom": "git_stacks",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "hawser_tokens": {
+ "name": "hawser_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token_prefix": {
+ "name": "token_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "hawser_tokens_token_unique": {
+ "name": "hawser_tokens_token_unique",
+ "columns": [
+ "token"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "hawser_tokens_environment_id_environments_id_fk": {
+ "name": "hawser_tokens_environment_id_environments_id_fk",
+ "tableFrom": "hawser_tokens",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "host_metrics": {
+ "name": "host_metrics",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cpu_percent": {
+ "name": "cpu_percent",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "memory_percent": {
+ "name": "memory_percent",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "memory_used": {
+ "name": "memory_used",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "memory_total": {
+ "name": "memory_total",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "host_metrics_env_timestamp_idx": {
+ "name": "host_metrics_env_timestamp_idx",
+ "columns": [
+ "environment_id",
+ "timestamp"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "host_metrics_environment_id_environments_id_fk": {
+ "name": "host_metrics_environment_id_environments_id_fk",
+ "tableFrom": "host_metrics",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ldap_config": {
+ "name": "ldap_config",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "server_url": {
+ "name": "server_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bind_dn": {
+ "name": "bind_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bind_password": {
+ "name": "bind_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "base_dn": {
+ "name": "base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_filter": {
+ "name": "user_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'(uid={{username}})'"
+ },
+ "username_attribute": {
+ "name": "username_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'uid'"
+ },
+ "email_attribute": {
+ "name": "email_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'mail'"
+ },
+ "display_name_attribute": {
+ "name": "display_name_attribute",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'cn'"
+ },
+ "group_base_dn": {
+ "name": "group_base_dn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "group_filter": {
+ "name": "group_filter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "admin_group": {
+ "name": "admin_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tls_enabled": {
+ "name": "tls_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "tls_ca": {
+ "name": "tls_ca",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_settings": {
+ "name": "notification_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "event_types": {
+ "name": "event_types",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "oidc_config": {
+ "name": "oidc_config",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "issuer_url": {
+ "name": "issuer_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'openid profile email'"
+ },
+ "username_claim": {
+ "name": "username_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'preferred_username'"
+ },
+ "email_claim": {
+ "name": "email_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'email'"
+ },
+ "display_name_claim": {
+ "name": "display_name_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'name'"
+ },
+ "admin_claim": {
+ "name": "admin_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "admin_value": {
+ "name": "admin_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role_mappings_claim": {
+ "name": "role_mappings_claim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'groups'"
+ },
+ "role_mappings": {
+ "name": "role_mappings",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "pending_container_updates": {
+ "name": "pending_container_updates",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "container_name": {
+ "name": "container_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "current_image": {
+ "name": "current_image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "checked_at": {
+ "name": "checked_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "pending_container_updates_environment_id_container_id_unique": {
+ "name": "pending_container_updates_environment_id_container_id_unique",
+ "columns": [
+ "environment_id",
+ "container_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "pending_container_updates_environment_id_environments_id_fk": {
+ "name": "pending_container_updates_environment_id_environments_id_fk",
+ "tableFrom": "pending_container_updates",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "registries": {
+ "name": "registries",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "registries_name_unique": {
+ "name": "registries_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "roles": {
+ "name": "roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_system": {
+ "name": "is_system",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_ids": {
+ "name": "environment_ids",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "roles_name_unique": {
+ "name": "roles_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "schedule_executions": {
+ "name": "schedule_executions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "schedule_type": {
+ "name": "schedule_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "entity_name": {
+ "name": "entity_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "triggered_by": {
+ "name": "triggered_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "triggered_at": {
+ "name": "triggered_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "logs": {
+ "name": "logs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "schedule_executions_type_id_idx": {
+ "name": "schedule_executions_type_id_idx",
+ "columns": [
+ "schedule_type",
+ "schedule_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "schedule_executions_environment_id_environments_id_fk": {
+ "name": "schedule_executions_environment_id_environments_id_fk",
+ "tableFrom": "schedule_executions",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sessions": {
+ "name": "sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "sessions_user_id_idx": {
+ "name": "sessions_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ },
+ "sessions_expires_at_idx": {
+ "name": "sessions_expires_at_idx",
+ "columns": [
+ "expires_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "settings": {
+ "name": "settings",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stack_environment_variables": {
+ "name": "stack_environment_variables",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_secret": {
+ "name": "is_secret",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "stack_environment_variables_stack_name_environment_id_key_unique": {
+ "name": "stack_environment_variables_stack_name_environment_id_key_unique",
+ "columns": [
+ "stack_name",
+ "environment_id",
+ "key"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "stack_environment_variables_environment_id_environments_id_fk": {
+ "name": "stack_environment_variables_environment_id_environments_id_fk",
+ "tableFrom": "stack_environment_variables",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stack_events": {
+ "name": "stack_events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stack_events_environment_id_environments_id_fk": {
+ "name": "stack_events_environment_id_environments_id_fk",
+ "tableFrom": "stack_events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "stack_sources": {
+ "name": "stack_sources",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "stack_name": {
+ "name": "stack_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'internal'"
+ },
+ "git_repository_id": {
+ "name": "git_repository_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "git_stack_id": {
+ "name": "git_stack_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "compose_path": {
+ "name": "compose_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "env_path": {
+ "name": "env_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "stack_sources_stack_name_environment_id_unique": {
+ "name": "stack_sources_stack_name_environment_id_unique",
+ "columns": [
+ "stack_name",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "stack_sources_environment_id_environments_id_fk": {
+ "name": "stack_sources_environment_id_environments_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_repository_id_git_repositories_id_fk": {
+ "name": "stack_sources_git_repository_id_git_repositories_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_repositories",
+ "columnsFrom": [
+ "git_repository_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "stack_sources_git_stack_id_git_stacks_id_fk": {
+ "name": "stack_sources_git_stack_id_git_stacks_id_fk",
+ "tableFrom": "stack_sources",
+ "tableTo": "git_stacks",
+ "columnsFrom": [
+ "git_stack_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_preferences": {
+ "name": "user_preferences",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "user_preferences_user_id_environment_id_key_unique": {
+ "name": "user_preferences_user_id_environment_id_key_unique",
+ "columns": [
+ "user_id",
+ "environment_id",
+ "key"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "user_preferences_user_id_users_id_fk": {
+ "name": "user_preferences_user_id_users_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_preferences_environment_id_environments_id_fk": {
+ "name": "user_preferences_environment_id_environments_id_fk",
+ "tableFrom": "user_preferences",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_roles": {
+ "name": "user_roles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "user_roles_user_id_role_id_environment_id_unique": {
+ "name": "user_roles_user_id_role_id_environment_id_unique",
+ "columns": [
+ "user_id",
+ "role_id",
+ "environment_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_role_id_roles_id_fk": {
+ "name": "user_roles_role_id_roles_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "roles",
+ "columnsFrom": [
+ "role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_roles_environment_id_environments_id_fk": {
+ "name": "user_roles_environment_id_environments_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "avatar": {
+ "name": "avatar",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "auth_provider": {
+ "name": "auth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'local'"
+ },
+ "mfa_enabled": {
+ "name": "mfa_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "mfa_secret": {
+ "name": "mfa_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": true
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "columns": [
+ "username"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "vulnerability_scans": {
+ "name": "vulnerability_scans",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image_id": {
+ "name": "image_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image_name": {
+ "name": "image_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scanner": {
+ "name": "scanner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scanned_at": {
+ "name": "scanned_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scan_duration": {
+ "name": "scan_duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "critical_count": {
+ "name": "critical_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "high_count": {
+ "name": "high_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "medium_count": {
+ "name": "medium_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "low_count": {
+ "name": "low_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "negligible_count": {
+ "name": "negligible_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "unknown_count": {
+ "name": "unknown_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "vulnerabilities": {
+ "name": "vulnerabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP"
+ }
+ },
+ "indexes": {
+ "vulnerability_scans_env_image_idx": {
+ "name": "vulnerability_scans_env_image_idx",
+ "columns": [
+ "environment_id",
+ "image_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "vulnerability_scans_environment_id_environments_id_fk": {
+ "name": "vulnerability_scans_environment_id_environments_id_fk",
+ "tableFrom": "vulnerability_scans",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
new file mode 100644
index 0000000..f4192f3
--- /dev/null
+++ b/drizzle/meta/_journal.json
@@ -0,0 +1,34 @@
+{
+ "version": "7",
+ "dialect": "sqlite",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "6",
+ "when": 1765804016391,
+ "tag": "0000_initial_schema",
+ "breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "6",
+ "when": 1766378754939,
+ "tag": "0001_add_stack_env_vars",
+ "breakpoints": true
+ },
+ {
+ "idx": 2,
+ "version": "6",
+ "when": 1766763860091,
+ "tag": "0002_add_pending_container_updates",
+ "breakpoints": true
+ },
+ {
+ "idx": 3,
+ "version": "6",
+ "when": 1767689000000,
+ "tag": "0003_add_stack_paths",
+ "breakpoints": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/lib/.DS_Store b/lib/.DS_Store
deleted file mode 100644
index cd8d495..0000000
Binary files a/lib/.DS_Store and /dev/null differ
diff --git a/lib/components/StackEnvVarsPanel.svelte b/lib/components/StackEnvVarsPanel.svelte
deleted file mode 100644
index 15446ec..0000000
--- a/lib/components/StackEnvVarsPanel.svelte
+++ /dev/null
@@ -1,236 +0,0 @@
-
-
-
-
-
-
-
-
Environment variables
- {#if infoText}
-
-
-
-
-
- {infoText}
-
-
- {/if}
-
- {#if !readonly}
-
-
-
- Load .env
-
-
-
- Add
-
- {#if hasVariables}
-
-
-
- Clear
-
-
- {/if}
-
-
- {/if}
-
-
-
- ${`{VAR}`} required
- ${`{VAR:-default}`} optional
- ${`{VAR:?error}`} required w/ error
-
-
- {#if validation}
-
- {#if validation.missing.length > 0}
-
- {validation.missing.length} missing
-
- {/if}
- {#if validation.required.length > 0}
-
- {validation.required.length - validation.missing.length} required
-
- {/if}
- {#if validation.optional.length > 0}
-
- {validation.optional.length} optional
-
- {/if}
- {#if validation.unused.length > 0}
-
- {validation.unused.length} unused
-
- {/if}
-
- {/if}
-
- {#if validation && validation.missing.length > 0 && !readonly}
-
- Add missing:
- {#each validation.missing as missing}
- {
- variables = [...variables, { key: missing, value: '', isSecret: false }];
- }}
- class="text-xs px-1.5 py-0.5 rounded bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-300 dark:hover:bg-red-900/50 transition-colors"
- >
- {missing}
-
- {/each}
-
- {/if}
-
-
-
-
-
-
diff --git a/lib/server/.DS_Store b/lib/server/.DS_Store
deleted file mode 100644
index 7d5961e..0000000
Binary files a/lib/server/.DS_Store and /dev/null differ
diff --git a/lib/server/db/.DS_Store b/lib/server/db/.DS_Store
deleted file mode 100644
index cff8ab3..0000000
Binary files a/lib/server/db/.DS_Store and /dev/null differ
diff --git a/lib/server/metrics-collector.ts b/lib/server/metrics-collector.ts
deleted file mode 100644
index dbccbff..0000000
--- a/lib/server/metrics-collector.ts
+++ /dev/null
@@ -1,271 +0,0 @@
-import { saveHostMetric, getEnvironments, getEnvSetting } from './db';
-import { listContainers, getContainerStats, getDockerInfo, getDiskUsage } from './docker';
-import { sendEventNotification } from './notifications';
-import os from 'node:os';
-
-const COLLECT_INTERVAL = 10000; // 10 seconds
-const DISK_CHECK_INTERVAL = 300000; // 5 minutes
-const DEFAULT_DISK_THRESHOLD = 80; // 80% threshold for disk warnings
-
-let collectorInterval: ReturnType | null = null;
-let diskCheckInterval: ReturnType | null = null;
-
-// Track last disk warning sent per environment to avoid spamming
-const lastDiskWarning: Map = new Map();
-const DISK_WARNING_COOLDOWN = 3600000; // 1 hour between warnings
-
-/**
- * Collect metrics for a single environment
- */
-async function collectEnvMetrics(env: { id: number; name: string; collectMetrics?: boolean }) {
- try {
- // Skip environments where metrics collection is disabled
- if (env.collectMetrics === false) {
- return;
- }
-
- // Get running containers
- const containers = await listContainers(false, env.id); // Only running
- let totalCpuPercent = 0;
- let totalMemUsed = 0;
-
- // Get stats for each running container
- const statsPromises = containers.map(async (container) => {
- try {
- const stats = await getContainerStats(container.id, env.id) as any;
-
- // Calculate CPU percentage
- const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - stats.precpu_stats.cpu_usage.total_usage;
- const systemDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
- const cpuCount = stats.cpu_stats.online_cpus || os.cpus().length;
-
- let cpuPercent = 0;
- if (systemDelta > 0 && cpuDelta > 0) {
- cpuPercent = (cpuDelta / systemDelta) * cpuCount * 100;
- }
-
- // Get container memory usage
- const memUsage = stats.memory_stats?.usage || 0;
- const memCache = stats.memory_stats?.stats?.cache || 0;
- // Subtract cache from usage to get actual memory used by the container
- const actualMemUsed = memUsage - memCache;
-
- return { cpu: cpuPercent, mem: actualMemUsed > 0 ? actualMemUsed : memUsage };
- } catch {
- return { cpu: 0, mem: 0 };
- }
- });
-
- const statsResults = await Promise.all(statsPromises);
- totalCpuPercent = statsResults.reduce((sum, v) => sum + v.cpu, 0);
- totalMemUsed = statsResults.reduce((sum, v) => sum + v.mem, 0);
-
- // Get host total memory from Docker info (this is the remote host's memory)
- const info = await getDockerInfo(env.id) as any;
- const memTotal = info.MemTotal || os.totalmem();
-
- // Calculate memory percentage based on container usage vs host total
- const memPercent = memTotal > 0 ? (totalMemUsed / memTotal) * 100 : 0;
-
- // Normalize CPU by number of cores from the remote host
- const cpuCount = info.NCPU || os.cpus().length;
- const normalizedCpu = totalCpuPercent / cpuCount;
-
- // Save to database
- await saveHostMetric(
- normalizedCpu,
- memPercent,
- totalMemUsed,
- memTotal,
- env.id
- );
- } catch (error) {
- // Skip this environment if it fails (might be offline)
- console.error(`Failed to collect metrics for ${env.name}:`, error);
- }
-}
-
-async function collectMetrics() {
- try {
- const environments = await getEnvironments();
-
- // Filter enabled environments and collect metrics in parallel
- const enabledEnvs = environments.filter(env => env.collectMetrics !== false);
-
- // Process all environments in parallel for better performance
- await Promise.all(enabledEnvs.map(env => collectEnvMetrics(env)));
- } catch (error) {
- console.error('Metrics collection error:', error);
- }
-}
-
-/**
- * Check disk space for a single environment
- */
-async function checkEnvDiskSpace(env: { id: number; name: string; collectMetrics?: boolean }) {
- try {
- // Skip environments where metrics collection is disabled
- if (env.collectMetrics === false) {
- return;
- }
-
- // Check if we're in cooldown for this environment
- const lastWarningTime = lastDiskWarning.get(env.id);
- if (lastWarningTime && Date.now() - lastWarningTime < DISK_WARNING_COOLDOWN) {
- return; // Skip this environment, still in cooldown
- }
-
- // Get Docker disk usage data
- const diskData = await getDiskUsage(env.id) as any;
- if (!diskData) return;
-
- // Calculate total Docker disk usage using reduce for cleaner code
- let totalUsed = 0;
- if (diskData.Images) {
- totalUsed += diskData.Images.reduce((sum: number, img: any) => sum + (img.Size || 0), 0);
- }
- if (diskData.Containers) {
- totalUsed += diskData.Containers.reduce((sum: number, c: any) => sum + (c.SizeRw || 0), 0);
- }
- if (diskData.Volumes) {
- totalUsed += diskData.Volumes.reduce((sum: number, v: any) => sum + (v.UsageData?.Size || 0), 0);
- }
- if (diskData.BuildCache) {
- totalUsed += diskData.BuildCache.reduce((sum: number, bc: any) => sum + (bc.Size || 0), 0);
- }
-
- // Get Docker root filesystem info from Docker info
- const info = await getDockerInfo(env.id) as any;
- const driverStatus = info?.DriverStatus;
-
- // Try to find "Data Space Total" from driver status
- let dataSpaceTotal = 0;
- let diskPercentUsed = 0;
-
- if (driverStatus) {
- for (const [key, value] of driverStatus) {
- if (key === 'Data Space Total' && typeof value === 'string') {
- dataSpaceTotal = parseSize(value);
- break;
- }
- }
- }
-
- // If we found total disk space, calculate percentage
- if (dataSpaceTotal > 0) {
- diskPercentUsed = (totalUsed / dataSpaceTotal) * 100;
- } else {
- // Fallback: just report absolute usage if we can't determine percentage
- const GB = 1024 * 1024 * 1024;
- if (totalUsed > 50 * GB) {
- await sendEventNotification('disk_space_warning', {
- title: 'High Docker disk usage',
- message: `Environment "${env.name}" is using ${formatSize(totalUsed)} of Docker disk space`,
- type: 'warning'
- }, env.id);
- lastDiskWarning.set(env.id, Date.now());
- }
- return;
- }
-
- // Check against threshold
- const threshold = await getEnvSetting('disk_warning_threshold', env.id) || DEFAULT_DISK_THRESHOLD;
- if (diskPercentUsed >= threshold) {
- console.log(`[Metrics] Docker disk usage for ${env.name}: ${diskPercentUsed.toFixed(1)}% (threshold: ${threshold}%)`);
-
- await sendEventNotification('disk_space_warning', {
- title: 'Disk space warning',
- message: `Environment "${env.name}" Docker disk usage is at ${diskPercentUsed.toFixed(1)}% (${formatSize(totalUsed)} used)`,
- type: 'warning'
- }, env.id);
-
- lastDiskWarning.set(env.id, Date.now());
- }
- } catch (error) {
- // Skip this environment if it fails
- console.error(`Failed to check disk space for ${env.name}:`, error);
- }
-}
-
-/**
- * Check Docker disk usage and send warnings if above threshold
- */
-async function checkDiskSpace() {
- try {
- const environments = await getEnvironments();
-
- // Filter enabled environments and check disk space in parallel
- const enabledEnvs = environments.filter(env => env.collectMetrics !== false);
-
- // Process all environments in parallel for better performance
- await Promise.all(enabledEnvs.map(env => checkEnvDiskSpace(env)));
- } catch (error) {
- console.error('Disk space check error:', error);
- }
-}
-
-/**
- * Parse size string like "107.4GB" to bytes
- */
-function parseSize(sizeStr: string): number {
- const units: Record = {
- 'B': 1,
- 'KB': 1024,
- 'MB': 1024 * 1024,
- 'GB': 1024 * 1024 * 1024,
- 'TB': 1024 * 1024 * 1024 * 1024
- };
-
- const match = sizeStr.match(/^([\d.]+)\s*([KMGT]?B)$/i);
- if (!match) return 0;
-
- const value = parseFloat(match[1]);
- const unit = match[2].toUpperCase();
- return value * (units[unit] || 1);
-}
-
-/**
- * Format bytes to human readable string
- */
-function formatSize(bytes: number): string {
- const units = ['B', 'KB', 'MB', 'GB', 'TB'];
- let unitIndex = 0;
- let size = bytes;
-
- while (size >= 1024 && unitIndex < units.length - 1) {
- size /= 1024;
- unitIndex++;
- }
-
- return `${size.toFixed(1)} ${units[unitIndex]}`;
-}
-
-export function startMetricsCollector() {
- if (collectorInterval) return; // Already running
-
- console.log('Starting server-side metrics collector (every 10s)');
-
- // Initial collection
- collectMetrics();
-
- // Schedule regular collection
- collectorInterval = setInterval(collectMetrics, COLLECT_INTERVAL);
-
- // Start disk space checking (every 5 minutes)
- console.log('Starting disk space monitoring (every 5 minutes)');
- checkDiskSpace(); // Initial check
- diskCheckInterval = setInterval(checkDiskSpace, DISK_CHECK_INTERVAL);
-}
-
-export function stopMetricsCollector() {
- if (collectorInterval) {
- clearInterval(collectorInterval);
- collectorInterval = null;
- }
- if (diskCheckInterval) {
- clearInterval(diskCheckInterval);
- diskCheckInterval = null;
- }
- lastDiskWarning.clear();
- console.log('Metrics collector stopped');
-}
diff --git a/lib/server/stacks.ts b/lib/server/stacks.ts
deleted file mode 100644
index d4c99a1..0000000
--- a/lib/server/stacks.ts
+++ /dev/null
@@ -1,1109 +0,0 @@
-/**
- * Stack Management Module
- *
- * Provides compose-first stack operations for internal, git, and external stacks.
- * All lifecycle operations use docker compose commands.
- */
-
-import { existsSync, mkdirSync, rmSync, readdirSync } from 'node:fs';
-import { join, resolve } from 'node:path';
-import {
- getEnvironment,
- getStackEnvVarsAsRecord,
- getStackSource,
- upsertStackSource,
- deleteStackSource,
- getGitStackByName,
- deleteGitStack,
- getStackSources,
- deleteStackEnvVars
-} from './db';
-import { deleteGitStackFiles } from './git';
-
-// =============================================================================
-// TYPES
-// =============================================================================
-
-/**
- * Stack source types
- */
-export type StackSourceType = 'internal' | 'git' | 'external';
-
-/**
- * Stack operation result
- */
-export interface StackOperationResult {
- success: boolean;
- output?: string;
- error?: string;
-}
-
-/**
- * Container detail within a stack
- */
-export interface ContainerDetail {
- id: string;
- name: string;
- service: string;
- state: string;
- status: string;
- health?: string;
- image: string;
- ports: Array<{ publicPort: number; privatePort: number; type: string; display: string }>;
- networks: Array<{ name: string; ipAddress: string }>;
- volumeCount: number;
- restartCount: number;
- created: number;
-}
-
-/**
- * Compose stack information
- */
-export interface ComposeStackInfo {
- name: string;
- containers: string[];
- containerDetails: ContainerDetail[];
- status: 'running' | 'stopped' | 'partial' | 'created';
- sourceType?: StackSourceType;
- hasComposeFile?: boolean;
-}
-
-/**
- * Stack deployment options
- */
-export interface DeployStackOptions {
- name: string;
- compose: string;
- envId?: number | null;
- envFileVars?: Record;
- forceRecreate?: boolean;
-}
-
-// =============================================================================
-// ERRORS
-// =============================================================================
-
-/**
- * Error for operations on external stacks without compose files
- */
-export class ExternalStackError extends Error {
- public readonly stackName: string;
-
- constructor(stackName: string) {
- super(
- `Stack "${stackName}" was created outside of Dockhand. ` +
- `To manage this stack, first import it by clicking the Import button in the stack menu.`
- );
- this.name = 'ExternalStackError';
- this.stackName = stackName;
- }
-}
-
-/**
- * Error when compose file is missing for a managed stack
- */
-export class ComposeFileNotFoundError extends Error {
- public readonly stackName: string;
-
- constructor(stackName: string) {
- super(
- `Compose file not found for stack "${stackName}". ` +
- `The stack may have been deleted or was created outside of Dockhand.`
- );
- this.name = 'ComposeFileNotFoundError';
- this.stackName = stackName;
- }
-}
-
-// =============================================================================
-// INTERNAL STATE
-// =============================================================================
-
-// Cache stacks directory
-let _stacksDir: string | null = null;
-
-// Per-stack locking mechanism to prevent race conditions during concurrent operations
-const stackLocks = new Map>();
-
-/**
- * Execute a function with exclusive lock on a stack.
- * Prevents race conditions when multiple operations target the same stack.
- */
-async function withStackLock(stackName: string, fn: () => Promise): Promise {
- const lockKey = stackName;
-
- // Wait for any existing lock to release
- while (stackLocks.has(lockKey)) {
- await stackLocks.get(lockKey);
- }
-
- // Create new lock
- let releaseLock: () => void;
- const lockPromise = new Promise((resolve) => {
- releaseLock = resolve;
- });
- stackLocks.set(lockKey, lockPromise);
-
- try {
- return await fn();
- } finally {
- stackLocks.delete(lockKey);
- releaseLock!();
- }
-}
-
-// Timeout configuration for compose operations
-const COMPOSE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
-const COMPOSE_KILL_GRACE_MS = 5000; // 5 seconds grace period before SIGKILL
-
-// =============================================================================
-// DEBUG UTILITIES
-// =============================================================================
-
-/**
- * Mask sensitive values in environment variables for safe logging.
- * Masks values for keys containing common secret patterns and truncates long values.
- */
-function maskSecrets(vars: Record): Record {
- const masked: Record = {};
- const secretPatterns = /password|secret|token|key|api_key|apikey|auth|credential|private/i;
- for (const [key, value] of Object.entries(vars)) {
- if (secretPatterns.test(key)) {
- masked[key] = '***';
- } else if (value.length > 50) {
- // Truncate long values that might be secrets
- masked[key] = value.substring(0, 10) + '...(truncated)';
- } else {
- masked[key] = value;
- }
- }
- return masked;
-}
-
-// =============================================================================
-// UTILITIES
-// =============================================================================
-
-/**
- * Get the compose stacks directory (always returns absolute path)
- */
-export function getStacksDir(): string {
- if (_stacksDir) return _stacksDir;
- const dataDir = process.env.DATA_DIR || './data';
- // Resolve to absolute path to avoid issues with relative paths in docker compose
- _stacksDir = resolve(join(dataDir, 'stacks'));
- if (!existsSync(_stacksDir)) {
- mkdirSync(_stacksDir, { recursive: true });
- }
- return _stacksDir;
-}
-
-/**
- * List stacks that have compose files stored locally
- */
-export function listManagedStacks(): string[] {
- const stacksDir = getStacksDir();
- if (!existsSync(stacksDir)) {
- return [];
- }
-
- return readdirSync(stacksDir, { withFileTypes: true })
- .filter((dirent) => dirent.isDirectory())
- .filter((dirent) => {
- const composeYml = join(stacksDir, dirent.name, 'docker-compose.yml');
- const composeYaml = join(stacksDir, dirent.name, 'docker-compose.yaml');
- return existsSync(composeYml) || existsSync(composeYaml);
- })
- .map((dirent) => dirent.name);
-}
-
-// =============================================================================
-// COMPOSE FILE MANAGEMENT
-// =============================================================================
-
-/**
- * Get compose file content for a stack
- */
-export async function getStackComposeFile(
- stackName: string
-): Promise<{ success: boolean; content?: string; error?: string }> {
- const stacksDir = getStacksDir();
- const stackDir = join(stacksDir, stackName);
- const composeFile = join(stackDir, 'docker-compose.yml');
-
- const ymlFile = Bun.file(composeFile);
- if (await ymlFile.exists()) {
- return {
- success: true,
- content: await ymlFile.text()
- };
- }
-
- const yamlFile = Bun.file(join(stackDir, 'docker-compose.yaml'));
- if (await yamlFile.exists()) {
- return {
- success: true,
- content: await yamlFile.text()
- };
- }
-
- return {
- success: false,
- error: `Compose file not found for stack "${stackName}". The stack may have been created outside of Dockhand.`
- };
-}
-
-/**
- * Save or create a stack compose file without deploying.
- * @param name - Stack name
- * @param content - Compose file content
- * @param create - If true, creates a new stack (fails if exists). If false, updates existing (fails if not exists).
- */
-export async function saveStackComposeFile(
- name: string,
- content: string,
- create = false
-): Promise<{ success: boolean; error?: string }> {
- // Validate stack name
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
- return {
- success: false,
- error: 'Stack name can only contain letters, numbers, hyphens, and underscores'
- };
- }
-
- const stacksDir = getStacksDir();
- const stackDir = join(stacksDir, name);
- const composeFile = join(stackDir, 'docker-compose.yml');
- const exists = existsSync(stackDir);
-
- if (create) {
- // Creating new stack - if directory exists, it's orphaned (clean it up)
- if (exists) {
- try {
- console.log(`Cleaning up orphaned stack directory: ${stackDir}`);
- rmSync(stackDir, { recursive: true, force: true });
- } catch (err: any) {
- return { success: false, error: `Stack directory exists and cleanup failed: ${err.message}` };
- }
- }
- try {
- mkdirSync(stackDir, { recursive: true });
- } catch (err: any) {
- return { success: false, error: `Failed to create stack directory: ${err.message}` };
- }
- } else {
- // Updating existing stack - must exist
- if (!exists) {
- return { success: false, error: `Stack "${name}" not found` };
- }
- }
-
- try {
- await Bun.write(composeFile, content);
- return { success: true };
- } catch (err: any) {
- return { success: false, error: `Failed to ${create ? 'create' : 'save'} compose file: ${err.message}` };
- }
-}
-
-// =============================================================================
-// COMPOSE COMMAND EXECUTION
-// =============================================================================
-
-interface ComposeCommandOptions {
- stackName: string;
- envId?: number | null;
- forceRecreate?: boolean;
- removeVolumes?: boolean;
-}
-
-/**
- * Execute a docker compose command locally via Bun.spawn
- */
-async function executeLocalCompose(
- operation: 'up' | 'down' | 'stop' | 'start' | 'restart' | 'pull',
- stackName: string,
- composeContent: string,
- dockerHost?: string,
- envVars?: Record,
- forceRecreate?: boolean,
- removeVolumes?: boolean
-): Promise {
- const logPrefix = `[Stack:${stackName}]`;
- const stacksDir = getStacksDir();
- const stackDir = join(stacksDir, stackName);
- mkdirSync(stackDir, { recursive: true });
-
- const composeFile = join(stackDir, 'docker-compose.yml');
- await Bun.write(composeFile, composeContent);
-
- const spawnEnv: Record = { ...(process.env as Record) };
- if (dockerHost) {
- spawnEnv.DOCKER_HOST = dockerHost;
- }
- // Add stack-specific environment variables
- if (envVars) {
- Object.assign(spawnEnv, envVars);
- }
-
- // Build command based on operation
- const args = ['docker', 'compose', '-p', stackName, '-f', composeFile];
-
- switch (operation) {
- case 'up':
- args.push('up', '-d', '--remove-orphans');
- if (forceRecreate) args.push('--force-recreate');
- break;
- case 'down':
- args.push('down');
- if (removeVolumes) args.push('--volumes');
- break;
- case 'stop':
- args.push('stop');
- break;
- case 'start':
- args.push('start');
- break;
- case 'restart':
- args.push('restart');
- break;
- case 'pull':
- args.push('pull');
- break;
- }
-
- console.log(`${logPrefix} ----------------------------------------`);
- console.log(`${logPrefix} EXECUTE LOCAL COMPOSE`);
- console.log(`${logPrefix} ----------------------------------------`);
- console.log(`${logPrefix} Operation:`, operation);
- console.log(`${logPrefix} Command:`, args.join(' '));
- console.log(`${logPrefix} Working directory:`, stackDir);
- console.log(`${logPrefix} Compose file:`, composeFile);
- console.log(`${logPrefix} DOCKER_HOST:`, dockerHost || '(local socket)');
- console.log(`${logPrefix} Force recreate:`, forceRecreate ?? false);
- console.log(`${logPrefix} Remove volumes:`, removeVolumes ?? false);
- console.log(`${logPrefix} Env vars count:`, envVars ? Object.keys(envVars).length : 0);
- if (envVars && Object.keys(envVars).length > 0) {
- console.log(`${logPrefix} Env vars being injected (masked):`, JSON.stringify(maskSecrets(envVars), null, 2));
- }
-
- try {
- console.log(`${logPrefix} Spawning docker compose process...`);
- const proc = Bun.spawn(args, {
- cwd: stackDir,
- env: spawnEnv,
- stdout: 'pipe',
- stderr: 'pipe'
- });
-
- // Set up timeout with SIGTERM -> SIGKILL escalation
- let timedOut = false;
- const timeoutId = setTimeout(() => {
- timedOut = true;
- console.log(`${logPrefix} TIMEOUT: Process exceeded ${COMPOSE_TIMEOUT_MS / 1000} seconds, sending SIGTERM`);
- proc.kill('SIGTERM');
- // Give process grace period to terminate cleanly before SIGKILL
- setTimeout(() => {
- try {
- proc.kill('SIGKILL');
- console.log(`${logPrefix} TIMEOUT: Sent SIGKILL after grace period`);
- } catch {
- // Process may already be dead
- }
- }, COMPOSE_KILL_GRACE_MS);
- }, COMPOSE_TIMEOUT_MS);
-
- try {
- const [stdout, stderr] = await Promise.all([
- new Response(proc.stdout).text(),
- new Response(proc.stderr).text()
- ]);
-
- const code = await proc.exited;
-
- console.log(`${logPrefix} ----------------------------------------`);
- console.log(`${logPrefix} COMPOSE PROCESS COMPLETE`);
- console.log(`${logPrefix} ----------------------------------------`);
- console.log(`${logPrefix} Exit code:`, code);
- console.log(`${logPrefix} Timed out:`, timedOut);
- if (stdout) {
- console.log(`${logPrefix} STDOUT:`);
- console.log(stdout);
- }
- if (stderr) {
- console.log(`${logPrefix} STDERR:`);
- console.log(stderr);
- }
-
- if (timedOut) {
- return {
- success: false,
- output: stdout,
- error: `docker compose ${operation} timed out after ${COMPOSE_TIMEOUT_MS / 1000} seconds`
- };
- }
-
- if (code === 0) {
- return {
- success: true,
- output: stdout || stderr || `Stack "${stackName}" ${operation} completed successfully`
- };
- } else {
- return {
- success: false,
- output: stdout,
- error: stderr || `docker compose ${operation} exited with code ${code}`
- };
- }
- } finally {
- clearTimeout(timeoutId);
- }
- } catch (err: any) {
- console.log(`${logPrefix} EXCEPTION in executeLocalCompose:`, err.message);
- return {
- success: false,
- output: '',
- error: `Failed to run docker compose ${operation}: ${err.message}`
- };
- }
-}
-
-/**
- * Execute a docker compose command via Hawser agent
- */
-async function executeComposeViaHawser(
- operation: 'up' | 'down' | 'stop' | 'start' | 'restart' | 'pull',
- stackName: string,
- composeContent: string,
- envId: number,
- envVars?: Record,
- forceRecreate?: boolean,
- removeVolumes?: boolean
-): Promise {
- const logPrefix = `[Stack:${stackName}]`;
- // Import dockerFetch dynamically to avoid circular dependency
- const { dockerFetch } = await import('./docker.js');
-
- console.log(`${logPrefix} ----------------------------------------`);
- console.log(`${logPrefix} EXECUTE COMPOSE VIA HAWSER`);
- console.log(`${logPrefix} ----------------------------------------`);
- console.log(`${logPrefix} Operation:`, operation);
- console.log(`${logPrefix} Environment ID:`, envId);
- console.log(`${logPrefix} Force recreate:`, forceRecreate ?? false);
- console.log(`${logPrefix} Remove volumes:`, removeVolumes ?? false);
- console.log(`${logPrefix} Env vars count:`, envVars ? Object.keys(envVars).length : 0);
- if (envVars && Object.keys(envVars).length > 0) {
- console.log(`${logPrefix} Env vars being sent (masked):`, JSON.stringify(maskSecrets(envVars), null, 2));
- }
- console.log(`${logPrefix} Compose content length:`, composeContent.length, 'chars');
-
- try {
- const body = JSON.stringify({
- operation,
- projectName: stackName,
- composeFile: composeContent,
- envVars: envVars || {},
- forceRecreate: forceRecreate || false,
- removeVolumes: removeVolumes || false
- });
-
- console.log(`${logPrefix} Sending request to Hawser agent...`);
- const response = await dockerFetch(
- '/_hawser/compose',
- {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body
- },
- envId
- );
-
- const result = (await response.json()) as {
- success: boolean;
- output?: string;
- error?: string;
- };
-
- console.log(`${logPrefix} ----------------------------------------`);
- console.log(`${logPrefix} HAWSER RESPONSE`);
- console.log(`${logPrefix} ----------------------------------------`);
- console.log(`${logPrefix} Success:`, result.success);
- if (result.output) {
- console.log(`${logPrefix} Output:`, result.output);
- }
- if (result.error) {
- console.log(`${logPrefix} Error:`, result.error);
- }
-
- if (result.success) {
- return {
- success: true,
- output: result.output || `Stack "${stackName}" ${operation} completed via Hawser`
- };
- } else {
- return {
- success: false,
- output: result.output || '',
- error: result.error || `Compose ${operation} failed`
- };
- }
- } catch (err: any) {
- console.log(`${logPrefix} EXCEPTION in executeComposeViaHawser:`, err.message);
- return {
- success: false,
- output: '',
- error: `Failed to ${operation} via Hawser: ${err.message}`
- };
- }
-}
-
-/**
- * Route compose command to appropriate executor based on connection type
- */
-async function executeComposeCommand(
- operation: 'up' | 'down' | 'stop' | 'start' | 'restart' | 'pull',
- options: ComposeCommandOptions,
- composeContent: string,
- envVars?: Record
-): Promise {
- const { stackName, envId, forceRecreate, removeVolumes } = options;
-
- // Get environment configuration
- const env = envId ? await getEnvironment(envId) : null;
-
- if (!env) {
- // Local socket connection (no environment specified)
- return executeLocalCompose(
- operation,
- stackName,
- composeContent,
- undefined,
- envVars,
- forceRecreate,
- removeVolumes
- );
- }
-
- switch (env.connectionType) {
- case 'hawser-standard':
- case 'hawser-edge':
- return executeComposeViaHawser(
- operation,
- stackName,
- composeContent,
- envId!,
- envVars,
- forceRecreate,
- removeVolumes
- );
-
- case 'direct': {
- const port = env.port || 2375;
- const dockerHost = `tcp://${env.host}:${port}`;
- return executeLocalCompose(
- operation,
- stackName,
- composeContent,
- dockerHost,
- envVars,
- forceRecreate,
- removeVolumes
- );
- }
-
- case 'socket':
- default:
- return executeLocalCompose(
- operation,
- stackName,
- composeContent,
- undefined,
- envVars,
- forceRecreate,
- removeVolumes
- );
- }
-}
-
-// =============================================================================
-// STACK DISCOVERY
-// =============================================================================
-
-/**
- * List all compose stacks from Docker containers
- */
-export async function listComposeStacks(envId?: number | null): Promise {
- // Import dynamically to avoid circular dependency
- const { listContainers } = await import('./docker.js');
-
- const containers = await listContainers(true, envId);
- const stacks = new Map>();
-
- containers.forEach((container) => {
- const projectLabel = container.labels['com.docker.compose.project'];
- if (projectLabel) {
- if (!stacks.has(projectLabel)) {
- stacks.set(projectLabel, new Set());
- }
- stacks.get(projectLabel)?.add(container.id);
- }
- });
-
- const result: ComposeStackInfo[] = Array.from(stacks.entries()).map(([name, containerIds]) => {
- const stackContainers = containers.filter((c) => containerIds.has(c.id));
- const runningCount = stackContainers.filter((c) => c.state === 'running').length;
-
- const containerDetails: ContainerDetail[] = stackContainers
- .map((c) => {
- const service = c.labels['com.docker.compose.service'] || c.name;
-
- // Build ports with structured data for clickable links
- const ports = (c.ports || [])
- .filter((p) => p.PublicPort)
- .map((p) => ({
- publicPort: p.PublicPort!,
- privatePort: p.PrivatePort,
- type: p.Type,
- display: `${p.PublicPort}:${p.PrivatePort}/${p.Type}`
- }));
-
- // Build networks with IP addresses
- const networks = Object.entries(c.networks || {}).map(([name, data]) => ({
- name,
- ipAddress: data?.ipAddress || ''
- }));
-
- const volumeCount = c.mounts?.length || 0;
-
- return {
- id: c.id,
- name: c.name,
- service,
- state: c.state,
- status: c.status,
- health: c.health,
- image: c.image,
- ports,
- networks,
- volumeCount,
- restartCount: c.restartCount || 0,
- created: c.created
- };
- })
- .sort((a, b) => a.service.localeCompare(b.service));
-
- return {
- name,
- containers: Array.from(containerIds),
- containerDetails,
- status:
- runningCount === stackContainers.length
- ? 'running'
- : runningCount === 0
- ? 'stopped'
- : 'partial'
- };
- });
-
- return result;
-}
-
-/**
- * Get containers for a specific stack by label
- */
-async function getStackContainers(stackName: string, envId?: number | null): Promise {
- const { listContainers } = await import('./docker.js');
- const containers = await listContainers(true, envId);
- return containers.filter((c) => c.labels['com.docker.compose.project'] === stackName);
-}
-
-/**
- * Helper to perform container-based operations for external stacks
- * Used as fallback when no compose file exists.
- * Uses Promise.allSettled for parallel execution.
- */
-async function withContainerFallback(
- stackName: string,
- envId: number | null | undefined,
- operation: 'start' | 'stop' | 'restart' | 'remove'
-): Promise {
- const { startContainer, stopContainer, restartContainer, removeContainer } = await import('./docker.js');
-
- const containers = await getStackContainers(stackName, envId);
- if (containers.length === 0) {
- return { success: false, error: `No containers found for stack "${stackName}"` };
- }
-
- // Execute all container operations in parallel
- // Note: listContainers returns containers with lowercase property names: id, name, labels
- const operationResults = await Promise.allSettled(
- containers.map(async (container) => {
- const containerName = container.name || container.id;
- switch (operation) {
- case 'start':
- await startContainer(container.id, envId);
- break;
- case 'stop':
- await stopContainer(container.id, envId);
- break;
- case 'restart':
- await restartContainer(container.id, envId);
- break;
- case 'remove':
- await removeContainer(container.id, true, envId);
- break;
- }
- return containerName;
- })
- );
-
- // Collect successes and failures
- const successes: string[] = [];
- const errors: string[] = [];
-
- operationResults.forEach((result, index) => {
- const containerName = containers[index].name || containers[index].id;
- if (result.status === 'fulfilled') {
- successes.push(result.value);
- } else {
- errors.push(`${containerName}: ${result.reason?.message || 'Unknown error'}`);
- }
- });
-
- if (errors.length > 0) {
- return {
- success: successes.length > 0,
- error: errors.join('; '),
- output: successes.length > 0 ? `Partial success: ${successes.join(', ')}` : undefined
- };
- }
-
- return {
- success: true,
- output: `${operation} completed for ${successes.length} container(s): ${successes.join(', ')}`
- };
-}
-
-// =============================================================================
-// STACK LIFECYCLE OPERATIONS
-// =============================================================================
-
-/**
- * Ensure we have a compose file for operations, throw appropriate error if not
- */
-async function requireComposeFile(
- stackName: string,
- envId?: number | null
-): Promise<{ content: string; envVars: Record }> {
- const composeResult = await getStackComposeFile(stackName);
-
- if (!composeResult.success) {
- // Check if this is an external stack
- const source = await getStackSource(stackName, envId);
- if (!source || source.sourceType === 'external') {
- throw new ExternalStackError(stackName);
- }
- throw new ComposeFileNotFoundError(stackName);
- }
-
- // Get environment variables from database
- const envVars = await getStackEnvVarsAsRecord(stackName, envId);
-
- return { content: composeResult.content!, envVars };
-}
-
-/**
- * Start a stack using docker compose up
- * Falls back to individual container start for external stacks
- */
-export async function startStack(
- stackName: string,
- envId?: number | null
-): Promise {
- try {
- const { content, envVars } = await requireComposeFile(stackName, envId);
- return executeComposeCommand('up', { stackName, envId }, content, envVars);
- } catch (err) {
- if (err instanceof ExternalStackError) {
- return withContainerFallback(stackName, envId, 'start');
- }
- throw err;
- }
-}
-
-/**
- * Stop a stack using docker compose stop
- * Falls back to individual container stop for external stacks
- */
-export async function stopStack(
- stackName: string,
- envId?: number | null
-): Promise {
- try {
- const { content, envVars } = await requireComposeFile(stackName, envId);
- return executeComposeCommand('stop', { stackName, envId }, content, envVars);
- } catch (err) {
- if (err instanceof ExternalStackError) {
- return withContainerFallback(stackName, envId, 'stop');
- }
- throw err;
- }
-}
-
-/**
- * Restart a stack using docker compose restart
- * Falls back to individual container restart for external stacks
- */
-export async function restartStack(
- stackName: string,
- envId?: number | null
-): Promise {
- try {
- const { content, envVars } = await requireComposeFile(stackName, envId);
- return executeComposeCommand('restart', { stackName, envId }, content, envVars);
- } catch (err) {
- if (err instanceof ExternalStackError) {
- return withContainerFallback(stackName, envId, 'restart');
- }
- throw err;
- }
-}
-
-/**
- * Down a stack using docker compose down (removes containers, keeps files)
- * For external stacks, this is equivalent to stop (no compose file to "down")
- */
-export async function downStack(
- stackName: string,
- envId?: number | null,
- removeVolumes = false
-): Promise {
- try {
- const { content, envVars } = await requireComposeFile(stackName, envId);
- return executeComposeCommand('down', { stackName, envId, removeVolumes }, content, envVars);
- } catch (err) {
- if (err instanceof ExternalStackError) {
- // For external stacks, down is the same as stop (no compose file to tear down)
- return withContainerFallback(stackName, envId, 'stop');
- }
- throw err;
- }
-}
-
-/**
- * Remove a stack completely (compose down + delete files + cleanup database)
- * Uses stack locking to prevent concurrent operations.
- */
-export async function removeStack(
- stackName: string,
- envId?: number | null,
- force = false
-): Promise {
- return withStackLock(stackName, async () => {
- // Get compose file (may not exist for external stacks)
- const composeResult = await getStackComposeFile(stackName);
-
- // If compose file exists, run docker compose down first
- if (composeResult.success) {
- const envVars = await getStackEnvVarsAsRecord(stackName, envId);
- const downResult = await executeComposeCommand(
- 'down',
- { stackName, envId },
- composeResult.content!,
- envVars
- );
- if (!downResult.success && !force) {
- return downResult;
- }
- } else {
- // External stack - remove containers directly in parallel
- const { removeContainer } = await import('./docker.js');
- const stackContainers = await getStackContainers(stackName, envId);
-
- const removalResults = await Promise.allSettled(
- stackContainers.map((container) =>
- removeContainer(container.id, force, envId).then(() => container.name)
- )
- );
-
- const errors: string[] = [];
- removalResults.forEach((result, index) => {
- if (result.status === 'rejected') {
- const containerName = stackContainers[index].name || stackContainers[index].id;
- errors.push(`Failed to remove ${containerName}: ${result.reason?.message || 'Unknown error'}`);
- }
- });
-
- if (errors.length > 0 && !force) {
- return {
- success: false,
- error: errors.join('; ')
- };
- }
- }
-
- // Clean up database records - collect errors but don't stop
- const cleanupErrors: string[] = [];
-
- // Delete compose file and directory
- const stacksDir = getStacksDir();
- const stackDir = join(stacksDir, stackName);
- if (existsSync(stackDir)) {
- try {
- rmSync(stackDir, { recursive: true, force: true });
- } catch (err: any) {
- console.error(`Failed to delete stack directory: ${err.message}`);
- cleanupErrors.push(`directory: ${err.message}`);
- }
- // Verify deletion succeeded (rmSync with force:true may not throw on some failures)
- if (existsSync(stackDir)) {
- const verifyErr = 'Directory still exists after deletion attempt';
- console.error(`Failed to delete stack directory: ${verifyErr}`);
- cleanupErrors.push(`directory: ${verifyErr}`);
- }
- }
-
- try {
- await deleteStackSource(stackName, envId);
- } catch (err: any) {
- cleanupErrors.push(`stack source: ${err.message}`);
- }
-
- try {
- await deleteStackEnvVars(stackName, envId);
- } catch (err: any) {
- cleanupErrors.push(`env vars: ${err.message}`);
- }
-
- // If git stack, clean up git stack record
- try {
- const gitStack = await getGitStackByName(stackName, envId);
- if (gitStack) {
- await deleteGitStack(gitStack.id);
- deleteGitStackFiles(gitStack.id);
- }
- // Also cleanup any orphaned git stacks with NULL environment_id for this stack name
- if (envId !== undefined && envId !== null) {
- const orphanedGitStack = await getGitStackByName(stackName, null);
- if (orphanedGitStack) {
- await deleteGitStack(orphanedGitStack.id);
- deleteGitStackFiles(orphanedGitStack.id);
- }
- }
- } catch (err: any) {
- cleanupErrors.push(`git stack: ${err.message}`);
- }
-
- // Check if directory deletion failed - this blocks stack recreation
- const directoryError = cleanupErrors.find(e => e.startsWith('directory:'));
- if (directoryError) {
- return {
- success: false,
- error: `Stack containers stopped but directory cleanup failed (${directoryError}). Cannot recreate stack with same name until directory is manually removed.`
- };
- }
-
- // Return success with optional cleanup warnings for non-critical errors
- const output = cleanupErrors.length > 0
- ? `Stack "${stackName}" removed with cleanup warnings: ${cleanupErrors.join('; ')}`
- : `Stack "${stackName}" removed successfully`;
-
- return { success: true, output };
- });
-}
-
-/**
- * Deploy a stack (create or update)
- * Uses stack locking to prevent concurrent deployments.
- */
-export async function deployStack(options: DeployStackOptions): Promise {
- const { name, compose, envId, envFileVars, forceRecreate } = options;
- const logPrefix = `[Stack:${name}]`;
-
- console.log(`${logPrefix} ========================================`);
- console.log(`${logPrefix} DEPLOY STACK START`);
- console.log(`${logPrefix} ========================================`);
- console.log(`${logPrefix} Environment ID:`, envId ?? '(none - local)');
- console.log(`${logPrefix} Force recreate:`, forceRecreate ?? false);
- console.log(`${logPrefix} Env file vars provided:`, envFileVars ? Object.keys(envFileVars).length : 0);
- if (envFileVars && Object.keys(envFileVars).length > 0) {
- console.log(`${logPrefix} Env file var keys:`, Object.keys(envFileVars).join(', '));
- console.log(`${logPrefix} Env file vars (masked):`, JSON.stringify(maskSecrets(envFileVars), null, 2));
- }
-
- // Validate stack name
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
- console.log(`${logPrefix} ERROR: Invalid stack name format`);
- return {
- success: false,
- output: '',
- error: 'Stack name can only contain letters, numbers, hyphens, and underscores'
- };
- }
-
- return withStackLock(name, async () => {
- // Ensure stack directory exists and write compose file (for local reference)
- const stacksDir = getStacksDir();
- const stackDir = join(stacksDir, name);
- mkdirSync(stackDir, { recursive: true });
-
- const composeFile = join(stackDir, 'docker-compose.yml');
- await Bun.write(composeFile, compose);
- console.log(`${logPrefix} Compose file written to:`, composeFile);
- console.log(`${logPrefix} Compose content length:`, compose.length, 'chars');
- console.log(`${logPrefix} Compose content (full):`);
- console.log(compose);
-
- // Fetch stack environment variables from database (these are user overrides)
- const dbEnvVars = await getStackEnvVarsAsRecord(name, envId);
- console.log(`${logPrefix} DB env vars count:`, Object.keys(dbEnvVars).length);
- if (Object.keys(dbEnvVars).length > 0) {
- console.log(`${logPrefix} DB env var keys:`, Object.keys(dbEnvVars).join(', '));
- console.log(`${logPrefix} DB env vars (masked):`, JSON.stringify(maskSecrets(dbEnvVars), null, 2));
- }
-
- // Merge: env file vars as base, database overrides take precedence
- const envVars = { ...envFileVars, ...dbEnvVars };
- console.log(`${logPrefix} Merged env vars count:`, Object.keys(envVars).length);
- if (Object.keys(envVars).length > 0) {
- console.log(`${logPrefix} Merged env var keys:`, Object.keys(envVars).join(', '));
- console.log(`${logPrefix} Merged env vars (masked):`, JSON.stringify(maskSecrets(envVars), null, 2));
- }
-
- console.log(`${logPrefix} Calling executeComposeCommand...`);
- const result = await executeComposeCommand('up', { stackName: name, envId, forceRecreate }, compose, envVars);
- console.log(`${logPrefix} ========================================`);
- console.log(`${logPrefix} DEPLOY STACK RESULT`);
- console.log(`${logPrefix} ========================================`);
- console.log(`${logPrefix} Success:`, result.success);
- if (result.output) {
- console.log(`${logPrefix} Output:`, result.output);
- }
- if (result.error) {
- console.log(`${logPrefix} Error:`, result.error);
- }
- return result;
- });
-}
-
-/**
- * Pull images for a stack
- */
-export async function pullStackImages(
- stackName: string,
- envId?: number | null
-): Promise<{ success: boolean; output?: string; error?: string }> {
- const { content, envVars } = await requireComposeFile(stackName, envId);
-
- return executeComposeCommand('pull', { stackName, envId }, content, envVars);
-}
-
-// =============================================================================
-// RE-EXPORTS FOR BACKWARDS COMPATIBILITY
-// =============================================================================
-
-// These exports maintain API compatibility with code that imports from docker.ts
-// They can be removed once all imports are updated
-
-export type { StackOperationResult as CreateStackResult };
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..eea5398
--- /dev/null
+++ b/package.json
@@ -0,0 +1,118 @@
+{
+ "name": "dockhand",
+ "private": true,
+ "version": "1.0.7",
+ "type": "module",
+ "scripts": {
+ "dev": "bunx --bun vite dev",
+ "prebuild": "bunx license-checker --json --production | jq 'to_entries | map({name: (.key | split(\"@\")[0:-1] | join(\"@\")), version: (.key | split(\"@\")[-1]), license: .value.licenses, repository: .value.repository}) | sort_by(.name)' > src/lib/data/dependencies.json.tmp && mv src/lib/data/dependencies.json.tmp src/lib/data/dependencies.json || true",
+ "build": "bunx --bun vite build && bun scripts/patch-build.ts && bun scripts/build-subprocesses.ts",
+ "start": "bun ./build/index.js",
+ "preview": "bun ./build/index.js",
+ "prepare": "bunx --bun svelte-kit sync || echo ''",
+ "check": "bunx --bun svelte-kit sync && bunx --bun svelte-check --tsconfig ./tsconfig.json",
+ "check:watch": "bunx --bun svelte-kit sync && bunx --bun svelte-check --tsconfig ./tsconfig.json --watch",
+ "test": "bun test",
+ "test:smoke": "bun test tests/api-smoke.test.ts",
+ "test:containers": "bun test tests/container-lifecycle.test.ts",
+ "test:notifications": "bun test tests/notifications.test.ts",
+ "test:hawser": "bun test tests/hawser-connection.test.ts",
+ "test:build": "SKIP_BUILD_TEST=1 bun test tests/build.test.ts",
+ "test:postgres": "bun test tests/database-postgres.test.ts",
+ "test:crud": "bun test tests/crud-operations.test.ts",
+ "test:scheduling": "bun test tests/scheduling.test.ts",
+ "test:images": "bun test tests/images.test.ts",
+ "test:volumes": "bun test tests/volumes-networks.test.ts",
+ "test:stacks": "bun test tests/stacks.test.ts",
+ "test:stacks:matrix": "bun test tests/stack-matrix.test.ts",
+ "test:stacks:git": "bun test tests/stack-git-flow.test.ts",
+ "test:stacks:env": "bun test tests/stack-env-vars.test.ts",
+ "test:stacks:all": "bun test tests/stack-*.test.ts tests/stacks.test.ts",
+ "test:files": "bun test tests/container-files.test.ts",
+ "test:license": "bun test tests/license.test.ts",
+ "test:activity": "bun test tests/activity-dashboard.test.ts",
+ "test:all": "bun test tests/",
+ "test:quick": "bun test tests/api-smoke.test.ts tests/notifications.test.ts",
+ "test:integration": "bun test tests/api-smoke.test.ts tests/crud-operations.test.ts tests/scheduling.test.ts tests/hawser-connection.test.ts",
+ "test:e2e": "bunx playwright test tests/e2e/",
+ "generate:legal": "bun scripts/generate-legal-pages.ts"
+ },
+ "dependencies": {
+ "@codemirror/autocomplete": "6.20.0",
+ "@codemirror/commands": "6.10.1",
+ "@codemirror/lang-css": "6.3.1",
+ "@codemirror/lang-html": "6.4.11",
+ "@codemirror/lang-javascript": "6.2.4",
+ "@codemirror/lang-json": "6.0.2",
+ "@codemirror/lang-markdown": "6.5.0",
+ "@codemirror/lang-python": "6.2.1",
+ "@codemirror/lang-sql": "6.10.0",
+ "@codemirror/lang-xml": "6.1.0",
+ "@codemirror/lang-yaml": "6.1.2",
+ "@codemirror/language": "6.12.1",
+ "@codemirror/search": "6.5.11",
+ "@codemirror/state": "6.5.3",
+ "@codemirror/theme-one-dark": "6.1.3",
+ "@codemirror/view": "6.39.9",
+ "@lezer/highlight": "1.2.3",
+ "@lucide/lab": "^0.1.2",
+ "codemirror": "6.0.2",
+ "croner": "9.1.0",
+ "cronstrue": "3.9.0",
+ "drizzle-orm": "0.45.1",
+ "hash-wasm": "4.12.0",
+ "js-yaml": "^4.1.1",
+ "ldapts": "^8.1.3",
+ "nodemailer": "^7.0.12",
+ "otpauth": "^9.4.1",
+ "postgres": "3.4.8",
+ "qrcode": "^1.5.4",
+ "svelte-dnd-action": "0.9.69",
+ "svelte-sonner": "1.0.7"
+ },
+ "devDependencies": {
+ "@internationalized/date": "^3.10.1",
+ "@layerstack/tailwind": "^1.0.1",
+ "@lucide/svelte": "^0.562.0",
+ "@playwright/test": "1.57.0",
+ "@sveltejs/kit": "^2.49.3",
+ "@sveltejs/vite-plugin-svelte": "^6.2.3",
+ "@tailwindcss/vite": "^4.1.18",
+ "@types/bun": "^1.3.5",
+ "@types/js-yaml": "^4.0.9",
+ "@types/nodemailer": "^7.0.4",
+ "@types/qrcode": "^1.5.6",
+ "@xterm/addon-fit": "^0.11.0",
+ "@xterm/addon-web-links": "^0.12.0",
+ "@xterm/xterm": "^6.0.0",
+ "autoprefixer": "^10.4.23",
+ "bits-ui": "^2.15.4",
+ "clsx": "^2.1.1",
+ "cytoscape": "^3.33.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.2.0",
+ "drizzle-kit": "0.31.8",
+ "layerchart": "^1.0.13",
+ "lucide-svelte": "^0.562.0",
+ "mode-watcher": "^1.1.0",
+ "postcss": "^8.5.6",
+ "svelte": "^5.46.1",
+ "svelte-adapter-bun": "1.0.1",
+ "svelte-check": "^4.3.5",
+ "svelte-easy-crop": "^5.0.0",
+ "svelte-virtual-scroll-list": "^1.3.0",
+ "tailwind-merge": "^3.4.0",
+ "tailwind-variants": "^3.2.2",
+ "tailwindcss": "^4.1.18",
+ "tw-animate-css": "^1.4.0",
+ "typescript": "^5.9.3",
+ "vite": "^7.3.1"
+ },
+ "overrides": {
+ "@codemirror/state": "6.5.3",
+ "@codemirror/view": "6.39.9",
+ "@codemirror/language": "6.12.1",
+ "@lezer/common": "1.5.0",
+ "@lezer/highlight": "1.2.3"
+ }
+}
diff --git a/routes/api/stacks/[name]/env/+server.ts b/routes/api/stacks/[name]/env/+server.ts
deleted file mode 100644
index 23d7a4a..0000000
--- a/routes/api/stacks/[name]/env/+server.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import { json } from '@sveltejs/kit';
-import { getStackEnvVars, setStackEnvVars } from '$lib/server/db';
-import { authorize } from '$lib/server/authorize';
-import type { RequestHandler } from './$types';
-
-/**
- * GET /api/stacks/[name]/env?env=X
- * Get all environment variables for a stack.
- * Secrets are masked with '***' in the response.
- */
-export const GET: RequestHandler = async ({ params, url, cookies }) => {
- const auth = await authorize(cookies);
- const envId = url.searchParams.get('env');
- const envIdNum = envId ? parseInt(envId) : null;
-
- // Permission check with environment context
- if (auth.authEnabled && !await auth.can('stacks', 'view', envIdNum ?? undefined)) {
- return json({ error: 'Permission denied' }, { status: 403 });
- }
-
- // Environment access check (enterprise only)
- if (envIdNum && auth.isEnterprise && !await auth.canAccessEnvironment(envIdNum)) {
- return json({ error: 'Access denied to this environment' }, { status: 403 });
- }
-
- try {
- const stackName = decodeURIComponent(params.name);
- const variables = await getStackEnvVars(stackName, envIdNum, true);
-
- return json({
- variables: variables.map(v => ({
- key: v.key,
- value: v.value,
- isSecret: v.isSecret
- }))
- });
- } catch (error) {
- console.error('Error getting stack env vars:', error);
- return json({ error: 'Failed to get environment variables' }, { status: 500 });
- }
-};
-
-/**
- * PUT /api/stacks/[name]/env?env=X
- * Set/replace all environment variables for a stack.
- * Body: { variables: [{ key, value, isSecret? }] }
- *
- * Note: For secrets, if the value is '***' (the masked placeholder), the original
- * secret value from the database is preserved instead of overwriting with '***'.
- */
-export const PUT: RequestHandler = async ({ params, url, cookies, request }) => {
- const auth = await authorize(cookies);
- const envId = url.searchParams.get('env');
- const envIdNum = envId ? parseInt(envId) : null;
-
- // Permission check with environment context
- if (auth.authEnabled && !await auth.can('stacks', 'edit', envIdNum ?? undefined)) {
- return json({ error: 'Permission denied' }, { status: 403 });
- }
-
- // Environment access check (enterprise only)
- if (envIdNum && auth.isEnterprise && !await auth.canAccessEnvironment(envIdNum)) {
- return json({ error: 'Access denied to this environment' }, { status: 403 });
- }
-
- try {
- const stackName = decodeURIComponent(params.name);
- const body = await request.json();
-
- if (!body.variables || !Array.isArray(body.variables)) {
- return json({ error: 'Invalid request body: variables array required' }, { status: 400 });
- }
-
- // Validate variables
- for (const v of body.variables) {
- if (!v.key || typeof v.key !== 'string') {
- return json({ error: 'Invalid variable: key is required and must be a string' }, { status: 400 });
- }
- if (typeof v.value !== 'string') {
- return json({ error: `Invalid variable "${v.key}": value must be a string` }, { status: 400 });
- }
- // Validate key format (env var naming convention)
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(v.key)) {
- return json({ error: `Invalid variable name "${v.key}": must start with a letter or underscore and contain only alphanumeric characters and underscores` }, { status: 400 });
- }
- }
-
- // Check if any secrets have the masked placeholder '***'
- // If so, we need to preserve their original values from the database
- const secretsWithMaskedValue = body.variables.filter(
- (v: { key: string; value: string; isSecret?: boolean }) =>
- v.isSecret && v.value === '***'
- );
-
- let variablesToSave = body.variables;
-
- if (secretsWithMaskedValue.length > 0) {
- // Get existing variables (unmasked) to preserve secret values
- const existingVars = await getStackEnvVars(stackName, envIdNum, false);
- const existingByKey = new Map(existingVars.map(v => [v.key, v]));
-
- // Replace masked secrets with their original values
- variablesToSave = body.variables.map((v: { key: string; value: string; isSecret?: boolean }) => {
- if (v.isSecret && v.value === '***') {
- const existing = existingByKey.get(v.key);
- if (existing && existing.isSecret) {
- // Preserve the original secret value
- return { ...v, value: existing.value };
- }
- }
- return v;
- });
- }
-
- await setStackEnvVars(stackName, envIdNum, variablesToSave);
-
- return json({ success: true, count: variablesToSave.length });
- } catch (error) {
- console.error('Error setting stack env vars:', error);
- return json({ error: 'Failed to set environment variables' }, { status: 500 });
- }
-};
diff --git a/routes/containers/CreateContainerModal.svelte b/routes/containers/CreateContainerModal.svelte
deleted file mode 100644
index 6796704..0000000
--- a/routes/containers/CreateContainerModal.svelte
+++ /dev/null
@@ -1,1657 +0,0 @@
-
-
- isOpen && focusFirstInput()}>
-
-
- Create new container
-
-
- Close
-
-
-
-
- {#if !skipPullTab}
-
-
-
activeTab = 'pull'}
- >
-
- Pull
- {#if pullStatus === 'complete'}
-
- {:else if pullStatus === 'pulling'}
-
- {:else if pullStatus === 'error'}
-
- {/if}
-
- {#if envHasScanning}
-
-
-
activeTab = 'scan'}
- disabled={pullStatus === 'idle' || pullStatus === 'pulling'}
- >
-
- Scan
- {#if scanStatus === 'complete' && scanResults.length > 0}
- {#if hasCriticalOrHigh}
-
- {:else if totalVulnerabilities > 0}
-
- {:else}
-
- {/if}
- {:else if scanStatus === 'scanning'}
-
- {/if}
-
- {/if}
-
-
-
activeTab = 'container'}
- >
-
- Container
-
-
- {/if}
-
-
-
-
-
image = newImage}
- />
-
-
-
-
- {#if envHasScanning}
-
- {:else}
-
-
-
-
-
Vulnerability scanning is disabled for this environment.
-
Enable it in Settings → Environments to scan images.
-
-
- {/if}
-
-
-
-
-
-
-
-
-
-
Image: {image || 'Not set'}
- {#if isPulling || isScanning}
-
-
- {isScanning ? 'Scanning...' : 'Pulling...'}
-
- {:else if imageReady}
-
-
- Image pulled and ready
- {#if scanResults.length > 0}
- • {totalVulnerabilities} vulnerabilities
- {/if}
-
- {:else if !image && !skipPullTab}
-
-
- Go to "Pull" tab to set the image
-
- {/if}
-
-
-
-
-
- {#if configSets.length > 0}
-
-
-
-
Config set
-
-
-
-
-
- {selectedConfigSetId ? configSets.find(c => c.id === parseInt(selectedConfigSetId))?.name : 'Select a config set to pre-fill values...'}
-
-
- {#each configSets as configSet}
-
-
- {configSet.name}
- {#if configSet.description}
- {configSet.description}
- {/if}
-
-
- {/each}
-
-
-
-
-
- {/if}
-
-
-
-
-
Basic settings
-
-
-
-
Container name *
-
errors.name = undefined}
- />
- {#if errors.name}
-
{errors.name}
- {/if}
-
-
-
- Command (optional)
-
-
-
-
-
-
Restart policy
-
-
-
- {#if restartPolicy === 'no'}
-
- {:else if restartPolicy === 'always'}
-
- {:else if restartPolicy === 'on-failure'}
-
- {:else}
-
- {/if}
- {restartPolicy === 'no' ? 'No' : restartPolicy === 'always' ? 'Always' : restartPolicy === 'on-failure' ? 'On failure' : 'Unless stopped'}
-
-
-
-
- {#snippet children()}
-
- No
- {/snippet}
-
-
- {#snippet children()}
-
- Always
- {/snippet}
-
-
- {#snippet children()}
-
- On failure
- {/snippet}
-
-
- {#snippet children()}
-
- Unless stopped
- {/snippet}
-
-
-
-
-
-
- Network mode
-
-
-
- {#if networkMode === 'bridge'}
-
- {:else if networkMode === 'host'}
-
- {:else}
-
- {/if}
- {networkMode === 'bridge' ? 'Bridge' : networkMode === 'host' ? 'Host' : 'None'}
-
-
-
-
- {#snippet children()}
-
- Bridge
- {/snippet}
-
-
- {#snippet children()}
-
- Host
- {/snippet}
-
-
- {#snippet children()}
-
- None
- {/snippet}
-
-
-
-
-
-
-
- Start container after creation
-
-
-
-
-
- {#if availableNetworks.length > 0}
-
-
-
-
-
-
- Select network to add...
-
-
- {#each availableNetworks.filter(n => !selectedNetworks.includes(n.name) && !['bridge', 'host', 'none'].includes(n.name)) as network}
-
- {#snippet children()}
-
- {network.name}
- {network.driver}
-
- {/snippet}
-
- {/each}
-
-
-
- {#if selectedNetworks.length > 0}
-
- {#each selectedNetworks as networkName}
- {@const network = availableNetworks.find(n => n.name === networkName)}
-
- {networkName}
- {#if network}
- {network.driver}
- {/if}
- removeNetwork(networkName)}
- class="ml-0.5 hover:bg-destructive/20 rounded p-0.5"
- >
-
-
-
- {/each}
-
- {/if}
-
-
- {/if}
-
-
-
-
-
Port mappings
-
-
- Add
-
-
-
-
- {#each portMappings as mapping, index}
-
- {/each}
-
-
-
-
-
-
-
Volume mappings
-
-
- Add
-
-
-
-
- {#each volumeMappings as mapping, index}
-
- {/each}
-
-
-
-
-
-
-
Environment variables
-
-
- Add
-
-
-
-
- {#each envVars as envVar, index}
-
-
- Key
-
-
-
- Value
-
-
-
removeEnvVar(index)}
- disabled={envVars.length === 1}
- class="h-9 w-9 text-muted-foreground hover:text-destructive"
- >
-
-
-
- {/each}
-
-
-
-
-
-
-
-
- {#each labels as label, index}
-
-
- Key
-
-
-
- Value
-
-
-
removeLabel(index)}
- disabled={labels.length === 1}
- class="h-9 w-9 text-muted-foreground hover:text-destructive"
- >
-
-
-
- {/each}
-
-
-
-
-
-
Advanced container options (click to expand)
-
-
-
-
-
showResources = !showResources}
- class="w-full flex items-center justify-between p-3 hover:bg-muted/50 transition-colors"
- >
-
-
- Resources
- {#if memoryLimit || nanoCpus || cpuShares}
- configured
- {/if}
-
- {#if showResources}
-
- {:else}
-
- {/if}
-
- {#if showResources}
-
-
Configure memory and CPU limits for this container
-
-
-
- {/if}
-
-
-
-
-
showSecurity = !showSecurity}
- class="w-full flex items-center justify-between p-3 hover:bg-muted/50 transition-colors"
- >
-
-
- Security
- {#if privilegedMode || containerUser || capAdd.length > 0 || capDrop.length > 0}
- configured
- {/if}
-
- {#if showSecurity}
-
- {:else}
-
- {/if}
-
- {#if showSecurity}
-
-
-
- User
-
-
-
-
-
-
-
- Privileged mode
-
-
-
-
-
-
-
Add capabilities
-
{ addCapability('add', v); }}>
-
- Select capability to add...
-
-
- {#each commonCapabilities.filter(c => !capAdd.includes(c)) as cap}
-
- {/each}
-
-
- {#if capAdd.length > 0}
-
- {#each capAdd as cap}
-
- +{cap}
- removeCapability('add', cap)} class="ml-1 hover:text-destructive">
-
-
-
- {/each}
-
- {/if}
-
-
-
-
Drop capabilities
-
{ addCapability('drop', v); }}>
-
- Select capability to drop...
-
-
- {#each commonCapabilities.filter(c => !capDrop.includes(c)) as cap}
-
- {/each}
-
-
- {#if capDrop.length > 0}
-
- {#each capDrop as cap}
-
- -{cap}
- removeCapability('drop', cap)} class="ml-1 hover:text-destructive">
-
-
-
- {/each}
-
- {/if}
-
-
- {/if}
-
-
-
-
-
showHealth = !showHealth}
- class="w-full flex items-center justify-between p-3 hover:bg-muted/50 transition-colors"
- >
-
-
- Healthcheck
- {#if healthcheckEnabled}
- enabled
- {/if}
-
- {#if showHealth}
-
- {:else}
-
- {/if}
-
- {#if showHealth}
-
-
-
- Enable healthcheck
-
- {#if healthcheckEnabled}
-
- Command
-
-
-
- {/if}
-
- {/if}
-
-
-
-
-
showDns = !showDns}
- class="w-full flex items-center justify-between p-3 hover:bg-muted/50 transition-colors"
- >
-
-
- DNS settings
- {#if dnsServers.length > 0 || dnsSearch.length > 0}
- configured
- {/if}
-
- {#if showDns}
-
- {:else}
-
- {/if}
-
- {#if showDns}
-
-
-
DNS servers
-
-
{ if (e.key === 'Enter') { e.preventDefault(); addDnsServer(); } }}
- />
-
-
-
-
- {#if dnsServers.length > 0}
-
- {#each dnsServers as server}
-
- {server}
- removeDnsServer(server)} class="ml-1 hover:text-destructive">
-
-
-
- {/each}
-
- {/if}
-
-
- {/if}
-
-
-
-
-
showDevices = !showDevices}
- class="w-full flex items-center justify-between p-3 hover:bg-muted/50 transition-colors"
- >
-
-
- Devices
- {#if deviceMappings.length > 0}
- {deviceMappings.length}
- {/if}
-
- {#if showDevices}
-
- {:else}
-
- {/if}
-
- {#if showDevices}
-
- {/if}
-
-
-
-
-
showUlimits = !showUlimits}
- class="w-full flex items-center justify-between p-3 hover:bg-muted/50 transition-colors"
- >
-
-
- Ulimits
- {#if ulimits.length > 0}
- {ulimits.length}
- {/if}
-
- {#if showUlimits}
-
- {:else}
-
- {/if}
-
- {#if showUlimits}
-
- {/if}
-
-
-
-
-
-
-
-
- {#if activeTab === 'container' && hasCriticalOrHigh}
-
-
-
Critical/high vulnerabilities found in image
-
- {/if}
-
-
-
- Cancel
-
-
- {#if loading}
-
- Creating...
- {:else}
-
- Create container
- {/if}
-
-
-
-
-
diff --git a/routes/containers/EditContainerModal.svelte b/routes/containers/EditContainerModal.svelte
deleted file mode 100644
index f0f41f3..0000000
--- a/routes/containers/EditContainerModal.svelte
+++ /dev/null
@@ -1,1299 +0,0 @@
-
-
- isOpen && focusFirstInput()}>
-
-
-
- Edit container
- {#if isEditingTitle}
- -
- {
- if (e.key === 'Enter') saveEditingTitle();
- if (e.key === 'Escape') cancelEditingTitle();
- }}
- />
-
-
-
-
-
-
- {:else if name}
- - {name}
-
-
-
- {/if}
-
-
-
- {#if loadingData}
-
-
- Loading container data...
-
- {:else}
-
-
- {#if configSets.length > 0}
-
-
-
-
Apply config set
-
-
-
-
-
- {selectedConfigSetId ? configSets.find(c => c.id === parseInt(selectedConfigSetId))?.name : 'Select a config set to merge values...'}
-
-
- {#each configSets as configSet}
-
-
- {configSet.name}
- {#if configSet.description}
- {configSet.description}
- {/if}
-
-
- {/each}
-
-
-
-
- {#if selectedConfigSetId}
- {@const selectedSet = configSets.find(c => c.id === parseInt(selectedConfigSetId))}
- {#if selectedSet?.description}
-
{selectedSet.description}
- {/if}
- {/if}
-
Note: Values from the config set will be merged with existing settings. Existing keys won't be overwritten.
-
- {/if}
-
-
- {#if showComposeConfigWarning}
-
-
-
-
- This container belongs to stack "{composeStackName}"
-
-
- Modifying settings will remove this container from the stack. The container will be recreated and lose its stack association. To avoid this, edit the stack's compose file instead.
-
-
-
- {:else if showComposeRenameWarning}
-
-
-
-
- Renaming container from stack "{composeStackName}"
-
-
- The container will stay in the stack, but the compose file will be out of sync. Running docker compose up may recreate it with the original name.
-
-
-
- {:else if isComposeContainer}
-
-
-
-
- Stack container: {composeStackName}
-
-
- This container is managed by a Docker Compose stack.
-
-
-
- {/if}
-
-
-
-
-
Basic settings
-
-
-
-
-
Container name *
-
errors.name = undefined}
- />
- {#if errors.name}
-
{errors.name}
- {/if}
-
-
-
Image *
-
errors.image = undefined}
- />
- {#if errors.image}
-
{errors.image}
- {/if}
-
-
-
-
- Command (optional)
-
-
-
-
-
-
Restart policy
-
-
-
- {#if restartPolicy === 'no'}
-
- {:else if restartPolicy === 'always'}
-
- {:else if restartPolicy === 'on-failure'}
-
- {:else}
-
- {/if}
- {restartPolicy === 'no' ? 'No' : restartPolicy === 'always' ? 'Always' : restartPolicy === 'on-failure' ? 'On failure' : 'Unless stopped'}
-
-
-
-
- {#snippet children()}
-
- No
- {/snippet}
-
-
- {#snippet children()}
-
- Always
- {/snippet}
-
-
- {#snippet children()}
-
- On failure
- {/snippet}
-
-
- {#snippet children()}
-
- Unless stopped
- {/snippet}
-
-
-
-
-
-
- Network mode
-
-
-
- {#if networkMode === 'bridge'}
-
- {:else if networkMode === 'host'}
-
- {:else}
-
- {/if}
- {networkMode === 'bridge' ? 'Bridge' : networkMode === 'host' ? 'Host' : 'None'}
-
-
-
-
- {#snippet children()}
-
- Bridge
- {/snippet}
-
-
- {#snippet children()}
-
- Host
- {/snippet}
-
-
- {#snippet children()}
-
- None
- {/snippet}
-
-
-
-
-
-
-
-
- Start container after update
-
-
-
-
- {#if availableNetworks.length > 0}
-
-
-
-
-
-
- Select network to add...
-
-
- {#each availableNetworks.filter(n => !selectedNetworks.includes(n.name) && !['bridge', 'host', 'none'].includes(n.name)) as network}
-
- {#snippet children()}
-
- {network.name}
- {network.driver}
-
- {/snippet}
-
- {/each}
-
-
-
- {#if selectedNetworks.length > 0}
-
- {#each selectedNetworks as networkName}
- {@const network = availableNetworks.find(n => n.name === networkName)}
-
- {networkName}
- {#if network}
- {network.driver}
- {/if}
- removeNetwork(networkName)}
- class="ml-0.5 hover:bg-destructive/20 rounded p-0.5"
- >
-
-
-
- {/each}
-
- {/if}
-
Container will be connected to selected networks in addition to the network mode above
-
-
- {/if}
-
-
-
-
-
Port mappings
-
-
- Add
-
-
-
-
- {#each portMappings as mapping, index}
-
-
- Host
-
-
-
- Container
-
-
-
- Protocol
-
-
- {mapping.protocol.toUpperCase()}
-
-
-
-
-
-
-
-
removePortMapping(index)}
- disabled={portMappings.length === 1}
- class="h-9 w-9 text-muted-foreground hover:text-destructive"
- >
-
-
-
- {/each}
-
-
-
-
-
-
-
Volume mappings
-
-
- Add
-
-
-
-
- {#each volumeMappings as mapping, index}
-
-
- Host path
-
-
-
- Container path
-
-
-
- Mode
-
-
- {mapping.mode.toUpperCase()}
-
-
-
-
-
-
-
-
removeVolumeMapping(index)}
- disabled={volumeMappings.length === 1}
- class="h-9 w-9 text-muted-foreground hover:text-destructive"
- >
-
-
-
- {/each}
-
-
-
-
-
-
-
Environment variables
-
-
- Add
-
-
-
-
- {#each envVars as envVar, index}
-
-
- Key
-
-
-
- Value
-
-
-
removeEnvVar(index)}
- disabled={envVars.length === 1}
- class="h-9 w-9 text-muted-foreground hover:text-destructive"
- >
-
-
-
- {/each}
-
-
-
-
-
-
-
-
- {#each labels as label, index}
-
-
- Key
-
-
-
- Value
-
-
-
removeLabel(index)}
- disabled={labels.length === 1}
- class="h-9 w-9 text-muted-foreground hover:text-destructive"
- >
-
-
-
- {/each}
-
-
-
-
-
-
-
-
Auto-update
-
-
-
-
-
- {#if statusMessage}
-
- {statusMessage}
-
- {/if}
-
- {#if error}
-
- {error}
-
- {/if}
-
-
-
-
- Cancel
-
-
- {#if loading}
-
- Updating...
- {:else}
- Update container
- {/if}
-
-
- {/if}
-
-
diff --git a/routes/profile/MfaSetupModal.svelte b/routes/profile/MfaSetupModal.svelte
deleted file mode 100644
index c41370d..0000000
--- a/routes/profile/MfaSetupModal.svelte
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
- { if (o) { resetForm(); focusFirstInput(); } else onClose(); }}>
-
-
-
-
- Setup two-factor authentication
-
-
-
- {#if error}
-
-
- {error}
-
- {/if}
-
-
- Scan this QR code with your authenticator app (Google Authenticator, Authy, etc.)
-
-
- {#if qrCode}
-
-
-
- {/if}
-
-
- Or enter this code manually:
- {secret}
-
-
-
-
Verification code
-
-
- Enter the code from your authenticator app to verify setup
-
-
-
-
- Cancel
-
- {#if loading}
-
- {:else}
-
- {/if}
- Enable MFA
-
-
-
-
diff --git a/routes/stacks/StackModal.svelte b/routes/stacks/StackModal.svelte
deleted file mode 100644
index 8ed7b7c..0000000
--- a/routes/stacks/StackModal.svelte
+++ /dev/null
@@ -1,740 +0,0 @@
-
-
- {
- if (isOpen) {
- focusFirstInput();
- } else {
- // Prevent closing if there are unsaved changes - show confirmation instead
- if (hasChanges) {
- // Re-open the dialog and show confirmation
- open = true;
- showConfirmClose = true;
- }
- // If no changes, let it close naturally
- }
- }}
->
-
-
-
-
-
-
-
-
-
-
- {#if mode === 'create'}
- Create compose stack
- {:else}
- {stackName}
- {/if}
-
-
- {#if mode === 'create'}
- Create a new Docker Compose stack
- {:else}
- Edit compose file and view stack structure
- {/if}
-
-
-
-
-
-
- activeTab = 'editor'}
- >
-
- Editor
-
- activeTab = 'graph'}
- >
-
- Graph
-
-
-
-
-
-
- {#if activeTab === 'editor'}
-
- {#if editorTheme === 'light'}
-
- {:else}
-
- {/if}
-
- {/if}
-
-
-
-
-
-
-
-
-
-
- {#if error}
-
-
- {error}
-
- {/if}
-
- {#if errors.compose}
-
-
- {errors.compose}
-
- {/if}
-
- {#if mode === 'edit' && loading}
-
-
-
- Loading compose file...
-
-
- {:else if mode === 'edit' && loadError}
-
-
-
-
Could not load compose file
-
{loadError}
-
- This stack may have been created outside of Dockhand or the compose file may have been moved.
-
-
-
- {:else}
-
- {#if mode === 'create'}
-
-
-
Stack name
-
errors.stackName = undefined}
- />
- {#if errors.stackName}
-
{errors.stackName}
- {/if}
-
-
- {/if}
-
-
-
- {#if activeTab === 'editor'}
-
-
- {#if open}
-
-
-
- {/if}
-
-
-
-
-
- Environment variables
-
-
- validateEnvVars()}
- />
-
-
- {:else if activeTab === 'graph'}
-
-
- {/if}
-
- {/if}
-
-
-
-
-
- {#if hasChanges}
- Unsaved changes
- {:else}
- No changes
- {/if}
-
-
-
-
- Cancel
-
-
- {#if mode === 'create'}
-
-
handleCreate(false)} disabled={saving}>
- {#if saving}
-
- Creating...
- {:else}
-
- Create
- {/if}
-
-
handleCreate(true)} disabled={saving}>
- {#if saving}
-
- Starting...
- {:else}
-
- Create & Start
- {/if}
-
- {:else}
-
-
handleSave(false)} disabled={saving || !hasChanges || loading || !!loadError}>
- {#if saving}
-
- Saving...
- {:else}
-
- Save
- {/if}
-
-
handleSave(true)} disabled={saving || !hasChanges || loading || !!loadError}>
- {#if saving}
-
- Applying...
- {:else}
-
- Save & apply
- {/if}
-
- {/if}
-
-
-
-
-
-
-
-
-
- Unsaved changes
-
- You have unsaved changes. Are you sure you want to close without saving?
-
-
-
- showConfirmClose = false}>
- Continue editing
-
-
- Discard changes
-
-
-
-
diff --git a/routes/terminal/+page.svelte b/routes/terminal/+page.svelte
deleted file mode 100644
index 4dd3fc2..0000000
--- a/routes/terminal/+page.svelte
+++ /dev/null
@@ -1,349 +0,0 @@
-
-
-{#if $environments.length === 0 || !$currentEnvironment}
-
-{:else}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {#if dropdownOpen}
-
- {#if filteredContainers().length === 0}
-
- {containers.length === 0 ? 'No running containers' : 'No matches found'}
-
- {:else}
- {#each filteredContainers() as container}
-
selectContainer(container)}
- class="w-full px-3 py-2 text-left text-sm hover:bg-muted transition-colors flex items-center gap-2 {selectedContainer?.id === container.id ? 'bg-muted' : ''}"
- >
- {container.name}
- ({container.image})
- {#if selectedContainer?.id === container.id}
- connected
- {/if}
-
- {/each}
- {/if}
-
- {/if}
-
-
- {#if selectedContainer}
-
-
- Disconnect
-
- {/if}
-
- {#if !selectedContainer}
-
- Shell:
-
-
-
- {shellOptions.find(o => o.value === selectedShell)?.label || 'Select'}
-
-
- {#each shellOptions as option}
-
-
- {option.label}
-
- {/each}
-
-
-
-
- User:
-
-
-
- {userOptions.find(o => o.value === selectedUser)?.label || 'Select'}
-
-
- {#each userOptions as option}
-
-
- {option.label}
-
- {/each}
-
-
-
- {/if}
-
-
-
-
- {#if !selectedContainer}
-
-
-
-
Select a container to open shell
-
-
- {:else}
-
-
-
- {#if connected}
-
-
- Connected
-
- {:else}
- Disconnected
- {/if}
-
-
- changeFontSize(Number(v))}>
-
- {terminalFontSize}px
-
-
- {#each fontSizeOptions as size}
- {size}px
- {/each}
-
-
- terminalComponent?.copyOutput()}
- class="p-1 rounded hover:bg-zinc-800 transition-colors"
- title="Copy output"
- >
-
-
- terminalComponent?.clear()}
- class="p-1 rounded hover:bg-zinc-800 transition-colors"
- title="Clear (Cmd+L)"
- >
-
-
- terminalComponent?.reconnect()}
- class="p-1 rounded hover:bg-zinc-800 transition-colors"
- title="Reconnect"
- >
-
-
-
-
-
- {#key selectedContainer.id}
-
- {/key}
-
- {/if}
-
-
-{/if}
-
-
diff --git a/scripts/build-subprocesses.ts b/scripts/build-subprocesses.ts
new file mode 100644
index 0000000..35958e5
--- /dev/null
+++ b/scripts/build-subprocesses.ts
@@ -0,0 +1,31 @@
+/**
+ * Build subprocess scripts as standalone bundles for production.
+ *
+ * Subprocesses run via Bun.spawn and need all dependencies bundled
+ * since they can't access the SvelteKit build output's chunked modules.
+ */
+
+const subprocesses = ['metrics-subprocess', 'event-subprocess'];
+
+console.log('[build-subprocesses] Bundling subprocess scripts...');
+
+for (const name of subprocesses) {
+ const result = await Bun.build({
+ entrypoints: [`./src/lib/server/subprocesses/${name}.ts`],
+ outdir: './build/subprocesses',
+ target: 'bun',
+ minify: false
+ });
+
+ if (!result.success) {
+ console.error(`[build-subprocesses] Failed to bundle ${name}:`);
+ for (const log of result.logs) {
+ console.error(log);
+ }
+ process.exit(1);
+ }
+
+ console.log(`[build-subprocesses] Bundled ${name}.js`);
+}
+
+console.log('[build-subprocesses] Done');
diff --git a/scripts/emergency/backup-db.sh b/scripts/emergency/backup-db.sh
new file mode 100755
index 0000000..bde5e45
--- /dev/null
+++ b/scripts/emergency/backup-db.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Emergency script to backup the database
+# Automatically detects database type (SQLite or PostgreSQL)
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/backup-db.sh [output_dir]
+#
+# Example:
+# docker exec -it dockhand /app/scripts/emergency/backup-db.sh /app/data/backups
+#
+
+SCRIPT_DIR="$(dirname "$0")"
+
+# Detect database type
+if [ -n "$DATABASE_URL" ] && (echo "$DATABASE_URL" | grep -qE '^postgres(ql)?://'); then
+ exec "$SCRIPT_DIR/postgres/backup-db.sh" "$@"
+else
+ exec "$SCRIPT_DIR/sqlite/backup-db.sh" "$@"
+fi
diff --git a/scripts/emergency/clear-sessions.sh b/scripts/emergency/clear-sessions.sh
new file mode 100755
index 0000000..efe7e21
--- /dev/null
+++ b/scripts/emergency/clear-sessions.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# Emergency script to clear all user sessions
+# Automatically detects database type (SQLite or PostgreSQL)
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/clear-sessions.sh
+#
+
+SCRIPT_DIR="$(dirname "$0")"
+
+# Detect database type
+if [ -n "$DATABASE_URL" ] && (echo "$DATABASE_URL" | grep -qE '^postgres(ql)?://'); then
+ exec "$SCRIPT_DIR/postgres/clear-sessions.sh" "$@"
+else
+ exec "$SCRIPT_DIR/sqlite/clear-sessions.sh" "$@"
+fi
diff --git a/scripts/emergency/create-admin.sh b/scripts/emergency/create-admin.sh
new file mode 100755
index 0000000..35b94fc
--- /dev/null
+++ b/scripts/emergency/create-admin.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Emergency script to create an admin user
+# Automatically detects database type (SQLite or PostgreSQL)
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/create-admin.sh
+#
+# Default credentials: admin / admin123
+# CHANGE THE PASSWORD IMMEDIATELY after logging in!
+#
+
+SCRIPT_DIR="$(dirname "$0")"
+
+# Detect database type
+if [ -n "$DATABASE_URL" ] && (echo "$DATABASE_URL" | grep -qE '^postgres(ql)?://'); then
+ exec "$SCRIPT_DIR/postgres/create-admin.sh" "$@"
+else
+ exec "$SCRIPT_DIR/sqlite/create-admin.sh" "$@"
+fi
diff --git a/scripts/emergency/disable-auth.sh b/scripts/emergency/disable-auth.sh
new file mode 100755
index 0000000..c9d25a2
--- /dev/null
+++ b/scripts/emergency/disable-auth.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# Emergency script to disable authentication
+# Automatically detects database type (SQLite or PostgreSQL)
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/disable-auth.sh
+#
+
+SCRIPT_DIR="$(dirname "$0")"
+
+# Detect database type
+if [ -n "$DATABASE_URL" ] && (echo "$DATABASE_URL" | grep -qE '^postgres(ql)?://'); then
+ exec "$SCRIPT_DIR/postgres/disable-auth.sh" "$@"
+else
+ exec "$SCRIPT_DIR/sqlite/disable-auth.sh" "$@"
+fi
diff --git a/scripts/emergency/export-stacks.sh b/scripts/emergency/export-stacks.sh
new file mode 100755
index 0000000..04c3095
--- /dev/null
+++ b/scripts/emergency/export-stacks.sh
@@ -0,0 +1,94 @@
+#!/bin/sh
+#
+# Emergency script to export all compose stacks
+# Exports docker-compose.yml files from the stacks directory
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/export-stacks.sh [output_dir]
+#
+# Example:
+# docker exec -it dockhand /app/scripts/export-stacks.sh /tmp/stacks-backup
+#
+# Default output: /app/data/stacks-export
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Export Compose Stacks"
+echo "========================================"
+echo ""
+
+# Default paths
+STACKS_DIR="${DOCKHAND_STACKS:-/home/dockhand/.dockhand/stacks}"
+OUTPUT_DIR="${1:-/app/data/stacks-export}"
+
+# Check if running locally (not in Docker)
+if [ ! -d "$STACKS_DIR" ] && [ -d "$HOME/.dockhand/stacks" ]; then
+ STACKS_DIR="$HOME/.dockhand/stacks"
+fi
+
+if [ ! -d "$STACKS_DIR" ]; then
+ echo "Error: Stacks directory not found at $STACKS_DIR"
+ exit 1
+fi
+
+# Count stacks
+STACK_COUNT=$(find "$STACKS_DIR" -maxdepth 1 -type d ! -path "$STACKS_DIR" 2>/dev/null | wc -l | tr -d ' ')
+
+echo "This script will export all compose stacks."
+echo ""
+echo "Stacks directory: $STACKS_DIR"
+echo "Output directory: $OUTPUT_DIR"
+echo "Stacks found: $STACK_COUNT"
+echo ""
+
+if [ "$STACK_COUNT" -eq "0" ]; then
+ echo "No stacks found to export."
+ exit 0
+fi
+
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+echo ""
+
+# Create output directory
+mkdir -p "$OUTPUT_DIR"
+
+echo "Exporting stacks..."
+echo ""
+
+# Export each stack
+find "$STACKS_DIR" -maxdepth 1 -type d ! -path "$STACKS_DIR" | while read stack_dir; do
+ STACK_NAME=$(basename "$stack_dir")
+ COMPOSE_FILE="$stack_dir/docker-compose.yml"
+
+ if [ -f "$COMPOSE_FILE" ]; then
+ mkdir -p "$OUTPUT_DIR/$STACK_NAME"
+ cp "$COMPOSE_FILE" "$OUTPUT_DIR/$STACK_NAME/"
+
+ # Also copy .env file if exists
+ if [ -f "$stack_dir/.env" ]; then
+ cp "$stack_dir/.env" "$OUTPUT_DIR/$STACK_NAME/"
+ fi
+
+ echo " Exported: $STACK_NAME"
+ fi
+done
+
+echo ""
+echo "Export complete!"
+echo "Stacks exported to: $OUTPUT_DIR"
+echo ""
+echo "To copy from Docker container to host:"
+echo " docker cp dockhand:$OUTPUT_DIR ./stacks-backup"
diff --git a/scripts/emergency/list-users.sh b/scripts/emergency/list-users.sh
new file mode 100755
index 0000000..b68e901
--- /dev/null
+++ b/scripts/emergency/list-users.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# Emergency script to list all users
+# Automatically detects database type (SQLite or PostgreSQL)
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/list-users.sh
+#
+
+SCRIPT_DIR="$(dirname "$0")"
+
+# Detect database type
+if [ -n "$DATABASE_URL" ] && (echo "$DATABASE_URL" | grep -qE '^postgres(ql)?://'); then
+ exec "$SCRIPT_DIR/postgres/list-users.sh" "$@"
+else
+ exec "$SCRIPT_DIR/sqlite/list-users.sh" "$@"
+fi
diff --git a/scripts/emergency/postgres/backup-db.sh b/scripts/emergency/postgres/backup-db.sh
new file mode 100755
index 0000000..ccc490f
--- /dev/null
+++ b/scripts/emergency/postgres/backup-db.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+#
+# PostgreSQL: Emergency script to backup the database
+# Creates a timestamped dump of the database
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/postgres/backup-db.sh [output_dir]
+#
+# Example:
+# docker exec -it dockhand /app/scripts/emergency/postgres/backup-db.sh /app/data/backups
+#
+# Default output: /app/data
+#
+# Requires: DATABASE_URL environment variable
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Backup Database (PostgreSQL)"
+echo "========================================"
+echo ""
+
+# Check DATABASE_URL
+if [ -z "$DATABASE_URL" ]; then
+ echo "Error: DATABASE_URL environment variable not set"
+ echo ""
+ echo "Example: DATABASE_URL=postgres://user:pass@host:5432/dockhand"
+ exit 1
+fi
+
+OUTPUT_DIR="${1:-/app/data}"
+
+# Parse DATABASE_URL
+# Format: postgres://user:password@host:port/database
+DB_URL="$DATABASE_URL"
+DB_URL="${DB_URL#postgres://}"
+DB_URL="${DB_URL#postgresql://}"
+
+# Extract credentials
+DB_USER="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PASS="${DB_URL%%@*}"
+DB_URL="${DB_URL#*@}"
+DB_HOST="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PORT="${DB_URL%%/*}"
+DB_NAME="${DB_URL#*/}"
+DB_NAME="${DB_NAME%%\?*}"
+
+# Generate backup filename with timestamp
+TIMESTAMP=$(date +%Y%m%d_%H%M%S)
+BACKUP_FILE="$OUTPUT_DIR/dockhand_backup_$TIMESTAMP.sql"
+
+echo "This script will create a backup of the database."
+echo ""
+echo "Host: $DB_HOST:$DB_PORT"
+echo "Database: $DB_NAME"
+echo "Backup: $BACKUP_FILE"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+echo ""
+
+# Create output directory if needed
+mkdir -p "$OUTPUT_DIR"
+
+echo "Creating database backup..."
+
+# Use pg_dump to create backup
+export PGPASSWORD="$DB_PASS"
+if command -v pg_dump >/dev/null 2>&1; then
+ pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -F p -f "$BACKUP_FILE"
+else
+ echo "Error: pg_dump not found"
+ echo "Install PostgreSQL client tools to use this script"
+ exit 1
+fi
+
+if [ $? -eq 0 ] && [ -f "$BACKUP_FILE" ]; then
+ SIZE=$(ls -lh "$BACKUP_FILE" | awk '{print $5}')
+ echo ""
+ echo "Backup created successfully!"
+ echo "Size: $SIZE"
+ echo ""
+ echo "To copy from Docker container to host:"
+ echo " docker cp dockhand:$BACKUP_FILE ./dockhand_backup_$TIMESTAMP.sql"
+else
+ echo "Error: Failed to create backup"
+ exit 1
+fi
diff --git a/scripts/emergency/postgres/clear-sessions.sh b/scripts/emergency/postgres/clear-sessions.sh
new file mode 100755
index 0000000..3621bc4
--- /dev/null
+++ b/scripts/emergency/postgres/clear-sessions.sh
@@ -0,0 +1,75 @@
+#!/bin/sh
+#
+# PostgreSQL: Emergency script to clear all user sessions
+# Use this to force all users to re-login
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/postgres/clear-sessions.sh
+#
+# Requires: DATABASE_URL environment variable
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Clear All Sessions (PostgreSQL)"
+echo "========================================"
+echo ""
+echo "This script will clear all user sessions,"
+echo "forcing all users to log in again."
+echo ""
+
+# Check DATABASE_URL
+if [ -z "$DATABASE_URL" ]; then
+ echo "Error: DATABASE_URL environment variable not set"
+ echo ""
+ echo "Example: DATABASE_URL=postgres://user:pass@host:5432/dockhand"
+ exit 1
+fi
+
+# Parse DATABASE_URL
+DB_URL="$DATABASE_URL"
+DB_URL="${DB_URL#postgres://}"
+DB_URL="${DB_URL#postgresql://}"
+
+DB_USER="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PASS="${DB_URL%%@*}"
+DB_URL="${DB_URL#*@}"
+DB_HOST="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PORT="${DB_URL%%/*}"
+DB_NAME="${DB_URL#*/}"
+DB_NAME="${DB_NAME%%\?*}"
+
+export PGPASSWORD="$DB_PASS"
+
+COUNT=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT COUNT(*) FROM sessions;" 2>/dev/null | tr -d ' ')
+
+echo "Database: $DB_HOST:$DB_PORT/$DB_NAME"
+echo "Active sessions: $COUNT"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+echo ""
+echo "Clearing all user sessions..."
+psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "DELETE FROM sessions;"
+
+if [ $? -eq 0 ]; then
+ echo ""
+ echo "Cleared $COUNT session(s) successfully."
+ echo "All users will need to log in again."
+else
+ echo "Error: Failed to clear sessions"
+ exit 1
+fi
diff --git a/scripts/emergency/postgres/create-admin.sh b/scripts/emergency/postgres/create-admin.sh
new file mode 100755
index 0000000..528a534
--- /dev/null
+++ b/scripts/emergency/postgres/create-admin.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+#
+# PostgreSQL: Emergency script to create an admin user
+# Use this if you're locked out of Dockhand and need to create a new admin
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/postgres/create-admin.sh
+#
+# Default credentials: admin / admin123
+# CHANGE THE PASSWORD IMMEDIATELY after logging in!
+#
+# Requires: DATABASE_URL environment variable
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Create Admin User (PostgreSQL)"
+echo "========================================"
+echo ""
+echo "This script will create an admin user with:"
+echo " Username: admin"
+echo " Password: admin123"
+echo ""
+echo "If user 'admin' already exists, password will"
+echo "be reset and admin privileges restored."
+echo ""
+
+# Check DATABASE_URL
+if [ -z "$DATABASE_URL" ]; then
+ echo "Error: DATABASE_URL environment variable not set"
+ echo ""
+ echo "Example: DATABASE_URL=postgres://user:pass@host:5432/dockhand"
+ exit 1
+fi
+
+# Parse DATABASE_URL
+DB_URL="$DATABASE_URL"
+DB_URL="${DB_URL#postgres://}"
+DB_URL="${DB_URL#postgresql://}"
+
+DB_USER="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PASS="${DB_URL%%@*}"
+DB_URL="${DB_URL#*@}"
+DB_HOST="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PORT="${DB_URL%%/*}"
+DB_NAME="${DB_URL#*/}"
+DB_NAME="${DB_NAME%%\?*}"
+
+export PGPASSWORD="$DB_PASS"
+
+echo "Database: $DB_HOST:$DB_PORT/$DB_NAME"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+# Username and password
+USERNAME="admin"
+# Password: admin123
+# This is an argon2id hash of "admin123" - generated with default argon2 settings
+PASSWORD_HASH='$argon2id$v=19$m=65536,t=3,p=4$Jq4am2SfyYKmc0PAHe+yzg$cq/27vK/Qg2eZb/jMDy0ExLDhOG+58cKAximxpG5Dss'
+
+echo ""
+echo "Creating admin user..."
+
+# Check if admin user already exists
+EXISTING=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT COUNT(*) FROM users WHERE username='$USERNAME';" 2>/dev/null | tr -d ' ')
+
+if [ "$EXISTING" -gt "0" ]; then
+ echo "User '$USERNAME' already exists."
+ echo "Resetting password and ensuring active status..."
+ psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "UPDATE users SET password_hash='$PASSWORD_HASH', is_active=true WHERE username='$USERNAME';"
+ USER_ID=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT id FROM users WHERE username='$USERNAME';" 2>/dev/null | tr -d ' ')
+else
+ echo "Creating new admin user..."
+ psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "INSERT INTO users (username, password_hash, is_active, auth_provider, created_at, updated_at) VALUES ('$USERNAME', '$PASSWORD_HASH', true, 'local', NOW(), NOW());"
+ USER_ID=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT id FROM users WHERE username='$USERNAME';" 2>/dev/null | tr -d ' ')
+ echo "Admin user created successfully."
+fi
+
+# Get the Admin role ID (it's a system role)
+ADMIN_ROLE_ID=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT id FROM roles WHERE name='Admin';" 2>/dev/null | tr -d ' ')
+
+if [ -z "$ADMIN_ROLE_ID" ]; then
+ echo "Warning: Admin role not found in database."
+ echo "The user was created but may not have admin privileges."
+ echo "Please check Settings > Auth > Roles after logging in."
+else
+ # Check if user already has Admin role
+ HAS_ROLE=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT COUNT(*) FROM user_roles WHERE user_id=$USER_ID AND role_id=$ADMIN_ROLE_ID;" 2>/dev/null | tr -d ' ')
+
+ if [ "$HAS_ROLE" -eq "0" ]; then
+ echo "Assigning Admin role..."
+ psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "INSERT INTO user_roles (user_id, role_id, created_at) VALUES ($USER_ID, $ADMIN_ROLE_ID, NOW());"
+ echo "Admin role assigned."
+ else
+ echo "User already has Admin role."
+ fi
+fi
+
+echo ""
+echo "Credentials:"
+echo " Username: admin"
+echo " Password: admin123"
+echo ""
+echo "WARNING: Change the password immediately after logging in!"
diff --git a/scripts/emergency/postgres/disable-auth.sh b/scripts/emergency/postgres/disable-auth.sh
new file mode 100755
index 0000000..bf3f562
--- /dev/null
+++ b/scripts/emergency/postgres/disable-auth.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+#
+# PostgreSQL: Emergency script to disable authentication
+# Use this if you're locked out of Dockhand
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/postgres/disable-auth.sh
+#
+# Requires: DATABASE_URL environment variable
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Disable Authentication (PostgreSQL)"
+echo "========================================"
+echo ""
+echo "This script will disable authentication,"
+echo "allowing access to Dockhand without login."
+echo ""
+
+# Check DATABASE_URL
+if [ -z "$DATABASE_URL" ]; then
+ echo "Error: DATABASE_URL environment variable not set"
+ echo ""
+ echo "Example: DATABASE_URL=postgres://user:pass@host:5432/dockhand"
+ exit 1
+fi
+
+# Parse DATABASE_URL
+DB_URL="$DATABASE_URL"
+DB_URL="${DB_URL#postgres://}"
+DB_URL="${DB_URL#postgresql://}"
+
+DB_USER="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PASS="${DB_URL%%@*}"
+DB_URL="${DB_URL#*@}"
+DB_HOST="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PORT="${DB_URL%%/*}"
+DB_NAME="${DB_URL#*/}"
+DB_NAME="${DB_NAME%%\?*}"
+
+export PGPASSWORD="$DB_PASS"
+
+echo "Database: $DB_HOST:$DB_PORT/$DB_NAME"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+echo ""
+echo "Disabling authentication..."
+psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "UPDATE auth_settings SET auth_enabled = false WHERE id = 1;"
+
+if [ $? -eq 0 ]; then
+ echo ""
+ echo "Authentication disabled successfully."
+ echo "You can now access Dockhand without logging in."
+ echo ""
+ echo "Remember to re-enable authentication in Settings after regaining access."
+else
+ echo "Error: Failed to disable authentication"
+ exit 1
+fi
diff --git a/scripts/emergency/postgres/list-users.sh b/scripts/emergency/postgres/list-users.sh
new file mode 100755
index 0000000..9ec1d1f
--- /dev/null
+++ b/scripts/emergency/postgres/list-users.sh
@@ -0,0 +1,94 @@
+#!/bin/sh
+#
+# PostgreSQL: Emergency script to list all users
+# Shows username, admin status, active status, and last login
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/postgres/list-users.sh
+#
+# Requires: DATABASE_URL environment variable
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - List Users (PostgreSQL)"
+echo "========================================"
+echo ""
+
+# Check DATABASE_URL
+if [ -z "$DATABASE_URL" ]; then
+ echo "Error: DATABASE_URL environment variable not set"
+ echo ""
+ echo "Example: DATABASE_URL=postgres://user:pass@host:5432/dockhand"
+ exit 1
+fi
+
+# Parse DATABASE_URL
+DB_URL="$DATABASE_URL"
+DB_URL="${DB_URL#postgres://}"
+DB_URL="${DB_URL#postgresql://}"
+
+DB_USER="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PASS="${DB_URL%%@*}"
+DB_URL="${DB_URL#*@}"
+DB_HOST="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PORT="${DB_URL%%/*}"
+DB_NAME="${DB_URL#*/}"
+DB_NAME="${DB_NAME%%\?*}"
+
+export PGPASSWORD="$DB_PASS"
+
+# Get user count
+USER_COUNT=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT COUNT(*) FROM users;" 2>/dev/null | tr -d ' ')
+
+if [ "$USER_COUNT" -eq "0" ]; then
+ echo "No users found."
+ exit 0
+fi
+
+# Get Admin role ID for checking admin status
+ADMIN_ROLE_ID=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT id FROM roles WHERE name='Admin';" 2>/dev/null | tr -d ' ')
+
+# Print header
+printf "%-4s %-20s %-8s %-8s %-6s %s\n" "ID" "Username" "Admin" "Active" "MFA" "Last Login"
+printf "%-4s %-20s %-8s %-8s %-6s %s\n" "----" "--------------------" "--------" "--------" "------" "-------------------"
+
+# List users (check admin status via user_roles table)
+psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -A -F '|' -c "SELECT id, username, is_active, mfa_enabled, COALESCE(last_login::text, 'Never') FROM users ORDER BY id;" 2>/dev/null | while IFS='|' read id username is_active mfa_enabled last_login; do
+ # Check if user has Admin role
+ if [ -n "$ADMIN_ROLE_ID" ]; then
+ HAS_ADMIN=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT COUNT(*) FROM user_roles WHERE user_id=$id AND role_id=$ADMIN_ROLE_ID;" 2>/dev/null | tr -d ' ')
+ if [ "$HAS_ADMIN" -gt "0" ]; then
+ admin_str="Yes"
+ else
+ admin_str="No"
+ fi
+ else
+ admin_str="N/A"
+ fi
+
+ # Convert boolean values (PostgreSQL returns t/f)
+ if [ "$is_active" = "t" ]; then
+ active_str="Yes"
+ else
+ active_str="No"
+ fi
+
+ if [ "$mfa_enabled" = "t" ]; then
+ mfa_str="Yes"
+ else
+ mfa_str="No"
+ fi
+
+ printf "%-4s %-20s %-8s %-8s %-6s %s\n" "$id" "$username" "$admin_str" "$active_str" "$mfa_str" "$last_login"
+done
+
+echo ""
+echo "Total: $USER_COUNT user(s)"
+
+# Show session count
+SESSION_COUNT=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT COUNT(*) FROM sessions;" 2>/dev/null | tr -d ' ')
+echo "Active sessions: $SESSION_COUNT"
diff --git a/scripts/emergency/postgres/reset-db.sh b/scripts/emergency/postgres/reset-db.sh
new file mode 100755
index 0000000..7fd8d98
--- /dev/null
+++ b/scripts/emergency/postgres/reset-db.sh
@@ -0,0 +1,118 @@
+#!/bin/sh
+#
+# PostgreSQL: Emergency script to factory reset the database
+# WARNING: This will DELETE ALL DATA including users, settings, and activity logs!
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/postgres/reset-db.sh
+#
+# Requires: DATABASE_URL environment variable
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Factory Reset Database (PostgreSQL)"
+echo "========================================"
+echo ""
+echo "WARNING: This will DELETE ALL DATA!"
+echo ""
+echo "This includes:"
+echo " - All users and their settings"
+echo " - All sessions"
+echo " - Authentication settings"
+echo " - Activity logs"
+echo " - Environment configurations"
+echo " - OIDC/SSO settings"
+echo ""
+echo "The database tables will be truncated."
+echo ""
+
+# Check DATABASE_URL
+if [ -z "$DATABASE_URL" ]; then
+ echo "Error: DATABASE_URL environment variable not set"
+ echo ""
+ echo "Example: DATABASE_URL=postgres://user:pass@host:5432/dockhand"
+ exit 1
+fi
+
+# Parse DATABASE_URL
+DB_URL="$DATABASE_URL"
+DB_URL="${DB_URL#postgres://}"
+DB_URL="${DB_URL#postgresql://}"
+
+DB_USER="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PASS="${DB_URL%%@*}"
+DB_URL="${DB_URL#*@}"
+DB_HOST="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PORT="${DB_URL%%/*}"
+DB_NAME="${DB_URL#*/}"
+DB_NAME="${DB_NAME%%\?*}"
+
+export PGPASSWORD="$DB_PASS"
+
+echo "Database: $DB_HOST:$DB_PORT/$DB_NAME"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+echo ""
+echo "Creating backup before reset..."
+TIMESTAMP=$(date +%Y%m%d_%H%M%S)
+BACKUP_FILE="/app/data/dockhand_backup_pre_reset_$TIMESTAMP.sql"
+if command -v pg_dump >/dev/null 2>&1; then
+ pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -F p -f "$BACKUP_FILE" 2>/dev/null || true
+ if [ -f "$BACKUP_FILE" ]; then
+ echo "Backup saved to: $BACKUP_FILE"
+ fi
+fi
+
+echo ""
+echo "Truncating all tables..."
+
+# Truncate all tables in the correct order (respecting foreign keys)
+psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" <
+#
+# Example:
+# docker exec -it dockhand /app/scripts/emergency/postgres/reset-password.sh admin MyNewPassword123
+#
+# Requires: DATABASE_URL environment variable
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Reset User Password (PostgreSQL)"
+echo "========================================"
+echo ""
+
+# Check arguments
+if [ -z "$1" ] || [ -z "$2" ]; then
+ echo "Usage: $0 "
+ echo ""
+ echo "Example:"
+ echo " $0 admin MyNewPassword123"
+ exit 1
+fi
+
+USERNAME="$1"
+NEW_PASSWORD="$2"
+
+# Validate password length
+if [ ${#NEW_PASSWORD} -lt 8 ]; then
+ echo "Error: Password must be at least 8 characters"
+ exit 1
+fi
+
+# Check DATABASE_URL
+if [ -z "$DATABASE_URL" ]; then
+ echo "Error: DATABASE_URL environment variable not set"
+ echo ""
+ echo "Example: DATABASE_URL=postgres://user:pass@host:5432/dockhand"
+ exit 1
+fi
+
+# Parse DATABASE_URL
+DB_URL="$DATABASE_URL"
+DB_URL="${DB_URL#postgres://}"
+DB_URL="${DB_URL#postgresql://}"
+
+DB_USER="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PASS="${DB_URL%%@*}"
+DB_URL="${DB_URL#*@}"
+DB_HOST="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PORT="${DB_URL%%/*}"
+DB_NAME="${DB_URL#*/}"
+DB_NAME="${DB_NAME%%\?*}"
+
+export PGPASSWORD="$DB_PASS"
+
+# Check if user exists
+EXISTING=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT COUNT(*) FROM users WHERE username='$USERNAME';" 2>/dev/null | tr -d ' ')
+
+if [ "$EXISTING" -eq "0" ]; then
+ echo "Error: User '$USERNAME' not found"
+ echo ""
+ echo "Available users:"
+ psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT username FROM users;" 2>/dev/null | while read user; do
+ user=$(echo "$user" | tr -d ' ')
+ if [ -n "$user" ]; then
+ echo " - $user"
+ fi
+ done
+ exit 1
+fi
+
+echo "This script will reset the password for user '$USERNAME'."
+echo ""
+echo "Database: $DB_HOST:$DB_PORT/$DB_NAME"
+echo "Username: $USERNAME"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+# Generate password hash using node (argon2 is available in the app)
+echo ""
+echo "Generating password hash..."
+
+# Check if node and argon2 are available
+if command -v node >/dev/null 2>&1; then
+ # Try to use argon2 from node_modules
+ PASSWORD_HASH=$(node -e "
+ try {
+ const argon2 = require('argon2');
+ argon2.hash('$NEW_PASSWORD').then(h => console.log(h)).catch(e => process.exit(1));
+ } catch(e) {
+ process.exit(1);
+ }
+ " 2>/dev/null)
+
+ if [ -z "$PASSWORD_HASH" ]; then
+ echo "Error: Could not generate password hash (argon2 not available)"
+ echo "This script requires Node.js with argon2 module"
+ exit 1
+ fi
+else
+ echo "Error: Node.js is required to generate password hash"
+ exit 1
+fi
+
+echo "Resetting password for user '$USERNAME'..."
+psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "UPDATE users SET password_hash='$PASSWORD_HASH', updated_at=NOW() WHERE username='$USERNAME';"
+
+if [ $? -eq 0 ]; then
+ echo ""
+ echo "Password reset successfully for user '$USERNAME'"
+ echo ""
+ # Invalidate sessions
+ USER_ID=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT id FROM users WHERE username='$USERNAME';" 2>/dev/null | tr -d ' ')
+ psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "DELETE FROM sessions WHERE user_id=$USER_ID;" 2>/dev/null || true
+ echo "All existing sessions have been invalidated."
+ echo "The user can now log in with the new password."
+else
+ echo "Error: Failed to reset password"
+ exit 1
+fi
diff --git a/scripts/emergency/postgres/restore-db.sh b/scripts/emergency/postgres/restore-db.sh
new file mode 100755
index 0000000..8676a8f
--- /dev/null
+++ b/scripts/emergency/postgres/restore-db.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+#
+# PostgreSQL: Emergency script to restore the database from a backup
+# WARNING: This will overwrite the current database!
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/postgres/restore-db.sh
+#
+# Example:
+# docker exec -it dockhand /app/scripts/emergency/postgres/restore-db.sh /app/data/dockhand_backup_20240115_120000.sql
+#
+# To copy backup into container first:
+# docker cp ./dockhand_backup.sql dockhand:/app/data/
+#
+# Requires: DATABASE_URL environment variable
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Restore Database (PostgreSQL)"
+echo "========================================"
+echo ""
+
+# Check argument
+if [ -z "$1" ]; then
+ echo "Usage: $0 "
+ echo ""
+ echo "Example:"
+ echo " $0 /app/data/dockhand_backup_20240115_120000.sql"
+ echo ""
+ echo "To copy backup into container first:"
+ echo " docker cp ./dockhand_backup.sql dockhand:/app/data/"
+ exit 1
+fi
+
+BACKUP_FILE="$1"
+
+# Check DATABASE_URL
+if [ -z "$DATABASE_URL" ]; then
+ echo "Error: DATABASE_URL environment variable not set"
+ echo ""
+ echo "Example: DATABASE_URL=postgres://user:pass@host:5432/dockhand"
+ exit 1
+fi
+
+# Parse DATABASE_URL
+DB_URL="$DATABASE_URL"
+DB_URL="${DB_URL#postgres://}"
+DB_URL="${DB_URL#postgresql://}"
+
+DB_USER="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PASS="${DB_URL%%@*}"
+DB_URL="${DB_URL#*@}"
+DB_HOST="${DB_URL%%:*}"
+DB_URL="${DB_URL#*:}"
+DB_PORT="${DB_URL%%/*}"
+DB_NAME="${DB_URL#*/}"
+DB_NAME="${DB_NAME%%\?*}"
+
+export PGPASSWORD="$DB_PASS"
+
+# Check if backup file exists
+if [ ! -f "$BACKUP_FILE" ]; then
+ echo "Error: Backup file not found: $BACKUP_FILE"
+ exit 1
+fi
+
+# Get backup file size
+BACKUP_SIZE=$(ls -lh "$BACKUP_FILE" | awk '{print $5}')
+
+echo "WARNING: This will overwrite the current database!"
+echo ""
+echo "Database: $DB_HOST:$DB_PORT/$DB_NAME"
+echo "Backup to restore: $BACKUP_FILE ($BACKUP_SIZE)"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+# Create backup of current database before restoring
+echo ""
+echo "Creating backup of current database..."
+TIMESTAMP=$(date +%Y%m%d_%H%M%S)
+PRE_RESTORE_BACKUP="/app/data/dockhand_pre_restore_$TIMESTAMP.sql"
+if command -v pg_dump >/dev/null 2>&1; then
+ pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -F p -f "$PRE_RESTORE_BACKUP" 2>/dev/null || true
+ if [ -f "$PRE_RESTORE_BACKUP" ]; then
+ echo "Current database backed up to: $PRE_RESTORE_BACKUP"
+ fi
+fi
+
+echo ""
+echo "Restoring database..."
+
+# Drop and recreate all tables by running the backup
+psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$BACKUP_FILE"
+
+if [ $? -eq 0 ]; then
+ echo ""
+ echo "Database restored successfully!"
+ echo ""
+ echo "Restart Dockhand to apply changes:"
+ echo " docker restart dockhand"
+else
+ echo "Error: Failed to restore database"
+ exit 1
+fi
diff --git a/scripts/emergency/reset-db.sh b/scripts/emergency/reset-db.sh
new file mode 100755
index 0000000..cc88591
--- /dev/null
+++ b/scripts/emergency/reset-db.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# Emergency script to factory reset the database
+# Automatically detects database type (SQLite or PostgreSQL)
+# WARNING: This will DELETE ALL DATA!
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/reset-db.sh
+#
+
+SCRIPT_DIR="$(dirname "$0")"
+
+# Detect database type
+if [ -n "$DATABASE_URL" ] && (echo "$DATABASE_URL" | grep -qE '^postgres(ql)?://'); then
+ exec "$SCRIPT_DIR/postgres/reset-db.sh" "$@"
+else
+ exec "$SCRIPT_DIR/sqlite/reset-db.sh" "$@"
+fi
diff --git a/scripts/emergency/reset-password.sh b/scripts/emergency/reset-password.sh
new file mode 100755
index 0000000..a41fad9
--- /dev/null
+++ b/scripts/emergency/reset-password.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Emergency script to reset a user's password
+# Automatically detects database type (SQLite or PostgreSQL)
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/reset-password.sh
+#
+# Example:
+# docker exec -it dockhand /app/scripts/emergency/reset-password.sh admin MyNewPassword123
+#
+
+SCRIPT_DIR="$(dirname "$0")"
+
+# Detect database type
+if [ -n "$DATABASE_URL" ] && (echo "$DATABASE_URL" | grep -qE '^postgres(ql)?://'); then
+ exec "$SCRIPT_DIR/postgres/reset-password.sh" "$@"
+else
+ exec "$SCRIPT_DIR/sqlite/reset-password.sh" "$@"
+fi
diff --git a/scripts/emergency/restore-db.sh b/scripts/emergency/restore-db.sh
new file mode 100755
index 0000000..db0575e
--- /dev/null
+++ b/scripts/emergency/restore-db.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+#
+# Emergency script to restore the database from a backup
+# Automatically detects database type (SQLite or PostgreSQL)
+# WARNING: This will overwrite the current database!
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/restore-db.sh
+#
+# Example:
+# docker exec -it dockhand /app/scripts/emergency/restore-db.sh /app/data/dockhand_backup_20240115_120000.db
+#
+
+SCRIPT_DIR="$(dirname "$0")"
+
+# Detect database type
+if [ -n "$DATABASE_URL" ] && (echo "$DATABASE_URL" | grep -qE '^postgres(ql)?://'); then
+ exec "$SCRIPT_DIR/postgres/restore-db.sh" "$@"
+else
+ exec "$SCRIPT_DIR/sqlite/restore-db.sh" "$@"
+fi
diff --git a/scripts/emergency/sqlite/backup-db.sh b/scripts/emergency/sqlite/backup-db.sh
new file mode 100755
index 0000000..7242ef9
--- /dev/null
+++ b/scripts/emergency/sqlite/backup-db.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+#
+# SQLite: Emergency script to backup the database
+# Creates a timestamped copy of the database file
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/sqlite/backup-db.sh [output_dir]
+#
+# Example:
+# docker exec -it dockhand /app/scripts/emergency/sqlite/backup-db.sh /app/data/backups
+#
+# Default output: /app/data (same directory as database)
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Backup Database (SQLite)"
+echo "========================================"
+echo ""
+
+# Default database path
+DB_PATH="${DOCKHAND_DB:-/app/data/db/dockhand.db}"
+OUTPUT_DIR="${1:-$(dirname "$DB_PATH")}"
+
+# Check if running locally (not in Docker)
+if [ ! -f "$DB_PATH" ] && [ -f "./data/db/dockhand.db" ]; then
+ DB_PATH="./data/db/dockhand.db"
+ OUTPUT_DIR="${1:-./data/db}"
+fi
+
+if [ ! -f "$DB_PATH" ]; then
+ echo "Error: Database not found at $DB_PATH"
+ echo "Set DOCKHAND_DB environment variable to specify the database path"
+ exit 1
+fi
+
+# Generate backup filename with timestamp
+TIMESTAMP=$(date +%Y%m%d_%H%M%S)
+BACKUP_FILE="$OUTPUT_DIR/dockhand_backup_$TIMESTAMP.db"
+
+# Get database size
+DB_SIZE=$(ls -lh "$DB_PATH" | awk '{print $5}')
+
+echo "This script will create a backup of the database."
+echo ""
+echo "Source: $DB_PATH ($DB_SIZE)"
+echo "Backup: $BACKUP_FILE"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+echo ""
+
+# Create output directory if needed
+mkdir -p "$OUTPUT_DIR"
+
+echo "Creating database backup..."
+
+# Use sqlite3 backup command for safe backup (handles WAL mode)
+if command -v sqlite3 >/dev/null 2>&1; then
+ sqlite3 "$DB_PATH" ".backup '$BACKUP_FILE'"
+else
+ # Fallback to file copy if sqlite3 not available
+ cp "$DB_PATH" "$BACKUP_FILE"
+fi
+
+if [ $? -eq 0 ] && [ -f "$BACKUP_FILE" ]; then
+ SIZE=$(ls -lh "$BACKUP_FILE" | awk '{print $5}')
+ echo ""
+ echo "Backup created successfully!"
+ echo "Size: $SIZE"
+ echo ""
+ echo "To copy from Docker container to host:"
+ echo " docker cp dockhand:$BACKUP_FILE ./dockhand_backup_$TIMESTAMP.db"
+else
+ echo "Error: Failed to create backup"
+ exit 1
+fi
diff --git a/scripts/emergency/sqlite/clear-sessions.sh b/scripts/emergency/sqlite/clear-sessions.sh
new file mode 100755
index 0000000..914c8ed
--- /dev/null
+++ b/scripts/emergency/sqlite/clear-sessions.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# SQLite: Emergency script to clear all user sessions
+# Use this to force all users to re-login
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/sqlite/clear-sessions.sh
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Clear All Sessions (SQLite)"
+echo "========================================"
+echo ""
+echo "This script will clear all user sessions,"
+echo "forcing all users to log in again."
+echo ""
+
+# Default database path
+DB_PATH="${DOCKHAND_DB:-/app/data/db/dockhand.db}"
+
+# Check if running locally (not in Docker)
+if [ ! -f "$DB_PATH" ] && [ -f "./data/db/dockhand.db" ]; then
+ DB_PATH="./data/db/dockhand.db"
+fi
+
+if [ ! -f "$DB_PATH" ]; then
+ echo "Error: Database not found at $DB_PATH"
+ echo "Set DOCKHAND_DB environment variable to specify the database path"
+ exit 1
+fi
+
+COUNT=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM sessions;")
+
+echo "Database: $DB_PATH"
+echo "Active sessions: $COUNT"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+echo ""
+echo "Clearing all user sessions..."
+sqlite3 "$DB_PATH" "DELETE FROM sessions;"
+
+if [ $? -eq 0 ]; then
+ echo ""
+ echo "Cleared $COUNT session(s) successfully."
+ echo "All users will need to log in again."
+else
+ echo "Error: Failed to clear sessions"
+ exit 1
+fi
diff --git a/scripts/emergency/sqlite/create-admin.sh b/scripts/emergency/sqlite/create-admin.sh
new file mode 100755
index 0000000..91ff9c7
--- /dev/null
+++ b/scripts/emergency/sqlite/create-admin.sh
@@ -0,0 +1,104 @@
+#!/bin/sh
+#
+# SQLite: Emergency script to create an admin user
+# Use this if you're locked out of Dockhand and need to create a new admin
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/sqlite/create-admin.sh
+#
+# Default credentials: admin / admin123
+# CHANGE THE PASSWORD IMMEDIATELY after logging in!
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Create Admin User (SQLite)"
+echo "========================================"
+echo ""
+echo "This script will create an admin user with:"
+echo " Username: admin"
+echo " Password: admin123"
+echo ""
+echo "If user 'admin' already exists, password will"
+echo "be reset and admin privileges restored."
+echo ""
+
+# Default database path
+DB_PATH="${DOCKHAND_DB:-/app/data/db/dockhand.db}"
+
+# Check if running locally (not in Docker)
+if [ ! -f "$DB_PATH" ] && [ -f "./data/db/dockhand.db" ]; then
+ DB_PATH="./data/db/dockhand.db"
+fi
+
+if [ ! -f "$DB_PATH" ]; then
+ echo "Error: Database not found at $DB_PATH"
+ echo "Set DOCKHAND_DB environment variable to specify the database path"
+ exit 1
+fi
+
+echo "Database: $DB_PATH"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+# Username and password
+USERNAME="admin"
+# Password: admin123
+# This is an argon2id hash of "admin123" - generated with default argon2 settings
+PASSWORD_HASH='$argon2id$v=19$m=65536,t=3,p=4$Jq4am2SfyYKmc0PAHe+yzg$cq/27vK/Qg2eZb/jMDy0ExLDhOG+58cKAximxpG5Dss'
+
+echo ""
+echo "Creating admin user..."
+
+# Check if admin user already exists
+EXISTING=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM users WHERE username='$USERNAME';")
+
+if [ "$EXISTING" -gt "0" ]; then
+ echo "User '$USERNAME' already exists."
+ echo "Resetting password and ensuring active status..."
+ sqlite3 "$DB_PATH" "UPDATE users SET password_hash='$PASSWORD_HASH', is_active=1 WHERE username='$USERNAME';"
+ USER_ID=$(sqlite3 "$DB_PATH" "SELECT id FROM users WHERE username='$USERNAME';")
+else
+ echo "Creating new admin user..."
+ sqlite3 "$DB_PATH" "INSERT INTO users (username, password_hash, is_active, auth_provider, created_at, updated_at) VALUES ('$USERNAME', '$PASSWORD_HASH', 1, 'local', datetime('now'), datetime('now'));"
+ USER_ID=$(sqlite3 "$DB_PATH" "SELECT id FROM users WHERE username='$USERNAME';")
+ echo "Admin user created successfully."
+fi
+
+# Get the Admin role ID (it's a system role)
+ADMIN_ROLE_ID=$(sqlite3 "$DB_PATH" "SELECT id FROM roles WHERE name='Admin';")
+
+if [ -z "$ADMIN_ROLE_ID" ]; then
+ echo "Warning: Admin role not found in database."
+ echo "The user was created but may not have admin privileges."
+ echo "Please check Settings > Auth > Roles after logging in."
+else
+ # Check if user already has Admin role
+ HAS_ROLE=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM user_roles WHERE user_id=$USER_ID AND role_id=$ADMIN_ROLE_ID;")
+
+ if [ "$HAS_ROLE" -eq "0" ]; then
+ echo "Assigning Admin role..."
+ sqlite3 "$DB_PATH" "INSERT INTO user_roles (user_id, role_id, created_at) VALUES ($USER_ID, $ADMIN_ROLE_ID, datetime('now'));"
+ echo "Admin role assigned."
+ else
+ echo "User already has Admin role."
+ fi
+fi
+
+echo ""
+echo "Credentials:"
+echo " Username: admin"
+echo " Password: admin123"
+echo ""
+echo "WARNING: Change the password immediately after logging in!"
diff --git a/scripts/emergency/sqlite/disable-auth.sh b/scripts/emergency/sqlite/disable-auth.sh
new file mode 100755
index 0000000..1ebcc49
--- /dev/null
+++ b/scripts/emergency/sqlite/disable-auth.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# SQLite: Emergency script to disable authentication
+# Use this if you're locked out of Dockhand
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/sqlite/disable-auth.sh
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Disable Authentication (SQLite)"
+echo "========================================"
+echo ""
+echo "This script will disable authentication,"
+echo "allowing access to Dockhand without login."
+echo ""
+
+# Default database path
+DB_PATH="${DOCKHAND_DB:-/app/data/db/dockhand.db}"
+
+# Check if running locally (not in Docker)
+if [ ! -f "$DB_PATH" ] && [ -f "./data/db/dockhand.db" ]; then
+ DB_PATH="./data/db/dockhand.db"
+fi
+
+if [ ! -f "$DB_PATH" ]; then
+ echo "Error: Database not found at $DB_PATH"
+ echo "Set DOCKHAND_DB environment variable to specify the database path"
+ exit 1
+fi
+
+echo "Database: $DB_PATH"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+echo ""
+echo "Disabling authentication..."
+sqlite3 "$DB_PATH" "UPDATE auth_settings SET auth_enabled = 0 WHERE id = 1;"
+
+if [ $? -eq 0 ]; then
+ echo ""
+ echo "Authentication disabled successfully."
+ echo "You can now access Dockhand without logging in."
+ echo ""
+ echo "Remember to re-enable authentication in Settings after regaining access."
+else
+ echo "Error: Failed to disable authentication"
+ exit 1
+fi
diff --git a/scripts/emergency/sqlite/list-users.sh b/scripts/emergency/sqlite/list-users.sh
new file mode 100755
index 0000000..9df5c25
--- /dev/null
+++ b/scripts/emergency/sqlite/list-users.sh
@@ -0,0 +1,80 @@
+#!/bin/sh
+#
+# SQLite: Emergency script to list all users
+# Shows username, admin status, active status, and last login
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/sqlite/list-users.sh
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - List Users (SQLite)"
+echo "========================================"
+echo ""
+
+# Default database path
+DB_PATH="${DOCKHAND_DB:-/app/data/db/dockhand.db}"
+
+# Check if running locally (not in Docker)
+if [ ! -f "$DB_PATH" ] && [ -f "./data/db/dockhand.db" ]; then
+ DB_PATH="./data/db/dockhand.db"
+fi
+
+if [ ! -f "$DB_PATH" ]; then
+ echo "Error: Database not found at $DB_PATH"
+ echo "Set DOCKHAND_DB environment variable to specify the database path"
+ exit 1
+fi
+
+# Get user count
+USER_COUNT=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM users;")
+
+if [ "$USER_COUNT" -eq "0" ]; then
+ echo "No users found."
+ exit 0
+fi
+
+# Get Admin role ID for checking admin status
+ADMIN_ROLE_ID=$(sqlite3 "$DB_PATH" "SELECT id FROM roles WHERE name='Admin';" 2>/dev/null || echo "")
+
+# Print header
+printf "%-4s %-20s %-8s %-8s %-6s %s\n" "ID" "Username" "Admin" "Active" "MFA" "Last Login"
+printf "%-4s %-20s %-8s %-8s %-6s %s\n" "----" "--------------------" "--------" "--------" "------" "-------------------"
+
+# List users (check admin status via user_roles table)
+sqlite3 -separator '|' "$DB_PATH" "SELECT id, username, is_active, mfa_enabled, COALESCE(last_login, 'Never') FROM users ORDER BY id;" | while IFS='|' read id username is_active mfa_enabled last_login; do
+ # Check if user has Admin role
+ if [ -n "$ADMIN_ROLE_ID" ]; then
+ HAS_ADMIN=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM user_roles WHERE user_id=$id AND role_id=$ADMIN_ROLE_ID;")
+ if [ "$HAS_ADMIN" -gt "0" ]; then
+ admin_str="Yes"
+ else
+ admin_str="No"
+ fi
+ else
+ admin_str="N/A"
+ fi
+
+ if [ "$is_active" = "1" ]; then
+ active_str="Yes"
+ else
+ active_str="No"
+ fi
+
+ if [ "$mfa_enabled" = "1" ]; then
+ mfa_str="Yes"
+ else
+ mfa_str="No"
+ fi
+
+ printf "%-4s %-20s %-8s %-8s %-6s %s\n" "$id" "$username" "$admin_str" "$active_str" "$mfa_str" "$last_login"
+done
+
+echo ""
+echo "Total: $USER_COUNT user(s)"
+
+# Show session count
+SESSION_COUNT=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM sessions;")
+echo "Active sessions: $SESSION_COUNT"
diff --git a/scripts/emergency/sqlite/reset-db.sh b/scripts/emergency/sqlite/reset-db.sh
new file mode 100755
index 0000000..d53532b
--- /dev/null
+++ b/scripts/emergency/sqlite/reset-db.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+#
+# SQLite: Emergency script to factory reset the database
+# WARNING: This will DELETE ALL DATA including users, settings, and activity logs!
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/sqlite/reset-db.sh
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Factory Reset Database (SQLite)"
+echo "========================================"
+echo ""
+echo "WARNING: This will DELETE ALL DATA!"
+echo ""
+echo "This includes:"
+echo " - All users and their settings"
+echo " - All sessions"
+echo " - Authentication settings"
+echo " - Activity logs"
+echo " - Environment configurations"
+echo " - OIDC/SSO settings"
+echo ""
+echo "The database will be recreated on next startup."
+echo ""
+
+# Default database path
+DB_PATH="${DOCKHAND_DB:-/app/data/db/dockhand.db}"
+
+# Check if running locally (not in Docker)
+if [ ! -f "$DB_PATH" ] && [ -f "./data/db/dockhand.db" ]; then
+ DB_PATH="./data/db/dockhand.db"
+fi
+
+if [ ! -f "$DB_PATH" ]; then
+ echo "Error: Database not found at $DB_PATH"
+ echo "Nothing to reset."
+ exit 0
+fi
+
+echo "Database: $DB_PATH"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+echo ""
+echo "Creating backup before reset..."
+BACKUP_FILE="${DB_PATH}.backup.$(date +%Y%m%d_%H%M%S)"
+cp "$DB_PATH" "$BACKUP_FILE"
+echo "Backup saved to: $BACKUP_FILE"
+
+echo ""
+echo "Deleting database..."
+rm -f "$DB_PATH"
+rm -f "${DB_PATH}-wal"
+rm -f "${DB_PATH}-shm"
+
+echo ""
+echo "Database deleted successfully."
+echo ""
+echo "Restart Dockhand to recreate a fresh database:"
+echo " docker restart dockhand"
diff --git a/scripts/emergency/sqlite/reset-password.sh b/scripts/emergency/sqlite/reset-password.sh
new file mode 100755
index 0000000..9a38214
--- /dev/null
+++ b/scripts/emergency/sqlite/reset-password.sh
@@ -0,0 +1,123 @@
+#!/bin/sh
+#
+# SQLite: Emergency script to reset a user's password
+# Use this if a user is locked out and needs a password reset
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/sqlite/reset-password.sh
+#
+# Example:
+# docker exec -it dockhand /app/scripts/emergency/sqlite/reset-password.sh admin MyNewPassword123
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Reset User Password (SQLite)"
+echo "========================================"
+echo ""
+
+# Check arguments
+if [ -z "$1" ] || [ -z "$2" ]; then
+ echo "Usage: $0 "
+ echo ""
+ echo "Example:"
+ echo " $0 admin MyNewPassword123"
+ exit 1
+fi
+
+USERNAME="$1"
+NEW_PASSWORD="$2"
+
+# Validate password length
+if [ ${#NEW_PASSWORD} -lt 8 ]; then
+ echo "Error: Password must be at least 8 characters"
+ exit 1
+fi
+
+# Default database path
+DB_PATH="${DOCKHAND_DB:-/app/data/db/dockhand.db}"
+
+# Check if running locally (not in Docker)
+if [ ! -f "$DB_PATH" ] && [ -f "./data/db/dockhand.db" ]; then
+ DB_PATH="./data/db/dockhand.db"
+fi
+
+if [ ! -f "$DB_PATH" ]; then
+ echo "Error: Database not found at $DB_PATH"
+ echo "Set DOCKHAND_DB environment variable to specify the database path"
+ exit 1
+fi
+
+# Check if user exists
+EXISTING=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM users WHERE username='$USERNAME';")
+
+if [ "$EXISTING" -eq "0" ]; then
+ echo "Error: User '$USERNAME' not found"
+ echo ""
+ echo "Available users:"
+ sqlite3 "$DB_PATH" "SELECT username FROM users;" | while read user; do
+ echo " - $user"
+ done
+ exit 1
+fi
+
+echo "This script will reset the password for user '$USERNAME'."
+echo ""
+echo "Database: $DB_PATH"
+echo "Username: $USERNAME"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+# Generate password hash using node (argon2 is available in the app)
+echo ""
+echo "Generating password hash..."
+
+# Check if node and argon2 are available
+if command -v node >/dev/null 2>&1; then
+ # Try to use argon2 from node_modules
+ PASSWORD_HASH=$(node -e "
+ try {
+ const argon2 = require('argon2');
+ argon2.hash('$NEW_PASSWORD').then(h => console.log(h)).catch(e => process.exit(1));
+ } catch(e) {
+ process.exit(1);
+ }
+ " 2>/dev/null)
+
+ if [ -z "$PASSWORD_HASH" ]; then
+ echo "Error: Could not generate password hash (argon2 not available)"
+ echo "This script requires Node.js with argon2 module"
+ exit 1
+ fi
+else
+ echo "Error: Node.js is required to generate password hash"
+ exit 1
+fi
+
+echo "Resetting password for user '$USERNAME'..."
+sqlite3 "$DB_PATH" "UPDATE users SET password_hash='$PASSWORD_HASH', updated_at=datetime('now') WHERE username='$USERNAME';"
+
+if [ $? -eq 0 ]; then
+ echo ""
+ echo "Password reset successfully for user '$USERNAME'"
+ echo ""
+ # Invalidate sessions
+ USER_ID=$(sqlite3 "$DB_PATH" "SELECT id FROM users WHERE username='$USERNAME';")
+ sqlite3 "$DB_PATH" "DELETE FROM sessions WHERE user_id=$USER_ID;" 2>/dev/null || true
+ echo "All existing sessions have been invalidated."
+ echo "The user can now log in with the new password."
+else
+ echo "Error: Failed to reset password"
+ exit 1
+fi
diff --git a/scripts/emergency/sqlite/restore-db.sh b/scripts/emergency/sqlite/restore-db.sh
new file mode 100755
index 0000000..96aa9e6
--- /dev/null
+++ b/scripts/emergency/sqlite/restore-db.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+#
+# SQLite: Emergency script to restore the database from a backup
+# WARNING: This will overwrite the current database!
+#
+# Usage:
+# docker exec -it dockhand /app/scripts/emergency/sqlite/restore-db.sh
+#
+# Example:
+# docker exec -it dockhand /app/scripts/emergency/sqlite/restore-db.sh /app/data/dockhand_backup_20240115_120000.db
+#
+# To copy backup into container first:
+# docker cp ./dockhand_backup.db dockhand:/app/data/
+#
+
+set -e
+
+echo "========================================"
+echo " Dockhand - Restore Database (SQLite)"
+echo "========================================"
+echo ""
+
+# Check argument
+if [ -z "$1" ]; then
+ echo "Usage: $0 "
+ echo ""
+ echo "Example:"
+ echo " $0 /app/data/dockhand_backup_20240115_120000.db"
+ echo ""
+ echo "To copy backup into container first:"
+ echo " docker cp ./dockhand_backup.db dockhand:/app/data/"
+ exit 1
+fi
+
+BACKUP_FILE="$1"
+
+# Default database path
+DB_PATH="${DOCKHAND_DB:-/app/data/db/dockhand.db}"
+
+# Check if running locally (not in Docker)
+if [ ! -f "$DB_PATH" ] && [ -f "./data/db/dockhand.db" ]; then
+ DB_PATH="./data/db/dockhand.db"
+fi
+
+# Check if backup file exists
+if [ ! -f "$BACKUP_FILE" ]; then
+ echo "Error: Backup file not found: $BACKUP_FILE"
+ exit 1
+fi
+
+# Verify it's a valid SQLite database
+if ! sqlite3 "$BACKUP_FILE" "SELECT 1;" >/dev/null 2>&1; then
+ echo "Error: File is not a valid SQLite database: $BACKUP_FILE"
+ exit 1
+fi
+
+# Get backup file size
+BACKUP_SIZE=$(ls -lh "$BACKUP_FILE" | awk '{print $5}')
+
+echo "WARNING: This will overwrite the current database!"
+echo ""
+echo "Current database: $DB_PATH"
+echo "Backup to restore: $BACKUP_FILE ($BACKUP_SIZE)"
+echo ""
+printf "Continue? [y/N]: "
+read CONFIRM
+
+case "$CONFIRM" in
+ [yY]|[yY][eE][sS])
+ ;;
+ *)
+ echo "Aborted."
+ exit 0
+ ;;
+esac
+
+# Create backup of current database before restoring
+if [ -f "$DB_PATH" ]; then
+ TIMESTAMP=$(date +%Y%m%d_%H%M%S)
+ PRE_RESTORE_BACKUP="${DB_PATH}.pre-restore.$TIMESTAMP"
+ echo ""
+ echo "Creating backup of current database..."
+ cp "$DB_PATH" "$PRE_RESTORE_BACKUP"
+ echo "Current database backed up to: $PRE_RESTORE_BACKUP"
+fi
+
+echo ""
+echo "Restoring database..."
+
+# Remove WAL files if they exist
+rm -f "${DB_PATH}-wal"
+rm -f "${DB_PATH}-shm"
+
+# Copy backup to database location
+cp "$BACKUP_FILE" "$DB_PATH"
+
+if [ $? -eq 0 ]; then
+ echo ""
+ echo "Database restored successfully!"
+ echo ""
+ echo "Restart Dockhand to apply changes:"
+ echo " docker restart dockhand"
+else
+ echo "Error: Failed to restore database"
+ exit 1
+fi
diff --git a/scripts/generate-changelog-page.ts b/scripts/generate-changelog-page.ts
new file mode 100644
index 0000000..6ab60d1
--- /dev/null
+++ b/scripts/generate-changelog-page.ts
@@ -0,0 +1,164 @@
+#!/usr/bin/env bun
+/**
+ * Generate changelog section in webpage/index.html from src/lib/data/changelog.json
+ * This ensures a single source of truth for release information
+ */
+
+import { readFileSync, writeFileSync } from 'fs';
+import { join } from 'path';
+
+const ROOT_DIR = join(import.meta.dir, '..');
+const CHANGELOG_PATH = join(ROOT_DIR, 'src/lib/data/changelog.json');
+const INDEX_PATH = join(ROOT_DIR, 'webpage/index.html');
+
+interface ChangelogEntry {
+ version: string;
+ date: string;
+ changes: Array<{ type: 'feature' | 'fix'; text: string }>;
+ imageTag: string;
+}
+
+// SVG icons for change types
+const FEATURE_SVG = ` `;
+
+const FIX_SVG = ` `;
+
+const TOGGLE_SVG = ` `;
+
+const COPY_SVG = ` `;
+
+function formatDate(dateStr: string): string {
+ const date = new Date(dateStr);
+ return date.toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ });
+}
+
+function generateChangeItem(change: { type: 'feature' | 'fix'; text: string }): string {
+ const pillClass = change.type === 'feature' ? 'changelog-pill-feature' : 'changelog-pill-fix';
+ const svg = change.type === 'feature' ? FEATURE_SVG : FIX_SVG;
+ const label = change.type === 'feature' ? 'New' : 'Fix';
+ return ` ${svg}${label} ${change.text} `;
+}
+
+function generateLatestEntry(entry: ChangelogEntry): string {
+ const changes = entry.changes.map(generateChangeItem).join('\n');
+ const version = entry.version.startsWith('v') ? entry.version : `v${entry.version}`;
+
+ return `
+
+
+
+
+ Docker image:
+ ${entry.imageTag}
+ ${COPY_SVG}
+ or
+ fnsys/dockhand:latest
+ ${COPY_SVG}
+
+
`;
+}
+
+function generateCollapsibleEntry(entry: ChangelogEntry): string {
+ const changes = entry.changes.map(generateChangeItem).join('\n');
+ const version = entry.version.startsWith('v') ? entry.version : `v${entry.version}`;
+
+ return `
+
+
+
+
+
+ Docker image:
+ ${entry.imageTag}
+ ${COPY_SVG}
+
+
+
`;
+}
+
+function generateChangelogSection(entries: ChangelogEntry[]): string {
+ if (entries.length === 0) {
+ return '';
+ }
+
+ const [latest, ...rest] = entries;
+ const latestHtml = generateLatestEntry(latest);
+ const restHtml = rest.map(generateCollapsibleEntry).join('\n');
+
+ return `
+
+
+
+
+${latestHtml}
+${restHtml}
+
+
+ `;
+}
+
+// Read changelog.json
+console.log('Reading changelog from:', CHANGELOG_PATH);
+const changelog: ChangelogEntry[] = JSON.parse(readFileSync(CHANGELOG_PATH, 'utf-8'));
+console.log(`Found ${changelog.length} changelog entries`);
+
+// Read index.html
+console.log('Reading index.html from:', INDEX_PATH);
+let indexHtml = readFileSync(INDEX_PATH, 'utf-8');
+
+// Generate new changelog section
+const newChangelogSection = generateChangelogSection(changelog);
+
+// Replace changelog section using regex
+// Match from "" to the closing "" before ""
+const changelogRegex = / [\s\S]*?<\/section>(?=\s*\n\s*)/;
+
+if (!changelogRegex.test(indexHtml)) {
+ console.error('ERROR: Could not find changelog section in index.html');
+ console.error('Looking for pattern: ... followed by ');
+ process.exit(1);
+}
+
+indexHtml = indexHtml.replace(changelogRegex, newChangelogSection);
+
+// Also update softwareVersion in JSON-LD schema
+if (changelog.length > 0) {
+ const latestVersion = changelog[0].version;
+ // Match "softwareVersion": "X.X" or "softwareVersion": "X.X.X"
+ const versionRegex = /"softwareVersion":\s*"[\d.]+"/;
+ if (versionRegex.test(indexHtml)) {
+ indexHtml = indexHtml.replace(versionRegex, `"softwareVersion": "${latestVersion}"`);
+ console.log(`Updated softwareVersion to: ${latestVersion}`);
+ }
+}
+
+// Write back to index.html
+writeFileSync(INDEX_PATH, indexHtml);
+console.log('');
+console.log('Generated changelog in webpage/index.html');
+console.log(` - Latest version: v${changelog[0]?.version || 'unknown'}`);
+console.log(` - Total entries: ${changelog.length}`);
diff --git a/scripts/generate-legal-pages.ts b/scripts/generate-legal-pages.ts
new file mode 100644
index 0000000..5056190
--- /dev/null
+++ b/scripts/generate-legal-pages.ts
@@ -0,0 +1,137 @@
+#!/usr/bin/env bun
+/**
+ * Generate static HTML pages for License and Privacy from .txt files
+ * This ensures a single source of truth for legal documents
+ */
+
+import { readFileSync, writeFileSync } from 'fs';
+import { join } from 'path';
+
+const ROOT_DIR = join(import.meta.dir, '..');
+const WEBPAGE_DIR = join(ROOT_DIR, 'webpage');
+
+function escapeHtml(text: string): string {
+ return text
+ .replace(/&/g, '&')
+ .replace(//g, '>');
+}
+
+function generateHtmlPage(title: string, content: string): string {
+ return `
+
+
+
+
+ ${title} - Dockhand
+
+
+
+
+
+
+
+
${title}
+
+
+
${escapeHtml(content)}
+
+
+
+
+
+`;
+}
+
+// Read the source files
+const licenseContent = readFileSync(join(ROOT_DIR, 'LICENSE.txt'), 'utf-8');
+const privacyContent = readFileSync(join(ROOT_DIR, 'PRIVACY.txt'), 'utf-8');
+
+// Generate HTML pages
+const licenseHtml = generateHtmlPage('License Terms and Conditions', licenseContent);
+const privacyHtml = generateHtmlPage('Privacy Policy', privacyContent);
+
+// Write to webpage directory
+writeFileSync(join(WEBPAGE_DIR, 'license.html'), licenseHtml);
+writeFileSync(join(WEBPAGE_DIR, 'privacy.html'), privacyHtml);
+
+console.log('Generated legal pages:');
+console.log(' - webpage/license.html');
+console.log(' - webpage/privacy.html');
diff --git a/scripts/patch-build.ts b/scripts/patch-build.ts
new file mode 100644
index 0000000..df1b85a
--- /dev/null
+++ b/scripts/patch-build.ts
@@ -0,0 +1,604 @@
+/**
+ * Post-build script to fix svelte-adapter-bun WebSocket issue
+ * The adapter calls server.websocket() which doesn't exist in SvelteKit.
+ *
+ * IMPORTANT: Terminal WebSocket logic is shared with vite.config.ts
+ * Core functions like resolveDockerTarget are defined in:
+ * src/lib/server/ws-terminal-shared.ts
+ *
+ * When updating WebSocket terminal handling, update the shared module
+ * and this file will use the same logic at build time.
+ */
+
+import { join } from 'node:path';
+
+const BUILD_DIR = join(import.meta.dir, '../build');
+
+async function patchHandler() {
+ const handlerPath = join(BUILD_DIR, 'handler.js');
+ const handlerFile = Bun.file(handlerPath);
+
+ if (!await handlerFile.exists()) {
+ console.error('handler.js not found');
+ process.exit(1);
+ }
+
+ let content = await handlerFile.text();
+
+ // Replace broken server.websocket() call
+ content = content.replace(
+ 'const websocket = server.websocket();',
+ 'const websocket = null;'
+ );
+
+ // Add WebSocket upgrade detection before ssr handler
+ const ssrIndex = content.indexOf('var ssr = async (request, bunServer) => {');
+ if (ssrIndex > -1) {
+ const upgradeCode = `
+var handleUpgrade = (request, bunServer) => {
+ const url = new URL(request.url);
+ const isUpgrade = request.headers.get('connection')?.toLowerCase().includes('upgrade') &&
+ request.headers.get('upgrade')?.toLowerCase() === 'websocket';
+ if (!isUpgrade) return null;
+
+ // Handle terminal exec WebSocket
+ if (url.pathname.includes('/api/containers/') && url.pathname.includes('/exec')) {
+ const pathParts = url.pathname.split('/');
+ const containerIdIndex = pathParts.indexOf('containers') + 1;
+ const containerId = pathParts[containerIdIndex];
+ const shell = url.searchParams.get('shell') || '/bin/sh';
+ const user = url.searchParams.get('user') || 'root';
+ const envId = url.searchParams.get('envId') ? parseInt(url.searchParams.get('envId'), 10) : undefined;
+ if (bunServer.upgrade(request, { data: { type: 'terminal', containerId, shell, user, envId } })) {
+ return new Response(null, { status: 101 });
+ }
+ }
+
+ // Handle terminal attach WebSocket
+ if (url.pathname.includes('/api/containers/') && url.pathname.includes('/attach')) {
+ const pathParts = url.pathname.split('/');
+ const containerIdIndex = pathParts.indexOf('containers') + 1;
+ const containerId = pathParts[containerIdIndex];
+ const envId = url.searchParams.get('envId') ? parseInt(url.searchParams.get('envId'), 10) : undefined;
+ if (bunServer.upgrade(request, { data: { type: 'terminal', mode: 'attach', containerId, envId } })) {
+ return new Response(null, { status: 101 });
+ }
+ }
+
+ // Handle Hawser Edge WebSocket
+ if (url.pathname === '/api/hawser/connect') {
+ if (bunServer.upgrade(request, { data: { type: 'hawser' } })) {
+ return new Response(null, { status: 101 });
+ }
+ }
+
+ return null;
+};
+`;
+ content = content.slice(0, ssrIndex) + upgradeCode + content.slice(ssrIndex);
+ }
+
+ // Modify handler to check for upgrade first
+ content = content.replace(
+ 'return ssr(request, server2);',
+ 'const upgradeResponse = handleUpgrade(request, server2); if (upgradeResponse) return upgradeResponse; return ssr(request, server2);'
+ );
+
+ await Bun.write(handlerPath, content);
+ console.log('✓ Patched handler.js');
+}
+
+async function patchIndex() {
+ const indexPath = join(BUILD_DIR, 'index.js');
+ const indexFile = Bun.file(indexPath);
+
+ if (!await indexFile.exists()) {
+ console.error('index.js not found');
+ process.exit(1);
+ }
+
+ let content = await indexFile.text();
+
+ const wsHandler = `
+import { existsSync as _existsSync } from 'fs';
+import { homedir as _homedir } from 'os';
+import { Database as _Database } from 'bun:sqlite';
+import { SQL as _SQL } from 'bun';
+import { join as _join } from 'path';
+
+// Database connection (supports both SQLite and PostgreSQL)
+let _db = null;
+let _isPostgres = false;
+function _getDb() {
+ if (!_db) {
+ const dbUrl = process.env.DATABASE_URL;
+ if (dbUrl && (dbUrl.startsWith('postgres://') || dbUrl.startsWith('postgresql://'))) {
+ _db = new _SQL(dbUrl);
+ _isPostgres = true;
+ } else {
+ const _dbPath = _join(process.cwd(), 'data', 'db', 'dockhand.db');
+ if (_existsSync(_dbPath)) {
+ _db = new _Database(_dbPath);
+ }
+ }
+ }
+ return _db;
+}
+
+async function _getEnvironment(id) {
+ const db = _getDb();
+ if (!db) return null;
+ let row;
+ if (_isPostgres) {
+ const result = await db.unsafe('SELECT * FROM environments WHERE id = $1', [id]);
+ row = result[0];
+ } else {
+ row = db.prepare('SELECT * FROM environments WHERE id = ?').get(id);
+ }
+ return row ? { ...row, is_local: Boolean(row.is_local), connection_type: row.connection_type, hawser_token: row.hawser_token } : null;
+}
+
+function detectDockerSocket() {
+ if (process.env.DOCKER_SOCKET && _existsSync(process.env.DOCKER_SOCKET)) return process.env.DOCKER_SOCKET;
+ if (process.env.DOCKER_HOST?.startsWith('unix://')) {
+ const p = process.env.DOCKER_HOST.replace('unix://', '');
+ if (_existsSync(p)) return p;
+ }
+ for (const s of ['/var/run/docker.sock', _homedir() + '/.docker/run/docker.sock', _homedir() + '/.orbstack/run/docker.sock', '/run/docker.sock']) {
+ if (_existsSync(s)) return s;
+ }
+ return '/var/run/docker.sock';
+}
+const dockerSocketPath = detectDockerSocket();
+console.log('Detected Docker socket at:', dockerSocketPath);
+
+const dockerStreams = new Map();
+let _wsConnCounter = 0;
+
+async function _getDockerTarget(envId) {
+ if (!envId) return { type: 'unix', socket: dockerSocketPath };
+ const env = await _getEnvironment(envId);
+ if (!env) return { type: 'unix', socket: dockerSocketPath };
+ // Check for socket connection type (local Unix socket)
+ if (env.is_local || env.connection_type === 'socket' || !env.connection_type) {
+ return { type: 'unix', socket: env.socket_path || dockerSocketPath };
+ }
+ if (env.connection_type === 'hawser-edge') return { type: 'hawser-edge', environmentId: envId };
+ return { type: 'tcp', host: env.host, port: env.port || 2375, hawserToken: env.connection_type === 'hawser-standard' ? env.hawser_token : undefined };
+}
+
+async function createExec(containerId, cmd, user, target) {
+ const headers = { 'Content-Type': 'application/json' };
+ const fetchOpts = {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({ AttachStdin: true, AttachStdout: true, AttachStderr: true, Tty: true, Cmd: cmd, User: user })
+ };
+ let url;
+ if (target.type === 'unix') {
+ url = 'http://localhost/containers/' + containerId + '/exec';
+ fetchOpts.unix = target.socket;
+ } else {
+ url = 'http://' + target.host + ':' + target.port + '/containers/' + containerId + '/exec';
+ if (target.hawserToken) headers['X-Hawser-Token'] = target.hawserToken;
+ }
+ const res = await fetch(url, fetchOpts);
+ if (!res.ok) throw new Error('Failed to create exec: ' + (await res.text()));
+ return res.json();
+}
+
+async function resizeExec(execId, cols, rows, target) {
+ try {
+ const fetchOpts = { method: 'POST' };
+ let url;
+ if (target.type === 'unix') {
+ url = 'http://localhost/exec/' + execId + '/resize?h=' + rows + '&w=' + cols;
+ fetchOpts.unix = target.socket;
+ } else {
+ url = 'http://' + target.host + ':' + target.port + '/exec/' + execId + '/resize?h=' + rows + '&w=' + cols;
+ if (target.hawserToken) fetchOpts.headers = { 'X-Hawser-Token': target.hawserToken };
+ }
+ await fetch(url, fetchOpts);
+ } catch {}
+}
+
+// ============ Hawser Edge Support ============
+// Global edge connections map (shared with hawser.ts via globalThis)
+if (!globalThis.__hawserEdgeConnections) globalThis.__hawserEdgeConnections = new Map();
+const _edgeConnections = globalThis.__hawserEdgeConnections;
+
+// Map WebSocket to environmentId for quick lookup
+const _wsToEnvId = new Map();
+
+// Edge exec sessions (execId -> frontend WebSocket)
+const _edgeExecSessions = new Map();
+
+// Validate Hawser token against database
+async function _validateHawserToken(token) {
+ const db = _getDb();
+ if (!db) return { valid: false };
+ let tokens;
+ if (_isPostgres) {
+ tokens = await db.unsafe('SELECT * FROM hawser_tokens WHERE is_active = true');
+ } else {
+ tokens = db.prepare('SELECT * FROM hawser_tokens WHERE is_active = 1').all();
+ }
+ for (const t of tokens) {
+ try {
+ const isValid = await Bun.password.verify(token, t.token);
+ if (isValid) {
+ if (_isPostgres) {
+ await db.unsafe('UPDATE hawser_tokens SET last_used = NOW() WHERE id = $1', [t.id]);
+ } else {
+ db.prepare('UPDATE hawser_tokens SET last_used = datetime(\\'now\\') WHERE id = ?').run(t.id);
+ }
+ return { valid: true, environmentId: t.environment_id, tokenId: t.id };
+ }
+ } catch {}
+ }
+ return { valid: false };
+}
+
+// Update environment status in database
+async function _updateEnvStatus(envId, conn) {
+ const db = _getDb();
+ if (!db) return;
+ try {
+ if (conn) {
+ if (_isPostgres) {
+ await db.unsafe('UPDATE environments SET hawser_last_seen = NOW(), hawser_agent_id = $1, hawser_agent_name = $2, hawser_version = $3, hawser_capabilities = $4 WHERE id = $5',
+ [conn.agentId, conn.agentName, conn.agentVersion, JSON.stringify(conn.capabilities || []), envId]);
+ } else {
+ db.prepare('UPDATE environments SET hawser_last_seen = datetime(\\'now\\'), hawser_agent_id = ?, hawser_agent_name = ?, hawser_version = ?, hawser_capabilities = ? WHERE id = ?')
+ .run(conn.agentId, conn.agentName, conn.agentVersion, JSON.stringify(conn.capabilities || []), envId);
+ }
+ } else {
+ if (_isPostgres) {
+ await db.unsafe('UPDATE environments SET hawser_last_seen = NOW() WHERE id = $1', [envId]);
+ } else {
+ db.prepare('UPDATE environments SET hawser_last_seen = datetime(\\'now\\') WHERE id = ?').run(envId);
+ }
+ }
+ } catch {}
+}
+
+// Handle Hawser Edge protocol messages
+async function _handleHawserMessage(ws, msg) {
+ if (msg.type === 'hello') {
+ console.log('[Hawser] Hello from agent:', msg.agentName, '(' + msg.agentId + ')');
+ const validation = await _validateHawserToken(msg.token);
+ if (!validation.valid) {
+ console.log('[Hawser] Invalid token');
+ ws.send(JSON.stringify({ type: 'error', error: 'Invalid token' }));
+ ws.close();
+ return;
+ }
+ const envId = validation.environmentId;
+ const existing = _edgeConnections.get(envId);
+ if (existing) {
+ const pendingCount = existing.pendingRequests.size;
+ const streamCount = existing.pendingStreamRequests.size;
+ console.log('[Hawser] Replacing existing connection for env', envId, '- rejecting', pendingCount, 'pending requests and', streamCount, 'stream requests');
+ // Reject all pending requests before closing
+ for (const [requestId, pending] of existing.pendingRequests) {
+ clearTimeout(pending.timeout);
+ pending.reject(new Error('Connection replaced by new agent'));
+ }
+ for (const [requestId, pending] of existing.pendingStreamRequests) {
+ pending.onEnd?.('Connection replaced by new agent');
+ }
+ existing.pendingRequests.clear();
+ existing.pendingStreamRequests.clear();
+ existing.ws.close(1000, 'Replaced');
+ _wsToEnvId.delete(existing.ws);
+ }
+ const conn = {
+ ws, environmentId: envId, agentId: msg.agentId, agentName: msg.agentName,
+ agentVersion: msg.version || 'unknown', dockerVersion: msg.dockerVersion || 'unknown',
+ hostname: msg.hostname || 'unknown', capabilities: msg.capabilities || [],
+ connectedAt: new Date(), lastHeartbeat: new Date(),
+ pendingRequests: new Map(), pendingStreamRequests: new Map(),
+ pingInterval: null
+ };
+ _edgeConnections.set(envId, conn);
+ _wsToEnvId.set(ws, envId);
+ await _updateEnvStatus(envId, conn);
+ ws.send(JSON.stringify({ type: 'welcome', environmentId: envId, message: 'Connected to Dockhand' }));
+ // Start server-side ping interval to keep connection alive through Traefik/proxies (5s)
+ conn.pingInterval = setInterval(() => {
+ try { ws.send(JSON.stringify({ type: 'ping', timestamp: Date.now() })); }
+ catch { if (conn.pingInterval) { clearInterval(conn.pingInterval); conn.pingInterval = null; } }
+ }, 5000);
+ console.log('[Hawser] Agent', msg.agentName, 'connected for env', envId);
+ } else if (msg.type === 'ping') {
+ const envId = _wsToEnvId.get(ws);
+ if (envId) { const c = _edgeConnections.get(envId); if (c) c.lastHeartbeat = new Date(); }
+ ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }));
+ } else if (msg.type === 'pong') {
+ const envId = _wsToEnvId.get(ws);
+ if (envId) { const c = _edgeConnections.get(envId); if (c) c.lastHeartbeat = new Date(); }
+ } else if (msg.type === 'response') {
+ const envId = _wsToEnvId.get(ws);
+ if (!envId) {
+ console.warn('[Hawser] Response from unknown WebSocket, requestId=' + msg.requestId);
+ return;
+ }
+ const conn = _edgeConnections.get(envId);
+ if (conn) {
+ const pending = conn.pendingRequests.get(msg.requestId);
+ if (pending) {
+ clearTimeout(pending.timeout);
+ conn.pendingRequests.delete(msg.requestId);
+ pending.resolve({ statusCode: msg.statusCode, headers: msg.headers || {}, body: msg.body || '', isBinary: msg.isBinary || false });
+ } else {
+ console.warn('[Hawser] Response for unknown request ' + msg.requestId + ' on env ' + envId);
+ }
+ }
+ } else if (msg.type === 'stream') {
+ const envId = _wsToEnvId.get(ws);
+ if (!envId) {
+ console.warn('[Hawser] Stream data from unknown WebSocket, requestId=' + msg.requestId);
+ return;
+ }
+ const conn = _edgeConnections.get(envId);
+ if (conn?.pendingStreamRequests) {
+ const pending = conn.pendingStreamRequests.get(msg.requestId);
+ if (pending) {
+ pending.onData(msg.data, msg.stream);
+ } else {
+ console.warn('[Hawser] Stream data for unknown request ' + msg.requestId + ' on env ' + envId);
+ }
+ }
+ } else if (msg.type === 'stream_end') {
+ const envId = _wsToEnvId.get(ws);
+ if (!envId) {
+ console.warn('[Hawser] Stream end from unknown WebSocket, requestId=' + msg.requestId);
+ return;
+ }
+ const conn = _edgeConnections.get(envId);
+ if (conn?.pendingStreamRequests) {
+ const pending = conn.pendingStreamRequests.get(msg.requestId);
+ if (pending) {
+ conn.pendingStreamRequests.delete(msg.requestId);
+ pending.onEnd(msg.reason);
+ } else {
+ console.warn('[Hawser] Stream end for unknown request ' + msg.requestId + ' on env ' + envId);
+ }
+ }
+ } else if (msg.type === 'exec_ready') {
+ const session = _edgeExecSessions.get(msg.execId);
+ if (session?.ws?.readyState === 1) console.log('[Hawser] Exec ready:', msg.execId);
+ } else if (msg.type === 'exec_output') {
+ const session = _edgeExecSessions.get(msg.execId);
+ if (session?.ws?.readyState === 1) {
+ const data = Buffer.from(msg.data, 'base64').toString('utf-8');
+ session.ws.send(JSON.stringify({ type: 'output', data }));
+ }
+ } else if (msg.type === 'exec_end') {
+ const session = _edgeExecSessions.get(msg.execId);
+ if (session) {
+ console.log('[Hawser] Exec ended:', msg.execId);
+ if (session.ws?.readyState === 1) { session.ws.send(JSON.stringify({ type: 'exit' })); session.ws.close(); }
+ _edgeExecSessions.delete(msg.execId);
+ }
+ } else if (msg.type === 'container_event') {
+ const envId = _wsToEnvId.get(ws);
+ if (envId && msg.event) {
+ // Call the global handler registered by hawser.ts
+ if (globalThis.__hawserHandleContainerEvent) {
+ globalThis.__hawserHandleContainerEvent(envId, msg.event).catch((err) => {
+ console.error('[Hawser] Error handling container event:', err);
+ });
+ }
+ }
+ } else if (msg.type === 'metrics') {
+ // Metrics from agent - save to database for dashboard graphs
+ const envId = _wsToEnvId.get(ws);
+ if (envId && msg.metrics) {
+ if (globalThis.__hawserHandleMetrics) {
+ globalThis.__hawserHandleMetrics(envId, msg.metrics).catch((err) => {
+ console.error('[Hawser] Error saving metrics:', err);
+ });
+ }
+ }
+ }
+}
+
+// Expose send function for hawser.ts module
+globalThis.__hawserSendMessage = (envId, message) => {
+ const conn = _edgeConnections.get(envId);
+ if (!conn?.ws) return false;
+ try { conn.ws.send(message); return true; } catch { return false; }
+};
+
+// ============ Combined WebSocket Handler ============
+const combinedWebsocket = {
+ async open(ws) {
+ const connType = ws.data?.type;
+
+ // Hawser Edge connection - wait for hello message
+ if (connType === 'hawser') {
+ console.log('[Hawser] New connection pending authentication');
+ return;
+ }
+
+ // Terminal connection
+ const connId = 'ws-' + (++_wsConnCounter);
+ ws.data = ws.data || {};
+ ws.data.connId = connId;
+ const { containerId, shell, user, envId, mode } = ws.data;
+ if (!containerId) { ws.send(JSON.stringify({ type: 'error', message: 'No container ID' })); ws.close(); return; }
+ const target = await _getDockerTarget(envId);
+ const isAttach = mode === 'attach';
+ console.log('[WS] Open:', connId, containerId, 'mode:', mode || 'exec', 'target:', target.type);
+
+ // Handle Hawser Edge terminal
+ if (target.type === 'hawser-edge') {
+ const conn = _edgeConnections.get(target.environmentId);
+ if (!conn) { ws.send(JSON.stringify({ type: 'error', message: 'Edge agent not connected' })); ws.close(); return; }
+ const execId = crypto.randomUUID();
+ _edgeExecSessions.set(execId, { ws, execId, environmentId: target.environmentId });
+ ws.data.edgeExecId = execId;
+ if (isAttach) {
+ conn.ws.send(JSON.stringify({ type: 'attach', containerId, attachId: execId }));
+ } else {
+ conn.ws.send(JSON.stringify({ type: 'exec_start', execId, containerId, cmd: shell || '/bin/sh', user: user || 'root', cols: 120, rows: 30 }));
+ }
+ return;
+ }
+
+ try {
+ let execId;
+ let httpRequest;
+
+ if (isAttach) {
+ // Attach directly to container streams
+ execId = crypto.randomUUID();
+ const tokenHeader = target.type === 'tcp' && target.hawserToken ? 'X-Hawser-Token: ' + target.hawserToken + '\\r\\n' : '';
+ httpRequest = 'POST /containers/' + containerId + '/attach?stream=1&stdout=1&stderr=1&stdin=1 HTTP/1.1\\r\\nHost: localhost\\r\\nContent-Type: application/json\\r\\n' + tokenHeader + 'Connection: Upgrade\\r\\nUpgrade: tcp\\r\\nContent-Length: 0\\r\\n\\r\\n';
+ } else {
+ // Create exec instance for shell
+ const exec = await createExec(containerId, [shell || '/bin/sh'], user || 'root', target);
+ execId = exec.Id;
+ const body = JSON.stringify({ Detach: false, Tty: true });
+ const tokenHeader = target.type === 'tcp' && target.hawserToken ? 'X-Hawser-Token: ' + target.hawserToken + '\\r\\n' : '';
+ httpRequest = 'POST /exec/' + execId + '/start HTTP/1.1\\r\\nHost: localhost\\r\\nContent-Type: application/json\\r\\n' + tokenHeader + 'Connection: Upgrade\\r\\nUpgrade: tcp\\r\\nContent-Length: ' + body.length + '\\r\\n\\r\\n' + body;
+ }
+
+ let dockerStream;
+ let headersStripped = false;
+ let isChunked = false;
+ const socketHandler = {
+ data(socket, data) {
+ if (ws.readyState === 1) {
+ let text = new TextDecoder().decode(data);
+ if (!headersStripped) {
+ if (text.toLowerCase().includes('transfer-encoding: chunked')) isChunked = true;
+ const i = text.indexOf('\\r\\n\\r\\n');
+ if (i > -1) { text = text.slice(i + 4); headersStripped = true; }
+ else if (text.startsWith('HTTP/')) return;
+ }
+ if (isChunked && text) text = text.replace(/^[0-9a-fA-F]+\\r\\n/gm, '').replace(/\\r\\n$/g, '');
+ if (text) ws.send(JSON.stringify({ type: 'output', data: text }));
+ }
+ },
+ close() { if (ws.readyState === 1) { ws.send(JSON.stringify({ type: 'exit' })); ws.close(); } },
+ error() {},
+ open(socket) {
+ socket.write(httpRequest);
+ }
+ };
+ if (target.type === 'unix') {
+ dockerStream = await Bun.connect({ unix: target.socket, socket: socketHandler });
+ } else {
+ dockerStream = await Bun.connect({ hostname: target.host, port: target.port, socket: socketHandler });
+ }
+ dockerStreams.set(connId, { stream: dockerStream, execId, target });
+ } catch (e) { ws.send(JSON.stringify({ type: 'error', message: e.message })); ws.close(); }
+ },
+ async message(ws, message) {
+ const connType = ws.data?.type;
+
+ // Hawser Edge message
+ if (connType === 'hawser') {
+ try {
+ let msgStr = typeof message === 'string' ? message : message instanceof ArrayBuffer ? new TextDecoder().decode(message) : Buffer.isBuffer(message) ? message.toString('utf-8') : new TextDecoder().decode(new Uint8Array(message));
+ const msg = JSON.parse(msgStr);
+ await _handleHawserMessage(ws, msg);
+ } catch (e) {
+ console.error('[Hawser] Error:', e.message);
+ ws.send(JSON.stringify({ type: 'error', error: e.message }));
+ }
+ return;
+ }
+
+ // Edge exec session input
+ const edgeExecId = ws.data?.edgeExecId;
+ if (edgeExecId) {
+ const session = _edgeExecSessions.get(edgeExecId);
+ if (session) {
+ const conn = _edgeConnections.get(session.environmentId);
+ if (conn) {
+ try {
+ const msg = JSON.parse(message.toString());
+ if (msg.type === 'input') conn.ws.send(JSON.stringify({ type: 'exec_input', execId: edgeExecId, data: Buffer.from(msg.data).toString('base64') }));
+ else if (msg.type === 'resize') conn.ws.send(JSON.stringify({ type: 'exec_resize', execId: edgeExecId, cols: msg.cols, rows: msg.rows }));
+ } catch {}
+ }
+ }
+ return;
+ }
+
+ // Terminal message
+ const connId = ws.data?.connId;
+ if (!connId) return;
+ const d = dockerStreams.get(connId);
+ if (!d) return;
+ try {
+ const msg = JSON.parse(message.toString());
+ if (msg.type === 'input' && d.stream) d.stream.write(msg.data);
+ else if (msg.type === 'resize' && d.execId) resizeExec(d.execId, msg.cols, msg.rows, d.target);
+ } catch { if (d.stream) d.stream.write(message); }
+ },
+ close(ws) {
+ const connType = ws.data?.type;
+
+ // Hawser Edge disconnection
+ if (connType === 'hawser') {
+ const envId = _wsToEnvId.get(ws);
+ if (envId) {
+ const conn = _edgeConnections.get(envId);
+ if (conn) {
+ console.log('[Hawser] Agent disconnected:', conn.agentId);
+ // Clear server-side ping interval
+ if (conn.pingInterval) { clearInterval(conn.pingInterval); conn.pingInterval = null; }
+ for (const [, p] of conn.pendingRequests) { clearTimeout(p.timeout); p.reject(new Error('Connection closed')); }
+ for (const [, p] of conn.pendingStreamRequests) { p.onEnd('Connection closed'); }
+ _edgeConnections.delete(envId);
+ _updateEnvStatus(envId, null);
+ }
+ _wsToEnvId.delete(ws);
+ }
+ return;
+ }
+
+ // Edge exec session close
+ const edgeExecId = ws.data?.edgeExecId;
+ if (edgeExecId) {
+ const session = _edgeExecSessions.get(edgeExecId);
+ if (session) {
+ const conn = _edgeConnections.get(session.environmentId);
+ if (conn) conn.ws.send(JSON.stringify({ type: 'exec_end', execId: edgeExecId, reason: 'user_closed' }));
+ _edgeExecSessions.delete(edgeExecId);
+ }
+ return;
+ }
+
+ // Terminal close
+ const connId = ws.data?.connId;
+ if (!connId) return;
+ const d = dockerStreams.get(connId);
+ if (d?.stream) d.stream.end();
+ dockerStreams.delete(connId);
+ }
+};
+`;
+
+ const insertPoint = content.indexOf('var path = env(');
+ if (insertPoint > -1) {
+ content = content.slice(0, insertPoint) + wsHandler + content.slice(insertPoint);
+ }
+
+ content = content.replace(
+ 'var { fetch: handlerFetch, websocket } = getHandler();',
+ 'var { fetch: handlerFetch, websocket: _ } = getHandler(); var websocket = combinedWebsocket;'
+ );
+
+ await Bun.write(indexPath, content);
+ console.log('✓ Patched index.js');
+}
+
+console.log('Patching build...');
+await patchHandler();
+await patchIndex();
+console.log('✓ Done');
diff --git a/src/LICENSE.txt b/src/LICENSE.txt
new file mode 100644
index 0000000..86472ee
--- /dev/null
+++ b/src/LICENSE.txt
@@ -0,0 +1,128 @@
+Business Source License 1.1
+
+License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
+"Business Source License" is a trademark of MariaDB Corporation Ab.
+
+-----------------------------------------------------------------------------
+
+Parameters
+
+Licensor: Finsys / Jarek Krochmalski
+
+Licensed Work: Dockhand
+ The Licensed Work is (c) 2025-2026 Finsys / Jarek Krochmalski.
+
+Additional Use Grant: You may use the Licensed Work for any purpose, including
+ production use, provided that you do not offer the Licensed
+ Work, or any derivative work of the Licensed Work, to third
+ parties as a commercial hosted service, managed service, or
+ software-as-a-service (SaaS) offering where the primary value
+ proposition to users is Docker container management
+ functionality substantially similar to the Licensed Work.
+
+ For clarity, the following uses are explicitly permitted
+ without any restriction:
+
+ (a) Personal use, including home labs and hobby projects
+ (b) Internal business use within your organization, regardless
+ of the number of Docker environments managed
+ (c) Use by non-profit organizations and charitable entities
+ (d) Educational, academic, and research purposes
+ (e) Evaluation, testing, development, and demonstration purposes
+ (f) Embedding or integrating the Licensed Work into internal
+ tools or platforms that are not offered commercially to
+ third parties
+ (g) Use by managed service providers (MSPs) to manage Docker
+ infrastructure on behalf of their clients, provided the
+ MSP does not offer Dockhand itself as the service
+
+Change Date: January 1, 2029
+
+Change License: Apache License, Version 2.0
+
+-----------------------------------------------------------------------------
+
+Terms
+
+The Licensor hereby grants you the right to copy, modify, create derivative
+works, redistribute, and make non-production use of the Licensed Work. The
+Licensor may make an Additional Use Grant, above, permitting limited
+production use.
+
+Effective on the Change Date, or the fourth anniversary of the first publicly
+available distribution of a specific version of the Licensed Work under this
+License, whichever comes first, the Licensor hereby grants you rights under
+the terms of the Change License, and the rights granted in the paragraph
+above terminate.
+
+If your use of the Licensed Work does not comply with the requirements
+currently in effect as described in this License, you must purchase a
+commercial license from the Licensor, its affiliated entities, or authorized
+resellers, or you must refrain from using the Licensed Work.
+
+All copies of the original and modified Licensed Work, and derivative works
+of the Licensed Work, are subject to this License. This License applies
+separately for each version of the Licensed Work and the Change Date may vary
+for each version of the Licensed Work released by Licensor.
+
+You must conspicuously display this License on each original or modified copy
+of the Licensed Work. If you receive the Licensed Work in original or
+modified form from a third party, the terms and conditions set forth in this
+License apply to your use of that work.
+
+Any use of the Licensed Work in violation of this License will automatically
+terminate your rights under this License for the current and all other
+versions of the Licensed Work.
+
+This License does not grant you any right in any trademark or logo of
+Licensor or its affiliates (provided that you may use a trademark or logo of
+Licensor as expressly required by this License).
+
+TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
+AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
+EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
+TITLE.
+
+MariaDB hereby grants you permission to use this License's text to license
+your works, and to refer to it using the trademark "Business Source License",
+as long as you comply with the Covenants of Licensor below.
+
+-----------------------------------------------------------------------------
+
+Covenants of Licensor
+
+In consideration of the right to use this License's text and the "Business
+Source License" name and trademark, Licensor covenants to MariaDB, and to all
+other recipients of the licensed work to be provided by Licensor:
+
+1. To specify as the Change License the GPL Version 2.0 or any later version,
+ or a license that is compatible with GPL Version 2.0 or a later version,
+ where "compatible" means that software provided under the Change License can
+ be included in a program with software provided under GPL Version 2.0 or a
+ later version. Licensor may specify additional Change Licenses without
+ limitation.
+
+2. To either: (a) specify an additional grant of rights to use that does not
+ impose any additional restriction on the right granted in this License, as
+ the Additional Use Grant; or (b) insert the text "None".
+
+3. To specify a Change Date.
+
+4. Not to modify this License in any other way.
+
+-----------------------------------------------------------------------------
+
+Notice
+
+The Business Source License (this document, or the "License") is not an Open
+Source license. However, the Licensed Work will eventually be made available
+under an Open Source License, as stated in this License.
+
+-----------------------------------------------------------------------------
+
+For licensing inquiries, commercial licensing, or enterprise features:
+
+ Website: https://dockhand.io
+
+-----------------------------------------------------------------------------
diff --git a/app.css b/src/app.css
similarity index 100%
rename from app.css
rename to src/app.css
diff --git a/app.d.ts b/src/app.d.ts
similarity index 100%
rename from app.d.ts
rename to src/app.d.ts
diff --git a/app.html b/src/app.html
similarity index 99%
rename from app.html
rename to src/app.html
index 5f6ec02..d96ece1 100644
--- a/app.html
+++ b/src/app.html
@@ -13,5 +13,3 @@
%sveltekit.body%