Merge branch 'next' into next
This commit is contained in:
44
.github/workflows/docker-image.yml
vendored
44
.github/workflows/docker-image.yml
vendored
@@ -1,44 +0,0 @@
|
|||||||
name: Docker Image CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
# push:
|
|
||||||
# branches: [ "main" ]
|
|
||||||
# pull_request:
|
|
||||||
# branches: [ "*" ]
|
|
||||||
push:
|
|
||||||
branches: ["this-does-not-exist"]
|
|
||||||
pull_request:
|
|
||||||
branches: ["this-does-not-exist"]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
/usr/local/share/ca-certificates
|
|
||||||
/var/cache/apt/archives
|
|
||||||
/var/lib/apt/lists
|
|
||||||
~/.cache
|
|
||||||
key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-docker-
|
|
||||||
- name: Build the Docker image
|
|
||||||
run: |
|
|
||||||
cp .env.example .env
|
|
||||||
docker run --rm -u "$(id -u):$(id -g)" \
|
|
||||||
-v "$(pwd):/app" \
|
|
||||||
-w /app composer:2 \
|
|
||||||
composer install --ignore-platform-reqs
|
|
||||||
./vendor/bin/spin build
|
|
||||||
- name: Start the stack
|
|
||||||
run: |
|
|
||||||
./vendor/bin/spin up -d
|
|
||||||
./vendor/bin/spin exec coolify php artisan key:generate
|
|
||||||
./vendor/bin/spin exec coolify php artisan migrate:fresh --seed
|
|
||||||
- name: Test (missing E2E tests)
|
|
||||||
run: |
|
|
||||||
./vendor/bin/spin exec coolify php artisan test
|
|
||||||
25
.github/workflows/fix-php-code-style-issues
vendored
25
.github/workflows/fix-php-code-style-issues
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Fix PHP code style issues
|
|
||||||
|
|
||||||
on: [push]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
php-code-styling:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.head_ref }}
|
|
||||||
|
|
||||||
- name: Fix PHP code style issues
|
|
||||||
uses: aglipanci/laravel-pint-action@2.4
|
|
||||||
|
|
||||||
- name: Commit changes
|
|
||||||
uses: stefanzweifel/git-auto-commit-action@v5
|
|
||||||
with:
|
|
||||||
commit_message: Fix styling
|
|
||||||
17
.github/workflows/lock-closed-issues-discussions-and-prs.yml
vendored
Normal file
17
.github/workflows/lock-closed-issues-discussions-and-prs.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Lock closed Issues, Discussions, and PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lock-threads:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Lock threads after 30 days of inactivity
|
||||||
|
uses: dessant/lock-threads@v5
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
issue-inactive-days: '30'
|
||||||
|
pr-inactive-days: '30'
|
||||||
|
discussion-inactive-days: '30'
|
||||||
28
.github/workflows/manage-stale-issues-and-prs.yml
vendored
Normal file
28
.github/workflows/manage-stale-issues-and-prs.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
name: Manage Stale Issues and PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 2 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
manage-stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Manage stale issues and PRs
|
||||||
|
uses: actions/stale@v9
|
||||||
|
id: stale
|
||||||
|
with:
|
||||||
|
stale-issue-message: 'This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information.'
|
||||||
|
stale-pr-message: 'This pull request will be automatically closed in a few days if no response is received. Please update your PR or comment if you would like to continue working on it.'
|
||||||
|
close-issue-message: 'This issue has been automatically closed due to inactivity.'
|
||||||
|
close-pr-message: 'This pull request has been automatically closed due to inactivity.'
|
||||||
|
days-before-stale: 14
|
||||||
|
days-before-close: 7
|
||||||
|
stale-issue-label: '⏱︎ Stale'
|
||||||
|
stale-pr-label: '⏱︎ Stale'
|
||||||
|
only-labels: '💤 Waiting for feedback'
|
||||||
|
remove-stale-when-updated: true
|
||||||
|
operations-per-run: 100
|
||||||
|
labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback'
|
||||||
|
close-issue-reason: 'not_planned'
|
||||||
|
exempt-all-milestones: false
|
||||||
93
.github/workflows/pr-build.yml
vendored
93
.github/workflows/pr-build.yml
vendored
@@ -1,93 +0,0 @@
|
|||||||
name: PR Build (v4)
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
branches-ignore: ["main", "v3"]
|
|
||||||
paths-ignore:
|
|
||||||
- .github/workflows/coolify-helper.yml
|
|
||||||
- docker/coolify-helper/Dockerfile
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
IMAGE_NAME: "coollabsio/coolify"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
amd64:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
attestations: write
|
|
||||||
id-token: write
|
|
||||||
actions: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/prod/Dockerfile
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
|
|
||||||
aarch64:
|
|
||||||
runs-on: [self-hosted, arm64]
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
attestations: write
|
|
||||||
id-token: write
|
|
||||||
actions: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/prod/Dockerfile
|
|
||||||
platforms: linux/aarch64
|
|
||||||
push: true
|
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64
|
|
||||||
merge-manifest:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
attestations: write
|
|
||||||
id-token: write
|
|
||||||
actions: write
|
|
||||||
needs: [amd64, aarch64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Create & publish manifest
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
|
||||||
113
RELEASE.md
113
RELEASE.md
@@ -2,35 +2,120 @@
|
|||||||
|
|
||||||
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed.
|
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Release Process](#release-process)
|
||||||
|
- [Version Types](#version-types)
|
||||||
|
- [Stable](#stable)
|
||||||
|
- [Nightly](#nightly)
|
||||||
|
- [Beta](#beta)
|
||||||
|
- [Version Availability](#version-availability)
|
||||||
|
- [Self-Hosted](#self-hosted)
|
||||||
|
- [Cloud](#cloud)
|
||||||
|
- [Manually Update to Specific Versions](#manually-update-to-specific-versions)
|
||||||
|
|
||||||
## Release Process
|
## Release Process
|
||||||
|
|
||||||
1. **Development on `next` or separate branches**
|
1. **Development on `next` or Feature Branches**
|
||||||
- Changes, fixes and new features are developed on the `next` or even separate branches.
|
- Improvements, fixes, and new features are developed on the `next` branch or separate feature branches.
|
||||||
|
|
||||||
2. **Merging to `main`**
|
2. **Merging to `main`**
|
||||||
- Once changes are ready, they are merged from `next` into the `main` branch.
|
- Once ready, changes are merged from the `next` branch into the `main` branch.
|
||||||
|
|
||||||
3. **Building the release**
|
3. **Building the Release**
|
||||||
- After merging to `main`, a new release is built.
|
- After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry with the version tag and the `latest` tag.
|
||||||
- Note: A push to `main` does not automatically mean a new version is released.
|
|
||||||
|
|
||||||
4. **Creating a GitHub release**
|
4. **Creating a GitHub Release**
|
||||||
- A new release is created on GitHub with the new version details.
|
- A new GitHub release is manually created with details of the changes made in the version.
|
||||||
|
|
||||||
5. **Updating the CDN**
|
5. **Updating the CDN**
|
||||||
- The final step is updating the version information on the CDN:
|
- To make a new version publicly available, the version information on the CDN needs to be updated: [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json)
|
||||||
[https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json)
|
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> The CDN update may not occur immediately after the GitHub release. It can happen hours or even days later due to additional testing, stability checks, or potential hotfixes.
|
> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated.**
|
||||||
|
|
||||||
|
## Version Types
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Stable (coming soon)</strong></summary>
|
||||||
|
|
||||||
|
- **Stable**
|
||||||
|
- The production version suitable for stable, production environments (generally recommended).
|
||||||
|
- **Update Frequency:** Every 2 to 4 weeks, with more frequent possible hotfixes.
|
||||||
|
- **Release Size:** Larger but less frequent releases. Multiple nightly versions are consolidated into a single stable release.
|
||||||
|
- **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`).
|
||||||
|
- **Installation Command:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Nightly</strong></summary>
|
||||||
|
|
||||||
|
- **Nightly**
|
||||||
|
- The latest development version, suitable for testing the latest changes and experimenting with new features.
|
||||||
|
- **Update Frequency:** Daily or bi-weekly updates.
|
||||||
|
- **Release Size:** Smaller, more frequent releases.
|
||||||
|
- **Versioning Scheme:** TO BE DETERMINED
|
||||||
|
- **Installation Command:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify-nightly/install.sh | bash -s next
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Beta</strong></summary>
|
||||||
|
|
||||||
|
- **Beta**
|
||||||
|
- Test releases for the upcoming stable version.
|
||||||
|
- **Purpose:** Allows users to test and provide feedback on new features and changes before they become stable.
|
||||||
|
- **Update Frequency:** Available if we think beta testing is necessary.
|
||||||
|
- **Release Size:** Same size as stable release as it will become the next stabe release after some time.
|
||||||
|
- **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`).
|
||||||
|
- **Installation Command:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Do not use nightly/beta builds in production as there is no guarantee of stability.
|
||||||
|
|
||||||
## Version Availability
|
## Version Availability
|
||||||
|
|
||||||
It's important to understand that a new version released on GitHub may not immediately become available for users to update (through manual or auto-update).
|
When a new version is released and a new GitHub release is created, it doesn't immediately become available for your instance. Here's how version availability works for different instance types.
|
||||||
|
|
||||||
|
### Self-Hosted
|
||||||
|
|
||||||
|
- **Update Frequency:** More frequent updates, especially on the nightly release channel.
|
||||||
|
- **Update Availability:** New versions are available once the CDN has been updated.
|
||||||
|
- **Update Methods:**
|
||||||
|
1. **Manual Update in Instance Settings:**
|
||||||
|
- Go to `Settings > Update Check Frequency` and click the `Check Manually` button.
|
||||||
|
- If an update is available, an upgrade button will appear on the sidebar.
|
||||||
|
2. **Automatic Update:**
|
||||||
|
- If enabled, the instance will update automatically at the time set in the settings.
|
||||||
|
3. **Re-run Installation Script:**
|
||||||
|
- Run the installation script again to upgrade to the latest version available on the CDN:
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> If you see a new release on GitHub but haven't received the update, it's likely because the CDN hasn't been updated yet. This is intentional and ensures stability and allows for hotfixes before the new version is officially released.
|
> If a new release is available on GitHub but your instance hasn't updated yet or no upgrade button is shown in the UI, the CDN might not have been updated yet. This intentional delay ensures stability and allows for hotfixes before official release.
|
||||||
|
|
||||||
|
### Cloud
|
||||||
|
|
||||||
|
- **Update Frequency:** Less frequent as it's a managed service.
|
||||||
|
- **Update Availability:** New versions are available once Andras has updated the cloud version manually.
|
||||||
|
- **Update Method:**
|
||||||
|
- Updates are managed by Andras, who ensures each cloud version is thoroughly tested and stable before releasing it.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> The cloud version of Coolify may be several versions behind the latest GitHub releases even if the CDN is updated. This is intentional to ensure stability and reliability for cloud users and Andras will manully update the cloud version when the update is ready.
|
||||||
|
|
||||||
## Manually Update to Specific Versions
|
## Manually Update to Specific Versions
|
||||||
|
|
||||||
@@ -42,4 +127,4 @@ To update your Coolify instance to a specific (unreleased) version, use the foll
|
|||||||
```bash
|
```bash
|
||||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s <version>
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s <version>
|
||||||
```
|
```
|
||||||
-> Replace `<version>` with the version you want to update to (for example `4.0.0-beta.332`).
|
Replace `<version>` with the version you want to update to (for example `4.0.0-beta.332`).
|
||||||
|
|||||||
@@ -651,8 +651,9 @@ class GetContainersStatus
|
|||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if proxy is running
|
if (! $this->server->proxySet() || $this->server->proxy->force_stop) {
|
||||||
$this->server->proxyType();
|
return;
|
||||||
|
}
|
||||||
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class StartProxy
|
|||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
'docker stack deploy -c docker-compose.yml coolify-proxy',
|
'docker stack deploy -c docker-compose.yml coolify-proxy',
|
||||||
"echo 'Proxy started successfully.'",
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$caddfile = 'import /dynamic/*.caddy';
|
$caddfile = 'import /dynamic/*.caddy';
|
||||||
@@ -46,12 +46,14 @@ class StartProxy
|
|||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Pulling docker image.'",
|
"echo 'Pulling docker image.'",
|
||||||
'docker compose pull',
|
'docker compose pull',
|
||||||
"echo 'Stopping existing coolify-proxy.'",
|
'if docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then',
|
||||||
'docker stop -t 10 coolify-proxy || true',
|
" echo 'Stopping and removing existing coolify-proxy.'",
|
||||||
'docker rm coolify-proxy || true',
|
' docker rm -f coolify-proxy || true',
|
||||||
|
" echo 'Successfully stopped and removed existing coolify-proxy.'",
|
||||||
|
'fi',
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
'docker compose up -d --remove-orphans',
|
'docker compose up -d --remove-orphans',
|
||||||
"echo 'Proxy started successfully.'",
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,28 +12,29 @@ class CleanupDocker
|
|||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
$helperImageVersion = data_get($settings, 'helper_version');
|
||||||
|
$helperImage = config('coolify.helper_image');
|
||||||
|
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
||||||
|
|
||||||
$commands = $this->getCommands();
|
$commands = [
|
||||||
|
'docker container prune -f --filter "label=coolify.managed=true"',
|
||||||
|
'docker image prune -af --filter "label!=coolify.managed=true"',
|
||||||
|
'docker builder prune -af',
|
||||||
|
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
|
||||||
|
];
|
||||||
|
|
||||||
|
$serverSettings = $server->settings;
|
||||||
|
if ($serverSettings->delete_unused_volumes) {
|
||||||
|
$commands[] = 'docker volume prune -af';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($serverSettings->delete_unused_networks) {
|
||||||
|
$commands[] = 'docker network prune -f';
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($commands as $command) {
|
foreach ($commands as $command) {
|
||||||
instant_remote_process([$command], $server, false);
|
instant_remote_process([$command], $server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getCommands(): array
|
|
||||||
{
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
$helperImageVersion = data_get($settings, 'helper_version');
|
|
||||||
$helperImage = config('coolify.helper_image');
|
|
||||||
$helperImageWithVersion = config('coolify.helper_image').':'.$helperImageVersion;
|
|
||||||
|
|
||||||
$commonCommands = [
|
|
||||||
'docker container prune -f --filter "label=coolify.managed=true"',
|
|
||||||
'docker image prune -af --filter "label!=coolify.managed=true"',
|
|
||||||
'docker builder prune -af',
|
|
||||||
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi",
|
|
||||||
];
|
|
||||||
|
|
||||||
return $commonCommands;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
app/Enums/ContainerStatusTypes.php
Normal file
14
app/Enums/ContainerStatusTypes.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum ContainerStatusTypes: string
|
||||||
|
{
|
||||||
|
case PAUSED = 'paused';
|
||||||
|
case RESTARTING = 'restarting';
|
||||||
|
case REMOVING = 'removing';
|
||||||
|
case RUNNING = 'running';
|
||||||
|
case DEAD = 'dead';
|
||||||
|
case CREATED = 'created';
|
||||||
|
case EXITED = 'exited';
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public ?string $usageBefore = null;
|
public ?string $usageBefore = null;
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server, public bool $manualCleanup = false) {}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
@@ -31,8 +31,9 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if (! $this->server->isFunctional()) {
|
if (! $this->server->isFunctional()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->server->settings->force_docker_cleanup) {
|
|
||||||
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) {
|
||||||
|
Log::info('DockerCleanupJob '.($this->manualCleanup ? 'manual' : 'force').' cleanup on '.$this->server->name);
|
||||||
CleanupDocker::run(server: $this->server);
|
CleanupDocker::run(server: $this->server);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -69,7 +69,9 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return 'No containers found.';
|
return 'No containers found.';
|
||||||
}
|
}
|
||||||
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
||||||
$this->checkLogDrainContainer();
|
if ($this->server->isLogDrainEnabled()) {
|
||||||
|
$this->checkLogDrainContainer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -115,9 +117,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private function checkLogDrainContainer()
|
private function checkLogDrainContainer()
|
||||||
{
|
{
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
||||||
return data_get($value, 'Name') === '/coolify-log-drain';
|
return data_get($value, 'Name') === '/coolify-log-drain';
|
||||||
})->first();
|
})->first();
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
|
class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Security;
|
namespace App\Livewire\Security;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class ApiTokens extends Component
|
class ApiTokens extends Component
|
||||||
@@ -16,6 +17,8 @@ class ApiTokens extends Component
|
|||||||
|
|
||||||
public array $permissions = ['read-only'];
|
public array $permissions = ['read-only'];
|
||||||
|
|
||||||
|
public $isApiEnabled;
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.security.api-tokens');
|
return view('livewire.security.api-tokens');
|
||||||
@@ -23,6 +26,7 @@ class ApiTokens extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
|
||||||
$this->tokens = auth()->user()->tokens->sortByDesc('created_at');
|
$this->tokens = auth()->user()->tokens->sortByDesc('created_at');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Server;
|
|||||||
|
|
||||||
use App\Actions\Server\StartSentinel;
|
use App\Actions\Server\StartSentinel;
|
||||||
use App\Actions\Server\StopSentinel;
|
use App\Actions\Server\StopSentinel;
|
||||||
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\PullSentinelImageJob;
|
use App\Jobs\PullSentinelImageJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -24,6 +25,10 @@ class Form extends Component
|
|||||||
|
|
||||||
public $timezones;
|
public $timezones;
|
||||||
|
|
||||||
|
public $delete_unused_volumes = false;
|
||||||
|
|
||||||
|
public $delete_unused_networks = false;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$teamId = auth()->user()->currentTeam()->id;
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
@@ -58,6 +63,8 @@ class Form extends Component
|
|||||||
'server.settings.force_docker_cleanup' => 'required|boolean',
|
'server.settings.force_docker_cleanup' => 'required|boolean',
|
||||||
'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string',
|
'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string',
|
||||||
'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100',
|
'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100',
|
||||||
|
'server.settings.delete_unused_volumes' => 'boolean',
|
||||||
|
'server.settings.delete_unused_networks' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -79,6 +86,8 @@ class Form extends Component
|
|||||||
'server.settings.metrics_history_days' => 'Metrics History',
|
'server.settings.metrics_history_days' => 'Metrics History',
|
||||||
'server.settings.is_server_api_enabled' => 'Server API',
|
'server.settings.is_server_api_enabled' => 'Server API',
|
||||||
'server.settings.server_timezone' => 'Server Timezone',
|
'server.settings.server_timezone' => 'Server Timezone',
|
||||||
|
'server.settings.delete_unused_volumes' => 'Delete Unused Volumes',
|
||||||
|
'server.settings.delete_unused_networks' => 'Delete Unused Networks',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount(Server $server)
|
public function mount(Server $server)
|
||||||
@@ -88,6 +97,8 @@ class Form extends Component
|
|||||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||||
$this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
|
$this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
|
||||||
$this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
|
$this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
|
||||||
|
$this->server->settings->delete_unused_volumes = $server->settings->delete_unused_volumes;
|
||||||
|
$this->server->settings->delete_unused_networks = $server->settings->delete_unused_networks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updated($field)
|
public function updated($field)
|
||||||
@@ -137,6 +148,7 @@ class Form extends Component
|
|||||||
try {
|
try {
|
||||||
refresh_server_connection($this->server->privateKey);
|
refresh_server_connection($this->server->privateKey);
|
||||||
$this->validateServer(false);
|
$this->validateServer(false);
|
||||||
|
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
$this->dispatch('success', 'Server updated.');
|
$this->dispatch('success', 'Server updated.');
|
||||||
@@ -154,6 +166,7 @@ class Form extends Component
|
|||||||
ray('Sentinel is not enabled');
|
ray('Sentinel is not enabled');
|
||||||
StopSentinel::dispatch($this->server);
|
StopSentinel::dispatch($this->server);
|
||||||
}
|
}
|
||||||
|
$this->server->settings->save();
|
||||||
// $this->checkPortForServerApi();
|
// $this->checkPortForServerApi();
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -234,9 +247,9 @@ class Form extends Component
|
|||||||
$this->server->settings->server_timezone = $newTimezone;
|
$this->server->settings->server_timezone = $newTimezone;
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
|
||||||
$this->dispatch('success', 'Server updated.');
|
$this->dispatch('success', 'Server updated.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -250,6 +263,16 @@ class Form extends Component
|
|||||||
$this->dispatch('success', 'Server timezone updated.');
|
$this->dispatch('success', 'Server timezone updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function manualCleanup()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DockerCleanupJob::dispatch($this->server, true);
|
||||||
|
$this->dispatch('success', 'Manual cleanup job started. Depending on the amount of data, this might take a while.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function manualCloudflareConfig()
|
public function manualCloudflareConfig()
|
||||||
{
|
{
|
||||||
$this->server->settings->is_cloudflare_tunnel = true;
|
$this->server->settings->is_cloudflare_tunnel = true;
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ class Status extends Component
|
|||||||
if ($this->server->proxy->status === 'running') {
|
if ($this->server->proxy->status === 'running') {
|
||||||
$this->polling = false;
|
$this->polling = false;
|
||||||
$notification && $this->dispatch('success', 'Proxy is running.');
|
$notification && $this->dispatch('success', 'Proxy is running.');
|
||||||
|
} elseif ($this->server->proxy->status === 'exited' and ! $this->server->proxy->force_stop) {
|
||||||
|
$notification && $this->dispatch('error', 'Proxy has exited.');
|
||||||
|
} elseif ($this->server->proxy->force_stop) {
|
||||||
|
$notification && $this->dispatch('error', 'Proxy is stopped manually.');
|
||||||
} else {
|
} else {
|
||||||
$notification && $this->dispatch('error', 'Proxy is not running.');
|
$notification && $this->dispatch('error', 'Proxy is not running.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ use Symfony\Component\Yaml\Yaml;
|
|||||||
'validation_logs' => ['type' => 'string'],
|
'validation_logs' => ['type' => 'string'],
|
||||||
'log_drain_notification_sent' => ['type' => 'boolean'],
|
'log_drain_notification_sent' => ['type' => 'boolean'],
|
||||||
'swarm_cluster' => ['type' => 'string'],
|
'swarm_cluster' => ['type' => 'string'],
|
||||||
|
'delete_unused_volumes' => ['type' => 'boolean'],
|
||||||
|
'delete_unused_networks' => ['type' => 'boolean'],
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
|
|
||||||
@@ -105,6 +107,8 @@ class Server extends BaseModel
|
|||||||
'proxy' => SchemalessAttributes::class,
|
'proxy' => SchemalessAttributes::class,
|
||||||
'logdrain_axiom_api_key' => 'encrypted',
|
'logdrain_axiom_api_key' => 'encrypted',
|
||||||
'logdrain_newrelic_license_key' => 'encrypted',
|
'logdrain_newrelic_license_key' => 'encrypted',
|
||||||
|
'delete_unused_volumes' => 'boolean',
|
||||||
|
'delete_unused_networks' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $schemalessAttributes = [
|
protected $schemalessAttributes = [
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class Input extends Component
|
|||||||
public bool $allowToPeak = true,
|
public bool $allowToPeak = true,
|
||||||
public bool $isMultiline = false,
|
public bool $isMultiline = false,
|
||||||
public string $defaultClass = 'input',
|
public string $defaultClass = 'input',
|
||||||
|
public string $autocomplete = 'off',
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function render(): View|Closure|string
|
public function render(): View|Closure|string
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ function connectProxyToNetworks(Server $server)
|
|||||||
"echo 'Connecting coolify-proxy to $network network...'",
|
"echo 'Connecting coolify-proxy to $network network...'",
|
||||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --driver overlay --attachable $network >/dev/null",
|
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --driver overlay --attachable $network >/dev/null",
|
||||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||||
|
"echo 'Successfully connected coolify-proxy to $network network.'",
|
||||||
|
"echo 'Proxy started and configured successfully!'",
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -104,6 +106,8 @@ function connectProxyToNetworks(Server $server)
|
|||||||
"echo 'Connecting coolify-proxy to $network network...'",
|
"echo 'Connecting coolify-proxy to $network network...'",
|
||||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
|
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
|
||||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||||
|
"echo 'Successfully connected coolify-proxy to $network network.'",
|
||||||
|
"echo 'Proxy started and configured successfully!'",
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -217,7 +221,6 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
}
|
}
|
||||||
} elseif ($proxy_type === 'CADDY') {
|
} elseif ($proxy_type === 'CADDY') {
|
||||||
$config = [
|
$config = [
|
||||||
'version' => '3.8',
|
|
||||||
'networks' => $array_of_networks->toArray(),
|
'networks' => $array_of_networks->toArray(),
|
||||||
'services' => [
|
'services' => [
|
||||||
'caddy' => [
|
'caddy' => [
|
||||||
@@ -236,12 +239,9 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
'80:80',
|
'80:80',
|
||||||
'443:443',
|
'443:443',
|
||||||
],
|
],
|
||||||
// "healthcheck" => [
|
'labels' => [
|
||||||
// "test" => "wget -qO- http://localhost:80|| exit 1",
|
'coolify.managed=true',
|
||||||
// "interval" => "4s",
|
],
|
||||||
// "timeout" => "2s",
|
|
||||||
// "retries" => 5,
|
|
||||||
// ],
|
|
||||||
'volumes' => [
|
'volumes' => [
|
||||||
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
||||||
"{$proxy_path}/dynamic:/dynamic",
|
"{$proxy_path}/dynamic:/dynamic",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.346',
|
'release' => '4.0.0-beta.347',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.346';
|
return '4.0.0-beta.347';
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('delete_unused_volumes')->default(false);
|
||||||
|
$table->boolean('delete_unused_networks')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('delete_unused_volumes');
|
||||||
|
$table->dropColumn('delete_unused_networks');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('instance_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_api_enabled')->default(false)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class ApplicationPreviewSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
// $application_1 = Application::find(1);
|
|
||||||
// ApplicationPreview::create([
|
|
||||||
// 'application_id' => $application_1->id,
|
|
||||||
// 'pull_request_id' => 1
|
|
||||||
// ]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,22 +17,14 @@ class DatabaseSeeder extends Seeder
|
|||||||
ServerSeeder::class,
|
ServerSeeder::class,
|
||||||
ServerSettingSeeder::class,
|
ServerSettingSeeder::class,
|
||||||
ProjectSeeder::class,
|
ProjectSeeder::class,
|
||||||
ProjectSettingSeeder::class,
|
|
||||||
EnvironmentSeeder::class,
|
|
||||||
StandaloneDockerSeeder::class,
|
StandaloneDockerSeeder::class,
|
||||||
SwarmDockerSeeder::class,
|
|
||||||
KubernetesSeeder::class,
|
|
||||||
GithubAppSeeder::class,
|
GithubAppSeeder::class,
|
||||||
GitlabAppSeeder::class,
|
GitlabAppSeeder::class,
|
||||||
ApplicationSeeder::class,
|
ApplicationSeeder::class,
|
||||||
ApplicationSettingsSeeder::class,
|
ApplicationSettingsSeeder::class,
|
||||||
ApplicationPreviewSeeder::class,
|
|
||||||
EnvironmentVariableSeeder::class,
|
|
||||||
LocalPersistentVolumeSeeder::class,
|
LocalPersistentVolumeSeeder::class,
|
||||||
S3StorageSeeder::class,
|
S3StorageSeeder::class,
|
||||||
StandalonePostgresqlSeeder::class,
|
StandalonePostgresqlSeeder::class,
|
||||||
ScheduledDatabaseBackupSeeder::class,
|
|
||||||
ScheduledDatabaseBackupExecutionSeeder::class,
|
|
||||||
OauthSettingSeeder::class,
|
OauthSettingSeeder::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class EnvironmentSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void {}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class EnvironmentVariableSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
// EnvironmentVariable::create([
|
|
||||||
// 'key' => 'NODE_ENV',
|
|
||||||
// 'value' => 'production',
|
|
||||||
// 'is_build_time' => true,
|
|
||||||
// 'application_id' => 1,
|
|
||||||
// ]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use App\Models\Git;
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class GitSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
// $project = Project::find(1);
|
|
||||||
// $private_key_1 = PrivateKey::find(1);
|
|
||||||
|
|
||||||
// Git::create([
|
|
||||||
// 'api_url' => 'https://api.github.com',
|
|
||||||
// 'html_url' => 'https://github.com',
|
|
||||||
// 'is_public' => false,
|
|
||||||
// 'private_key_id' => $private_key_1->id,
|
|
||||||
// 'project_id' => $project->id,
|
|
||||||
// ]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class KubernetesSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class LocalFileVolumeSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class ProjectSettingSeeder extends Seeder
|
|
||||||
{
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
// $first_project = Project::find(1);
|
|
||||||
// $first_project->settings->wildcard_domain = 'wildcard.example.com';
|
|
||||||
// $first_project->settings->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class ScheduledDatabaseBackupExecutionSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
// ScheduledDatabaseBackupExecution::create([
|
|
||||||
// 'status' => 'success',
|
|
||||||
// 'message' => 'Backup created successfully.',
|
|
||||||
// 'size' => '10243467789556',
|
|
||||||
// 'scheduled_database_backup_id' => 1,
|
|
||||||
// ]);
|
|
||||||
// ScheduledDatabaseBackupExecution::create([
|
|
||||||
// 'status' => 'failed',
|
|
||||||
// 'message' => 'Backup failed.',
|
|
||||||
// 'size' => '10243456',
|
|
||||||
// 'scheduled_database_backup_id' => 1,
|
|
||||||
// ]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class ScheduledDatabaseBackupSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
// ScheduledDatabaseBackup::create([
|
|
||||||
// 'enabled' => true,
|
|
||||||
// 'frequency' => '* * * * *',
|
|
||||||
// 'number_of_backups_locally' => 2,
|
|
||||||
// 'database_id' => 1,
|
|
||||||
// 'database_type' => 'App\Models\StandalonePostgresql',
|
|
||||||
// 's3_storage_id' => 1,
|
|
||||||
// 'team_id' => 0,
|
|
||||||
// ]);
|
|
||||||
// ScheduledDatabaseBackup::create([
|
|
||||||
// 'enabled' => true,
|
|
||||||
// 'frequency' => '* * * * *',
|
|
||||||
// 'number_of_backups_locally' => 3,
|
|
||||||
// 'database_id' => 1,
|
|
||||||
// 'database_type' => 'App\Models\StandalonePostgresql',
|
|
||||||
// 'team_id' => 0,
|
|
||||||
// ]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class ServiceApplicationSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class ServiceDatabaseSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class ServiceSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class SubscriptionSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use App\Models\SwarmDocker;
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class SwarmDockerSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
// SwarmDocker::create([
|
|
||||||
// 'name' => 'Swarm Docker 1',
|
|
||||||
// 'server_id' => 1,
|
|
||||||
// ]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class WaitlistSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class WebhookSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the database seeds.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -68,7 +68,11 @@ wss.on('connection', (ws) => {
|
|||||||
|
|
||||||
const messageHandlers = {
|
const messageHandlers = {
|
||||||
message: (session, data) => session.ptyProcess.write(data),
|
message: (session, data) => session.ptyProcess.write(data),
|
||||||
resize: (session, { cols, rows }) => session.ptyProcess.resize(cols, rows),
|
resize: (session, { cols, rows }) => {
|
||||||
|
cols = cols > 0 ? cols : 80;
|
||||||
|
rows = rows > 0 ? rows : 30;
|
||||||
|
session.ptyProcess.resize(cols, rows)
|
||||||
|
},
|
||||||
pause: (session) => session.ptyProcess.pause(),
|
pause: (session) => session.ptyProcess.pause(),
|
||||||
resume: (session) => session.ptyProcess.resume(),
|
resume: (session) => session.ptyProcess.resume(),
|
||||||
checkActive: (session, data) => {
|
checkActive: (session, data) => {
|
||||||
@@ -140,6 +144,7 @@ async function handleCommand(ws, command, userId) {
|
|||||||
|
|
||||||
ptyProcess.onData((data) => ws.send(data));
|
ptyProcess.onData((data) => ws.send(data));
|
||||||
|
|
||||||
|
// when parent closes
|
||||||
ptyProcess.onExit(({ exitCode, signal }) => {
|
ptyProcess.onExit(({ exitCode, signal }) => {
|
||||||
console.error(`Process exited with code ${exitCode} and signal ${signal}`);
|
console.error(`Process exited with code ${exitCode} and signal ${signal}`);
|
||||||
userSession.isActive = false;
|
userSession.isActive = false;
|
||||||
|
|||||||
@@ -404,10 +404,10 @@ if [ ! -f ~/.ssh/authorized_keys ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
IS_COOLIFY_VOLUME_EXISTS=$(docker volume ls | grep coolify-db | wc -l)
|
IF_COOLIFY_VOLUME_EXISTS=$(docker volume ls | grep coolify-db | wc -l)
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ "$IS_COOLIFY_VOLUME_EXISTS" -eq 0 ]; then
|
if [ "$IF_COOLIFY_VOLUME_EXISTS" -eq 0 ]; then
|
||||||
echo " - Generating SSH key."
|
echo " - Generating SSH key."
|
||||||
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.$CURRENT_USER@host.docker.internal -q -N "" -C coolify
|
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.$CURRENT_USER@host.docker.internal -q -N "" -C coolify
|
||||||
chown 9999 /data/coolify/ssh/keys/id.$CURRENT_USER@host.docker.internal
|
chown 9999 /data/coolify/ssh/keys/id.$CURRENT_USER@host.docker.internal
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.346"
|
|
||||||
|
"version": "4.0.0-beta.347"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.347"
|
"version": "4.0.0-beta.348"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.1"
|
"version": "1.0.1"
|
||||||
|
|||||||
@@ -5,17 +5,13 @@
|
|||||||
// app.component("magic-bar", MagicBar);
|
// app.component("magic-bar", MagicBar);
|
||||||
// app.mount("#vue");
|
// app.mount("#vue");
|
||||||
|
|
||||||
import { Terminal } from '@xterm/xterm';
|
import { initializeTerminalComponent } from './terminal.js';
|
||||||
import '@xterm/xterm/css/xterm.css';
|
|
||||||
import { FitAddon } from '@xterm/addon-fit';
|
|
||||||
|
|
||||||
if (!window.term) {
|
['livewire:navigated', 'alpine:init'].forEach((event) => {
|
||||||
window.term = new Terminal({
|
document.addEventListener(event, () => {
|
||||||
cols: 80,
|
// tree-shaking
|
||||||
rows: 30,
|
if (document.getElementById('terminal-container')) {
|
||||||
fontFamily: '"Fira Code", courier-new, courier, monospace, "Powerline Extra Symbols"',
|
initializeTerminalComponent()
|
||||||
cursorBlink: true,
|
}
|
||||||
});
|
});
|
||||||
window.fitAddon = new FitAddon();
|
});
|
||||||
window.term.loadAddon(window.fitAddon);
|
|
||||||
}
|
|
||||||
|
|||||||
228
resources/js/terminal.js
Normal file
228
resources/js/terminal.js
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import { Terminal } from '@xterm/xterm';
|
||||||
|
import '@xterm/xterm/css/xterm.css';
|
||||||
|
import { FitAddon } from '@xterm/addon-fit';
|
||||||
|
|
||||||
|
export function initializeTerminalComponent() {
|
||||||
|
function terminalData() {
|
||||||
|
return {
|
||||||
|
fullscreen: false,
|
||||||
|
terminalActive: false,
|
||||||
|
message: '(connection closed)',
|
||||||
|
term: null,
|
||||||
|
fitAddon: null,
|
||||||
|
socket: null,
|
||||||
|
commandBuffer: '',
|
||||||
|
pendingWrites: 0,
|
||||||
|
paused: false,
|
||||||
|
MAX_PENDING_WRITES: 5,
|
||||||
|
keepAliveInterval: null,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.setupTerminal();
|
||||||
|
this.initializeWebSocket();
|
||||||
|
this.setupTerminalEventListeners();
|
||||||
|
|
||||||
|
this.$wire.on('send-back-command', (command) => {
|
||||||
|
this.socket.send(JSON.stringify({
|
||||||
|
command: command
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.keepAliveInterval = setInterval(this.keepAlive.bind(this), 30000);
|
||||||
|
|
||||||
|
this.$watch('terminalActive', (active) => {
|
||||||
|
if (!active && this.keepAliveInterval) {
|
||||||
|
clearInterval(this.keepAliveInterval);
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (active) {
|
||||||
|
this.$refs.terminalWrapper.style.display = 'block';
|
||||||
|
this.resizeTerminal();
|
||||||
|
} else {
|
||||||
|
this.$refs.terminalWrapper.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
['livewire:navigated', 'beforeunload'].forEach((event) => {
|
||||||
|
document.addEventListener(event, () => {
|
||||||
|
this.checkIfProcessIsRunningAndKillIt();
|
||||||
|
clearInterval(this.keepAliveInterval);
|
||||||
|
}, { once: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
window.onresize = () => {
|
||||||
|
this.resizeTerminal()
|
||||||
|
};
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
setupTerminal() {
|
||||||
|
const terminalElement = document.getElementById('terminal');
|
||||||
|
if (terminalElement) {
|
||||||
|
this.term = new Terminal({
|
||||||
|
cols: 80,
|
||||||
|
rows: 30,
|
||||||
|
fontFamily: '"Fira Code", courier-new, courier, monospace, "Powerline Extra Symbols"',
|
||||||
|
cursorBlink: true,
|
||||||
|
});
|
||||||
|
this.fitAddon = new FitAddon();
|
||||||
|
this.term.loadAddon(this.fitAddon);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
initializeWebSocket() {
|
||||||
|
if (!this.socket || this.socket.readyState === WebSocket.CLOSED) {
|
||||||
|
const predefined = window.terminalConfig
|
||||||
|
const connectionString = {
|
||||||
|
protocol: window.location.protocol === 'https:' ? 'wss' : 'ws',
|
||||||
|
host: window.location.hostname,
|
||||||
|
port: ":6002",
|
||||||
|
path: '/terminal/ws'
|
||||||
|
}
|
||||||
|
if (!window.location.port) {
|
||||||
|
connectionString.port = ''
|
||||||
|
}
|
||||||
|
if (predefined.host) {
|
||||||
|
connectionString.host = predefined.host
|
||||||
|
}
|
||||||
|
if (predefined.port) {
|
||||||
|
connectionString.port = `:${predefined.port}`
|
||||||
|
}
|
||||||
|
if (predefined.protocol) {
|
||||||
|
connectionString.protocol = predefined.protocol
|
||||||
|
}
|
||||||
|
|
||||||
|
const url =
|
||||||
|
`${connectionString.protocol}://${connectionString.host}${connectionString.port}${connectionString.path}`
|
||||||
|
this.socket = new WebSocket(url);
|
||||||
|
|
||||||
|
this.socket.onmessage = this.handleSocketMessage.bind(this);
|
||||||
|
this.socket.onerror = (e) => {
|
||||||
|
console.error('WebSocket error:', e);
|
||||||
|
};
|
||||||
|
this.socket.onclose = () => {
|
||||||
|
console.log('WebSocket connection closed');
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSocketMessage(event) {
|
||||||
|
this.message = '(connection closed)';
|
||||||
|
if (event.data === 'pty-ready') {
|
||||||
|
if (!this.term._initialized) {
|
||||||
|
this.term.open(document.getElementById('terminal'));
|
||||||
|
this.term._initialized = true;
|
||||||
|
} else {
|
||||||
|
this.term.reset();
|
||||||
|
}
|
||||||
|
this.terminalActive = true;
|
||||||
|
this.term.focus();
|
||||||
|
document.querySelector('.xterm-viewport').classList.add('scrollbar', 'rounded');
|
||||||
|
this.resizeTerminal();
|
||||||
|
} else if (event.data === 'unprocessable') {
|
||||||
|
if (this.term) this.term.reset();
|
||||||
|
this.terminalActive = false;
|
||||||
|
this.message = '(sorry, something went wrong, please try again)';
|
||||||
|
} else {
|
||||||
|
this.pendingWrites++;
|
||||||
|
this.term.write(event.data, this.flowControlCallback.bind(this));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
flowControlCallback() {
|
||||||
|
this.pendingWrites--;
|
||||||
|
if (this.pendingWrites > this.MAX_PENDING_WRITES && !this.paused) {
|
||||||
|
this.paused = true;
|
||||||
|
this.socket.send(JSON.stringify({ pause: true }));
|
||||||
|
} else if (this.pendingWrites <= this.MAX_PENDING_WRITES && this.paused) {
|
||||||
|
this.paused = false;
|
||||||
|
this.socket.send(JSON.stringify({ resume: true }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setupTerminalEventListeners() {
|
||||||
|
if (!this.term) return;
|
||||||
|
|
||||||
|
this.term.onData((data) => {
|
||||||
|
this.socket.send(JSON.stringify({ message: data }));
|
||||||
|
// Handle CTRL + D or exit command
|
||||||
|
if (data === '\x04' || (data === '\r' && this.stripAnsiCommands(this.commandBuffer).trim().includes('exit'))) {
|
||||||
|
this.checkIfProcessIsRunningAndKillIt();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.terminalActive = false;
|
||||||
|
this.term.reset();
|
||||||
|
}, 500);
|
||||||
|
this.commandBuffer = '';
|
||||||
|
} else if (data === '\r') {
|
||||||
|
this.commandBuffer = '';
|
||||||
|
} else {
|
||||||
|
this.commandBuffer += data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy and paste functionality
|
||||||
|
this.term.attachCustomKeyEventHandler((arg) => {
|
||||||
|
if (arg.ctrlKey && arg.code === "KeyV" && arg.type === "keydown") {
|
||||||
|
navigator.clipboard.readText()
|
||||||
|
.then(text => {
|
||||||
|
this.socket.send(JSON.stringify({ message: text }));
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.ctrlKey && arg.code === "KeyC" && arg.type === "keydown") {
|
||||||
|
const selection = this.term.getSelection();
|
||||||
|
if (selection) {
|
||||||
|
navigator.clipboard.writeText(selection);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stripAnsiCommands(input) {
|
||||||
|
return input.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
||||||
|
},
|
||||||
|
|
||||||
|
keepAlive() {
|
||||||
|
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||||
|
this.socket.send(JSON.stringify({ ping: true }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
checkIfProcessIsRunningAndKillIt() {
|
||||||
|
if (this.socket && this.socket.readyState == WebSocket.OPEN) {
|
||||||
|
this.socket.send(JSON.stringify({ checkActive: 'force' }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
makeFullscreen() {
|
||||||
|
this.fullscreen = !this.fullscreen;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.resizeTerminal();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
resizeTerminal() {
|
||||||
|
if (!this.terminalActive || !this.term || !this.fitAddon) return;
|
||||||
|
|
||||||
|
this.fitAddon.fit();
|
||||||
|
const height = this.$refs.terminalWrapper.clientHeight;
|
||||||
|
const width = this.$refs.terminalWrapper.clientWidth;
|
||||||
|
const rows = Math.floor(height / this.term._core._renderService._charSizeService.height) - 1;
|
||||||
|
const cols = Math.floor(width / this.term._core._renderService._charSizeService.width) - 1;
|
||||||
|
const termWidth = cols;
|
||||||
|
const termHeight = rows;
|
||||||
|
this.term.resize(termWidth, termHeight);
|
||||||
|
this.socket.send(JSON.stringify({
|
||||||
|
resize: { cols: termWidth, rows: termHeight }
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Alpine.data('terminalData', terminalData);
|
||||||
|
}
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<input value="{{ $value }}" {{ $attributes->merge(['class' => $defaultClass]) }} @required($required)
|
<input autocomplete="{{ $autocomplete }}" value="{{ $value }}"
|
||||||
|
{{ $attributes->merge(['class' => $defaultClass]) }} @required($required)
|
||||||
@if ($id !== 'null') wire:model={{ $id }} @endif
|
@if ($id !== 'null') wire:model={{ $id }} @endif
|
||||||
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
||||||
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled"
|
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled"
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<input @if ($value) value="{{ $value }}" @endif
|
<input autocomplete="{{ $autocomplete }}" @if ($value) value="{{ $value }}" @endif
|
||||||
{{ $attributes->merge(['class' => $defaultClass]) }} @required($required) @readonly($readonly)
|
{{ $attributes->merge(['class' => $defaultClass]) }} @required($required) @readonly($readonly)
|
||||||
@if ($id !== 'null') wire:model={{ $id }} @endif
|
@if ($id !== 'null') wire:model={{ $id }} @endif
|
||||||
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div x-data="data()">
|
<div id="terminal-container" x-data="terminalData()">
|
||||||
{{-- <div x-show="!terminalActive" class="flex items-center justify-center w-full py-4 mx-auto h-[510px]">
|
{{-- <div x-show="!terminalActive" class="flex items-center justify-center w-full py-4 mx-auto h-[510px]">
|
||||||
<div class="p-1 w-full h-full rounded border dark:bg-coolgray-100 dark:border-coolgray-300">
|
<div class="p-1 w-full h-full rounded border dark:bg-coolgray-100 dark:border-coolgray-300">
|
||||||
<span class="font-mono text-sm text-gray-500" x-text="message"></span>
|
<span class="font-mono text-sm text-gray-500" x-text="message"></span>
|
||||||
@@ -22,219 +22,14 @@
|
|||||||
</g>
|
</g>
|
||||||
</svg></button>
|
</svg></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@script
|
@script
|
||||||
<script>
|
<script>
|
||||||
const MAX_PENDING_WRITES = 5;
|
// expose terminal config to the terminal.js file
|
||||||
let pendingWrites = 0;
|
window.terminalConfig = {
|
||||||
let paused = false;
|
protocol: "{{ env('TERMINAL_PROTOCOL') }}",
|
||||||
|
host: "{{ env('TERMINAL_HOST') }}",
|
||||||
let socket;
|
port: "{{ env('TERMINAL_PORT') }}"
|
||||||
let commandBuffer = '';
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
function keepAlive() {
|
|
||||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
ping: true
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const keepAliveInterval = setInterval(keepAlive, 30000);
|
|
||||||
|
|
||||||
// Clear the interval when the component is destroyed
|
|
||||||
document.addEventListener('livewire:navigating', () => {
|
|
||||||
clearInterval(keepAliveInterval);
|
|
||||||
});
|
|
||||||
|
|
||||||
function initializeWebSocket() {
|
|
||||||
if (!socket || socket.readyState === WebSocket.CLOSED) {
|
|
||||||
const predefined = {
|
|
||||||
protocol: "{{ env('TERMINAL_PROTOCOL') }}",
|
|
||||||
host: "{{ env('TERMINAL_HOST') }}",
|
|
||||||
port: "{{ env('TERMINAL_PORT') }}"
|
|
||||||
}
|
|
||||||
const connectionString = {
|
|
||||||
protocol: window.location.protocol === 'https:' ? 'wss' : 'ws',
|
|
||||||
host: window.location.hostname,
|
|
||||||
port: ":6002",
|
|
||||||
path: '/terminal/ws'
|
|
||||||
}
|
|
||||||
if (!window.location.port) {
|
|
||||||
connectionString.port = ''
|
|
||||||
}
|
|
||||||
if (predefined.host) {
|
|
||||||
connectionString.host = predefined.host
|
|
||||||
}
|
|
||||||
if (predefined.port) {
|
|
||||||
connectionString.port = `:${predefined.port}`
|
|
||||||
}
|
|
||||||
if (predefined.protocol) {
|
|
||||||
connectionString.protocol = predefined.protocol
|
|
||||||
}
|
|
||||||
|
|
||||||
const url =
|
|
||||||
`${connectionString.protocol}://${connectionString.host}${connectionString.port}${connectionString.path}`
|
|
||||||
socket = new WebSocket(url);
|
|
||||||
|
|
||||||
socket.onmessage = handleSocketMessage;
|
|
||||||
socket.onerror = (e) => {
|
|
||||||
console.error('WebSocket error:', e);
|
|
||||||
};
|
|
||||||
socket.onclose = () => {
|
|
||||||
console.log('WebSocket connection closed');
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSocketMessage(event) {
|
|
||||||
$data.message = '(connection closed)';
|
|
||||||
// Initialize Terminal
|
|
||||||
if (event.data === 'pty-ready') {
|
|
||||||
term.open(document.getElementById('terminal'));
|
|
||||||
$data.terminalActive = true;
|
|
||||||
term.reset();
|
|
||||||
term.focus();
|
|
||||||
document.querySelector('.xterm-viewport').classList.add('scrollbar', 'rounded')
|
|
||||||
$data.resizeTerminal()
|
|
||||||
} else if (event.data === 'unprocessable') {
|
|
||||||
term.reset();
|
|
||||||
$data.terminalActive = false;
|
|
||||||
$data.message = '(sorry, something went wrong, please try again)';
|
|
||||||
} else {
|
|
||||||
pendingWrites++;
|
|
||||||
term.write(event.data, flowControlCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function flowControlCallback() {
|
|
||||||
pendingWrites--;
|
|
||||||
if (pendingWrites > MAX_PENDING_WRITES && !paused) {
|
|
||||||
paused = true;
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
pause: true
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pendingWrites <= MAX_PENDING_WRITES && paused) {
|
|
||||||
paused = false;
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
resume: true
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
term.onData((data) => {
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
message: data
|
|
||||||
}));
|
|
||||||
// Type CTRL + D or exit in the terminal
|
|
||||||
if (data === '\x04' || (data === '\r' && stripAnsiCommands(commandBuffer).trim().includes('exit'))) {
|
|
||||||
checkIfProcessIsRunningAndKillIt();
|
|
||||||
setTimeout(() => {
|
|
||||||
$data.terminalActive = false;
|
|
||||||
term.reset();
|
|
||||||
}, 500);
|
|
||||||
commandBuffer = '';
|
|
||||||
} else if (data === '\r') {
|
|
||||||
commandBuffer = '';
|
|
||||||
} else {
|
|
||||||
commandBuffer += data;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function stripAnsiCommands(input) {
|
|
||||||
return input.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy and paste
|
|
||||||
// Enables ctrl + c and ctrl + v
|
|
||||||
// defaults otherwise to ctrl + insert, shift + insert
|
|
||||||
term.attachCustomKeyEventHandler((arg) => {
|
|
||||||
if (arg.ctrlKey && arg.code === "KeyV" && arg.type === "keydown") {
|
|
||||||
navigator.clipboard.readText()
|
|
||||||
.then(text => {
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
message: text
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
if (arg.ctrlKey && arg.code === "KeyC" && arg.type === "keydown") {
|
|
||||||
const selection = term.getSelection();
|
|
||||||
if (selection) {
|
|
||||||
navigator.clipboard.writeText(selection);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
$wire.on('send-back-command', function(command) {
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
command: command
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('beforeunload', function(e) {
|
|
||||||
checkIfProcessIsRunningAndKillIt();
|
|
||||||
});
|
|
||||||
|
|
||||||
function checkIfProcessIsRunningAndKillIt() {
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
checkActive: 'force'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onresize = function() {
|
|
||||||
$data.resizeTerminal()
|
|
||||||
};
|
|
||||||
|
|
||||||
Alpine.data('data', () => ({
|
|
||||||
fullscreen: false,
|
|
||||||
terminalActive: false,
|
|
||||||
message: '(connection closed)',
|
|
||||||
init() {
|
|
||||||
this.$watch('terminalActive', (value) => {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (value) {
|
|
||||||
$refs.terminalWrapper.style.display = 'block';
|
|
||||||
this.resizeTerminal();
|
|
||||||
} else {
|
|
||||||
$refs.terminalWrapper.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
makeFullscreen() {
|
|
||||||
this.fullscreen = !this.fullscreen;
|
|
||||||
$nextTick(() => {
|
|
||||||
this.resizeTerminal()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
resizeTerminal() {
|
|
||||||
if (!this.terminalActive) return;
|
|
||||||
|
|
||||||
fitAddon.fit();
|
|
||||||
const height = $refs.terminalWrapper.clientHeight;
|
|
||||||
const rows = height / term._core._renderService._charSizeService.height - 1;
|
|
||||||
var termWidth = term.cols;
|
|
||||||
var termHeight = parseInt(rows.toString(), 10);
|
|
||||||
term.resize(termWidth, termHeight);
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
resize: {
|
|
||||||
cols: termWidth,
|
|
||||||
rows: termHeight
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
initializeWebSocket();
|
|
||||||
</script>
|
|
||||||
@endscript
|
@endscript
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,19 +3,23 @@
|
|||||||
API Tokens | Coolify
|
API Tokens | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-security.navbar />
|
<x-security.navbar />
|
||||||
<div class="pb-4 ">
|
<div class="pb-4">
|
||||||
<h2>API Tokens</h2>
|
<h2>API Tokens</h2>
|
||||||
<div>Tokens are created with the current team as scope. You will only have access to this team's resources.
|
@if (!$isApiEnabled)
|
||||||
</div>
|
<div>API is disabled. If you want to use the API, please enable it in the Settings menu.</div>
|
||||||
|
@else
|
||||||
|
<div>Tokens are created with the current team as scope. You will only have access to this team's resources.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3>New Token</h3>
|
<h3>New Token</h3>
|
||||||
<form class="flex flex-col gap-2 pt-4" wire:submit='addNewToken'>
|
<form class="flex flex-col gap-2 pt-4" wire:submit='addNewToken'>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex gap-2 items-end">
|
||||||
<x-forms.input required id="description" label="Description" />
|
<x-forms.input required id="description" label="Description" />
|
||||||
<x-forms.button type="submit">Create New Token</x-forms.button>
|
<x-forms.button type="submit">Create New Token</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
Permissions <x-helper class="px-1" helper="These permissions will be granted to the token." /><span
|
Permissions
|
||||||
|
<x-helper class="px-1" helper="These permissions will be granted to the token." /><span
|
||||||
class="pr-1">:</span>
|
class="pr-1">:</span>
|
||||||
<div class="flex gap-1 font-bold dark:text-white">
|
<div class="flex gap-1 font-bold dark:text-white">
|
||||||
@if ($permissions)
|
@if ($permissions)
|
||||||
@@ -56,18 +60,15 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-modal-confirmation
|
<x-modal-confirmation title="Confirm API Token Revocation?" isErrorButton buttonTitle="Revoke token"
|
||||||
title="Confirm API Token Revocation?"
|
submitAction="revoke({{ data_get($token, 'id') }})" :actions="[
|
||||||
isErrorButton
|
'This API Token will be revoked and permanently deleted.',
|
||||||
buttonTitle="Revoke token"
|
'Any API call made with this token will fail.',
|
||||||
submitAction="revoke({{ data_get($token, 'id') }})"
|
]"
|
||||||
:actions="['This API Token will be revoked and permanently deleted.', 'Any API call made with this token will fail.']"
|
|
||||||
confirmationText="{{ $token->name }}"
|
confirmationText="{{ $token->name }}"
|
||||||
confirmationLabel="Please confirm the execution of the actions by entering the API Token Description below"
|
confirmationLabel="Please confirm the execution of the actions by entering the API Token Description below"
|
||||||
shortConfirmationLabel="API Token Description"
|
shortConfirmationLabel="API Token Description" :confirmWithPassword="false"
|
||||||
:confirmWithPassword="false"
|
step2ButtonText="Revoke API Token" />
|
||||||
step2ButtonText="Revoke API Token"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
<div>
|
<div>
|
||||||
@@ -75,5 +76,5 @@
|
|||||||
</div>
|
</div>
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<x-modal-confirmation title="Confirm Server Deletion?" isErrorButton buttonTitle="Delete"
|
<x-modal-confirmation title="Confirm Server Deletion?" isErrorButton buttonTitle="Delete"
|
||||||
submitAction="delete" :actions="['This server will be permanently deleted.']" confirmationText="{{ $server->name }}"
|
submitAction="delete" :actions="['This server will be permanently deleted.']" confirmationText="{{ $server->name }}"
|
||||||
confirmationLabel="Please confirm the execution of the actions by entering the Server Name below"
|
confirmationLabel="Please confirm the execution of the actions by entering the Server Name below"
|
||||||
shortConfirmationLabel="Server Name" step2ButtonText="Permanently Delete" />
|
shortConfirmationLabel="Server Name" step2ButtonText="Continue" step3ButtonText="Permanently Delete" />
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -194,26 +194,78 @@
|
|||||||
|
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
<h3 class="pt-4">Settings</h3>
|
<h3 class="pt-4">Settings</h3>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="flex flex-col flex-wrap gap-2 sm:flex-nowrap">
|
<div class="flex flex-wrap items-center gap-4">
|
||||||
<div class="w-64">
|
<div class="w-64">
|
||||||
<x-forms.checkbox
|
<x-forms.checkbox
|
||||||
helper="Enable force Docker Cleanup. This will cleanup build caches / unused images / etc."
|
helper="Enabling Force Docker Cleanup or manually triggering a cleanup will perform the following actions:
|
||||||
|
<ul class='list-disc pl-4 mt-2'>
|
||||||
|
<li>Removes stopped containers manged by Coolify (as containers are none persistent, no data will be lost).</li>
|
||||||
|
<li>Deletes unused images.</li>
|
||||||
|
<li>Clears build cache.</li>
|
||||||
|
<li>Removes old versions of the Coolify helper image.</li>
|
||||||
|
<li>Optionally delete unused volumes (if enabled in advanced options).</li>
|
||||||
|
<li>Optionally remove unused networks (if enabled in advanced options).</li>
|
||||||
|
</ul>"
|
||||||
instantSave id="server.settings.force_docker_cleanup" label="Force Docker Cleanup" />
|
instantSave id="server.settings.force_docker_cleanup" label="Force Docker Cleanup" />
|
||||||
</div>
|
</div>
|
||||||
@if ($server->settings->force_docker_cleanup)
|
<x-modal-confirmation
|
||||||
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
|
title="Confirm Docker Cleanup?"
|
||||||
label="Docker cleanup frequency" required
|
buttonTitle="Trigger Docker Cleanup"
|
||||||
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every night at midnight." />
|
submitAction="manualCleanup"
|
||||||
@else
|
:actions="[
|
||||||
|
'Permanently deletes all stopped containers managed by Coolify (as containers are non-persistent, no data will be lost)',
|
||||||
|
'Permanently deletes all unused images',
|
||||||
|
'Clears build cache',
|
||||||
|
'Removes old versions of the Coolify helper image',
|
||||||
|
'Optionally permanently deletes all unused volumes (if enabled in advanced options).',
|
||||||
|
'Optionally permanently deletes all unused networks (if enabled in advanced options).'
|
||||||
|
]"
|
||||||
|
:confirmWithText="false"
|
||||||
|
:confirmWithPassword="false"
|
||||||
|
step2ButtonText="Trigger Docker Cleanup"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
@if ($server->settings->force_docker_cleanup)
|
||||||
|
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
|
||||||
|
label="Docker cleanup frequency" required
|
||||||
|
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every night at midnight." />
|
||||||
|
@else
|
||||||
<x-forms.input id="server.settings.docker_cleanup_threshold"
|
<x-forms.input id="server.settings.docker_cleanup_threshold"
|
||||||
label="Docker cleanup threshold (%)" required
|
label="Docker cleanup threshold (%)" required
|
||||||
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
|
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
|
||||||
@endif
|
@endif
|
||||||
|
<div x-data="{ open: false }" class="mt-4 max-w-md">
|
||||||
|
<button @click="open = !open" type="button" class="flex items-center justify-between w-full text-left text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100">
|
||||||
|
<span>Advanced Options</span>
|
||||||
|
<svg :class="{'rotate-180': open}" class="w-5 h-5 transition-transform duration-200" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div x-show="open" class="mt-2 space-y-2">
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2"><strong>Warning: Enable these options only if you fully understand their implications and consequences!</strong><br>Improper use will result in data loss and could cause functional issues.</p>
|
||||||
|
<x-forms.checkbox instantSave id="server.settings.delete_unused_volumes" label="Delete Unused Volumes"
|
||||||
|
helper="This option will remove all unused Docker volumes during cleanup.<br><br><strong>Warning: Data form stopped containers will be lost!</strong><br><br>Consequences include:<br>
|
||||||
|
<ul class='list-disc pl-4 mt-2'>
|
||||||
|
<li>Volumes not attached to running containers will be deleted and data will be permanently lost (stopped containers are affected).</li>
|
||||||
|
<li>Data from stopped containers volumes will be permanently lost.</li>
|
||||||
|
<li>No way to recover deleted volume data.</li>
|
||||||
|
</ul>"
|
||||||
|
/>
|
||||||
|
<x-forms.checkbox instantSave id="server.settings.delete_unused_networks" label="Delete Unused Networks"
|
||||||
|
helper="This option will remove all unused Docker networks during cleanup.<br><br><strong>Warning: Functionality may be lost and containers may not be able to communicate with each other!</strong><br><br>Consequences include:<br>
|
||||||
|
<ul class='list-disc pl-4 mt-2'>
|
||||||
|
<li>Networks not attached to running containers will be permanently deleted (stopped containers are affected).</li>
|
||||||
|
<li>Custom networks for stopped containers will be permanently deleted.</li>
|
||||||
|
<li>Functionality may be lost and containers may not be able to communicate with each other.</li>
|
||||||
|
</ul>"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
|
|
||||||
|
<div class="flex flex-wrap gap-4 sm:flex-nowrap">
|
||||||
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
|
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
|
||||||
helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
|
helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
|
||||||
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required
|
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
<x-status.running status="Proxy Running" />
|
<x-status.running status="Proxy Running" />
|
||||||
@elseif (data_get($server, 'proxy.status') === 'restarting')
|
@elseif (data_get($server, 'proxy.status') === 'restarting')
|
||||||
<x-status.restarting status="Proxy Restarting" />
|
<x-status.restarting status="Proxy Restarting" />
|
||||||
@else
|
@elseif (data_get($server, 'proxy.force_stop'))
|
||||||
<x-status.stopped status="Proxy Stopped" />
|
<x-status.stopped status="Proxy Stopped" />
|
||||||
|
@elseif (data_get($server, 'proxy.status') === 'exited')
|
||||||
|
<x-status.stopped status="Proxy Exited" />
|
||||||
|
@else
|
||||||
|
<x-status.stopped status="Proxy Not Running" />
|
||||||
@endif
|
@endif
|
||||||
@if (data_get($server, 'proxy.status') === 'running')
|
<x-forms.button wire:click='checkProxy(true)'>Refresh</x-forms.button>
|
||||||
<x-forms.button wire:click='checkProxy(true)'>Refresh</x-forms.button>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ services:
|
|||||||
twenty:
|
twenty:
|
||||||
image: 'twentycrm/twenty:latest'
|
image: 'twentycrm/twenty:latest'
|
||||||
environment:
|
environment:
|
||||||
- SERVICE_FQDN_TRIGGER_3000
|
- SERVICE_FQDN_TWENTY_3000
|
||||||
- SERVER_URL=$SERVICE_FQDN_TWENTY
|
- SERVER_URL=$SERVICE_FQDN_TWENTY
|
||||||
- FRONT_BASE_URL=$SERVICE_FQDN_TWENTY
|
- FRONT_BASE_URL=$SERVICE_FQDN_TWENTY
|
||||||
- ENABLE_DB_MIGRATIONS=true
|
- ENABLE_DB_MIGRATIONS=true
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ services:
|
|||||||
uptime-kuma:
|
uptime-kuma:
|
||||||
image: louislam/uptime-kuma:1
|
image: louislam/uptime-kuma:1
|
||||||
environment:
|
environment:
|
||||||
- SERVICE_FQDN_UPTIME-KUMA_3001
|
- SERVICE_FQDN_UPTIMEKUMA_3001
|
||||||
volumes:
|
volumes:
|
||||||
- uptime-kuma:/app/data
|
- uptime-kuma:/app/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.346"
|
"version": "4.0.0-beta.347"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.347"
|
"version": "4.0.0-beta.348"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.1"
|
"version": "1.0.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user