- Remove duplicate php-gd package
- Disable directory listing (Options -Indexes)
- Propagate MYSQL_ROOT_PASS to all mysql commands
- Fix allowedHostnames sed pattern to match .env.example format
The .env file from release uses quoted values like 'localhost'
but sed patterns were looking for unquoted values, causing
database credentials to not be updated.
Release zip contains .env directly, not .env.example. The sed commands
to update database credentials were being skipped because the file check
looked for .env.example first, which doesn't exist in published releases.
Releases use opensourcepos.VERSION.HASH.zip naming format, not
opensourcepos-VERSION.zip. This fix fetches the actual asset URL
from the GitHub API and extracts the correct directory name.
The install script was failing on Ubuntu 22.04 because PHP 8.2 is not
available in default repositories. This fix checks if the requested
PHP version is available, and if not, adds the ondrej/php PPA which
provides all supported PHP versions.
- Prompts user for SSL preferences during installation
- Asks for domain name and email interactively
- Falls back to environment variables for non-interactive mode
- Shows SSL status in final output (Let's Encrypt / self-signed / none)
- Updates INSTALL.md with interactive/non-interactive examples
Interactive mode (recommended):
curl -sSL https://opensourcepos.org/install | sudo bash
# Prompts for SSL, domain, and email
Non-interactive mode:
curl -sSL https://opensourcepos.org/install | \
APACHE_SERVER_NAME=pos.example.com \
SSL_EMAIL=admin@example.com \
sudo -E bash
- Adds Let's Encrypt support for production (with auto-renewal via certbot.timer)
- Falls back to self-signed certificate for development/testing
- New SSL_EMAIL environment variable enables production SSL
- HTTPS redirect automatically configured for all sites
- Updates INSTALL.md with SSL documentation and examples
Production usage:
SSL_EMAIL=admin@example.com APACHE_SERVER_NAME=pos.example.com
Development usage (self-signed cert):
APACHE_SERVER_NAME=localhost (default)
- Fetches latest release version from GitHub API
- Downloads pre-built release zip instead of cloning repo
- Renamed OSPOS_BRANCH to OSPOS_VERSION for clarity
- Supports installing specific version via OSPOS_VERSION
- Removed need for composer install (release is pre-built)
- More stable for production deployments
- Keep DigitalOcean referral link ($100 credit)
- Simplify instructions to 3 steps: create droplet, SSH, run installer
- Move one-line installation section into Cloud Install
- Add security reminder to change password and configure SSL
- Retain link to wiki for manual installation options
- Preferred install URL: https://opensourcepos.org/install
- Falls back to direct GitHub URL if redirect unavailable
- More professional and easier to remember
- Creates scripts/install-ubuntu.sh for automated fresh Ubuntu server setup
- Installs Apache, MariaDB, PHP 8.2 with required extensions
- Downloads and configures OSPOS from GitHub
- Sets up Apache virtual host with proper permissions
- Generates secure random database password
- Supports environment variables for customization
- Updates INSTALL.md with curl pipe to bash instructions
This provides an alternative to cloud-specific instructions and
allows users to quickly set up OSPOS on any fresh Ubuntu server.
* fix: tax rate inputs blank with comma-decimal locales
The to_tax_decimals() function returns locale-formatted values
(e.g. "18,00" for comma-decimal locales like fr_FR, de_DE).
Browsers reject comma-decimal values in <input type="number">
and render the field blank.
Use raw float value instead - PHP serializes floats with period
decimal regardless of locale. The parse_tax() on the save side
already handles locale-aware parsing, so round-tripping works
correctly.
Fixes#4553
Regression from commit 42ba39d29
* fix: tax rate input locale handling - save path
The display fix (using (float) instead of to_tax_decimals()) was
correct but incomplete. The save path in Config.php also needed
fixing because parse_tax() misinterprets dot-decimal values from
type="number" inputs when locale uses comma as decimal separator.
Root cause: Browsers submit type="number" inputs as dot-decimal
(e.g., "5.5") regardless of locale. With comma-decimal locales
like de_DE, parse_tax() treats the dot as thousands separator,
causing 5.5 to be saved as 5.
Fix: Replace parse_tax() with direct (float) cast for these
inputs since type="number" already guarantees dot-decimal format.
Includes tests for tax rate handling with various decimal values.
Fixes#4553
* revert: remove type=number from tax rate inputs
Resolution from PR #4555 review: Revert to text inputs for locale-specific
tax rate fields.
The type='number' attribute was added in commit 42ba39d29, but it caused
issues with locale-specific decimal separators. Browsers submit type='number'
inputs as dot-decimal regardless of locale, which breaks comma-decimal locales.
Solution: Revert to text inputs which use to_tax_decimals() for display
and parse_tax() for saving, correctly handling locale-specific formatting.
Changes:
- tax_config.php: Remove type='number', step, min, max attributes
- tax_config.php: Restore to_tax_decimals() for value display
- Config.php: Restore parse_tax() for tax rate parsing
- ConfigTest.php: Remove tests added for the type='number' approach
Fixes#4553
---------
Co-authored-by: Ollama <ollama@steganos.dev>
* Properly replace key in config file when encryption key is updated
This fixes a break caused if there is a commented out key in the .env. It's a more robust replacement.
Signed-off-by: objec <objecttothis@gmail.com>
* Guard against dropping constraint that doesn't exist
- Updated wording in migration_helper.php PHPdoc
- Use migration_helper function to drop key which only drops the constraint if it exists.
The core logic was not changed here. It only adds a safety mechanism.
Signed-off-by: objec <objecttothis@gmail.com>
* Remove duplicate call to getResultArray in attribute_links loop
Signed-off-by: objec <objecttothis@gmail.com>
* PSR refactoring
Signed-off-by: objec <objecttothis@gmail.com>
* Remove dead parameter from reassignDuplicateAttributeValues method
The attribute value was not needed and is never used since we have the attribute_ids and those are unique.
Signed-off-by: objec <objecttothis@gmail.com>
* Create potentially missing primary keys before attempting to add constraints.
Signed-off-by: objec <objecttothis@gmail.com>
* Guard datetime creation
Signed-off-by: objec <objecttothis@gmail.com>
* Update regex pattern to handle double-quoted and non-quoted encryption keys
Signed-off-by: objec <objecttothis@gmail.com>
* Issue warning and fallback on unparseable attribute_date during
Signed-off-by: objec <objecttothis@gmail.com>
---------
Signed-off-by: objec <objecttothis@gmail.com>
When searching in the Takings view, entering a plain Sale ID (like '123')
did not return any results. The search only worked with customer names
or with the 'POS 123' format.
The issue was that is_valid_receipt() only recognized 'POS ####' format
or invoice numbers, so plain numeric Sale IDs fell through to the
customer name search branch which doesn't search sale_id.
This fix adds sale_id to the search conditions when the search term is
numeric (ctype_digit check), allowing direct Sale ID searches.
Fixes#4567
Co-authored-by: Ollama <ollama@steganos.dev>
* Add fallback for allowedHostnames environment variable
- In some cases allowedHostnames is set in env but not loaded at the time of check, yet available in other sources. This adds fallback checks.
- Add UnitTest
Signed-off-by: objec <objecttothis@gmail.com>
* Improve the fallback logic for allowedHostnames environment variable
Signed-off-by: objec <objecttothis@gmail.com>
---------
Signed-off-by: objec <objecttothis@gmail.com>
basename() returns string and database column values are strings,
but get_latest_migration() and get_current_version() declare int
return types. PHP 8.0+ enforces strict return types and no longer
silently coerces strings to int, causing a TypeError on fresh
installs.
Fixes#4559
Co-authored-by: Ollama <ollama@steganos.dev>
* Fix is_valid_receipt method bug
Strings submitted with a trailing space and no number caused an unhandled exception because Sale::exists() expects an int but a string was passed to it.
- Add guards
- Minor PSR refactor
Signed-off-by: objec <objecttothis@gmail.com>
* Address review comments
Signed-off-by: objec <objecttothis@gmail.com>
---------
Signed-off-by: objec <objecttothis@gmail.com>
* Bugfixes to get Migration working on MySQL
Signed-off-by: objec <objecttothis@gmail.com>
* MariaDB compatibility fixes
- Drop foreign key constraints before making charset changes
- Fix dropAllForeignKeyConstraints helper function.
- Added `IF EXISTS` to DROP statements
- Do not try to readd FK constraints for tables which were dropped.
- MariaDB 11.8.x changes the default implicit collation to uca1400 which breaks the IndiaGST migration, et. al. Explicitly declare utf8_general_ci in affected migrations.
Signed-off-by: objec <objecttothis@gmail.com>
* Fix changes which break MySQL migrations
- MySQL does not support IF EXISTS in foreign key constraints. Since the PHP is now handling dropping those constraints, these lines are redundant. Remove them.
Signed-off-by: objec <objecttothis@gmail.com>
* Resolve code review recommendations
- Add try/catch around DB connect statement
- Heed result of execute_script function and throw an exception on failure.
Signed-off-by: objec <objecttothis@gmail.com>
* Refactor out duplicate code
Signed-off-by: objec <objecttothis@gmail.com>
* Initialize array variable causing potential issues
Signed-off-by: objec <objecttothis@gmail.com>
---------
Signed-off-by: objec <objecttothis@gmail.com>
* fix: capture CSV import failures in save_tax_data and save_inventory_quantities
- Change save_tax_data() return type from void to bool
- Change save_inventory_quantities() return type from void to bool
- Accumulate failure status with &= operator in save_inventory_quantities
- Update postImportCsvFile() to capture return values and set isFailedRow
- Properly propagate failures to failCodes array
Fixes#4475
* fix: Change isset to !empty for items_taxes_data check
- isset was always true since array was initialized
- Use !empty to properly check if there are tax items to save
Address CodeRabbit review feedback
* fix: Capture inventory insert result in save_inventory_quantities
- Combine inventory insert result with success tracking
- Use &= operator to accumulate failures from both operations
- Ensure failures from inventory inserts are propagated
Address CodeRabbit review feedback
---------
Co-authored-by: Ollama <ollama@steganos.dev>
* docs: Update SECURITY.md with disclosure process and advisory template
- Update published advisories table with CVE-2026-41306 and CVE-2026-41307
- Add disclosure process timeline
- Add vulnerability template for researchers
- Explain GitHub advisory creation workflow
- Document security best practices for researchers
This streamlines the vulnerability reporting process by allowing
researchers to create draft advisories directly on GitHub, reducing
triage overhead.
* docs: Update SECURITY.md with CVE process and reporter acknowledgments
- Add CVE request procedure through GitHub
- Document that existing CVEs should be shared in reports
- Clarify no bug bounty program (voluntary triage)
- Add security best practices for researchers
- Thank security researchers for contributions
- Explain vulnerability template format
* docs: Simplify SECURITY.md - remove CVE table, link to GitHub advisories
---------
Co-authored-by: Ollama <ollama@steganos.dev>
* fix(ci): include hidden files in Docker build context
actions/upload-artifact@v4 excludes hidden files (dotfiles) by default,
causing .htaccess files to be missing from the Docker image. Add
include-hidden-files: true to preserve .htaccess in the build artifact.
* fix(docker): correct permissions and add barcode_type default
- Set proper permissions (750) for writable/logs, writable/uploads,
writable/cache, public/uploads, and public/uploads/item_pics
- Set permissions (640) for writable/uploads/importCustomers.csv
- Add barcode_type default value to prevent 'unknown key' error
during initial migration when database is not yet initialized
---------
Co-authored-by: Ollama <ollama@steganos.dev>
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Allow configuring allowed hostnames via ALLOWED_HOSTNAMES environment
variable as an alternative to app.allowedHostnames in .env file. This
is more convenient for Docker/Compose deployments where environment
variables are set directly in compose files.
The ALLOWED_HOSTNAMES variable takes precedence over app.allowedHostnames
if both are set, allowing deployment-specific overrides.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Ollama <ollama@steganos.dev>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
actions/upload-artifact@v4 excludes hidden files (dotfiles) by default,
causing .htaccess files to be missing from the Docker image. Add
include-hidden-files: true to preserve .htaccess in the build artifact.
Co-authored-by: Ollama <ollama@steganos.dev>
- updated some files to match the official CodeIgniter 4 skeleton.
- rebuilt package.json from a clean init and modernized metadata and formatting
- rebuilt composer.json with modernized metadata and formatting
- replaced code of conduct text with markdown
- updated Dockerfile to replace deprecated instruction
- reinstated 'update-licenses' task in gulp (accidentally removed in 3e844f2f89)
- updated bootstrap, bootswatch, and various dev dependencies
- refinded text across UI
- applied consistency fixes
- added 'number' and 'tel' input types to relevant settings
- improved system info layout (still room for improvement, but better)
- updated and fixed changelog
* fix: Catch mysqli_sql_exception in DB fallback handlers for fresh Docker installs
On a fresh Docker install with an empty database, the ospos_sessions
table doesn't exist yet. The CSRF filter triggers session initialization
before the login/migration page can be reached.
The existing code in Session.php, OSPOS.php, and MY_Migration.php
catches DatabaseException, but the MySQLi driver throws
mysqli_sql_exception (which extends RuntimeException, not
DatabaseException) when the table doesn't exist. This causes an
unhandled exception resulting in HTTP 500.
Fix: Change all three catch blocks from to
so that mysqli_sql_exception and any other unexpected
database errors are caught, allowing the app to fall back gracefully:
- Session.php: Falls back to FileHandler so sessions work without DB
- OSPOS.php: Falls back to empty settings so config loads work
- MY_Migration.php: Falls back to version 0 / false so the migration
check passes gracefully
This allows the login page with migration UI to be served on first
access, so the initial schema migration can run.
Fixes#4524
---------
Co-authored-by: Ollama <ollama@steganos.dev>