diff --git a/.docker/frankenphp/conf.d/app.ini b/.docker/frankenphp/conf.d/app.ini
index 10a062f2..08c71700 100644
--- a/.docker/frankenphp/conf.d/app.ini
+++ b/.docker/frankenphp/conf.d/app.ini
@@ -12,7 +12,7 @@ opcache.max_accelerated_files = 20000
opcache.memory_consumption = 256
opcache.enable_file_override = 1
-memory_limit = 256M
+memory_limit = 512M
upload_max_filesize=256M
-post_max_size=300M
\ No newline at end of file
+post_max_size=300M
diff --git a/.docker/frankenphp/docker-entrypoint.sh b/.docker/frankenphp/docker-entrypoint.sh
index 1655af5a..56b3bc31 100644
--- a/.docker/frankenphp/docker-entrypoint.sh
+++ b/.docker/frankenphp/docker-entrypoint.sh
@@ -26,6 +26,28 @@ if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
composer install --prefer-dist --no-progress --no-interaction
fi
+ # Install additional composer packages if COMPOSER_EXTRA_PACKAGES is set
+ if [ -n "$COMPOSER_EXTRA_PACKAGES" ]; then
+ echo "Installing additional composer packages: $COMPOSER_EXTRA_PACKAGES"
+ # Note: COMPOSER_EXTRA_PACKAGES is intentionally not quoted to allow word splitting
+ # This enables passing multiple package names separated by spaces
+ # shellcheck disable=SC2086
+ composer require $COMPOSER_EXTRA_PACKAGES --no-install --no-interaction --no-progress
+ if [ $? -eq 0 ]; then
+ echo "Running composer install to install packages without dev dependencies..."
+ composer install --no-dev --no-interaction --no-progress --optimize-autoloader
+ if [ $? -eq 0 ]; then
+ echo "Successfully installed additional composer packages"
+ else
+ echo "Failed to install composer dependencies"
+ exit 1
+ fi
+ else
+ echo "Failed to add additional composer packages to composer.json"
+ exit 1
+ fi
+ fi
+
if grep -q ^DATABASE_URL= .env; then
echo "Waiting for database to be ready..."
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
diff --git a/.docker/frankenphp/worker.Caddyfile b/.docker/frankenphp/worker.Caddyfile
index d384ae4c..eaea1892 100644
--- a/.docker/frankenphp/worker.Caddyfile
+++ b/.docker/frankenphp/worker.Caddyfile
@@ -1,4 +1,3 @@
worker {
file ./public/index.php
- env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
}
diff --git a/.docker/partdb-entrypoint.sh b/.docker/partdb-entrypoint.sh
index f5071e22..61e8d1e6 100644
--- a/.docker/partdb-entrypoint.sh
+++ b/.docker/partdb-entrypoint.sh
@@ -39,6 +39,28 @@ if [ -d /var/www/html/var/db ]; then
fi
fi
+# Install additional composer packages if COMPOSER_EXTRA_PACKAGES is set
+if [ -n "$COMPOSER_EXTRA_PACKAGES" ]; then
+ echo "Installing additional composer packages: $COMPOSER_EXTRA_PACKAGES"
+ # Note: COMPOSER_EXTRA_PACKAGES is intentionally not quoted to allow word splitting
+ # This enables passing multiple package names separated by spaces
+ # shellcheck disable=SC2086
+ sudo -E -u www-data composer require $COMPOSER_EXTRA_PACKAGES --no-install --no-interaction --no-progress
+ if [ $? -eq 0 ]; then
+ echo "Running composer install to install packages without dev dependencies..."
+ sudo -E -u www-data composer install --no-dev --no-interaction --no-progress --optimize-autoloader
+ if [ $? -eq 0 ]; then
+ echo "Successfully installed additional composer packages"
+ else
+ echo "Failed to install composer dependencies"
+ exit 1
+ fi
+ else
+ echo "Failed to add additional composer packages to composer.json"
+ exit 1
+ fi
+fi
+
# Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile)
php-fpmPHP_VERSION -F &
diff --git a/.env b/.env
index 982d4bbd..8d5e5a54 100644
--- a/.env
+++ b/.env
@@ -31,8 +31,7 @@ DATABASE_EMULATE_NATURAL_SORT=0
# General settings
###################################################################################
-# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates
-# This must end with a slash!
+# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates or when no request context is available.
DEFAULT_URI="https://partdb.changeme.invalid/"
###################################################################################
@@ -60,6 +59,28 @@ ERROR_PAGE_ADMIN_EMAIL=''
# If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
ERROR_PAGE_SHOW_HELP=1
+###################################################################################
+# Update Manager settings
+###################################################################################
+
+# Disable web-based updates from the Update Manager UI (0=enabled, 1=disabled).
+# When disabled, use the CLI command "php bin/console partdb:update" instead.
+DISABLE_WEB_UPDATES=1
+
+# Disable backup restore from the Update Manager UI (0=enabled, 1=disabled).
+# Restoring backups is a destructive operation that could overwrite your database.
+DISABLE_BACKUP_RESTORE=1
+
+# Disable backup download from the Update Manager UI (0=enabled, 1=disabled).
+# Backups contain sensitive data including password hashes and secrets.
+# When enabled, users must confirm their password before downloading.
+DISABLE_BACKUP_DOWNLOAD=1
+
+# Watchtower integration for Docker-based updates.
+# Set these to enable one-click updates via the Update Manager UI.
+# See https://containrrr.dev/watchtower/ for Watchtower setup.
+WATCHTOWER_API_URL=
+WATCHTOWER_API_TOKEN=
###################################################################################
# SAML Single sign on-settings
@@ -106,6 +127,10 @@ SAML_SP_PRIVATE_KEY="MIIE..."
# In demo mode things it is not possible for a user to change his password and his settings.
DEMO_MODE=0
+# When this is set to 1, users can make Part-DB directly download a file specified as a URL from the local network and create it as a local file.
+# This allows users access to all resources available in the local network, which could be a security risk, so use this only if you trust your users and have a secure local network.
+ALLOW_ATTACHMENT_DOWNLOADS_FROM_LOCALNETWORK=0
+
# Change this to true, if no url rewriting (like mod_rewrite for Apache) is available
# In that case all URL contains the index.php front controller in URL
NO_URL_REWRITE_AVAILABLE=0
@@ -134,4 +159,13 @@ CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###> symfony/framework-bundle ###
APP_ENV=prod
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
+APP_SHARE_DIR=var/share
###< symfony/framework-bundle ###
+
+###> symfony/ai-generic-platform ###
+# GENERIC_BASE_URL=https://api.example.com/v1
+###< symfony/ai-generic-platform ###
+
+###> symfony/ai-open-router-platform ###
+OPENROUTER_API_KEY=
+###< symfony/ai-open-router-platform ###
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 00000000..637a66b5
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,186 @@
+# Copilot Instructions for Part-DB
+
+Part-DB is an Open-Source inventory management system for electronic components built with Symfony 7.4 and modern web technologies.
+
+## Technology Stack
+
+- **Backend**: PHP 8.2+, Symfony 7.4, Doctrine ORM
+- **Frontend**: Bootstrap 5, Hotwire Stimulus/Turbo, TypeScript, Webpack Encore
+- **Database**: MySQL 5.7+/MariaDB 10.4+/PostgreSQL 10+/SQLite
+- **Testing**: PHPUnit with DAMA Doctrine Test Bundle
+- **Code Quality**: Easy Coding Standard (ECS), PHPStan (level 5)
+
+## Project Structure
+
+- `src/`: PHP application code organized by purpose (Controller, Entity, Service, Form, etc.)
+- `assets/`: Frontend TypeScript/JavaScript and CSS files
+- `templates/`: Twig templates for views
+- `tests/`: PHPUnit tests mirroring the `src/` structure
+- `config/`: Symfony configuration files
+- `public/`: Web-accessible files
+- `translations/`: Translation files for multi-language support
+
+## Coding Standards
+
+### PHP Code
+
+- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) and [Symfony coding standards](https://symfony.com/doc/current/contributing/code/standards.html)
+- Use type hints for all parameters and return types
+- Always declare strict types: `declare(strict_types=1);` at the top of PHP files
+- Use PHPDoc blocks for complex logic or when type information is needed
+
+### TypeScript/JavaScript
+
+- Use TypeScript for new frontend code
+- Follow existing Stimulus controller patterns in `assets/controllers/`
+- Use Bootstrap 5 components and utilities
+- Leverage Hotwire Turbo for dynamic page updates
+
+### Naming Conventions
+
+- Entities: Use descriptive names that reflect database models (e.g., `Part`, `StorageLocation`)
+- Controllers: Suffix with `Controller` (e.g., `PartController`)
+- Services: Descriptive names reflecting their purpose (e.g., `PartService`, `LabelGenerator`)
+- Tests: Match the class being tested with `Test` suffix (e.g., `PartTest`, `PartControllerTest`)
+
+## Development Workflow
+
+### Dependencies
+
+- Install PHP dependencies: `composer install`
+- Install JS dependencies: `yarn install`
+- Build frontend assets: `yarn build` (production) or `yarn watch` (development)
+
+### Database
+
+- Create database: `php bin/console doctrine:database:create --env=dev`
+- Run migrations: `php bin/console doctrine:migrations:migrate --env=dev`
+- Load fixtures: `php bin/console partdb:fixtures:load -n --env=dev`
+
+Or use Makefile shortcuts:
+- `make dev-setup`: Complete development environment setup
+- `make dev-reset`: Reset development environment (cache clear + migrate)
+
+### Testing
+
+- Set up test environment: `make test-setup`
+- Run all tests: `php bin/phpunit`
+- Run specific test: `php bin/phpunit tests/Path/To/SpecificTest.php`
+- Run tests with coverage: `php bin/phpunit --coverage-html var/coverage`
+- Test environment uses SQLite by default for speed
+
+### Static Analysis
+
+- Run PHPStan: `composer phpstan` or `COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G vendor/bin/phpstan analyse src --level 5`
+- PHPStan configuration is in `phpstan.dist.neon`
+
+### Running the Application
+
+- Development server: `symfony serve` (requires Symfony CLI)
+- Or configure Apache/nginx to serve from `public/` directory
+- Set `APP_ENV=dev` in `.env.local` for development mode
+
+## Best Practices
+
+### Security
+
+- Always sanitize user input
+- Use Symfony's security component for authentication/authorization
+- Check permissions using the permission system before allowing actions
+- Never expose sensitive data in logs or error messages
+- Use parameterized queries (Doctrine handles this automatically)
+
+### Performance
+
+- Use Doctrine query builder for complex queries instead of DQL when possible
+- Lazy load relationships to avoid N+1 queries
+- Cache results when appropriate using Symfony's cache component
+- Use pagination for large result sets (DataTables integration available)
+
+### Database
+
+- Always create migrations for schema changes: `php bin/console make:migration`
+- Review migration files before running them
+- Use Doctrine annotations or attributes for entity mapping
+- Follow existing entity patterns for relationships and lifecycle callbacks
+
+### Frontend
+
+- Use Stimulus controllers for interactive components
+- Leverage Turbo for dynamic page updates without full page reloads
+- Use Bootstrap 5 classes for styling
+- Keep JavaScript modular and organized in controllers
+- Use the translation system for user-facing strings
+
+### Translations
+
+- Use translation keys, not hardcoded strings: `{{ 'part.info.title'|trans }}`
+- Add new translation keys to `translations/` files
+- Primary language is English (en)
+- Translations are managed via Crowdin, but can be edited locally if needed
+
+### Testing
+
+- Write unit tests for services and helpers
+- Write functional tests for controllers
+- Use fixtures for test data
+- Tests should be isolated and not depend on execution order
+- Mock external dependencies when appropriate
+- Follow existing test patterns in the repository
+
+## Common Patterns
+
+### Creating an Entity
+
+1. Create entity class in `src/Entity/` with Doctrine attributes
+2. Generate migration: `php bin/console make:migration`
+3. Review and run migration: `php bin/console doctrine:migrations:migrate`
+4. Create repository if needed in `src/Repository/`
+5. Add fixtures in `src/DataFixtures/` for testing
+
+### Adding a Form
+
+1. Create form type in `src/Form/`
+2. Extend `AbstractType` and implement `buildForm()` and `configureOptions()`
+3. Use in controller and render in Twig template
+4. Follow existing form patterns for consistency
+
+### Creating a Controller Action
+
+1. Add method to appropriate controller in `src/Controller/`
+2. Use route attributes for routing
+3. Check permissions using security voters
+4. Return Response or render Twig template
+5. Add corresponding template in `templates/`
+
+### Adding a Service
+
+1. Create service class in `src/Services/`
+2. Use dependency injection via constructor
+3. Tag service in `config/services.yaml` if needed
+4. Services are autowired by default
+
+## Important Notes
+
+- Part-DB uses fine-grained permissions - always check user permissions before actions
+- Multi-language support is critical - use translation keys everywhere
+- The application supports multiple database backends - write portable code
+- Responsive design is important - test on mobile/tablet viewports
+- Event system is used for logging changes - emit events when appropriate
+- API Platform is integrated for REST API endpoints
+
+## Multi-tenancy Considerations
+
+- Part-DB is designed as a single-tenant application with multiple users
+- User groups have different permission levels
+- Always scope queries to respect user permissions
+- Use the security context to get current user information
+
+## Resources
+
+- [Documentation](https://docs.part-db.de/)
+- [Contributing Guide](CONTRIBUTING.md)
+- [Symfony Documentation](https://symfony.com/doc/current/index.html)
+- [Doctrine Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/current/)
+- [Bootstrap 5 Documentation](https://getbootstrap.com/docs/5.1/)
+- [Hotwire Documentation](https://hotwired.dev/)
diff --git a/.github/workflows/assets_artifact_build.yml b/.github/workflows/assets_artifact_build.yml
index 447f95bf..a74ae7cc 100644
--- a/.github/workflows/assets_artifact_build.yml
+++ b/.github/workflows/assets_artifact_build.yml
@@ -8,6 +8,9 @@ on:
branches:
- '*'
- "!l10n_*" # Dont test localization branches
+ tags:
+ - 'v*.*.*'
+ - 'v*.*.*-**'
pull_request:
branches:
- '*'
@@ -17,12 +20,14 @@ jobs:
assets_artifact_build:
name: Build assets artifact
runs-on: ubuntu-22.04
+ permissions:
+ contents: write
env:
APP_ENV: prod
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -37,7 +42,7 @@ jobs:
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- - uses: actions/cache@v4
+ - uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
@@ -51,7 +56,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- - uses: actions/cache@v4
+ - uses: actions/cache@v5
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@@ -60,9 +65,9 @@ jobs:
${{ runner.os }}-yarn-
- name: Setup node
- uses: actions/setup-node@v5
+ uses: actions/setup-node@v6
with:
- node-version: '20'
+ node-version: '22'
- name: Install yarn dependencies
run: yarn install
@@ -80,13 +85,20 @@ jobs:
run: zip -r /tmp/partdb_assets.zip public/build/ vendor/
- name: Upload assets artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: Only dependencies and built assets
path: /tmp/partdb_assets.zip
- name: Upload full artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: Full Part-DB including dependencies and built assets
path: /tmp/partdb_with_assets.zip
+
+ - name: Upload assets as release attachment
+ if: startsWith(github.ref, 'refs/tags/')
+ run: |
+ gh release upload "${{ github.ref_name }}" /tmp/partdb_assets.zip /tmp/partdb_with_assets.zip --clobber
+ env:
+ GH_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml
index c912e769..210dbc18 100644
--- a/.github/workflows/docker_build.yml
+++ b/.github/workflows/docker_build.yml
@@ -15,16 +15,28 @@ on:
- 'v*.*.*-**'
jobs:
- docker:
- runs-on: ubuntu-latest
+ build:
+ strategy:
+ matrix:
+ include:
+ - platform: linux/amd64
+ runner: ubuntu-latest
+ platform-slug: amd64
+ - platform: linux/arm64
+ runner: ubuntu-24.04-arm
+ platform-slug: arm64
+ - platform: linux/arm/v7
+ runner: ubuntu-24.04-arm
+ platform-slug: armv7
+ runs-on: ${{ matrix.runner }}
steps:
-
name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
-
name: Docker meta
id: docker_meta
- uses: docker/metadata-action@v5
+ uses: docker/metadata-action@v6
with:
# list of Docker images to use as base name for tags
images: |
@@ -32,13 +44,12 @@ jobs:
# Mark the image build from master as latest (as we dont have really releases yet)
tags: |
type=edge,branch=master
- type=ref,event=branch,
- type=ref,event=tag,
+ type=ref,event=branch
+ type=ref,event=tag
type=schedule
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- type=ref,event=branch
type=ref,event=pr
labels: |
org.opencontainers.image.source=${{ github.event.repository.clone_url }}
@@ -49,31 +60,101 @@ jobs:
org.opencontainers.image.source=https://github.com/Part-DB/Part-DB-symfony
org.opencontainers.image.authors=Jan Böhmer
org.opencontainers.licenses=AGPL-3.0-or-later
+ # Disable automatic 'latest' tag in build jobs - it will be created in merge job
+ flavor: |
+ latest=false
- -
- name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- with:
- platforms: 'arm64,arm'
-
name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
+ uses: docker/setup-buildx-action@v4
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
- uses: docker/login-action@v3
+ uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
- name: Build and push
- uses: docker/build-push-action@v6
+ name: Build and push by digest
+ id: build
+ uses: docker/build-push-action@v7
with:
context: .
- platforms: linux/amd64,linux/arm64,linux/arm/v7
- push: ${{ github.event_name != 'pull_request' }}
- tags: ${{ steps.docker_meta.outputs.tags }}
+ platforms: ${{ matrix.platform }}
labels: ${{ steps.docker_meta.outputs.labels }}
- cache-from: type=gha
- cache-to: type=gha,mode=max
+ outputs: type=image,name=jbtronics/part-db1,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
+ cache-from: type=gha,scope=build-${{ matrix.platform }}
+ cache-to: type=gha,mode=max,scope=build-${{ matrix.platform }}
+
+ -
+ name: Export digest
+ if: github.event_name != 'pull_request'
+ run: |
+ mkdir -p /tmp/digests
+ digest="${{ steps.build.outputs.digest }}"
+ touch "/tmp/digests/${digest#sha256:}"
+
+ -
+ name: Upload digest
+ if: github.event_name != 'pull_request'
+ uses: actions/upload-artifact@v7
+ with:
+ name: digests-${{ matrix.platform-slug }}
+ path: /tmp/digests/*
+ if-no-files-found: error
+ retention-days: 1
+
+ merge:
+ runs-on: ubuntu-latest
+ needs:
+ - build
+ if: github.event_name != 'pull_request'
+ steps:
+ -
+ name: Download digests
+ uses: actions/download-artifact@v8
+ with:
+ path: /tmp/digests
+ pattern: digests-*
+ merge-multiple: true
+
+ -
+ name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ -
+ name: Docker meta
+ id: docker_meta
+ uses: docker/metadata-action@v6
+ with:
+ images: |
+ jbtronics/part-db1
+ tags: |
+ type=edge,branch=master
+ type=ref,event=branch
+ type=ref,event=tag
+ type=schedule
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=semver,pattern={{major}}
+ type=ref,event=pr
+
+ -
+ name: Login to DockerHub
+ uses: docker/login-action@v4
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ -
+ name: Create manifest list and push
+ working-directory: /tmp/digests
+ run: |
+ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+ $(printf 'jbtronics/part-db1@sha256:%s ' *)
+
+ -
+ name: Inspect image
+ run: |
+ docker buildx imagetools inspect jbtronics/part-db1:${{ steps.docker_meta.outputs.version }}
diff --git a/.github/workflows/docker_frankenphp.yml b/.github/workflows/docker_frankenphp.yml
index 0b2eb874..36ec322d 100644
--- a/.github/workflows/docker_frankenphp.yml
+++ b/.github/workflows/docker_frankenphp.yml
@@ -15,16 +15,28 @@ on:
- 'v*.*.*-**'
jobs:
- docker:
- runs-on: ubuntu-latest
+ build:
+ strategy:
+ matrix:
+ include:
+ - platform: linux/amd64
+ runner: ubuntu-latest
+ platform-slug: amd64
+ - platform: linux/arm64
+ runner: ubuntu-24.04-arm
+ platform-slug: arm64
+ - platform: linux/arm/v7
+ runner: ubuntu-24.04-arm
+ platform-slug: armv7
+ runs-on: ${{ matrix.runner }}
steps:
-
name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
-
name: Docker meta
id: docker_meta
- uses: docker/metadata-action@v5
+ uses: docker/metadata-action@v6
with:
# list of Docker images to use as base name for tags
images: |
@@ -32,13 +44,12 @@ jobs:
# Mark the image build from master as latest (as we dont have really releases yet)
tags: |
type=edge,branch=master
- type=ref,event=branch,
- type=ref,event=tag,
+ type=ref,event=branch
+ type=ref,event=tag
type=schedule
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- type=ref,event=branch
type=ref,event=pr
labels: |
org.opencontainers.image.source=${{ github.event.repository.clone_url }}
@@ -49,32 +60,102 @@ jobs:
org.opencontainers.image.source=https://github.com/Part-DB/Part-DB-server
org.opencontainers.image.authors=Jan Böhmer
org.opencontainers.licenses=AGPL-3.0-or-later
+ # Disable automatic 'latest' tag in build jobs - it will be created in merge job
+ flavor: |
+ latest=false
- -
- name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- with:
- platforms: 'arm64,arm'
-
name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
+ uses: docker/setup-buildx-action@v4
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
- uses: docker/login-action@v3
+ uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
- name: Build and push
- uses: docker/build-push-action@v6
+ name: Build and push by digest
+ id: build
+ uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile-frankenphp
- platforms: linux/amd64,linux/arm64,linux/arm/v7
- push: ${{ github.event_name != 'pull_request' }}
- tags: ${{ steps.docker_meta.outputs.tags }}
+ platforms: ${{ matrix.platform }}
labels: ${{ steps.docker_meta.outputs.labels }}
- cache-from: type=gha
- cache-to: type=gha,mode=max
+ outputs: type=image,name=partdborg/part-db,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
+ cache-from: type=gha,scope=build-${{ matrix.platform }}
+ cache-to: type=gha,mode=max,scope=build-${{ matrix.platform }}
+
+ -
+ name: Export digest
+ if: github.event_name != 'pull_request'
+ run: |
+ mkdir -p /tmp/digests
+ digest="${{ steps.build.outputs.digest }}"
+ touch "/tmp/digests/${digest#sha256:}"
+
+ -
+ name: Upload digest
+ if: github.event_name != 'pull_request'
+ uses: actions/upload-artifact@v7
+ with:
+ name: digests-${{ matrix.platform-slug }}
+ path: /tmp/digests/*
+ if-no-files-found: error
+ retention-days: 1
+
+ merge:
+ runs-on: ubuntu-latest
+ needs:
+ - build
+ if: github.event_name != 'pull_request'
+ steps:
+ -
+ name: Download digests
+ uses: actions/download-artifact@v8
+ with:
+ path: /tmp/digests
+ pattern: digests-*
+ merge-multiple: true
+
+ -
+ name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ -
+ name: Docker meta
+ id: docker_meta
+ uses: docker/metadata-action@v6
+ with:
+ images: |
+ partdborg/part-db
+ tags: |
+ type=edge,branch=master
+ type=ref,event=branch
+ type=ref,event=tag
+ type=schedule
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=semver,pattern={{major}}
+ type=ref,event=pr
+
+ -
+ name: Login to DockerHub
+ uses: docker/login-action@v4
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ -
+ name: Create manifest list and push
+ working-directory: /tmp/digests
+ run: |
+ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+ $(printf 'partdborg/part-db@sha256:%s ' *)
+
+ -
+ name: Inspect image
+ run: |
+ docker buildx imagetools inspect partdborg/part-db:${{ steps.docker_meta.outputs.version }}
diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml
index 1de98ee9..f47ce87b 100644
--- a/.github/workflows/static_analysis.yml
+++ b/.github/workflows/static_analysis.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -34,7 +34,7 @@ jobs:
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- - uses: actions/cache@v4
+ - uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index c7c0965b..5b756228 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -46,7 +46,7 @@ jobs:
if: matrix.db-type == 'postgres'
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -81,7 +81,7 @@ jobs:
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- - uses: actions/cache@v4
+ - uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
@@ -92,7 +92,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- - uses: actions/cache@v4
+ - uses: actions/cache@v5
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@@ -104,9 +104,9 @@ jobs:
run: composer install --prefer-dist --no-progress
- name: Setup node
- uses: actions/setup-node@v5
+ uses: actions/setup-node@v6
with:
- node-version: '20'
+ node-version: '22'
- name: Install yarn dependencies
run: yarn install
@@ -129,7 +129,7 @@ jobs:
run: ./bin/phpunit --coverage-clover=coverage.xml
- name: Upload coverage
- uses: codecov/codecov-action@v5
+ uses: codecov/codecov-action@v6
with:
env_vars: PHP_VERSION,DB_TYPE
token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.gitignore b/.gitignore
index dd5c43db..704d6202 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,10 @@
uploads/*
!uploads/.keep
+# Some people use Certbot or similar tools to make SSL certificates.
+# Also see https://www.rfc-editor.org/rfc/rfc5785
+public/.well-known/
+
# Do not keep cache files
.php_cs.cache
.phpcs-cache
@@ -50,4 +54,11 @@ phpstan.neon
###< phpstan/phpstan ###
.claude/
-CLAUDE.md
\ No newline at end of file
+CLAUDE.md
+
+.codex
+migrations/.codex
+docker-data/
+scripts/
+db/
+docker-compose.yaml
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d31c904e..5994a115 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,7 +1,7 @@
# How to contribute
-Thank you for consider to contribute to Part-DB!
-Please read the text below, so your contributed content can be contributed easily to Part-DB.
+Thank you for considering contributing to Part-DB!
+Please read the text below, so your contributed content can be incorporated into Part-DB easily.
You can contribute to Part-DB in various ways:
* Report bugs and request new features via [issues](https://github.com/Part-DB/Part-DB-server/issues)
@@ -18,38 +18,38 @@ Part-DB uses translation keys (e.g. part.info.title) that are sorted by their us
was translated in other languages (this is possible via the "Other languages" dropdown in the translation editor).
## Project structure
-Part-DB uses symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html).
+Part-DB uses Symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html).
Interesting folders are:
-* `public`: Everything in this directory will be publicy accessible via web. Use this folder to serve static images.
-* `assets`: The frontend assets are saved here. You can find the javascript and CSS code here.
-* `src`: Part-DB's PHP code is saved here. Note that the sub directories are structured by the classes purposes (so use `Controller` Controllers, `Entities` for Database models, etc.)
-* `translations`: The translations used in Part-DB are saved here
+* `public`: Everything in this directory will be publicly accessible via web. Use this folder to serve static images.
+* `assets`: The frontend assets are saved here. You can find the JavaScript and CSS code here.
+* `src`: Part-DB's PHP code is saved here. Note that the subdirectories are structured by the classes' purposes (so use `Controller` for Controllers, `Entity` for Database models, etc.)
+* `translations`: The translations used in Part-DB are saved here.
* `templates`: The templates (HTML) that are used by Twig to render the different pages. Email templates are also saved here.
-* `tests/`: Tests that can be run by PHPunit.
+* `tests/`: Tests that can be run by PHPUnit.
## Development environment
-For setting up an development you will need to install PHP, composer, a database server (MySQL or MariaDB) and yarn (which needs an nodejs environment).
-* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (symfony profiler) and other features that
+For setting up a development environment, you will need to install PHP, Composer, a database server (MySQL or MariaDB) and yarn (which needs a Node.js environment).
+* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (Symfony profiler) and other features that
will simplify development.
-* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies
-* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files
-* For running Part-DB it is recommended to use [Symfony CLI](https://symfony.com/download).
-That way you can run a correct configured webserver with `symfony serve`
+* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies.
+* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files.
+* For running Part-DB, it is recommended to use [Symfony CLI](https://symfony.com/download).
+That way you can run a correctly configured webserver with `symfony serve`.
## Coding style
-Code should follow the [PSR12-Standard](https://www.php-fig.org/psr/psr-12/) and symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html).
+Code should follow the [PSR-12 Standard](https://www.php-fig.org/psr/psr-12/) and Symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html).
Part-DB uses [Easy Coding Standard](https://github.com/symplify/easy-coding-standard) to check and fix coding style violations:
-* To check your code for valid code style run `vendor/bin/ecs check src/`
-* To fix violations run `vendor/bin/ecs check src/` (please checks afterwards if the code is valid afterwards)
+* To check your code for valid code style, run `vendor/bin/ecs check src/`
+* To fix violations, run `vendor/bin/ecs check src/ --fix` (please check afterwards if the code is still valid)
## GitHub actions
-Part-DB uses GitHub actions to run various tests and checks on the code:
+Part-DB uses GitHub Actions to run various tests and checks on the code:
* Yarn dependencies can compile
-* PHPunit tests run successful
-* Config files, translations and templates has valid syntax
-* Doctrine schema valid
-* No known vulnerable dependecies are used
-* Static analysis successful (phpstan with `--level=2`)
+* PHPUnit tests run successfully
+* Config files, translations, and templates have valid syntax
+* Doctrine schema is valid
+* No known vulnerable dependencies are used
+* Static analysis is successful (phpstan with `--level=2`)
-Further the code coverage of the PHPunit tests is determined and uploaded to [CodeCov](https://codecov.io/gh/Part-DB/Part-DB-server).
+Further, the code coverage of the PHPUnit tests is determined and uploaded to [CodeCov](https://codecov.io/gh/Part-DB/Part-DB-server).
diff --git a/Dockerfile b/Dockerfile
index cb18c78f..e848acc1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,15 +1,75 @@
+# syntax=docker/dockerfile:1
ARG BASE_IMAGE=debian:bookworm-slim
ARG PHP_VERSION=8.4
+ARG NODE_VERSION=22
+# Node.js build stage for building frontend assets
+# Use native platform for build stage as it's platform-independent
+FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-bookworm-slim AS node-builder
+ARG TARGETARCH
+WORKDIR /app
+# Install composer and minimal PHP for running Symfony commands
+COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
+
+# Use BuildKit cache mounts for apt in builder stage
+RUN --mount=type=cache,id=apt-cache-node-$TARGETARCH,target=/var/cache/apt \
+ --mount=type=cache,id=apt-lists-node-$TARGETARCH,target=/var/lib/apt/lists \
+ apt-get update && apt-get install -y --no-install-recommends \
+ php-cli \
+ php-xml \
+ php-mbstring \
+ unzip \
+ git \
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+# Copy composer files and install dependencies (needed for Symfony UX assets)
+COPY composer.json composer.lock symfony.lock ./
+
+# Use BuildKit cache for Composer downloads
+RUN --mount=type=cache,id=composer-cache,target=/root/.cache/composer \
+ composer install --no-scripts --no-autoloader --no-dev --prefer-dist --ignore-platform-reqs
+
+# Copy all application files needed for cache warmup and webpack build
+COPY .env* ./
+COPY bin ./bin
+COPY config ./config
+COPY src ./src
+COPY translations ./translations
+COPY public ./public
+COPY assets ./assets
+COPY webpack.config.js ./
+
+# Generate autoloader
+RUN composer dump-autoload
+
+# Create required directories for cache warmup
+RUN mkdir -p var/cache var/log uploads public/media
+
+# Dump translations, which we need for cache warmup
+RUN php bin/console cache:warmup -n --env=prod 2>&1
+
+# Copy package files and install node dependencies
+COPY package.json yarn.lock ./
+# Use BuildKit cache for yarn/npm
+RUN --mount=type=cache,id=yarn-cache,target=/root/.cache/yarn \
+ --mount=type=cache,id=npm-cache,target=/root/.npm \
+ yarn install --network-timeout 600000
+
+# Build the assets
+RUN yarn build
+
+# Clean up
+RUN yarn cache clean && rm -rf node_modules/
+
+# Base stage for PHP
FROM ${BASE_IMAGE} AS base
ARG PHP_VERSION
+ARG TARGETARCH
-# Install needed dependencies for PHP build
-#RUN apt-get update && apt-get install -y pkg-config curl libcurl4-openssl-dev libicu-dev \
-# libpng-dev libjpeg-dev libfreetype6-dev gnupg zip libzip-dev libjpeg62-turbo-dev libonig-dev libxslt-dev libwebp-dev vim \
-# && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
-
-RUN apt-get update && apt-get -y install \
+# Use BuildKit cache mounts for apt in base stage
+RUN --mount=type=cache,id=apt-cache-$TARGETARCH,target=/var/cache/apt \
+ --mount=type=cache,id=apt-lists-$TARGETARCH,target=/var/lib/apt/lists \
+ apt-get update && apt-get -y install \
apt-transport-https \
lsb-release \
ca-certificates \
@@ -39,21 +99,10 @@ RUN apt-get update && apt-get -y install \
gpg \
sudo \
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/* \
-# Create workdir and set permissions if directory does not exists
&& mkdir -p /var/www/html \
&& chown -R www-data:www-data /var/www/html \
-# delete the "index.html" that installing Apache drops in here
&& rm -rvf /var/www/html/*
-# Install node and yarn
-RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
- echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
- curl -sL https://deb.nodesource.com/setup_22.x | bash - && \
- apt-get update && apt-get install -y \
- nodejs \
- yarn \
- && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
-
# Install composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
@@ -67,14 +116,12 @@ ENV APACHE_ENVVARS=$APACHE_CONFDIR/envvars
# : ${APACHE_RUN_USER:=www-data}
# export APACHE_RUN_USER
# so that they can be overridden at runtime ("-e APACHE_RUN_USER=...")
-RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \
- set -eux; . "$APACHE_ENVVARS"; \
- \
- # logs should go to stdout / stderr
- ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \
- ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log"; \
- ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \
- chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR";
+RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS" && \
+ set -eux; . "$APACHE_ENVVARS" && \
+ ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log" && \
+ ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log" && \
+ ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log" && \
+ chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR"
# ---
@@ -143,7 +190,6 @@ COPY --chown=www-data:www-data . .
# Setup apache2
RUN a2dissite 000-default.conf && \
a2ensite symfony.conf && \
-# Enable php-fpm
a2enmod proxy_fcgi setenvif && \
a2enconf php${PHP_VERSION}-fpm && \
a2enconf docker-php && \
@@ -151,12 +197,13 @@ RUN a2dissite 000-default.conf && \
# Install composer and yarn dependencies for Part-DB
USER www-data
-RUN composer install -a --no-dev && \
+# Use BuildKit cache for Composer when running as www-data by setting COMPOSER_CACHE_DIR
+RUN --mount=type=cache,id=composer-cache,target=/tmp/.composer-cache \
+ COMPOSER_CACHE_DIR=/tmp/.composer-cache composer install -a --no-dev && \
composer clear-cache
-RUN yarn install --network-timeout 600000 && \
- yarn build && \
- yarn cache clean && \
- rm -rf node_modules/
+
+# Copy built frontend assets from node-builder stage
+COPY --from=node-builder --chown=www-data:www-data /app/public/build ./public/build
# Use docker env to output logs to stdout
ENV APP_ENV=docker
@@ -168,10 +215,12 @@ USER root
RUN sed -i "s/PHP_VERSION/${PHP_VERSION}/g" ./.docker/partdb-entrypoint.sh
# Copy entrypoint and apache2-foreground to /usr/local/bin and make it executable
-RUN install ./.docker/partdb-entrypoint.sh /usr/local/bin && \
- install ./.docker/apache2-foreground /usr/local/bin
+# Convert CRLF -> LF and install entrypoint scripts with executable mode
+RUN sed -i 's/\r$//' ./.docker/partdb-entrypoint.sh ./.docker/apache2-foreground && \
+ install -m 0755 ./.docker/partdb-entrypoint.sh /usr/local/bin/ && \
+ install -m 0755 ./.docker/apache2-foreground /usr/local/bin/
ENTRYPOINT ["partdb-entrypoint.sh"]
-CMD ["apache2-foreground"]
+CMD ["/usr/local/bin/apache2-foreground"]
# https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop
STOPSIGNAL SIGWINCH
diff --git a/Dockerfile-frankenphp b/Dockerfile-frankenphp
index f381f330..bdf9c1fd 100644
--- a/Dockerfile-frankenphp
+++ b/Dockerfile-frankenphp
@@ -1,6 +1,72 @@
-FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream
+ARG NODE_VERSION=22
-RUN apt-get update && apt-get -y install \
+# Node.js build stage for building frontend assets
+# Use native platform for build stage as it's platform-independent
+FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-bookworm-slim AS node-builder
+ARG TARGETARCH
+WORKDIR /app
+
+# Install composer and minimal PHP for running Symfony commands
+COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
+
+# Use BuildKit cache mounts for apt in builder stage
+RUN --mount=type=cache,id=apt-cache-node-$TARGETARCH,target=/var/cache/apt \
+ --mount=type=cache,id=apt-lists-node-$TARGETARCH,target=/var/lib/apt/lists \
+ apt-get update && apt-get install -y --no-install-recommends \
+ php-cli \
+ php-xml \
+ php-mbstring \
+ unzip \
+ git \
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+# Copy composer files and install dependencies (needed for Symfony UX assets)
+COPY composer.json composer.lock symfony.lock ./
+
+# Use BuildKit cache for Composer downloads
+RUN --mount=type=cache,id=composer-cache,target=/root/.cache/composer \
+ composer install --no-scripts --no-autoloader --no-dev --prefer-dist --ignore-platform-reqs
+
+# Copy all application files needed for cache warmup and webpack build
+COPY .env* ./
+COPY bin ./bin
+COPY config ./config
+COPY src ./src
+COPY translations ./translations
+COPY public ./public
+COPY assets ./assets
+COPY webpack.config.js ./
+
+# Generate autoloader
+RUN composer dump-autoload
+
+# Create required directories for cache warmup
+RUN mkdir -p var/cache var/log uploads public/media
+
+# Dump translations, which we need for cache warmup
+RUN php bin/console cache:warmup -n --env=prod 2>&1
+
+# Copy package files and install node dependencies
+COPY package.json yarn.lock ./
+
+# Use BuildKit cache for yarn/npm
+RUN --mount=type=cache,id=yarn-cache,target=/root/.cache/yarn \
+ --mount=type=cache,id=npm-cache,target=/root/.npm \
+ yarn install --network-timeout 600000
+
+
+# Build the assets
+RUN yarn build
+
+# Clean up
+RUN yarn cache clean && rm -rf node_modules/
+
+# FrankenPHP base stage
+FROM dunglas/frankenphp:1-php8.4-bookworm AS frankenphp_upstream
+ARG TARGETARCH
+RUN --mount=type=cache,id=apt-cache-$TARGETARCH,target=/var/cache/apt \
+ --mount=type=cache,id=apt-lists-$TARGETARCH,target=/var/lib/apt/lists \
+ apt-get update && apt-get -y install \
curl \
ca-certificates \
mariadb-client \
@@ -13,34 +79,6 @@ RUN apt-get update && apt-get -y install \
zip \
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
-RUN set -eux; \
- # Prepare keyrings directory
- mkdir -p /etc/apt/keyrings; \
- \
- # Import Yarn GPG key
- curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg \
- | tee /etc/apt/keyrings/yarn.gpg >/dev/null; \
- chmod 644 /etc/apt/keyrings/yarn.gpg; \
- \
- # Add Yarn repo with signed-by
- echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian stable main" \
- | tee /etc/apt/sources.list.d/yarn.list; \
- \
- # Run NodeSource setup script (unchanged)
- curl -sL https://deb.nodesource.com/setup_22.x | bash -; \
- \
- # Install Node.js + Yarn
- apt-get update; \
- apt-get install -y --no-install-recommends \
- nodejs \
- yarn; \
- \
- # Cleanup
- apt-get -y autoremove; \
- apt-get clean autoclean; \
- rm -rf /var/lib/apt/lists/*
-
-
# Install PHP
RUN set -eux; \
install-php-extensions \
@@ -86,14 +124,11 @@ COPY --link . ./
RUN set -eux; \
mkdir -p var/cache var/log; \
composer dump-autoload --classmap-authoritative --no-dev; \
- composer dump-env prod; \
composer run-script --no-dev post-install-cmd; \
chmod +x bin/console; sync;
-RUN yarn install --network-timeout 600000 && \
- yarn build && \
- yarn cache clean && \
- rm -rf node_modules/
+# Copy built frontend assets from node-builder stage
+COPY --from=node-builder /app/public/build ./public/build
# Use docker env to output logs to stdout
ENV APP_ENV=docker
@@ -112,8 +147,8 @@ VOLUME ["/var/www/html/uploads", "/var/www/html/public/media"]
HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1
# See https://caddyserver.com/docs/conventions#file-locations for details
-ENV XDG_CONFIG_HOME /config
-ENV XDG_DATA_HOME /data
+ENV XDG_CONFIG_HOME=/config
+ENV XDG_DATA_HOME=/data
EXPOSE 80
EXPOSE 443
diff --git a/README.md b/README.md
index 291b574a..3c738025 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,3 @@
-[](https://scrutinizer-ci.com/g/Part-DB/Part-DB-symfony/?branch=master)


[](https://codecov.io/gh/Part-DB/Part-DB-server)
@@ -29,8 +28,8 @@ If you want to test Part-DB without installing it, you can use [this](https://de
You can log in with username: *user* and password: *user*.
-Every change to the master branch gets automatically deployed, so it represents the current development progress and is
-may not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
+Every change to the master branch gets automatically deployed, so it represents the current development progress and
+may not be completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
the page
for the first time.
@@ -62,6 +61,8 @@ for the first time.
* Automatic thumbnail generation for pictures
* Use cloud providers (like Octopart, Digikey, Farnell, LCSC or TME) to automatically get part information, datasheets, and
prices for parts
+* Retrieve part information from arbitrary shop websites, using either conventional data extraction from structured metadata, or AI based data extraction.
+A browser plugin allows to quickly submit parts from any website to your Part-DB instance, and even allows to circumvent anti-bot measures on shop websites.
* API to access Part-DB from other applications/scripts
* [Integration with KiCad](https://docs.part-db.de/usage/eda_integration.html): Use Part-DB as the central datasource for your
KiCad and see available parts from Part-DB directly inside KiCad.
@@ -74,11 +75,11 @@ Part-DB is also used by small companies and universities for managing their inve
## Requirements
* A **web server** (like Apache2 or nginx) that is capable of
- running [Symfony 6](https://symfony.com/doc/current/reference/requirements.html),
+ running [Symfony 7](https://symfony.com/doc/current/reference/requirements.html),
this includes a minimum PHP version of **PHP 8.2**
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.4) database server, or **PostgreSQL** 10+ if you do not want to use SQLite.
* Shell access to your server is highly recommended!
-* For building the client-side assets **yarn** and **nodejs** (>= 20.0) is needed.
+* For building the client-side assets **yarn** and **nodejs** (>= 22.0) is needed.
## Installation
@@ -142,7 +143,7 @@ There you will find various methods to support development on a monthly or a one
## Built with
-* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP
+* [Symfony 6](https://symfony.com/): The main framework used for the serverside PHP
* [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme
* [Fontawesome](https://fontawesome.com/): Used as icon set
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend
diff --git a/VERSION b/VERSION
index c043eea7..d8b69897 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.2.1
+2.12.0
diff --git a/assets/commands/kicad_populate_default_mappings.json b/assets/commands/kicad_populate_default_mappings.json
new file mode 100644
index 00000000..a942667c
--- /dev/null
+++ b/assets/commands/kicad_populate_default_mappings.json
@@ -0,0 +1,206 @@
+{
+ "_comment": "Default KiCad footprint/symbol mappings for partdb:kicad:populate command. Based on KiCad 9.x standard libraries. Use --mapping-file to override or extend these mappings.",
+ "footprints": {
+ "SOT-23": "Package_TO_SOT_SMD:SOT-23",
+ "SOT-23-3": "Package_TO_SOT_SMD:SOT-23",
+ "SOT-23-5": "Package_TO_SOT_SMD:SOT-23-5",
+ "SOT-23-6": "Package_TO_SOT_SMD:SOT-23-6",
+ "SOT-223": "Package_TO_SOT_SMD:SOT-223-3_TabPin2",
+ "SOT-223-3": "Package_TO_SOT_SMD:SOT-223-3_TabPin2",
+ "SOT-89": "Package_TO_SOT_SMD:SOT-89-3",
+ "SOT-89-3": "Package_TO_SOT_SMD:SOT-89-3",
+ "SOT-323": "Package_TO_SOT_SMD:SOT-323_SC-70",
+ "SOT-363": "Package_TO_SOT_SMD:SOT-363_SC-70-6",
+ "TSOT-25": "Package_TO_SOT_SMD:SOT-23-5",
+ "SC-70-5": "Package_TO_SOT_SMD:SOT-353_SC-70-5",
+ "SC-70-6": "Package_TO_SOT_SMD:SOT-363_SC-70-6",
+ "TO-220": "Package_TO_SOT_THT:TO-220-3_Vertical",
+ "TO-220AB": "Package_TO_SOT_THT:TO-220-3_Vertical",
+ "TO-220AB-3": "Package_TO_SOT_THT:TO-220-3_Vertical",
+ "TO-220FP": "Package_TO_SOT_THT:TO-220F-3_Vertical",
+ "TO-247-3": "Package_TO_SOT_THT:TO-247-3_Vertical",
+ "TO-92": "Package_TO_SOT_THT:TO-92_Inline",
+ "TO-92-3": "Package_TO_SOT_THT:TO-92_Inline",
+ "TO-252": "Package_TO_SOT_SMD:TO-252-2",
+ "TO-252-2L": "Package_TO_SOT_SMD:TO-252-2",
+ "TO-252-3L": "Package_TO_SOT_SMD:TO-252-3",
+ "TO-263": "Package_TO_SOT_SMD:TO-263-2",
+ "TO-263-2": "Package_TO_SOT_SMD:TO-263-2",
+ "D2PAK": "Package_TO_SOT_SMD:TO-252-2",
+ "DPAK": "Package_TO_SOT_SMD:TO-252-2",
+ "SOIC-8": "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
+ "ESOP-8": "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
+ "SOIC-14": "Package_SO:SOIC-14_3.9x8.7mm_P1.27mm",
+ "SOIC-16": "Package_SO:SOIC-16_3.9x9.9mm_P1.27mm",
+ "TSSOP-8": "Package_SO:TSSOP-8_3x3mm_P0.65mm",
+ "TSSOP-14": "Package_SO:TSSOP-14_4.4x5mm_P0.65mm",
+ "TSSOP-16": "Package_SO:TSSOP-16_4.4x5mm_P0.65mm",
+ "TSSOP-16L": "Package_SO:TSSOP-16_4.4x5mm_P0.65mm",
+ "TSSOP-20": "Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm",
+ "MSOP-8": "Package_SO:MSOP-8_3x3mm_P0.65mm",
+ "MSOP-10": "Package_SO:MSOP-10_3x3mm_P0.5mm",
+ "MSOP-16": "Package_SO:MSOP-16_3x4mm_P0.5mm",
+ "SO-5": "Package_TO_SOT_SMD:SOT-23-5",
+ "DIP-4": "Package_DIP:DIP-4_W7.62mm",
+ "DIP-6": "Package_DIP:DIP-6_W7.62mm",
+ "DIP-8": "Package_DIP:DIP-8_W7.62mm",
+ "DIP-14": "Package_DIP:DIP-14_W7.62mm",
+ "DIP-16": "Package_DIP:DIP-16_W7.62mm",
+ "DIP-18": "Package_DIP:DIP-18_W7.62mm",
+ "DIP-20": "Package_DIP:DIP-20_W7.62mm",
+ "DIP-24": "Package_DIP:DIP-24_W7.62mm",
+ "DIP-28": "Package_DIP:DIP-28_W7.62mm",
+ "DIP-40": "Package_DIP:DIP-40_W15.24mm",
+ "QFN-8": "Package_DFN_QFN:QFN-8-1EP_3x3mm_P0.65mm_EP1.55x1.55mm",
+ "QFN-12(3x3)": "Package_DFN_QFN:QFN-12-1EP_3x3mm_P0.5mm_EP1.65x1.65mm",
+ "QFN-16": "Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.45x1.45mm",
+ "QFN-20": "Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.5x2.5mm",
+ "QFN-24": "Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.45x2.45mm",
+ "QFN-32": "Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm",
+ "QFN-48": "Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.3x5.3mm",
+ "TQFP-32": "Package_QFP:TQFP-32_7x7mm_P0.8mm",
+ "TQFP-44": "Package_QFP:TQFP-44_10x10mm_P0.8mm",
+ "TQFP-48": "Package_QFP:TQFP-48_7x7mm_P0.5mm",
+ "TQFP-48(7x7)": "Package_QFP:TQFP-48_7x7mm_P0.5mm",
+ "TQFP-64": "Package_QFP:TQFP-64_10x10mm_P0.5mm",
+ "TQFP-100": "Package_QFP:TQFP-100_14x14mm_P0.5mm",
+ "LQFP-32": "Package_QFP:LQFP-32_7x7mm_P0.8mm",
+ "LQFP-48": "Package_QFP:LQFP-48_7x7mm_P0.5mm",
+ "LQFP-64": "Package_QFP:LQFP-64_10x10mm_P0.5mm",
+ "LQFP-100": "Package_QFP:LQFP-100_14x14mm_P0.5mm",
+
+ "SOD-123": "Diode_SMD:D_SOD-123",
+ "SOD-123F": "Diode_SMD:D_SOD-123F",
+ "SOD-123FL": "Diode_SMD:D_SOD-123F",
+ "SOD-323": "Diode_SMD:D_SOD-323",
+ "SOD-523": "Diode_SMD:D_SOD-523",
+ "SOD-882": "Diode_SMD:D_SOD-882",
+ "SOD-882D": "Diode_SMD:D_SOD-882",
+ "SMA(DO-214AC)": "Diode_SMD:D_SMA",
+ "SMA": "Diode_SMD:D_SMA",
+ "SMB": "Diode_SMD:D_SMB",
+ "SMC": "Diode_SMD:D_SMC",
+
+ "DO-35": "Diode_THT:D_DO-35_SOD27_P7.62mm_Horizontal",
+ "DO-35(DO-204AH)": "Diode_THT:D_DO-35_SOD27_P7.62mm_Horizontal",
+ "DO-41": "Diode_THT:D_DO-41_SOD81_P10.16mm_Horizontal",
+ "DO-201": "Diode_THT:D_DO-201_P15.24mm_Horizontal",
+
+ "DFN-2(0.6x1)": "Package_DFN_QFN:DFN-2-1EP_0.6x1.0mm_P0.65mm_EP0.2x0.55mm",
+ "DFN1006-2": "Package_DFN_QFN:DFN-2_1.0x0.6mm",
+ "DFN-6": "Package_DFN_QFN:DFN-6-1EP_2x2mm_P0.65mm_EP1x1.6mm",
+ "DFN-8": "Package_DFN_QFN:DFN-8-1EP_3x2mm_P0.5mm_EP1.3x1.5mm",
+
+ "0201": "Resistor_SMD:R_0201_0603Metric",
+ "0402": "Resistor_SMD:R_0402_1005Metric",
+ "0603": "Resistor_SMD:R_0603_1608Metric",
+ "0805": "Resistor_SMD:R_0805_2012Metric",
+ "1206": "Resistor_SMD:R_1206_3216Metric",
+ "1210": "Resistor_SMD:R_1210_3225Metric",
+ "1812": "Resistor_SMD:R_1812_4532Metric",
+ "2010": "Resistor_SMD:R_2010_5025Metric",
+ "2512": "Resistor_SMD:R_2512_6332Metric",
+ "2917": "Resistor_SMD:R_2917_7343Metric",
+ "2920": "Resistor_SMD:R_2920_7350Metric",
+
+ "CASE-A-3216-18(mm)": "Capacitor_Tantalum_SMD:CP_EIA-3216-18_Kemet-A",
+ "CASE-B-3528-21(mm)": "Capacitor_Tantalum_SMD:CP_EIA-3528-21_Kemet-B",
+ "CASE-C-6032-28(mm)": "Capacitor_Tantalum_SMD:CP_EIA-6032-28_Kemet-C",
+ "CASE-D-7343-31(mm)": "Capacitor_Tantalum_SMD:CP_EIA-7343-31_Kemet-D",
+ "CASE-E-7343-43(mm)": "Capacitor_Tantalum_SMD:CP_EIA-7343-43_Kemet-E",
+
+ "SMD,D4xL5.4mm": "Capacitor_SMD:CP_Elec_4x5.4",
+ "SMD,D5xL5.4mm": "Capacitor_SMD:CP_Elec_5x5.4",
+ "SMD,D6.3xL5.4mm": "Capacitor_SMD:CP_Elec_6.3x5.4",
+ "SMD,D6.3xL7.7mm": "Capacitor_SMD:CP_Elec_6.3x7.7",
+ "SMD,D8xL6.5mm": "Capacitor_SMD:CP_Elec_8x6.5",
+ "SMD,D8xL10mm": "Capacitor_SMD:CP_Elec_8x10",
+ "SMD,D10xL10mm": "Capacitor_SMD:CP_Elec_10x10",
+ "SMD,D10xL10.5mm": "Capacitor_SMD:CP_Elec_10x10.5",
+
+ "Through Hole,D5xL11mm": "Capacitor_THT:CP_Radial_D5.0mm_P2.00mm",
+ "Through Hole,D6.3xL11mm": "Capacitor_THT:CP_Radial_D6.3mm_P2.50mm",
+ "Through Hole,D8xL11mm": "Capacitor_THT:CP_Radial_D8.0mm_P3.50mm",
+ "Through Hole,D10xL16mm": "Capacitor_THT:CP_Radial_D10.0mm_P5.00mm",
+ "Through Hole,D10xL20mm": "Capacitor_THT:CP_Radial_D10.0mm_P5.00mm",
+ "Through Hole,D12.5xL20mm": "Capacitor_THT:CP_Radial_D12.5mm_P5.00mm",
+
+ "LED 3mm": "LED_THT:LED_D3.0mm",
+ "LED 5mm": "LED_THT:LED_D5.0mm",
+ "LED 0603": "LED_SMD:LED_0603_1608Metric",
+ "LED 0805": "LED_SMD:LED_0805_2012Metric",
+ "SMD5050-4P": "LED_SMD:LED_WS2812B_PLCC4_5.0x5.0mm_P3.2mm",
+ "SMD5050-6P": "LED_SMD:LED_WS2812B_PLCC4_5.0x5.0mm_P3.2mm",
+
+ "HC-49": "Crystal:Crystal_HC49-4H_Vertical",
+ "HC-49/U": "Crystal:Crystal_HC49-4H_Vertical",
+ "HC-49/S": "Crystal:Crystal_HC49-U_Vertical",
+ "HC-49/US": "Crystal:Crystal_HC49-U_Vertical",
+
+ "USB-A": "Connector_USB:USB_A_Stewart_SS-52100-001_Horizontal",
+ "USB-B": "Connector_USB:USB_B_OST_USB-B1HSxx_Horizontal",
+ "USB-Mini-B": "Connector_USB:USB_Mini-B_Lumberg_2486_01_Horizontal",
+ "USB-Micro-B": "Connector_USB:USB_Micro-B_Molex-105017-0001",
+ "USB-C": "Connector_USB:USB_C_Receptacle_GCT_USB4085",
+
+ "1x2 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical",
+ "1x3 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical",
+ "1x4 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical",
+ "1x5 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_Vertical",
+ "1x6 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_Vertical",
+ "1x8 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x08_P2.54mm_Vertical",
+ "1x10 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x10_P2.54mm_Vertical",
+ "2x2 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x02_P2.54mm_Vertical",
+ "2x3 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x03_P2.54mm_Vertical",
+ "2x4 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x04_P2.54mm_Vertical",
+ "2x5 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x05_P2.54mm_Vertical",
+ "2x10 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x10_P2.54mm_Vertical",
+ "2x20 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x20_P2.54mm_Vertical",
+ "SIP-3-2.54mm": "Package_SIP:SIP-3_P2.54mm",
+ "SIP-4-2.54mm": "Package_SIP:SIP-4_P2.54mm",
+ "SIP-5-2.54mm": "Package_SIP:SIP-5_P2.54mm"
+ },
+ "categories": {
+ "Electrolytic": "Device:C_Polarized",
+ "Polarized": "Device:C_Polarized",
+ "Tantalum": "Device:C_Polarized",
+ "Zener": "Device:D_Zener",
+ "Schottky": "Device:D_Schottky",
+ "TVS": "Device:D_TVS",
+ "LED": "Device:LED",
+ "NPN": "Device:Q_NPN_BCE",
+ "PNP": "Device:Q_PNP_BCE",
+ "N-MOSFET": "Device:Q_NMOS_GDS",
+ "NMOS": "Device:Q_NMOS_GDS",
+ "N-MOS": "Device:Q_NMOS_GDS",
+ "P-MOSFET": "Device:Q_PMOS_GDS",
+ "PMOS": "Device:Q_PMOS_GDS",
+ "P-MOS": "Device:Q_PMOS_GDS",
+ "MOSFET": "Device:Q_NMOS_GDS",
+ "JFET": "Device:Q_NJFET_DSG",
+ "Ferrite": "Device:Ferrite_Bead",
+ "Crystal": "Device:Crystal",
+ "Oscillator": "Oscillator:Oscillator_Crystal",
+ "Fuse": "Device:Fuse",
+ "Transformer": "Device:Transformer_1P_1S",
+ "Resistor": "Device:R",
+ "Capacitor": "Device:C",
+ "Inductor": "Device:L",
+ "Diode": "Device:D",
+ "Transistor": "Device:Q_NPN_BCE",
+ "Voltage Regulator": "Regulator_Linear:LM317_TO-220",
+ "LDO": "Regulator_Linear:AMS1117-3.3",
+ "Op-Amp": "Amplifier_Operational:LM358",
+ "Comparator": "Comparator:LM393",
+ "Optocoupler": "Isolator:PC817",
+ "Relay": "Relay:Relay_DPDT",
+ "Connector": "Connector:Conn_01x02",
+ "Switch": "Switch:SW_Push",
+ "Button": "Switch:SW_Push",
+ "Potentiometer": "Device:R_POT",
+ "Trimpot": "Device:R_POT",
+ "Thermistor": "Device:Thermistor",
+ "Varistor": "Device:Varistor",
+ "Photo": "Device:LED"
+ }
+}
diff --git a/assets/controllers/backup_restore_controller.js b/assets/controllers/backup_restore_controller.js
new file mode 100644
index 00000000..85ee327b
--- /dev/null
+++ b/assets/controllers/backup_restore_controller.js
@@ -0,0 +1,55 @@
+/*
+ * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
+ *
+ * Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see