diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..3f8f56c --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,11 @@ +{ + "permissions": { + "allow": [ + "Bash(git update-index:*)", + "Bash(pwsh -File \"D:\\\\repos\\\\piscal\\\\build-docker.ps1\")", + "Bash(bash:*)", + "Bash(ls:*)", + "Bash(git check-ignore:*)" + ] + } +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6e8d20d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,65 @@ +# Git files +.git +.github +.gitignore +.gitattributes + +# Documentation +*.md +README* +CHANGELOG* +LICENSE* + +# Build scripts (not needed in container) +build-docker*.ps1 +build-docker*.sh + +# Environment and credential files (SECURITY) +.env +.env.* +*.env +.env.local +.env.production +.env.development +*.secrets +credentials.txt +passwords.txt +*.key +*.pem +*.crt +*.pfx + +# IDE and editor files +.vscode +.idea +*.swp +*.swo +*~ +.DS_Store + +# Build artifacts +*.log +*.tmp +.docker-build/ +dist/ +build/ +out/ + +# Test files +test/ +tests/ +*.test.js +*.spec.js + +# CI/CD files +.github/ +.gitlab-ci.yml +.travis.yml +Jenkinsfile + +# Docker files (we only need Dockerfile) +docker-compose*.yml +.dockerignore + +# Claude Code +.claude/ diff --git a/.gitignore b/.gitignore index 8dc9ba5..2cb5b90 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,24 @@ piscal *~ .DS_Store *.bak + +# Docker build artifacts +*.log +.docker-build/ + +# Environment files (never commit credentials) +.env +.env.* +.env.local +.env.production +.env.development +*.env + +# Credential files (SECURITY - never commit these) +credentials.txt +passwords.txt +*.secrets +*.key +*.pem +*.crt +*.pfx diff --git a/Dockerfile b/Dockerfile index 041f590..29425d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,13 @@ RUN make # Stage 2: Runtime stage - Create minimal application container FROM ubuntu:latest +# Build arguments for configurable credentials and paths +ARG SSH_USERNAME=piscaladmin +ARG SSH_PASSWORD=piscaladmin +ARG SSH_GROUP=piscaladmin +ARG STORAGE_PATH=/home/piscaladmin/LeafWeb_storage +ARG PISCAL_EXECUTABLE=/srv/piscal + # Install runtime dependencies only RUN set -xe \ && apt-get update \ @@ -42,11 +49,11 @@ RUN set -xe \ && apt-get autoclean -y \ && apt-get autoremove -y -# Configure SSH server +# Configure SSH server with parameterized credentials RUN set -xe \ - && groupadd launcher \ - && useradd -g launcher -G sudo -m -s /bin/bash launcher \ - && echo 'launcher:launcher' | chpasswd + && groupadd ${SSH_GROUP} \ + && useradd -g ${SSH_GROUP} -G sudo -m -s /bin/bash ${SSH_USERNAME} \ + && echo "${SSH_USERNAME}:${SSH_PASSWORD}" | chpasswd RUN set -xe \ && sed -i -e 's/#PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config \ @@ -54,22 +61,34 @@ RUN set -xe \ && sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd RUN set -xe \ - && chown -R launcher:launcher /home/launcher + && chown -R ${SSH_USERNAME}:${SSH_GROUP} /home/${SSH_USERNAME} # Fix for SSHD - "Missing privilege separation directory: /run/sshd" RUN set -xe \ && mkdir /run/sshd +# Create storage directory structure with proper ownership +RUN set -xe \ + && mkdir -p ${STORAGE_PATH}/input \ + && mkdir -p ${STORAGE_PATH}/output \ + && chown -R ${SSH_USERNAME}:${SSH_GROUP} ${STORAGE_PATH} + # Copy compiled executable from builder stage COPY --from=builder /build/leafres/testrun/piscal /srv/piscal -# Copy piscal-manager scripts -COPY piscal-manager /srv +# Copy piscal-manager scripts (excluding .cfg, we'll generate it) +COPY piscal-manager/*.sh /srv/ +COPY piscal-manager/README.txt /srv/ -# Fix Windows line endings (CRLF -> LF) for scripts and config files, and make scripts executable +# Generate piscal_launcher.cfg with build-time parameters +RUN set -xe \ + && echo "piscal_executable=\"${PISCAL_EXECUTABLE}\"" > /srv/piscal_launcher.cfg \ + && echo "storage_directory=\"${STORAGE_PATH}\"" >> /srv/piscal_launcher.cfg \ + && chown ${SSH_USERNAME}:${SSH_GROUP} /srv/piscal_launcher.cfg + +# Fix Windows line endings (CRLF -> LF) for scripts and make scripts executable RUN set -xe \ && find /srv -name "*.sh" -type f -exec sed -i 's/\r$//' {} \; \ - && find /srv -name "*.cfg" -type f -exec sed -i 's/\r$//' {} \; \ && chmod +x /srv/*.sh || true WORKDIR /srv diff --git a/README-Docker.md b/README-Docker.md new file mode 100644 index 0000000..f9b112c --- /dev/null +++ b/README-Docker.md @@ -0,0 +1,291 @@ +# PISCAL Docker Build Guide + +This guide explains how to build and run PISCAL Docker images. + +## Prerequisites + +- Docker installed and running +- Git (for version tagging) +- PowerShell (Windows) or Bash (Linux/Mac) + +## Building the Image + +**Windows (PowerShell):** +```powershell +.\build-docker.ps1 +``` + +**Linux/Mac (Bash):** +```bash +./build-docker.sh +``` + +The build script automatically creates a versioned image with: +- `piscal:YYYYMMDD-gitsha` (e.g., `piscal:20260316-216cd3f`) +- `piscal:latest` +- `piscal:dev` + +## Default Credentials + +The image is built with default credentials: + +- **Username:** `piscaladmin` +- **Password:** `piscaladmin` +- **Storage Path:** `/home/piscaladmin/LeafWeb_storage` + +**Security Note:** Change the password after deployment for production use. You can change it by: +- SSHing into the container and running: `passwd piscaladmin` +- Or rebuilding with custom credentials using Docker build arguments (see Advanced section) + +## Running the Container + +```bash +# Start container +docker run -d -p 2222:22 --name piscal-server piscal:latest + +# SSH into container +ssh -p 2222 piscaladmin@localhost +# Password: piscaladmin + +# Stop and remove +docker stop piscal-server +docker rm piscal-server +``` + +## Container Configuration + +### Build Arguments + +The Dockerfile accepts these build arguments if you need to customize: + +| Argument | Default | Description | +|----------|---------|-------------| +| `SSH_USERNAME` | `piscaladmin` | SSH username for container access | +| `SSH_PASSWORD` | `piscaladmin` | SSH password for container access | +| `SSH_GROUP` | `piscaladmin` | Primary group for SSH user | +| `STORAGE_PATH` | `/home/piscaladmin/LeafWeb_storage` | Storage directory for PISCAL data | +| `PISCAL_EXECUTABLE` | `/srv/piscal` | Path to PISCAL executable | + +### Storage Directory Structure + +The storage directory is automatically created with: + +``` +/home/piscaladmin/LeafWeb_storage/ +├── input/ # Input files for processing +└── output/ # Results from PISCAL processing +``` + +### Port Mapping + +- Container exposes port `22` for SSH +- Map to host port as needed: `-p :22` +- Default examples use port `2222` to avoid conflicts with host SSH + +## Advanced Usage + +### Custom Credentials at Build Time + +If you need different credentials, use Docker build arguments: + +```bash +docker build \ + --build-arg SSH_USERNAME=customuser \ + --build-arg SSH_PASSWORD=custompass \ + -t piscal:custom \ + . +``` + +### Custom Storage Path + +```bash +docker build \ + --build-arg STORAGE_PATH=/data/piscal \ + -t piscal:custom \ + . +``` + +### Mounting External Storage + +Mount a host directory for persistent storage: + +```bash +docker run -d -p 2222:22 \ + -v /path/on/host:/home/piscaladmin/LeafWeb_storage \ + --name piscal-server \ + piscal:latest +``` + +## Troubleshooting + +### Build Issues + +**Problem:** Docker build fails with "permission denied" +```bash +# Solution: Ensure Docker daemon is running +docker ps +``` + +**Problem:** Git not found during build +```bash +# Solution: Install git or build without versioning +docker build -t piscal:latest . +``` + +### Runtime Issues + +**Problem:** Cannot SSH into container +```bash +# Check if container is running +docker ps + +# Check container logs +docker logs piscal-server + +# Verify SSH service +docker exec piscal-server service ssh status +``` + +**Problem:** Storage directory permission errors +```bash +# Verify ownership inside container +docker exec piscal-server ls -la /home/piscaladmin/LeafWeb_storage +``` + +**Problem:** Port conflict on 2222 +```bash +# Use a different port +docker run -d -p 2223:22 --name piscal-server piscal:latest +ssh -p 2223 piscaladmin@localhost +``` + +## Version Management + +### Listing Images + +```bash +docker images | grep piscal +``` + +### Removing Old Images + +```bash +# Remove specific version +docker rmi piscal:20260316-216cd3f + +# Remove all except latest +docker images | grep piscal | grep -v latest | awk '{print $3}' | xargs docker rmi +``` + +## Security Best Practices + +### 1. Change Default Password + +After deploying, always change the default password: + +```bash +# SSH into container +ssh -p 2222 piscaladmin@localhost + +# Change password +passwd piscaladmin +``` + +### 2. Use SSH Keys + +For better security, disable password authentication and use SSH keys: + +```bash +# Copy your public key to the container +ssh-copy-id -p 2222 piscaladmin@localhost + +# Disable password authentication (optional) +docker exec piscal-server sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config +docker exec piscal-server service ssh restart +``` + +### 3. Firewall Rules + +Restrict SSH access with firewall rules: + +```bash +# Example: Allow only from specific IP +iptables -A INPUT -p tcp -s 192.168.1.100 --dport 2222 -j ACCEPT +iptables -A INPUT -p tcp --dport 2222 -j DROP +``` + +### 4. Environment Variables for Secrets + +Store credentials as environment variables instead of hardcoding: + +```bash +export PISCAL_USER="piscaladmin" +export PISCAL_PASS="secure_password" + +docker build \ + --build-arg SSH_USERNAME="$PISCAL_USER" \ + --build-arg SSH_PASSWORD="$PISCAL_PASS" \ + -t piscal:latest . +``` + +## Integration with CI/CD + +### Example: GitHub Actions + +```yaml +name: Build PISCAL Docker Image + +on: + push: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Build Docker image + run: ./build-docker.sh + + - name: Push to registry + run: | + docker tag piscal:latest myregistry/piscal:latest + docker push myregistry/piscal:latest +``` + +### Example: Jenkins + +```groovy +pipeline { + agent any + + stages { + stage('Build') { + steps { + sh './build-docker.sh' + } + } + + stage('Deploy') { + steps { + sh 'docker tag piscal:latest myregistry/piscal:latest' + sh 'docker push myregistry/piscal:latest' + } + } + } +} +``` + +## Additional Resources + +- [Docker Documentation](https://docs.docker.com/) +- [Docker Security Best Practices](https://docs.docker.com/engine/security/) +- [SSH Hardening Guide](https://www.ssh.com/academy/ssh/security) + +## Support + +For issues or questions: +1. Check the troubleshooting section above +2. Review container logs: `docker logs piscal-server` +3. Inspect container: `docker exec -it piscal-server /bin/bash` diff --git a/README.md b/README.md new file mode 100644 index 0000000..f9f2a5d --- /dev/null +++ b/README.md @@ -0,0 +1,160 @@ +# PISCAL + +PISCAL is a Fortran-based photosynthesis calculation tool for analyzing leaf-level gas exchange data. It supports C3, C4, and CAM photosynthetic pathways and provides parameter optimization for photosynthesis models. + +## Quick Start with Docker + +### Build the Docker Image + +**Windows (PowerShell):** +```powershell +.\build-docker.ps1 +``` + +**Linux/Mac (Bash):** +```bash +./build-docker.sh +``` + +### Run the Container + +```bash +# Start container +docker run -d -p 2222:22 --name piscal-server piscal:latest + +# SSH into container +ssh -p 2222 piscaladmin@localhost +# Password: piscaladmin +``` + +### Full Docker Documentation + +See [README-Docker.md](README-Docker.md) for complete Docker build and deployment documentation, including: +- Container configuration +- Security best practices +- Troubleshooting +- CI/CD integration examples + +## Project Structure + +``` +piscal/ +├── Dockerfile # Multi-stage Docker build configuration +├── build-docker.ps1 # Build script (Windows/PowerShell) +├── build-docker.sh # Build script (Linux/Mac/Bash) +├── README-Docker.md # Comprehensive Docker documentation +│ +├── leafres/ # Leaf response calculation modules +│ ├── testarea/ # Test area source files +│ └── testrun/ # Test run configuration and Makefile +│ +├── dataassim/ # Data assimilation and optimization +│ └── math/ # Mathematical libraries +│ ├── optimization/ # Optimization algorithms +│ ├── othersupmath/ # Supporting mathematical functions +│ ├── algebra/ # Linear algebra routines +│ ├── specialfuncs/ # Special mathematical functions +│ └── nonlinsystems/ # Nonlinear system solvers +│ +└── piscal-manager/ # Container management scripts + ├── piscal_manager.sh # Job orchestration script + ├── piscal_launcher.sh # Execution wrapper + ├── subdir_year.sh # Output organization utility + ├── piscal_launcher.cfg # Configuration (reference for non-Docker) + └── README.txt # Manager scripts documentation +``` + +## Building from Source (Non-Docker) + +PISCAL is written in Fortran and requires: +- `gfortran` (GNU Fortran compiler) +- `make` +- `xutils-dev` + +To build manually: +```bash +cd leafres/testrun +make clean +make +``` + +The compiled executable will be located at `leafres/testrun/piscal`. + +## Photosynthetic Pathway Support + +PISCAL supports three photosynthetic pathway types: +- **C3_photosynthesis_leafweb** - C3 pathway (default) +- **C4_photosynthesis_leafweb** - C4 pathway +- **CAM_photosynthesis_leafweb** - CAM pathway + +Specify the pathway type in the `piscal.cfg` configuration file. + +## Input and Output + +### Input Files +Place leaf gas exchange data files (CSV format) in the `input/` directory. See `piscal-manager/README.txt` for input file format details. + +### Output Structure +PISCAL generates organized output in the following structure: +``` +output/ +├── clninput/ # Cleaned input data +└── fitresult/ # Optimization results + ├── touser/ # User-facing results + │ ├── cntrlbestparameters.csv + │ ├── errormessage + │ └── warningmessage + └── nottouser/ # Internal/storage results +``` + +## Usage with piscal-manager Scripts + +The container includes management scripts for job orchestration: + +```bash +# Start a job +./piscal_manager.sh -d /path/to/workdir -s -p C3_photosynthesis_leafweb + +# Check job status +./piscal_manager.sh -d /path/to/workdir + +# Cleanup completed job +./piscal_manager.sh -d /path/to/workdir -c +``` + +See `piscal-manager/README.txt` for detailed script usage. + +## Version History + +Images are automatically tagged with: +- `piscal:YYYYMMDD-gitsha` - Date and commit-based version +- `piscal:latest` - Latest build +- `piscal:dev` - Development alias + +## Security Notes + +**Default Credentials:** +- Username: `piscaladmin` +- Password: `piscaladmin` + +**Important:** Always change the default password after deployment: +```bash +ssh -p 2222 piscaladmin@localhost +passwd piscaladmin +``` + +See [README-Docker.md](README-Docker.md) for comprehensive security best practices. + +## License + +[Add license information here] + +## Contributing + +[Add contribution guidelines here] + +## Support + +For Docker-related issues, see the troubleshooting section in [README-Docker.md](README-Docker.md). + +For PISCAL algorithm or calculation issues, refer to `piscal-manager/README.txt`. diff --git a/build-docker.ps1 b/build-docker.ps1 new file mode 100644 index 0000000..36721f0 --- /dev/null +++ b/build-docker.ps1 @@ -0,0 +1,39 @@ +#!/usr/bin/env pwsh +# Build script for PISCAL Docker image with automatic versioning + +$ErrorActionPreference = "Stop" + +# Generate version tag: YYYYMMDD-gitsha +$DateTag = Get-Date -Format "yyyyMMdd" +$GitSha = (git rev-parse --short HEAD).Trim() +$Version = "$DateTag-$GitSha" + +Write-Host "Building PISCAL Docker image..." -ForegroundColor Cyan +Write-Host "Version: $Version" -ForegroundColor Cyan +Write-Host "" + +# Build with version tag and latest tag (using default ARGs for dev) +docker build ` + -t "piscal:$Version" ` + -t piscal:latest ` + -t piscal:dev ` + . + +if ($LASTEXITCODE -eq 0) { + Write-Host "" + Write-Host "Successfully built:" -ForegroundColor Green + Write-Host " - piscal:$Version" -ForegroundColor Green + Write-Host " - piscal:latest" -ForegroundColor Green + Write-Host " - piscal:dev" -ForegroundColor Green + Write-Host "" + Write-Host "Default credentials:" -ForegroundColor Cyan + Write-Host " Username: piscaladmin" -ForegroundColor Cyan + Write-Host " Password: piscaladmin" -ForegroundColor Cyan + Write-Host " Storage: /home/piscaladmin/LeafWeb_storage" -ForegroundColor Cyan + Write-Host "" + Write-Host "To run: docker run -d -p 2222:22 --name piscal-server piscal:latest" -ForegroundColor Yellow + Write-Host "To SSH: ssh -p 2222 piscaladmin@localhost (password: piscaladmin)" -ForegroundColor Yellow +} else { + Write-Host "Build failed!" -ForegroundColor Red + exit $LASTEXITCODE +} diff --git a/build-docker.sh b/build-docker.sh index 4c51f36..205a342 100755 --- a/build-docker.sh +++ b/build-docker.sh @@ -12,15 +12,23 @@ echo "Building PISCAL Docker image..." echo "Version: ${VERSION}" echo "" -# Build with version tag and latest tag +# Build with version tag and latest tag (using default ARGs for dev) docker build \ -t piscal:${VERSION} \ -t piscal:latest \ + -t piscal:dev \ . echo "" echo "Successfully built:" echo " - piscal:${VERSION}" echo " - piscal:latest" +echo " - piscal:dev" +echo "" +echo "Default credentials:" +echo " Username: piscaladmin" +echo " Password: piscaladmin" +echo " Storage: /home/piscaladmin/LeafWeb_storage" echo "" echo "To run: docker run -d -p 2222:22 --name piscal-server piscal:latest" +echo "To SSH: ssh -p 2222 piscaladmin@localhost (password: piscaladmin)" diff --git a/piscal-manager/piscal_launcher.cfg b/piscal-manager/piscal_launcher.cfg index b100a11..3493172 100644 --- a/piscal-manager/piscal_launcher.cfg +++ b/piscal-manager/piscal_launcher.cfg @@ -1,2 +1,7 @@ +# NOTE: This file is for reference only when using non-Docker deployments. +# For Docker builds, this file is generated dynamically at build time from +# Dockerfile ARG values (see Dockerfile). The Docker-generated version will +# use the configured SSH_USERNAME and STORAGE_PATH build arguments. + piscal_executable="/srv/piscal" storage_directory="/home/piscaladmin/LeafWeb_storage"