Compare commits
10
Commits
067318887f
...
6963f7da6a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6963f7da6a | ||
|
|
667cec42a4 | ||
|
|
a7e4ce9bda | ||
|
|
ab855f2b0c | ||
|
|
8f494d757e | ||
|
|
c12f9fec14 | ||
|
|
95a79d41e8 | ||
|
|
53e11bd891 | ||
|
|
2d9e5e12d6 | ||
|
|
ee6da80e81 |
No files matched your search
+2
-2
@@ -37,7 +37,7 @@ RUN APKO_ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "x86_64")
|
||||
" - busybox" \
|
||||
" - tzdata" \
|
||||
" - docker-cli" \
|
||||
" - docker-compose=5.5.0-r2" \
|
||||
" - docker-compose=5.5.0-r5" \
|
||||
" - docker-cli-buildx" \
|
||||
" - sqlite" \
|
||||
" - postgresql-client" \
|
||||
@@ -99,7 +99,7 @@ RUN cp -r node_modules/better-sqlite3/build /tmp/better-sqlite3-build \
|
||||
&& rm -rf node_modules/@types /tmp/better-sqlite3-build
|
||||
|
||||
# Build Go collector
|
||||
FROM --platform=$BUILDPLATFORM golang:1.25.13 AS go-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26.8 AS go-builder
|
||||
ARG TARGETARCH
|
||||
WORKDIR /app
|
||||
COPY collector/ ./collector/
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ RUN cp -r node_modules/better-sqlite3/build /tmp/better-sqlite3-build \
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 2: Go Collector Builder
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM golang:1.25.13 AS go-builder
|
||||
FROM golang:1.26.8 AS go-builder
|
||||
WORKDIR /app
|
||||
COPY collector/ ./collector/
|
||||
RUN cd collector && CGO_ENABLED=0 go build -o /app/bin/collection-worker .
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Alpine edge has docker-cli for riscv64 but NOT docker-compose.
|
||||
# Build it from source since Go has excellent riscv64 cross-compilation.
|
||||
FROM --platform=$BUILDPLATFORM golang:1.25.9-alpine AS compose-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26.8-alpine AS compose-builder
|
||||
ARG TARGETARCH
|
||||
|
||||
RUN apk add --no-cache git
|
||||
@@ -72,7 +72,7 @@ RUN MAKEFLAGS="-j$(nproc)" npm ci --omit=dev --ignore-scripts \
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 4: Go Collector Builder (cross-compile from build platform)
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM --platform=$BUILDPLATFORM golang:1.25.13 AS go-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26.8 AS go-builder
|
||||
ARG TARGETARCH
|
||||
WORKDIR /app
|
||||
COPY collector/ ./collector/
|
||||
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
# Dockhand License
|
||||
|
||||
Copyright (c) 2025-2026 Finsys Jaroslaw Krochmalski
|
||||
|
||||
Last updated: 14 September 2026
|
||||
|
||||
## How to read this document
|
||||
|
||||
The document has two parts:
|
||||
|
||||
**Part A — Business Source License 1.1.** The license under which Dockhand is published. It applies to everyone who obtains a copy of Dockhand, whether from GitHub, from a container registry or bundled in the application. The text of the Business Source License itself is reproduced unchanged; Licensor's choices are in the "Parameters" section. Within Part A, "this License" means Part A only.
|
||||
|
||||
**Part B — Dockhand Commercial License Terms.** These apply only if you purchase a Commercial License (currently the SMB or Enterprise tier). They add rights and obligations on top of Part A and never take away rights you have under Part A.
|
||||
|
||||
## Summary
|
||||
|
||||
- Free for everyone, no license key, any number of users or environments: development, testing, staging, CI/CD, evaluation, demos, training — including inside companies.
|
||||
- Free in production: individuals for personal use (home labs, hobby projects); non-profit and charitable organizations; schools, universities and non-commercial research institutions.
|
||||
- Paid (Commercial License, Part B): production use by or for any business, public body or other organization not listed above.
|
||||
- Never permitted without a separate written agreement: offering Dockhand itself, or a fork of it, as a hosted, managed or SaaS service to third parties.
|
||||
- Each released version becomes Apache 2.0 four years after its release.
|
||||
|
||||
If this summary and Parts A or B differ, Parts A and B govern.
|
||||
|
||||
## General terms (these apply to everyone)
|
||||
|
||||
The following apply to every user of the Licensed Work, whether you use it for free under Part A or under a Commercial License under Part B.
|
||||
|
||||
**Data and privacy.** The Licensed Work stores all data locally on your infrastructure and contains no telemetry or analytics. It does not contact Licensor's servers; any License Key is validated locally. Licensor does not collect, access or process data stored in your installation. You are solely responsible for the security, backup and protection of that data. Licensor processes only the personal data needed to handle Orders, License Keys and support requests, as described in the Privacy Policy at https://dockhand.pro.
|
||||
|
||||
**Ownership.** The Licensed Work is and remains the property of Licensor. All rights not expressly granted under Part A or Part B are reserved.
|
||||
|
||||
**Trademarks.** "Dockhand", "Finsys" and the associated logos are trademarks of Licensor. Nothing in this document grants any trademark rights beyond those expressly required by Part A.
|
||||
|
||||
**Feedback.** Licensor may use any suggestions, ideas, enhancement requests or other feedback you provide regarding the Licensed Work, without obligation to you.
|
||||
|
||||
**Limitation of liability.** To the maximum extent permitted by applicable law, Licensor is not liable for any indirect, incidental, special, consequential or punitive damages, or for loss of profits, revenue, data, goodwill or use, business interruption, security breaches, data corruption, container management errors, or third-party actions, arising out of or in connection with the Licensed Work or your use of or inability to use it, under any theory of liability, even if advised of the possibility of such damages. Nothing here excludes or limits liability that cannot be excluded or limited under applicable law, including liability for damage caused intentionally. This applies to every user; a Commercial Licensee's liability terms are in B.12.
|
||||
|
||||
---
|
||||
|
||||
# Part A — 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 Jaroslaw Krochmalski
|
||||
|
||||
**Licensed Work:** Dockhand, meaning all software published by Licensor in the repository https://github.com/Finsys/dockhand (including the Dockhand server and its agent and updater components) and the binaries and container images Licensor builds from that repository. The Licensed Work is (c) 2025-2026 Finsys Jaroslaw Krochmalski.
|
||||
|
||||
**Additional Use Grant:**
|
||||
|
||||
You may make production use of the Licensed Work if, and only for as long as, at least one of the following applies to you:
|
||||
|
||||
(a) Personal use. You are an individual using the Licensed Work for personal, non-commercial purposes (for example a home lab or a hobby project), and not for the benefit of any business or other organization.
|
||||
|
||||
(b) Non-profit use. You are a non-profit or charitable organization, recognized as such under the law of your jurisdiction, using the Licensed Work for your own non-profit activities.
|
||||
|
||||
(c) Educational and research use. You are a school, college, university or non-commercial research institution using the Licensed Work for teaching, learning or non-commercial research.
|
||||
|
||||
(d) Commercial License. You hold a valid Commercial License granted by Licensor under Part B of the Dockhand License or under another written agreement with Licensor, and your production use is within the scope and term of that Commercial License.
|
||||
|
||||
(e) Cure period. Your production use is not covered by (a) to (d) and Licensor has notified you of this in writing: you may continue that production use for thirty (30) days after the notice, solely in order to obtain a Commercial License or to stop the production use. If you do either within that period, Licensor will treat the earlier production use as cured.
|
||||
|
||||
Any other production use — including production use by or on behalf of a company, sole trader or self-employed individual, public body or any other organization — requires a Commercial License from Licensor.
|
||||
|
||||
Use of the Licensed Work by a contractor or service provider acting solely on behalf of a person or organization covered by (a) to (d), and only for that person's or organization's own infrastructure, is treated as use by that person or organization.
|
||||
|
||||
"Production use" means using the Licensed Work to manage, monitor or operate containers, images, stacks, volumes, networks or Docker environments that serve your customers or end users, that run your live business or organizational systems, or on whose availability you otherwise rely for your day-to-day operations. Using the Licensed Work for development, testing, staging, CI/CD, evaluation, demonstration, training or learning purposes is not production use, regardless of who you are and regardless of the number of users, installations or environments involved, and is permitted under this License without a Commercial License.
|
||||
|
||||
Nothing in this Additional Use Grant permits you to offer the Licensed Work, or a derivative work of the Licensed Work, to third parties as a hosted service, managed service or software-as-a-service offering whose primary value to users is Docker container management functionality substantially similar to the Licensed Work. Such an offering requires a separate written agreement with Licensor.
|
||||
|
||||
**Eligibility, enforcement and License Keys.** The rights in the Additional Use Grant depend on your eligibility, and Enterprise Features are gated by a License Key. Accordingly:
|
||||
|
||||
(i) You must not misrepresent your eligibility for any of the categories in the Additional Use Grant (for example, claiming personal, non-profit or educational use that does not genuinely apply to you).
|
||||
|
||||
(ii) You must not share, publish or distribute a License Key, nor circumvent or attempt to circumvent any license-enforcement mechanism (for example, to enable Enterprise Features without a valid Commercial License).
|
||||
|
||||
(iii) Licensor may request reasonable verification of your eligibility for personal, non-profit or educational use, and you will provide reasonable documentation on request. This does not permit Licensor to access data stored in your installation (see the General terms above).
|
||||
|
||||
**Change Date:** Four years from the date on which a given version of the Licensed Work is first publicly distributed by Licensor.
|
||||
|
||||
**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, email enterprise@dockhand.pro
|
||||
|
||||
---
|
||||
|
||||
# Part B — Dockhand Commercial License Terms
|
||||
|
||||
## B.1 Scope and relationship to Part A
|
||||
|
||||
1. Part B applies between Finsys Jaroslaw Krochmalski ("Licensor", "Finsys") and the person or entity named in an Order ("Licensee", "you") from the earlier of: (a) Licensee placing an Order, or (b) Licensee activating a License Key.
|
||||
2. Part B supplements Part A. Part A continues to apply to the Licensed Work. Nothing in Part B limits, conditions or withdraws any right granted to you under Part A.
|
||||
3. The Order, Part B and Part A together form the entire agreement regarding the Commercial License and supersede any prior or contemporaneous agreement on that subject. In case of conflict, the Order prevails over Part B as to tier, Scope, fees and License Term; Part B prevails over the Order in all other respects; and Part A always prevails as to the rights it grants.
|
||||
4. The Commercial License is intended for businesses, public bodies and other organizations. Licensor does not offer it to consumers.
|
||||
|
||||
## B.2 Definitions
|
||||
|
||||
"Commercial License" means the license granted under B.3, in the tier stated in the Order. Licensor currently offers the "SMB" and "Enterprise" tiers. Tiers, prices, included features and Scope limits are described at https://dockhand.pro and in the Order.
|
||||
|
||||
"Order" means the order confirmation, invoice, quote accepted by Licensee, purchase order accepted by Licensor, or other document recording Licensee's purchase, tier, Scope and License Term.
|
||||
|
||||
"License Key" means the cryptographic key issued by Licensor that enables Enterprise Features and identifies a Commercial License.
|
||||
|
||||
"Enterprise Features" means the features of the Licensed Work that are enabled with a License Key. At the date of these terms they include LDAP/Active Directory integration, role-based access control with environment scoping, and audit logging. The current list is maintained at https://dockhand.pro. The source code of the Enterprise Features is published in the Dockhand source repository and is part of the Licensed Work under Part A.
|
||||
|
||||
"License Term" means the subscription period stated in the Order or, if none is stated, twelve (12) months from activation of the License Key.
|
||||
|
||||
"Scope" means the limits stated in the Order and in the tier description, such as the number of installations, environments or users.
|
||||
|
||||
"Licensed Work" and "production use" have the meanings given in Part A.
|
||||
|
||||
## B.3 Grant
|
||||
|
||||
1. For the License Term, subject to payment of the fees in the Order and to Part B, Licensor grants Licensee a non-exclusive, non-transferable (except under B.7) license to:
|
||||
(a) make production use of the Licensed Work, within the Scope;
|
||||
(b) receive a License Key and use the Enterprise Features included in Licensee's tier, within the Scope; and
|
||||
(c) receive the support included in Licensee's tier (B.8).
|
||||
2. Licensee's employees and contractors, and any affiliates named in the Order, may exercise these rights on Licensee's behalf and for Licensee's own infrastructure. Licensee is responsible for their compliance with Part A and Part B.
|
||||
|
||||
## B.4 What the Commercial License does not include
|
||||
|
||||
1. The Commercial License does not permit Licensee to:
|
||||
(a) offer the Licensed Work or the Enterprise Features to third parties as a hosted service, managed service or software-as-a-service offering (see the Additional Use Grant in Part A); this requires a separate written agreement with Licensor;
|
||||
(b) share, publish, sublicense, rent, resell or otherwise make a License Key available to anyone outside the Scope;
|
||||
(c) make production use of the Licensed Work, or use Enterprise Features, outside the Scope or after the end of the License Term.
|
||||
2. Licensee's rights in the Licensed Work under Part A — including the rights to copy, modify, create derivative works and redistribute it — are unaffected by Part B.
|
||||
|
||||
## B.5 Term, renewal and expiry
|
||||
|
||||
1. The License Term starts on the date stated in the Order or, if none is stated, on activation of the License Key.
|
||||
2. Renewal is not automatic. To keep the Commercial License, Licensee must purchase a renewal before the License Term ends.
|
||||
3. When the License Term ends, or the Commercial License is terminated:
|
||||
(a) Enterprise Features are disabled automatically;
|
||||
(b) the Licensed Work continues to function with its remaining features, and all data, configuration and settings are preserved;
|
||||
(c) Part A alone governs any further use. A Licensee that is not covered by clauses (a) to (c) of the Additional Use Grant must therefore renew or stop production use. The thirty (30) day cure period in the Additional Use Grant applies after Licensor's written notice.
|
||||
|
||||
## B.6 Fees and taxes
|
||||
|
||||
Fees are as stated in the Order, payable as set out in the Order or invoice, and exclusive of VAT and other applicable taxes, which Licensee pays in addition. Fees are non-refundable except where required by applicable law or agreed by Licensor in writing.
|
||||
|
||||
## B.7 Transfer
|
||||
|
||||
1. Reassignment. Licensee may move a Commercial License to another installation within its organization (for example on hardware replacement or migration), provided the License Key is removed from the previous installation and the number of active installations stays within the Scope.
|
||||
2. Business transfers. Licensee may transfer the Commercial License to its legal successor in a merger, acquisition, reorganization or sale of all or substantially all of its assets, by written notice to enterprise@dockhand.pro within thirty (30) days of the transfer, stating the successor's legal name and contact details and confirming that the successor accepts Part A and Part B.
|
||||
3. No other transfer, resale or sublicensing of a Commercial License or License Key is permitted without Licensor's prior written consent.
|
||||
|
||||
## B.8 Support
|
||||
|
||||
1. Licensor provides the support level included in Licensee's tier on a commercially reasonable, best-effort basis, through enterprise@dockhand.pro.
|
||||
2. Unless separately agreed in writing, Licensor does not guarantee response or resolution times, the availability of updates, patches or new versions, or compatibility with future operating systems, Docker versions or other software, and no service level agreement applies.
|
||||
|
||||
## B.9 Data and privacy
|
||||
|
||||
The Licensed Work stores all data locally on Licensee's infrastructure and contains no telemetry or analytics. License Keys are validated locally; the Licensed Work does not contact Licensor's servers. Licensor does not collect, access or process data stored in Licensee's installation. Licensee is solely responsible for the security, backup and protection of that data. Licensor processes the personal data needed to handle Orders, License Keys and support requests as described in the Privacy Policy at https://dockhand.pro.
|
||||
|
||||
## B.10 Ownership, trademarks and feedback
|
||||
|
||||
1. The Licensed Work, including the Enterprise Features, is and remains the property of Licensor. All rights not expressly granted in Part A or Part B are reserved.
|
||||
2. "Dockhand", "Finsys" and the associated logos are trademarks of Licensor. Neither Part A nor Part B grants any trademark rights beyond those expressly required by Part A.
|
||||
3. Licensor may use any feedback, suggestions or enhancement requests Licensee provides, without obligation to Licensee.
|
||||
|
||||
## B.11 Warranty disclaimer
|
||||
|
||||
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK, INCLUDING THE ENTERPRISE FEATURES, IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. LICENSOR DOES NOT WARRANT THAT THE LICENSED WORK WILL MEET LICENSEE'S REQUIREMENTS, BE UNINTERRUPTED, TIMELY, SECURE OR ERROR-FREE, THAT ERRORS WILL BE CORRECTED, OR THAT THE LICENSED WORK WILL BE COMPATIBLE WITH LICENSEE'S HARDWARE, SOFTWARE OR NETWORK ENVIRONMENT. THE STATUTORY WARRANTY FOR DEFECTS (RĘKOJMIA) IS EXCLUDED TO THE EXTENT PERMITTED BY LAW.
|
||||
|
||||
LICENSEE ACKNOWLEDGES THAT THE LICENSED WORK IS USED TO MANAGE DOCKER CONTAINERS AND RELATED INFRASTRUCTURE, AND THAT DEFECTS OR IMPROPER USE MAY RESULT IN DATA LOSS, DOWNTIME, SYSTEM DAMAGE OR SECURITY INCIDENTS. LICENSEE ASSUMES THAT RISK.
|
||||
|
||||
## B.12 Limitation of liability
|
||||
|
||||
1. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR IS NOT LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR PUNITIVE DAMAGES, OR FOR LOSS OF PROFITS, REVENUE, DATA, GOODWILL OR USE, BUSINESS INTERRUPTION, SECURITY BREACHES, DATA CORRUPTION, CONTAINER MANAGEMENT ERRORS, THIRD-PARTY ACTIONS OR INTEGRATIONS, OR THE COST OF SUBSTITUTE GOODS OR SERVICES, ARISING OUT OF OR IN CONNECTION WITH PART A, PART B OR THE USE OF OR INABILITY TO USE THE LICENSED WORK, UNDER ANY THEORY OF LIABILITY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
2. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR'S TOTAL AGGREGATE LIABILITY ARISING OUT OF OR IN CONNECTION WITH PART A AND PART B IS LIMITED TO THE FEES PAID BY LICENSEE UNDER THE ORDER IN THE TWELVE (12) MONTHS PRECEDING THE EVENT GIVING RISE TO THE CLAIM.
|
||||
3. Nothing in Part B excludes or limits liability that cannot be excluded or limited under applicable law, including liability for damage caused intentionally.
|
||||
4. These limitations are an essential basis of the bargain between Licensee and Licensor; Licensor would not offer the Commercial License without them.
|
||||
|
||||
## B.13 Termination
|
||||
|
||||
1. Licensor may terminate the Commercial License by written notice if Licensee materially breaches Part A or Part B and, where the breach can be cured, fails to cure it within thirty (30) days of written notice.
|
||||
2. Licensee may stop using the Commercial License at any time. Fees already paid are not refunded.
|
||||
3. On termination, B.5(3) applies. B.6 and B.10 to B.16 survive termination or expiry.
|
||||
|
||||
## B.14 Governing law and disputes
|
||||
|
||||
Part B, and any dispute arising out of or in connection with it, is governed by the laws of Poland, excluding its conflict-of-law rules and the UN Convention on Contracts for the International Sale of Goods. The courts of Warsaw, Poland have exclusive jurisdiction. Part B is drafted in English; in case of discrepancy with any translation, the English text prevails.
|
||||
|
||||
## B.15 General
|
||||
|
||||
1. Changes. Licensor may publish updated Commercial License Terms. Updated terms apply to Orders placed and renewals purchased after publication. The terms in force when an Order is placed govern that License Term.
|
||||
2. Assignment. Licensee may assign Part B only as permitted in B.7. Licensor may assign Part B to a successor of the Dockhand business on written notice.
|
||||
3. Severability. If any provision of Part B is unenforceable, it is reformed only to the extent necessary to make it enforceable, and the remaining provisions remain in force.
|
||||
4. Waiver. Licensor's failure to enforce any provision is not a waiver of it.
|
||||
5. Independent contractors. Nothing in Part B creates a partnership, joint venture, agency or employment relationship.
|
||||
6. Export. Licensee will comply with applicable export and import laws in connection with its use of the Licensed Work.
|
||||
7. Notices. Notices to Licensor go to enterprise@dockhand.pro; notices to Licensee go to the contact details in the Order.
|
||||
|
||||
## B.16 Licensor
|
||||
|
||||
Finsys Jaroslaw Krochmalski
|
||||
ul. Borki 6, 05-119 Jozefow, Poland
|
||||
VAT ID: PL7121835977 · REGON: 061576391
|
||||
enterprise@dockhand.pro · https://dockhand.pro
|
||||
|
||||
---
|
||||
|
||||
# Third-party components
|
||||
|
||||
The Licensed Work incorporates open source components under their own licenses, including SvelteKit (MIT), Svelte (MIT), Dockerode (Apache 2.0), xterm.js (MIT), Drizzle ORM (Apache 2.0), Tailwind CSS (MIT), shadcn-svelte (MIT) and Lucide Icons (ISC). The full list is in the package manifest files. Your use of those components is governed by their respective licenses; nothing in Part A or Part B limits your rights under them.
|
||||
+451
-79
@@ -1,128 +1,500 @@
|
||||
Business Source License 1.1
|
||||
DOCKHAND LICENSE
|
||||
|
||||
Copyright (c) 2025-2026 Finsys Jaroslaw Krochmalski
|
||||
|
||||
Last updated: 14 September 2026
|
||||
|
||||
|
||||
HOW TO READ THIS DOCUMENT
|
||||
|
||||
The document has two parts:
|
||||
|
||||
Part A - Business Source License 1.1. The license under which Dockhand is
|
||||
published. It applies to everyone who obtains a copy of Dockhand, whether from
|
||||
GitHub, from a container registry or bundled in the application. The text of the
|
||||
Business Source License itself is reproduced unchanged; Licensor's choices are
|
||||
in the "Parameters" section. Within Part A, "this License" means Part A only.
|
||||
|
||||
Part B - Dockhand Commercial License Terms. These apply only if you purchase a
|
||||
Commercial License (currently the SMB or Enterprise tier). They add rights and
|
||||
obligations on top of Part A and never take away rights you have under Part A.
|
||||
|
||||
|
||||
SUMMARY
|
||||
|
||||
- Free for everyone, no license key, any number of users or environments:
|
||||
development, testing, staging, CI/CD, evaluation, demos, training - including
|
||||
inside companies.
|
||||
- Free in production: individuals for personal use (home labs, hobby projects);
|
||||
non-profit and charitable organizations; schools, universities and
|
||||
non-commercial research institutions.
|
||||
- Paid (Commercial License, Part B): production use by or for any business,
|
||||
public body or other organization not listed above.
|
||||
- Never permitted without a separate written agreement: offering Dockhand
|
||||
itself, or a fork of it, as a hosted, managed or SaaS service to third
|
||||
parties.
|
||||
- Each released version becomes Apache 2.0 four years after its release.
|
||||
|
||||
If this summary and Parts A or B differ, Parts A and B govern.
|
||||
|
||||
|
||||
GENERAL TERMS (THESE APPLY TO EVERYONE)
|
||||
|
||||
The following apply to every user of the Licensed Work, whether you use it for
|
||||
free under Part A or under a Commercial License under Part B.
|
||||
|
||||
Data and privacy. The Licensed Work stores all data locally on your
|
||||
infrastructure and contains no telemetry or analytics. It does not contact
|
||||
Licensor's servers; any License Key is validated locally. Licensor does not
|
||||
collect, access or process data stored in your installation. You are solely
|
||||
responsible for the security, backup and protection of that data. Licensor
|
||||
processes only the personal data needed to handle Orders, License Keys and
|
||||
support requests, as described in the Privacy Policy at https://dockhand.pro.
|
||||
|
||||
Ownership. The Licensed Work is and remains the property of Licensor. All rights
|
||||
not expressly granted under Part A or Part B are reserved.
|
||||
|
||||
Trademarks. "Dockhand", "Finsys" and the associated logos are trademarks of
|
||||
Licensor. Nothing in this document grants any trademark rights beyond those
|
||||
expressly required by Part A.
|
||||
|
||||
Feedback. Licensor may use any suggestions, ideas, enhancement requests or other
|
||||
feedback you provide regarding the Licensed Work, without obligation to you.
|
||||
|
||||
Limitation of liability. To the maximum extent permitted by applicable law,
|
||||
Licensor is not liable for any indirect, incidental, special, consequential or
|
||||
punitive damages, or for loss of profits, revenue, data, goodwill or use,
|
||||
business interruption, security breaches, data corruption, container management
|
||||
errors, or third-party actions, arising out of or in connection with the
|
||||
Licensed Work or your use of or inability to use it, under any theory of
|
||||
liability, even if advised of the possibility of such damages. Nothing here
|
||||
excludes or limits liability that cannot be excluded or limited under applicable
|
||||
law, including liability for damage caused intentionally. This applies to every
|
||||
user; a Commercial Licensee's liability terms are in B.12.
|
||||
|
||||
|
||||
===============================================================================
|
||||
PART A - 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
|
||||
|
||||
Parameters
|
||||
Licensor: Finsys Jaroslaw Krochmalski
|
||||
|
||||
Licensor: Finsys / Jarek Krochmalski
|
||||
Licensed Work: Dockhand, meaning all software published by Licensor in the
|
||||
repository https://github.com/Finsys/dockhand (including the Dockhand server and
|
||||
its agent and updater components) and the binaries and container images Licensor
|
||||
builds from that repository. The Licensed Work is (c) 2025-2026 Finsys Jaroslaw
|
||||
Krochmalski.
|
||||
|
||||
Licensed Work: Dockhand
|
||||
The Licensed Work is (c) 2025-2026 Finsys / Jarek Krochmalski.
|
||||
Additional Use Grant:
|
||||
|
||||
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.
|
||||
You may make production use of the Licensed Work if, and only for as long as, at
|
||||
least one of the following applies to you:
|
||||
|
||||
For clarity, the following uses are explicitly permitted
|
||||
without any restriction:
|
||||
(a) Personal use. You are an individual using the Licensed Work for personal,
|
||||
non-commercial purposes (for example a home lab or a hobby project), and not for
|
||||
the benefit of any business or other organization.
|
||||
|
||||
(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
|
||||
(b) Non-profit use. You are a non-profit or charitable organization, recognized
|
||||
as such under the law of your jurisdiction, using the Licensed Work for your own
|
||||
non-profit activities.
|
||||
|
||||
Change Date: January 1, 2029
|
||||
(c) Educational and research use. You are a school, college, university or
|
||||
non-commercial research institution using the Licensed Work for teaching,
|
||||
learning or non-commercial research.
|
||||
|
||||
Change License: Apache License, Version 2.0
|
||||
(d) Commercial License. You hold a valid Commercial License granted by Licensor
|
||||
under Part B of the Dockhand License or under another written agreement with
|
||||
Licensor, and your production use is within the scope and term of that
|
||||
Commercial License.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
(e) Cure period. Your production use is not covered by (a) to (d) and Licensor
|
||||
has notified you of this in writing: you may continue that production use for
|
||||
thirty (30) days after the notice, solely in order to obtain a Commercial
|
||||
License or to stop the production use. If you do either within that period,
|
||||
Licensor will treat the earlier production use as cured.
|
||||
|
||||
Terms
|
||||
Any other production use - including production use by or on behalf of a
|
||||
company, sole trader or self-employed individual, public body or any other
|
||||
organization - requires a Commercial License from Licensor.
|
||||
|
||||
Use of the Licensed Work by a contractor or service provider acting solely on
|
||||
behalf of a person or organization covered by (a) to (d), and only for that
|
||||
person's or organization's own infrastructure, is treated as use by that person
|
||||
or organization.
|
||||
|
||||
"Production use" means using the Licensed Work to manage, monitor or operate
|
||||
containers, images, stacks, volumes, networks or Docker environments that serve
|
||||
your customers or end users, that run your live business or organizational
|
||||
systems, or on whose availability you otherwise rely for your day-to-day
|
||||
operations. Using the Licensed Work for development, testing, staging, CI/CD,
|
||||
evaluation, demonstration, training or learning purposes is not production use,
|
||||
regardless of who you are and regardless of the number of users, installations
|
||||
or environments involved, and is permitted under this License without a
|
||||
Commercial License.
|
||||
|
||||
Nothing in this Additional Use Grant permits you to offer the Licensed Work, or
|
||||
a derivative work of the Licensed Work, to third parties as a hosted service,
|
||||
managed service or software-as-a-service offering whose primary value to users
|
||||
is Docker container management functionality substantially similar to the
|
||||
Licensed Work. Such an offering requires a separate written agreement with
|
||||
Licensor.
|
||||
|
||||
Eligibility, enforcement and License Keys. The rights in the Additional Use
|
||||
Grant depend on your eligibility, and Enterprise Features are gated by a License
|
||||
Key. Accordingly:
|
||||
|
||||
(i) You must not misrepresent your eligibility for any of the categories in the
|
||||
Additional Use Grant (for example, claiming personal, non-profit or educational
|
||||
use that does not genuinely apply to you).
|
||||
|
||||
(ii) You must not share, publish or distribute a License Key, nor circumvent or
|
||||
attempt to circumvent any license-enforcement mechanism (for example, to enable
|
||||
Enterprise Features without a valid Commercial License).
|
||||
|
||||
(iii) Licensor may request reasonable verification of your eligibility for
|
||||
personal, non-profit or educational use, and you will provide reasonable
|
||||
documentation on request. This does not permit Licensor to access data stored in
|
||||
your installation (see the General terms above).
|
||||
|
||||
Change Date: Four years from the date on which a given version of the Licensed
|
||||
Work is first publicly distributed by Licensor.
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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).
|
||||
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.
|
||||
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.
|
||||
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
|
||||
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.
|
||||
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".
|
||||
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
|
||||
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, email
|
||||
[email protected]
|
||||
|
||||
For licensing inquiries, commercial licensing, or enterprise features:
|
||||
|
||||
Website: https://dockhand.pro
|
||||
===============================================================================
|
||||
PART B - DOCKHAND COMMERCIAL LICENSE TERMS
|
||||
===============================================================================
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
B.1 SCOPE AND RELATIONSHIP TO PART A
|
||||
|
||||
1. Part B applies between Finsys Jaroslaw Krochmalski ("Licensor", "Finsys") and
|
||||
the person or entity named in an Order ("Licensee", "you") from the earlier of:
|
||||
(a) Licensee placing an Order, or (b) Licensee activating a License Key.
|
||||
|
||||
2. Part B supplements Part A. Part A continues to apply to the Licensed Work.
|
||||
Nothing in Part B limits, conditions or withdraws any right granted to you under
|
||||
Part A.
|
||||
|
||||
3. The Order, Part B and Part A together form the entire agreement regarding the
|
||||
Commercial License and supersede any prior or contemporaneous agreement on that
|
||||
subject. In case of conflict, the Order prevails over Part B as to tier, Scope,
|
||||
fees and License Term; Part B prevails over the Order in all other respects; and
|
||||
Part A always prevails as to the rights it grants.
|
||||
|
||||
4. The Commercial License is intended for businesses, public bodies and other
|
||||
organizations. Licensor does not offer it to consumers.
|
||||
|
||||
B.2 DEFINITIONS
|
||||
|
||||
"Commercial License" means the license granted under B.3, in the tier stated in
|
||||
the Order. Licensor currently offers the "SMB" and "Enterprise" tiers. Tiers,
|
||||
prices, included features and Scope limits are described at https://dockhand.pro
|
||||
and in the Order.
|
||||
|
||||
"Order" means the order confirmation, invoice, quote accepted by Licensee,
|
||||
purchase order accepted by Licensor, or other document recording Licensee's
|
||||
purchase, tier, Scope and License Term.
|
||||
|
||||
"License Key" means the cryptographic key issued by Licensor that enables
|
||||
Enterprise Features and identifies a Commercial License.
|
||||
|
||||
"Enterprise Features" means the features of the Licensed Work that are enabled
|
||||
with a License Key. At the date of these terms they include LDAP/Active
|
||||
Directory integration, role-based access control with environment scoping, and
|
||||
audit logging. The current list is maintained at https://dockhand.pro. The
|
||||
source code of the Enterprise Features is published in the Dockhand source
|
||||
repository and is part of the Licensed Work under Part A.
|
||||
|
||||
"License Term" means the subscription period stated in the Order or, if none is
|
||||
stated, twelve (12) months from activation of the License Key.
|
||||
|
||||
"Scope" means the limits stated in the Order and in the tier description, such
|
||||
as the number of installations, environments or users.
|
||||
|
||||
"Licensed Work" and "production use" have the meanings given in Part A.
|
||||
|
||||
B.3 GRANT
|
||||
|
||||
1. For the License Term, subject to payment of the fees in the Order and to Part
|
||||
B, Licensor grants Licensee a non-exclusive, non-transferable (except under B.7)
|
||||
license to:
|
||||
(a) make production use of the Licensed Work, within the Scope;
|
||||
(b) receive a License Key and use the Enterprise Features included in
|
||||
Licensee's tier, within the Scope; and
|
||||
(c) receive the support included in Licensee's tier (B.8).
|
||||
|
||||
2. Licensee's employees and contractors, and any affiliates named in the Order,
|
||||
may exercise these rights on Licensee's behalf and for Licensee's own
|
||||
infrastructure. Licensee is responsible for their compliance with Part A and
|
||||
Part B.
|
||||
|
||||
B.4 WHAT THE COMMERCIAL LICENSE DOES NOT INCLUDE
|
||||
|
||||
1. The Commercial License does not permit Licensee to:
|
||||
(a) offer the Licensed Work or the Enterprise Features to third parties as a
|
||||
hosted service, managed service or software-as-a-service offering (see
|
||||
the Additional Use Grant in Part A); this requires a separate written
|
||||
agreement with Licensor;
|
||||
(b) share, publish, sublicense, rent, resell or otherwise make a License Key
|
||||
available to anyone outside the Scope;
|
||||
(c) make production use of the Licensed Work, or use Enterprise Features,
|
||||
outside the Scope or after the end of the License Term.
|
||||
|
||||
2. Licensee's rights in the Licensed Work under Part A - including the rights to
|
||||
copy, modify, create derivative works and redistribute it - are unaffected by
|
||||
Part B.
|
||||
|
||||
B.5 TERM, RENEWAL AND EXPIRY
|
||||
|
||||
1. The License Term starts on the date stated in the Order or, if none is
|
||||
stated, on activation of the License Key.
|
||||
|
||||
2. Renewal is not automatic. To keep the Commercial License, Licensee must
|
||||
purchase a renewal before the License Term ends.
|
||||
|
||||
3. When the License Term ends, or the Commercial License is terminated:
|
||||
(a) Enterprise Features are disabled automatically;
|
||||
(b) the Licensed Work continues to function with its remaining features, and
|
||||
all data, configuration and settings are preserved;
|
||||
(c) Part A alone governs any further use. A Licensee that is not covered by
|
||||
clauses (a) to (c) of the Additional Use Grant must therefore renew or
|
||||
stop production use. The thirty (30) day cure period in the Additional
|
||||
Use Grant applies after Licensor's written notice.
|
||||
|
||||
B.6 FEES AND TAXES
|
||||
|
||||
Fees are as stated in the Order, payable as set out in the Order or invoice, and
|
||||
exclusive of VAT and other applicable taxes, which Licensee pays in addition.
|
||||
Fees are non-refundable except where required by applicable law or agreed by
|
||||
Licensor in writing.
|
||||
|
||||
B.7 TRANSFER
|
||||
|
||||
1. Reassignment. Licensee may move a Commercial License to another installation
|
||||
within its organization (for example on hardware replacement or migration),
|
||||
provided the License Key is removed from the previous installation and the
|
||||
number of active installations stays within the Scope.
|
||||
|
||||
2. Business transfers. Licensee may transfer the Commercial License to its legal
|
||||
successor in a merger, acquisition, reorganization or sale of all or
|
||||
substantially all of its assets, by written notice to [email protected]
|
||||
within thirty (30) days of the transfer, stating the successor's legal name and
|
||||
contact details and confirming that the successor accepts Part A and Part B.
|
||||
|
||||
3. No other transfer, resale or sublicensing of a Commercial License or License
|
||||
Key is permitted without Licensor's prior written consent.
|
||||
|
||||
B.8 SUPPORT
|
||||
|
||||
1. Licensor provides the support level included in Licensee's tier on a
|
||||
commercially reasonable, best-effort basis, through [email protected].
|
||||
|
||||
2. Unless separately agreed in writing, Licensor does not guarantee response or
|
||||
resolution times, the availability of updates, patches or new versions, or
|
||||
compatibility with future operating systems, Docker versions or other software,
|
||||
and no service level agreement applies.
|
||||
|
||||
B.9 DATA AND PRIVACY
|
||||
|
||||
The Licensed Work stores all data locally on Licensee's infrastructure and
|
||||
contains no telemetry or analytics. License Keys are validated locally; the
|
||||
Licensed Work does not contact Licensor's servers. Licensor does not collect,
|
||||
access or process data stored in Licensee's installation. Licensee is solely
|
||||
responsible for the security, backup and protection of that data. Licensor
|
||||
processes the personal data needed to handle Orders, License Keys and support
|
||||
requests as described in the Privacy Policy at https://dockhand.pro.
|
||||
|
||||
B.10 OWNERSHIP, TRADEMARKS AND FEEDBACK
|
||||
|
||||
1. The Licensed Work, including the Enterprise Features, is and remains the
|
||||
property of Licensor. All rights not expressly granted in Part A or Part B are
|
||||
reserved.
|
||||
|
||||
2. "Dockhand", "Finsys" and the associated logos are trademarks of Licensor.
|
||||
Neither Part A nor Part B grants any trademark rights beyond those expressly
|
||||
required by Part A.
|
||||
|
||||
3. Licensor may use any feedback, suggestions or enhancement requests Licensee
|
||||
provides, without obligation to Licensee.
|
||||
|
||||
B.11 WARRANTY DISCLAIMER
|
||||
|
||||
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK, INCLUDING
|
||||
THE ENTERPRISE FEATURES, IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. LICENSOR DOES NOT WARRANT THAT
|
||||
THE LICENSED WORK WILL MEET LICENSEE'S REQUIREMENTS, BE UNINTERRUPTED, TIMELY,
|
||||
SECURE OR ERROR-FREE, THAT ERRORS WILL BE CORRECTED, OR THAT THE LICENSED WORK
|
||||
WILL BE COMPATIBLE WITH LICENSEE'S HARDWARE, SOFTWARE OR NETWORK ENVIRONMENT.
|
||||
THE STATUTORY WARRANTY FOR DEFECTS (RĘKOJMIA) IS EXCLUDED TO THE EXTENT
|
||||
PERMITTED BY LAW.
|
||||
|
||||
LICENSEE ACKNOWLEDGES THAT THE LICENSED WORK IS USED TO MANAGE DOCKER CONTAINERS
|
||||
AND RELATED INFRASTRUCTURE, AND THAT DEFECTS OR IMPROPER USE MAY RESULT IN DATA
|
||||
LOSS, DOWNTIME, SYSTEM DAMAGE OR SECURITY INCIDENTS. LICENSEE ASSUMES THAT RISK.
|
||||
|
||||
B.12 LIMITATION OF LIABILITY
|
||||
|
||||
1. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR IS NOT LIABLE FOR
|
||||
ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR PUNITIVE DAMAGES, OR FOR LOSS
|
||||
OF PROFITS, REVENUE, DATA, GOODWILL OR USE, BUSINESS INTERRUPTION, SECURITY
|
||||
BREACHES, DATA CORRUPTION, CONTAINER MANAGEMENT ERRORS, THIRD-PARTY ACTIONS OR
|
||||
INTEGRATIONS, OR THE COST OF SUBSTITUTE GOODS OR SERVICES, ARISING OUT OF OR IN
|
||||
CONNECTION WITH PART A, PART B OR THE USE OF OR INABILITY TO USE THE LICENSED
|
||||
WORK, UNDER ANY THEORY OF LIABILITY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
2. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR'S TOTAL AGGREGATE
|
||||
LIABILITY ARISING OUT OF OR IN CONNECTION WITH PART A AND PART B IS LIMITED TO
|
||||
THE FEES PAID BY LICENSEE UNDER THE ORDER IN THE TWELVE (12) MONTHS PRECEDING THE
|
||||
EVENT GIVING RISE TO THE CLAIM.
|
||||
|
||||
3. Nothing in Part B excludes or limits liability that cannot be excluded or
|
||||
limited under applicable law, including liability for damage caused
|
||||
intentionally.
|
||||
|
||||
4. These limitations are an essential basis of the bargain between Licensee and
|
||||
Licensor; Licensor would not offer the Commercial License without them.
|
||||
|
||||
B.13 TERMINATION
|
||||
|
||||
1. Licensor may terminate the Commercial License by written notice if Licensee
|
||||
materially breaches Part A or Part B and, where the breach can be cured, fails to
|
||||
cure it within thirty (30) days of written notice.
|
||||
|
||||
2. Licensee may stop using the Commercial License at any time. Fees already paid
|
||||
are not refunded.
|
||||
|
||||
3. On termination, B.5(3) applies. B.6 and B.10 to B.16 survive termination or
|
||||
expiry.
|
||||
|
||||
B.14 GOVERNING LAW AND DISPUTES
|
||||
|
||||
Part B, and any dispute arising out of or in connection with it, is governed by
|
||||
the laws of Poland, excluding its conflict-of-law rules and the UN Convention on
|
||||
Contracts for the International Sale of Goods. The courts of Warsaw, Poland have
|
||||
exclusive jurisdiction. Part B is drafted in English; in case of discrepancy
|
||||
with any translation, the English text prevails.
|
||||
|
||||
B.15 GENERAL
|
||||
|
||||
1. Changes. Licensor may publish updated Commercial License Terms. Updated terms
|
||||
apply to Orders placed and renewals purchased after publication. The terms in
|
||||
force when an Order is placed govern that License Term.
|
||||
|
||||
2. Assignment. Licensee may assign Part B only as permitted in B.7. Licensor may
|
||||
assign Part B to a successor of the Dockhand business on written notice.
|
||||
|
||||
3. Severability. If any provision of Part B is unenforceable, it is reformed
|
||||
only to the extent necessary to make it enforceable, and the remaining
|
||||
provisions remain in force.
|
||||
|
||||
4. Waiver. Licensor's failure to enforce any provision is not a waiver of it.
|
||||
|
||||
5. Independent contractors. Nothing in Part B creates a partnership, joint
|
||||
venture, agency or employment relationship.
|
||||
|
||||
6. Export. Licensee will comply with applicable export and import laws in
|
||||
connection with its use of the Licensed Work.
|
||||
|
||||
7. Notices. Notices to Licensor go to [email protected]; notices to
|
||||
Licensee go to the contact details in the Order.
|
||||
|
||||
B.16 LICENSOR
|
||||
|
||||
Finsys Jaroslaw Krochmalski
|
||||
ul. Borki 6, 05-119 Jozefow, Poland
|
||||
VAT ID: PL7121835977 · REGON: 061576391
|
||||
[email protected] · https://dockhand.pro
|
||||
|
||||
|
||||
===============================================================================
|
||||
THIRD-PARTY COMPONENTS
|
||||
===============================================================================
|
||||
|
||||
The Licensed Work incorporates open source components under their own licenses,
|
||||
including SvelteKit (MIT), Svelte (MIT), Dockerode (Apache 2.0), xterm.js (MIT),
|
||||
Drizzle ORM (Apache 2.0), Tailwind CSS (MIT), shadcn-svelte (MIT) and Lucide
|
||||
Icons (ISC). The full list is in the package manifest files. Your use of those
|
||||
components is governed by their respective licenses; nothing in Part A or Part B
|
||||
limits your rights under them.
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
module github.com/Finsys/dockhand/collector
|
||||
|
||||
go 1.25.13
|
||||
go 1.26
|
||||
+2
-2
@@ -50,12 +50,12 @@
|
||||
"drizzle-orm": "0.45.2",
|
||||
"fast-xml-parser": "5.7.3",
|
||||
"isomorphic-dompurify": "3.22.0",
|
||||
"js-yaml": "4.3.1",
|
||||
"js-yaml": "4.3.2",
|
||||
"ldapts": "8.1.3",
|
||||
"marked": "18.0.9",
|
||||
"modern-tar": "0.8.0",
|
||||
"mqtt": "5.15.2",
|
||||
"nodemailer": "9.0.1",
|
||||
"nodemailer": "9.1.0",
|
||||
"otpauth": "9.4.1",
|
||||
"postgres": "3.4.8",
|
||||
"prom-client": "15.1.3",
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { AlertTriangle } from 'lucide-svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
/** Raw restic stderr for the failed batch (shown verbatim). */
|
||||
error: string;
|
||||
/** How many snapshots were still deleted before the failure. */
|
||||
deleted?: number;
|
||||
}
|
||||
let { open = $bindable(), error, deleted = 0 }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-2xl">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title class="flex items-center gap-2 text-destructive">
|
||||
<AlertTriangle class="h-5 w-5" />
|
||||
Snapshots could not be deleted
|
||||
</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
{#if deleted > 0}
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Deleted {deleted} snapshot{deleted === 1 ? '' : 's'}, but the rest failed. The backup
|
||||
tool reported:
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-sm text-muted-foreground">The backup tool reported:</p>
|
||||
{/if}
|
||||
<pre class="max-h-[50vh] overflow-auto rounded-md bg-muted p-3 text-xs whitespace-pre-wrap break-words font-mono">{error}</pre>
|
||||
<Dialog.Footer>
|
||||
<Button onclick={() => (open = false)}>OK</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -1,8 +1,8 @@
|
||||
[
|
||||
{
|
||||
"version": "1.0.48",
|
||||
"date": "",
|
||||
"comingSoon": true,
|
||||
"date": "2026-09-14",
|
||||
"comingSoon": false,
|
||||
"changes": [
|
||||
{ "type": "feature", "text": "bulk-delete snapshots, and optionally forget a config's snapshots when you delete it (#1556)" },
|
||||
{ "type": "feature", "text": "option to choose which stack operations show the full log (#1558)" },
|
||||
@@ -13,7 +13,10 @@
|
||||
{ "type": "fix", "text": "stop password managers from offering to save stack secret env-var fields (#1517)" },
|
||||
{ "type": "fix", "text": "repository stats no longer fail on the backups screen (#1561)" },
|
||||
{ "type": "fix", "text": "dashboard image disk usage no longer counts shared layers multiple times (PR#1461, @omggga)" },
|
||||
{ "type": "feature", "text": "add a docker attach terminal mode (#1078, PR#1377, @ivanjx)" }
|
||||
{ "type": "feature", "text": "add a docker attach terminal mode (#1078, PR#1377, @ivanjx)" },
|
||||
{ "type": "fix", "text": "stack action buttons show for stopped and freshly created stacks, regression (#1565)" },
|
||||
{ "type": "fix", "text": "the update dialog no longer shows an unreleased version as the target (#1566)" },
|
||||
{ "type": "fix", "text": "update Go 1.26.8, docker-compose 5.5.0-r5, nodemailer 9.1.0, js-yaml 4.3.2 for security fixes" }
|
||||
],
|
||||
"imageTag": "fnsys/dockhand:v1.0.48"
|
||||
},
|
||||
|
||||
@@ -631,7 +631,7 @@
|
||||
},
|
||||
{
|
||||
"name": "dockhand",
|
||||
"version": "1.0.45",
|
||||
"version": "1.0.46",
|
||||
"license": "UNLICENSED",
|
||||
"repository": null
|
||||
},
|
||||
@@ -817,7 +817,7 @@
|
||||
},
|
||||
{
|
||||
"name": "js-yaml",
|
||||
"version": "4.3.1",
|
||||
"version": "4.3.2",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/nodeca/js-yaml"
|
||||
},
|
||||
@@ -943,7 +943,7 @@
|
||||
},
|
||||
{
|
||||
"name": "nodemailer",
|
||||
"version": "9.0.1",
|
||||
"version": "9.1.0",
|
||||
"license": "MIT-0",
|
||||
"repository": "https://github.com/nodemailer/nodemailer"
|
||||
},
|
||||
|
||||
@@ -376,6 +376,39 @@ export function buildNextManifest(newCommit: string, newFiles: Record<string, st
|
||||
return { commit: newCommit, files: { ...newFiles } };
|
||||
}
|
||||
|
||||
/** Compare two commit refs by their 7-char short form (the DB stores 7-char,
|
||||
* git returns 40-char); null/empty never matches. */
|
||||
function sameCommit(a: string | null | undefined, b: string | null | undefined): boolean {
|
||||
if (!a || !b) return false;
|
||||
return a.substring(0, 7) === b.substring(0, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a sync can skip re-hashing the whole compose tree. Skipping is safe
|
||||
* ONLY when the git diff proved no compose-dir change AND the diff was taken
|
||||
* against the SAME commit the manifest was built at. The second condition is
|
||||
* load-bearing: a bare Sync advances the diff baseline (lastCommit) without
|
||||
* persisting the manifest (only a deploy does), so `changedInComposeDir===false`
|
||||
* alone does NOT imply the on-disk tree still matches the manifest. When the
|
||||
* baselines match and a prior manifest exists, nothing was added/modified/
|
||||
* deleted, so the manifest carries forward verbatim and the unconditional
|
||||
* full re-hash (which blocks the event loop on a large tree) is skipped. Any
|
||||
* mismatch, undefined change state (new clone / diff failed), or empty manifest
|
||||
* falls back to a full re-hash - the safe default that self-corrects drift.
|
||||
*/
|
||||
export function canSkipTreeRehash(
|
||||
changedInComposeDir: boolean | undefined,
|
||||
manifestSize: number,
|
||||
manifestCommit: string | null | undefined,
|
||||
diffBaselineCommit: string | null | undefined
|
||||
): boolean {
|
||||
return (
|
||||
changedInComposeDir === false &&
|
||||
manifestSize > 0 &&
|
||||
sameCommit(manifestCommit, diffBaselineCommit)
|
||||
);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Sync summary (per-file status table)
|
||||
// =============================================================================
|
||||
|
||||
+37
-6
@@ -34,6 +34,7 @@ import {
|
||||
hashDirFiles,
|
||||
computeDeletions,
|
||||
buildNextManifest,
|
||||
canSkipTreeRehash,
|
||||
buildSyncChangeSummary,
|
||||
formatChangeTable,
|
||||
skipReasonMessage,
|
||||
@@ -470,13 +471,34 @@ async function computeSyncDeletionPlan(options: {
|
||||
composeDir: string; // absolute path inside the clone
|
||||
composeFileName: string | undefined; // compose file relative to composeDir
|
||||
rawManifest: string | null | undefined;
|
||||
// When the caller's git diff proved NOTHING changed in the compose dir between
|
||||
// the deployed commit and the new clone, hashing the whole tree is pure waste:
|
||||
// no file changed, so none was deleted. Large trees (tens of thousands of files)
|
||||
// otherwise block the event loop re-hashing unchanged files on every deploy.
|
||||
// undefined = unknown -> hash (safe default; e.g. new clone / diff failed).
|
||||
changedInComposeDir?: boolean;
|
||||
// The commit the git diff was taken against. Skipping is only safe when this
|
||||
// equals the manifest's own commit; otherwise the diff baseline has drifted
|
||||
// from the manifest (e.g. a bare Sync advanced lastCommit without persisting
|
||||
// the manifest) and a full re-hash is required to stay correct.
|
||||
diffBaselineCommit?: string | null;
|
||||
}): Promise<{ plan: DeletionPlan; newFiles: Record<string, string>; previousManifest: SyncManifest }> {
|
||||
const { logPrefix, composeDir, composeFileName, rawManifest } = options;
|
||||
const { logPrefix, composeDir, composeFileName, rawManifest, changedInComposeDir, diffBaselineCommit } = options;
|
||||
|
||||
const previousManifest = parseManifest(rawManifest);
|
||||
const manifestSize = Object.keys(previousManifest.files).length;
|
||||
|
||||
// Fast path: git proved no compose-dir file changed, the diff baseline matches
|
||||
// the manifest's commit, and we have a prior manifest. Nothing can have been
|
||||
// added/modified/deleted, so carry the manifest forward verbatim instead of
|
||||
// re-hashing the entire tree.
|
||||
if (canSkipTreeRehash(changedInComposeDir, manifestSize, previousManifest.commit, diffBaselineCommit)) {
|
||||
console.log(`${logPrefix} Deletion sync: no compose-dir changes, skipping full re-hash of ${manifestSize} file(s)`);
|
||||
return { plan: { toDelete: [], skipped: [] }, newFiles: { ...previousManifest.files }, previousManifest };
|
||||
}
|
||||
|
||||
const newFiles = hashDirFiles(composeDir);
|
||||
|
||||
const manifestSize = Object.keys(previousManifest.files).length;
|
||||
console.log(`${logPrefix} Deletion sync: manifest has ${manifestSize} file(s)${manifestSize === 0 ? ' (first sync — nothing will be deleted)' : ''}`);
|
||||
|
||||
// First sync / legacy manifest: nothing was recorded, so nothing can be deleted
|
||||
@@ -1236,12 +1258,17 @@ export async function syncGitStack(stackId: number): Promise<SyncResult> {
|
||||
console.log(`${logPrefix} No env file path configured`);
|
||||
}
|
||||
|
||||
// Deletion sync (#966): manifest-vs-clone deletion plan
|
||||
// Deletion sync (#966): manifest-vs-clone deletion plan.
|
||||
// `updated` is the dir-scoped git-diff result (false when the commit didn't
|
||||
// change OR no compose-dir file changed) - lets the plan skip re-hashing an
|
||||
// unchanged tree.
|
||||
const deletionData = await computeSyncDeletionPlan({
|
||||
logPrefix,
|
||||
composeDir,
|
||||
composeFileName,
|
||||
rawManifest: gitStack.syncedFiles
|
||||
rawManifest: gitStack.syncedFiles,
|
||||
changedInComposeDir: updated,
|
||||
diffBaselineCommit: previousCommit
|
||||
});
|
||||
|
||||
// Update git stack status
|
||||
@@ -1875,13 +1902,17 @@ export async function deployGitStackWithProgress(
|
||||
}
|
||||
}
|
||||
|
||||
// Deletion sync (#966): manifest-vs-clone deletion plan
|
||||
// Deletion sync (#966): manifest-vs-clone deletion plan. `updated` is the
|
||||
// dir-scoped git-diff result; when nothing changed the plan skips re-hashing
|
||||
// the whole tree.
|
||||
const logPrefix = `[Stack:${gitStack.stackName}]`;
|
||||
const deletionData = await computeSyncDeletionPlan({
|
||||
logPrefix,
|
||||
composeDir,
|
||||
composeFileName: progressComposeFileName,
|
||||
rawManifest: gitStack.syncedFiles
|
||||
rawManifest: gitStack.syncedFiles,
|
||||
changedInComposeDir: updated,
|
||||
diffBaselineCommit: previousCommit
|
||||
});
|
||||
|
||||
// Update git stack status
|
||||
|
||||
+23
-3
@@ -458,6 +458,9 @@ export interface BulkDeleteResult {
|
||||
skipped: number;
|
||||
/** true if any destination's batch call returned a non-2xx. */
|
||||
failed: boolean;
|
||||
/** Raw restic error from the first failing destination (repo locked, timeout, etc.),
|
||||
* surfaced verbatim so the user sees the real reason instead of a generic message. */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -469,7 +472,7 @@ export interface BulkDeleteResult {
|
||||
export async function bulkDeleteSnapshots(
|
||||
targets: { id: string; _destinationId: number }[]
|
||||
): Promise<BulkDeleteResult> {
|
||||
let deleted = 0, skipped = 0, failed = false;
|
||||
let deleted = 0, skipped = 0, failed = false, error: string | undefined;
|
||||
for (const { destinationId, snapshotIds } of groupSnapshotIdsByDestination(targets)) {
|
||||
try {
|
||||
const res = await fetch('/api/backup/snapshots/batch-delete', {
|
||||
@@ -480,17 +483,34 @@ export async function bulkDeleteSnapshots(
|
||||
const data = await res.json().catch(() => ({}));
|
||||
deleted += data.deleted?.length ?? 0;
|
||||
if (res.ok) skipped += data.skipped?.length ?? 0;
|
||||
else failed = true;
|
||||
else {
|
||||
failed = true;
|
||||
if (!error && data.error) error = String(data.error);
|
||||
}
|
||||
} catch {
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
return { deleted, skipped, failed };
|
||||
return { deleted, skipped, failed, error };
|
||||
}
|
||||
|
||||
/**
|
||||
* A hard restic failure that warrants the full-error dialog (repo locked, timeout,
|
||||
* etc.) rather than a toast. Returns the raw restic error and how many were still
|
||||
* deleted, or null when there is no such failure (toast handles success/skipped).
|
||||
* A `failed` with no `error` (e.g. a thrown fetch) also returns null - nothing
|
||||
* verbatim to show - so the caller falls back to the toast.
|
||||
*/
|
||||
export function bulkDeleteFailure(r: BulkDeleteResult): { error: string; deleted: number } | null {
|
||||
if (r.failed && r.error) return { error: r.error, deleted: r.deleted };
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consistent toast wording for a bulk-delete result, shared by every snapshot list.
|
||||
* Returns { type, message } so each caller fires its own toast (import stays local).
|
||||
* A hard restic failure with a verbatim error is handled by the caller via
|
||||
* bulkDeleteFailure + the error dialog, not here.
|
||||
*/
|
||||
export function bulkDeleteToast(r: BulkDeleteResult): { type: 'success' | 'error'; message: string } {
|
||||
if (r.failed) return { type: 'error', message: r.deleted > 0 ? `Deleted ${r.deleted}, but some snapshots could not be deleted` : 'Snapshots could not be deleted' };
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Whether the stack row's management actions (edit / logs / start / restart /
|
||||
// stop) should render. They show for a stack in ANY state EXCEPT a git stack that
|
||||
// has not been deployed yet - that one shows only its dedicated deploy button, so
|
||||
// the management actions would be redundant. A non-git stack that is stopped or
|
||||
// freshly created still needs its Start button, so deploy status must not hide the
|
||||
// management actions. `status` is the raw stack status; a git stack that has never
|
||||
// deployed is raw "created" (shown as "not deployed" elsewhere).
|
||||
|
||||
export function showsManagementActions(sourceType: string | undefined, status: string): boolean {
|
||||
return !(sourceType === 'git' && status === 'created');
|
||||
}
|
||||
@@ -26,9 +26,10 @@
|
||||
import { formatDateTime, formatRelativeTime } from '$lib/stores/settings';
|
||||
import { currentEnvironment } from '$lib/stores/environment';
|
||||
import { watchJob, readJobResponse } from '$lib/utils/sse-fetch';
|
||||
import { getRepoTypeIcon, formatCron, retentionSummary, classifyJobResult, tagLogLine, bulkDeleteSnapshots, bulkDeleteToast } from '$lib/utils/backup';
|
||||
import { getRepoTypeIcon, formatCron, retentionSummary, classifyJobResult, tagLogLine, bulkDeleteSnapshots, bulkDeleteToast, bulkDeleteFailure } from '$lib/utils/backup';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import BulkDeleteSnapshotsDialog from '$lib/components/BulkDeleteSnapshotsDialog.svelte';
|
||||
import BulkDeleteErrorDialog from '$lib/components/BulkDeleteErrorDialog.svelte';
|
||||
import SnapshotBrowser from '../containers/SnapshotBrowser.svelte';
|
||||
import RestoreModal from '../containers/RestoreModal.svelte';
|
||||
import BackupLogModal from './BackupLogModal.svelte';
|
||||
@@ -113,6 +114,9 @@
|
||||
let bulkDialogOpen = $state(false);
|
||||
let bulkDeleting = $state(false);
|
||||
let bulkConfig = $state<BackupConfig | null>(null);
|
||||
let bulkDeleteErrorOpen = $state(false);
|
||||
let bulkDeleteError = $state('');
|
||||
let bulkDeleteErrorDeleted = $state(0);
|
||||
// Snapshots of a config, normalized to {id, _destinationId} for the shared helper.
|
||||
function configSelectedTargets(config: BackupConfig) {
|
||||
return (snapshotsMap.get(config.key) || [])
|
||||
@@ -139,8 +143,15 @@
|
||||
bulkDeleting = true;
|
||||
try {
|
||||
const result = await bulkDeleteSnapshots(configSelectedTargets(config));
|
||||
const t = bulkDeleteToast(result);
|
||||
toast[t.type](t.message);
|
||||
const failure = bulkDeleteFailure(result);
|
||||
if (failure) {
|
||||
bulkDeleteError = failure.error;
|
||||
bulkDeleteErrorDeleted = failure.deleted;
|
||||
bulkDeleteErrorOpen = true;
|
||||
} else {
|
||||
const t = bulkDeleteToast(result);
|
||||
toast[t.type](t.message);
|
||||
}
|
||||
selectedSnapshots = new Set();
|
||||
await loadSnapshots(config);
|
||||
const loaded = snapshotsMap.get(config.key);
|
||||
@@ -1146,6 +1157,7 @@
|
||||
</div>
|
||||
|
||||
<BulkDeleteSnapshotsDialog bind:open={bulkDialogOpen} count={bulkConfig ? configSelectedTargets(bulkConfig).length : 0} busy={bulkDeleting} onConfirm={bulkDeleteConfig} />
|
||||
<BulkDeleteErrorDialog bind:open={bulkDeleteErrorOpen} error={bulkDeleteError} deleted={bulkDeleteErrorDeleted} />
|
||||
|
||||
<SnapshotBrowser
|
||||
bind:open={showBrowser}
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
import { formatDateTime, formatRelativeTime } from '$lib/stores/settings';
|
||||
import { formatBytes } from '$lib/utils/format';
|
||||
import { readJobResponse } from '$lib/utils/sse-fetch';
|
||||
import { getRepoTypeIcon, selectOwnSnapshotsFromDestination, bulkDeleteSnapshots, bulkDeleteToast } from '$lib/utils/backup';
|
||||
import { getRepoTypeIcon, selectOwnSnapshotsFromDestination, bulkDeleteSnapshots, bulkDeleteToast, bulkDeleteFailure } from '$lib/utils/backup';
|
||||
import BulkDeleteSnapshotsDialog from '$lib/components/BulkDeleteSnapshotsDialog.svelte';
|
||||
import BulkDeleteErrorDialog from '$lib/components/BulkDeleteErrorDialog.svelte';
|
||||
import SnapshotBrowser from './SnapshotBrowser.svelte';
|
||||
import RestoreModal from './RestoreModal.svelte';
|
||||
import SnapshotDiffModal from '../backups/SnapshotDiffModal.svelte';
|
||||
@@ -57,6 +58,9 @@
|
||||
let bulkDeleting = $state(false);
|
||||
let bulkDialogOpen = $state(false);
|
||||
let bulkScope = $state<'selected' | 'all'>('selected');
|
||||
let bulkDeleteErrorOpen = $state(false);
|
||||
let bulkDeleteError = $state('');
|
||||
let bulkDeleteErrorDeleted = $state(0);
|
||||
// snapshotId → { filesNew, filesChanged, dataAdded } from schedule executions.
|
||||
let stats = $state<Map<string, { filesNew: number; filesChanged: number; dataAdded: number }>>(new Map());
|
||||
|
||||
@@ -251,8 +255,15 @@
|
||||
bulkDeleting = true;
|
||||
try {
|
||||
const result = await bulkDeleteSnapshots(targets);
|
||||
const t = bulkDeleteToast(result);
|
||||
toast[t.type](t.message);
|
||||
const failure = bulkDeleteFailure(result);
|
||||
if (failure) {
|
||||
bulkDeleteError = failure.error;
|
||||
bulkDeleteErrorDeleted = failure.deleted;
|
||||
bulkDeleteErrorOpen = true;
|
||||
} else {
|
||||
const t = bulkDeleteToast(result);
|
||||
toast[t.type](t.message);
|
||||
}
|
||||
selectedSnapshots = new Set();
|
||||
await loadSnapshots();
|
||||
} finally {
|
||||
@@ -411,6 +422,7 @@
|
||||
{/if}
|
||||
|
||||
<BulkDeleteSnapshotsDialog bind:open={bulkDialogOpen} count={bulkCount} busy={bulkDeleting} onConfirm={() => bulkDelete(bulkScope)} />
|
||||
<BulkDeleteErrorDialog bind:open={bulkDeleteErrorOpen} error={bulkDeleteError} deleted={bulkDeleteErrorDeleted} />
|
||||
|
||||
<SnapshotBrowser
|
||||
bind:open={showBrowser}
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
import { FolderOpen, Box, Layers, FileStack, Camera, ChevronRight, ChevronDown } from 'lucide-svelte';
|
||||
import { formatBytes } from '$lib/utils/format';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import { bulkDeleteSnapshots, bulkDeleteToast } from '$lib/utils/backup';
|
||||
import { bulkDeleteSnapshots, bulkDeleteToast, bulkDeleteFailure } from '$lib/utils/backup';
|
||||
import BulkDeleteSnapshotsDialog from '$lib/components/BulkDeleteSnapshotsDialog.svelte';
|
||||
import BulkDeleteErrorDialog from '$lib/components/BulkDeleteErrorDialog.svelte';
|
||||
import EnvironmentIcon from '$lib/components/EnvironmentIcon.svelte';
|
||||
import { LoadingState } from '$lib/components/ui/loading-state';
|
||||
import { getRepoTypeIcon, getRepoTypeLabel } from '$lib/utils/backup';
|
||||
@@ -290,6 +291,9 @@
|
||||
let selectedSnapshots = $state<Set<string>>(new Set());
|
||||
let bulkDialogOpen = $state(false);
|
||||
let bulkDeleting = $state(false);
|
||||
let bulkDeleteErrorOpen = $state(false);
|
||||
let bulkDeleteError = $state('');
|
||||
let bulkDeleteErrorDeleted = $state(0);
|
||||
function toggleSnapshotSel(id: string) {
|
||||
const next = new Set(selectedSnapshots);
|
||||
if (next.has(id)) next.delete(id); else next.add(id);
|
||||
@@ -310,8 +314,15 @@
|
||||
bulkDeleting = true;
|
||||
try {
|
||||
const result = await bulkDeleteSnapshots(targets);
|
||||
const t = bulkDeleteToast(result);
|
||||
toast[t.type](t.message);
|
||||
const failure = bulkDeleteFailure(result);
|
||||
if (failure) {
|
||||
bulkDeleteError = failure.error;
|
||||
bulkDeleteErrorDeleted = failure.deleted;
|
||||
bulkDeleteErrorOpen = true;
|
||||
} else {
|
||||
const t = bulkDeleteToast(result);
|
||||
toast[t.type](t.message);
|
||||
}
|
||||
selectedSnapshots = new Set();
|
||||
const dest = destinations.find((d) => d.id === browseDestId);
|
||||
if (dest) await browseDestination(dest);
|
||||
@@ -933,6 +944,7 @@
|
||||
</Dialog.Root>
|
||||
|
||||
<BulkDeleteSnapshotsDialog bind:open={bulkDialogOpen} count={browseSelectedCount} busy={bulkDeleting} onConfirm={bulkDeleteBrowsed} />
|
||||
<BulkDeleteErrorDialog bind:open={bulkDeleteErrorOpen} error={bulkDeleteError} deleted={bulkDeleteErrorDeleted} />
|
||||
|
||||
<SnapshotBrowser
|
||||
bind:open={snapshotBrowseOpen}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
import ContainerIcon from '$lib/components/ContainerIcon.svelte';
|
||||
import BatchOperationModal from '$lib/components/BatchOperationModal.svelte';
|
||||
import type { ComposeStackInfo, ContainerStats, StackContainer } from '$lib/types';
|
||||
import { showsManagementActions } from '$lib/utils/stack-actions';
|
||||
import StackModal from './StackModal.svelte';
|
||||
import DeleteStackModal from './DeleteStackModal.svelte';
|
||||
import ComposeOutputModal from './ComposeOutputModal.svelte';
|
||||
@@ -2180,7 +2181,7 @@
|
||||
{/snippet}
|
||||
</GitDeployProgressPopover>
|
||||
{/if}
|
||||
{#if stack.status !== 'not deployed' && stack.status !== 'created'}
|
||||
{#if showsManagementActions(source.sourceType, stack.status)}
|
||||
{#if $canAccess('stacks', 'edit')}
|
||||
{#if source.sourceType === 'git' && source.gitStack}
|
||||
<button
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import { canSkipTreeRehash } from '../src/lib/server/git-deletions';
|
||||
|
||||
const A = 'aaaaaaa1111111111111111111111111111111111';
|
||||
const A7 = 'aaaaaaa'; // same 7-char prefix, DB-stored short form
|
||||
const B = 'bbbbbbb2222222222222222222222222222222222';
|
||||
|
||||
describe('canSkipTreeRehash', () => {
|
||||
test('skips when no change, prior manifest, and baselines match (full vs full)', () => {
|
||||
expect(canSkipTreeRehash(false, 5, A, A)).toBe(true);
|
||||
});
|
||||
|
||||
test('skips when baselines match by 7-char prefix (manifest full vs DB short)', () => {
|
||||
expect(canSkipTreeRehash(false, 5, A, A7)).toBe(true);
|
||||
});
|
||||
|
||||
test('does NOT skip when the diff baseline drifted from the manifest commit', () => {
|
||||
// bare Sync advanced lastCommit to B while manifest stayed at A -> must re-hash
|
||||
expect(canSkipTreeRehash(false, 5, A, B)).toBe(false);
|
||||
});
|
||||
|
||||
test('does NOT skip when files changed', () => {
|
||||
expect(canSkipTreeRehash(true, 5, A, A)).toBe(false);
|
||||
});
|
||||
|
||||
test('does NOT skip on the first sync (empty manifest must be built)', () => {
|
||||
expect(canSkipTreeRehash(false, 0, A, A)).toBe(false);
|
||||
});
|
||||
|
||||
test('does NOT skip when change state is unknown (new clone / diff failed)', () => {
|
||||
expect(canSkipTreeRehash(undefined, 5, A, A)).toBe(false);
|
||||
});
|
||||
|
||||
test('does NOT skip when either commit is null/empty', () => {
|
||||
expect(canSkipTreeRehash(false, 5, null, A)).toBe(false);
|
||||
expect(canSkipTreeRehash(false, 5, A, null)).toBe(false);
|
||||
expect(canSkipTreeRehash(false, 5, A, '')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, afterEach } from 'bun:test';
|
||||
import { groupSnapshotIdsByDestination, bulkDeleteSnapshots, bulkDeleteToast } from '../src/lib/utils/backup';
|
||||
import { groupSnapshotIdsByDestination, bulkDeleteSnapshots, bulkDeleteToast, bulkDeleteFailure } from '../src/lib/utils/backup';
|
||||
|
||||
const snap = (id: string, dest: number) => ({ id, _destinationId: dest });
|
||||
|
||||
@@ -96,4 +96,29 @@ describe('bulkDeleteSnapshots', () => {
|
||||
expect(r.failed).toBe(true);
|
||||
expect(r.deleted).toBe(1);
|
||||
});
|
||||
|
||||
it('carries the raw restic error from the first failing repo', async () => {
|
||||
globalThis.fetch = (async (_url: string, init: any) => {
|
||||
const body = JSON.parse(init.body);
|
||||
if (body.destinationId === 2) return { ok: false, json: async () => ({ deleted: [], error: 'repository is already locked\ncontext canceled' }) } as any;
|
||||
return { ok: true, json: async () => ({ deleted: ['a'], skipped: [] }) } as any;
|
||||
}) as any;
|
||||
const r = await bulkDeleteSnapshots([snap('a', 1), snap('b', 2)]);
|
||||
expect(r.failed).toBe(true);
|
||||
expect(r.error).toBe('repository is already locked\ncontext canceled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('bulkDeleteFailure', () => {
|
||||
it('returns the raw error and deleted count on a hard failure', () => {
|
||||
expect(bulkDeleteFailure({ deleted: 1, skipped: 0, failed: true, error: 'repo locked' }))
|
||||
.toEqual({ error: 'repo locked', deleted: 1 });
|
||||
});
|
||||
it('is null when nothing failed (toast handles success/skipped)', () => {
|
||||
expect(bulkDeleteFailure({ deleted: 3, skipped: 0, failed: false })).toBeNull();
|
||||
expect(bulkDeleteFailure({ deleted: 0, skipped: 2, failed: false })).toBeNull();
|
||||
});
|
||||
it('is null when failed but no verbatim error (thrown fetch) - toast falls back', () => {
|
||||
expect(bulkDeleteFailure({ deleted: 0, skipped: 0, failed: true })).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import { showsManagementActions } from '../src/lib/utils/stack-actions';
|
||||
|
||||
describe('showsManagementActions', () => {
|
||||
test('non-git stopped/created stacks show actions (the Start button regression)', () => {
|
||||
expect(showsManagementActions('internal', 'created')).toBe(true);
|
||||
expect(showsManagementActions('internal', 'stopped')).toBe(true);
|
||||
expect(showsManagementActions('external', 'created')).toBe(true);
|
||||
expect(showsManagementActions(undefined, 'created')).toBe(true);
|
||||
});
|
||||
|
||||
test('git stack that has not been deployed (raw "created") hides actions - only the deploy button shows', () => {
|
||||
expect(showsManagementActions('git', 'created')).toBe(false);
|
||||
});
|
||||
|
||||
test('a deployed git stack shows its management actions', () => {
|
||||
expect(showsManagementActions('git', 'running')).toBe(true);
|
||||
expect(showsManagementActions('git', 'stopped')).toBe(true);
|
||||
expect(showsManagementActions('git', 'partial')).toBe(true);
|
||||
});
|
||||
|
||||
test('running/stopped stacks of any source show actions', () => {
|
||||
for (const src of ['internal', 'external', 'git', undefined]) {
|
||||
expect(showsManagementActions(src, 'running')).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user