Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a36d84048d | |||
| fa2029b1cb | |||
| 9be068962c | |||
| 44fd41f0e3 | |||
| 68525ad820 | |||
| 216cd3ff1a | |||
| 1875386157 |
@@ -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:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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/
|
||||||
+21
@@ -32,3 +32,24 @@ piscal
|
|||||||
*~
|
*~
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.bak
|
*.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
|
||||||
|
|||||||
+82
-34
@@ -1,29 +1,60 @@
|
|||||||
#Download base image
|
# Multi-stage Dockerfile for PISCAL
|
||||||
FROM ubuntu:latest
|
# Stage 1: Build stage - Compile PISCAL executable from Fortran sources
|
||||||
|
FROM ubuntu:latest AS builder
|
||||||
|
|
||||||
# Install and update software
|
# Install build dependencies
|
||||||
RUN set -xe \
|
RUN set -xe \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get upgrade -y \
|
&& apt-get install --no-install-recommends -y \
|
||||||
# SSHD install
|
make \
|
||||||
&& apt-get install --no-install-recommends -y openssh-server sudo \
|
xutils-dev \
|
||||||
# Piscal reqs
|
gfortran \
|
||||||
&& apt-get install make -y \
|
|
||||||
&& apt-get install xutils-dev -y \
|
|
||||||
&& apt-get install gfortran -y \
|
|
||||||
&& apt-get install libopenmpi-dev -y \
|
|
||||||
# utils
|
|
||||||
&& apt-get install iproute2 -y \
|
|
||||||
&& apt-get install vim -y \
|
|
||||||
# cleanup
|
|
||||||
&& apt-get autoclean -y \
|
&& apt-get autoclean -y \
|
||||||
&& apt-get autoremove -y
|
&& apt-get autoremove -y
|
||||||
|
|
||||||
# configure sshd, copied from wataken44/ubuntu-latest-sshd
|
# Copy source files from multiple directories
|
||||||
|
COPY leafres/testarea/ /build/leafres/testarea/
|
||||||
|
COPY dataassim/math/optimization/ /build/dataassim/math/optimization/
|
||||||
|
COPY dataassim/math/othersupmath/ /build/dataassim/math/othersupmath/
|
||||||
|
COPY dataassim/math/algebra/ /build/dataassim/math/algebra/
|
||||||
|
COPY dataassim/math/specialfuncs/ /build/dataassim/math/specialfuncs/
|
||||||
|
COPY dataassim/math/nonlinsystems/ /build/dataassim/math/nonlinsystems/
|
||||||
|
COPY leafres/testrun/Makefile /build/leafres/testrun/Makefile
|
||||||
|
|
||||||
|
# Build the executable
|
||||||
|
WORKDIR /build/leafres/testrun
|
||||||
|
RUN make clean || true
|
||||||
|
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 \
|
RUN set -xe \
|
||||||
&& groupadd launcher \
|
&& apt-get update \
|
||||||
&& useradd -g launcher -G sudo -m -s /bin/bash launcher \
|
&& apt-get upgrade -y \
|
||||||
&& echo 'launcher:launcher' | chpasswd
|
&& apt-get install --no-install-recommends -y \
|
||||||
|
openssh-server \
|
||||||
|
sudo \
|
||||||
|
iproute2 \
|
||||||
|
vim \
|
||||||
|
wget \
|
||||||
|
libgfortran5 \
|
||||||
|
&& apt-get autoclean -y \
|
||||||
|
&& apt-get autoremove -y
|
||||||
|
|
||||||
|
# Configure SSH server with parameterized credentials
|
||||||
|
RUN set -xe \
|
||||||
|
&& groupadd ${SSH_GROUP} \
|
||||||
|
&& useradd -g ${SSH_GROUP} -G sudo -m -s /bin/bash ${SSH_USERNAME} \
|
||||||
|
&& echo "${SSH_USERNAME}:${SSH_PASSWORD}" | chpasswd
|
||||||
|
|
||||||
RUN set -xe \
|
RUN set -xe \
|
||||||
&& sed -i -e 's/#PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config \
|
&& sed -i -e 's/#PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config \
|
||||||
@@ -31,26 +62,43 @@ RUN set -xe \
|
|||||||
&& sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
|
&& sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
|
||||||
|
|
||||||
RUN set -xe \
|
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"
|
# Fix for SSHD - "Missing privilege separation directory: /run/sshd"
|
||||||
RUN set -xe \
|
RUN set -xe \
|
||||||
&& mkdir /run/sshd
|
&& mkdir /run/sshd
|
||||||
|
|
||||||
##### add user l2g
|
# Ensure the job/workspace base under /srv is writable.
|
||||||
# RUN set -xe \
|
# The client uses `/srv/${PiscalDirectoryName}/input` and `PiscalDirectoryName`
|
||||||
# && groupadd l2g \
|
# can itself be prefixed with `LeafWeb/...`.
|
||||||
# && useradd -g l2g -G sudo -m -s /bin/bash l2g \
|
RUN set -xe \
|
||||||
# && echo 'l2g:pwdpwd' | chpasswd
|
&& mkdir -p /srv/LeafWeb \
|
||||||
# RUN set -xe \
|
&& chown -R ${SSH_USERNAME}:${SSH_GROUP} /srv/LeafWeb
|
||||||
# && chown -R l2g:l2g /home/l2g
|
|
||||||
# RUN set -xe \
|
# Create storage directory structure with proper ownership
|
||||||
# && apt-get install nano
|
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 (excluding .cfg, we'll generate it)
|
||||||
|
COPY piscal-manager/*.sh /srv/
|
||||||
|
COPY piscal-manager/README.txt /srv/
|
||||||
|
|
||||||
|
# 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$//' {} \; \
|
||||||
|
&& chmod +x /srv/*.sh || true
|
||||||
|
|
||||||
ADD piscal-manager /srv
|
|
||||||
ADD leafres/testrun/piscal /srv
|
|
||||||
#RUN chmod R +x /srv/*.sh
|
|
||||||
WORKDIR /srv
|
WORKDIR /srv
|
||||||
|
|
||||||
EXPOSE 22
|
EXPOSE 22
|
||||||
|
|||||||
@@ -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 <host_port>: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`
|
||||||
@@ -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`.
|
||||||
@@ -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
|
||||||
|
}
|
||||||
+32
-2
@@ -1,4 +1,34 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Build script for PISCAL Docker image with automatic versioning
|
||||||
|
|
||||||
sudo docker build -t piscal-server:latest .
|
set -e
|
||||||
#sudo docker run -it --rm -p 2222:22 --name my-piscal-server piscal-server
|
|
||||||
|
# Generate version tag: YYYYMMDD-gitsha
|
||||||
|
DATE_TAG=$(date +%Y%m%d)
|
||||||
|
GIT_SHA=$(git rev-parse --short HEAD)
|
||||||
|
VERSION="${DATE_TAG}-${GIT_SHA}"
|
||||||
|
|
||||||
|
echo "Building PISCAL Docker image..."
|
||||||
|
echo "Version: ${VERSION}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 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)"
|
||||||
|
|||||||
@@ -1,2 +1,7 @@
|
|||||||
piscal_executable="/home/piscaladmin/piscal_executable/piscal"
|
# 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"
|
storage_directory="/home/piscaladmin/LeafWeb_storage"
|
||||||
|
|||||||
@@ -19,9 +19,17 @@ cleaned_input_directory_name="clninput"
|
|||||||
touser_directory_name="fitresult/touser"
|
touser_directory_name="fitresult/touser"
|
||||||
nottouser_directory_name="fitresult/nottouser"
|
nottouser_directory_name="fitresult/nottouser"
|
||||||
|
|
||||||
# import the settings from piscal.cfg
|
# import the settings from piscal_launcher.cfg
|
||||||
# $piscal_executable and $storage_directory
|
# $piscal_executable and $storage_directory
|
||||||
. "$base_directory/piscal_launcher.cfg"
|
piscal_launcher_cfg="$base_directory/piscal_launcher.cfg"
|
||||||
|
if [ ! -f "$piscal_launcher_cfg" ]; then
|
||||||
|
piscal_launcher_cfg="/srv/piscal_launcher.cfg"
|
||||||
|
fi
|
||||||
|
if [ ! -f "$piscal_launcher_cfg" ]; then
|
||||||
|
echo "piscal_launcher.cfg not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
. "$piscal_launcher_cfg"
|
||||||
|
|
||||||
while getopts "hd:f:u:t" opt; do
|
while getopts "hd:f:u:t" opt; do
|
||||||
case "$opt" in
|
case "$opt" in
|
||||||
@@ -84,10 +92,11 @@ if [ ! -d "$run_directory" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# run piscal
|
# run piscal
|
||||||
pushd $run_directory >> /dev/null
|
pushd "$run_directory" >> /dev/null
|
||||||
|
|
||||||
if [ -z "$test_output_directory" ]; then
|
if [ -z "$test_output_directory" ]; then
|
||||||
eval $piscal_executable
|
# `piscal_executable` comes from a cfg file and may include args.
|
||||||
|
eval "$piscal_executable"
|
||||||
else
|
else
|
||||||
cp -r "$test_output_directory"/* "$output_directory_base"/
|
cp -r "$test_output_directory"/* "$output_directory_base"/
|
||||||
fi
|
fi
|
||||||
@@ -100,11 +109,14 @@ if [ -z "$suppress_storage_copy" ]; then
|
|||||||
cp "$output_directory_nottouser"/* "$storage_directory"/output/ 2>/dev/null
|
cp "$output_directory_nottouser"/* "$storage_directory"/output/ 2>/dev/null
|
||||||
cp "$cleaned_input_directory"/* "$storage_directory"/input/ 2>/dev/null
|
cp "$cleaned_input_directory"/* "$storage_directory"/input/ 2>/dev/null
|
||||||
|
|
||||||
mv_script=$base_directory/subdir_year.sh
|
mv_script="$base_directory/subdir_year.sh"
|
||||||
pushd "$storage_directory"/output >> /dev/null
|
if [ ! -f "$mv_script" ]; then
|
||||||
|
mv_script="/srv/subdir_year.sh"
|
||||||
|
fi
|
||||||
|
pushd "$storage_directory/output" >> /dev/null
|
||||||
"$mv_script"
|
"$mv_script"
|
||||||
popd >> /dev/null
|
popd >> /dev/null
|
||||||
pushd "$storage_directory"/input >> /dev/null
|
pushd "$storage_directory/input" >> /dev/null
|
||||||
"$mv_script"
|
"$mv_script"
|
||||||
popd >> /dev/null
|
popd >> /dev/null
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -75,24 +75,56 @@ if [ -z "$directory_name" ]; then
|
|||||||
echo "directory name required (-d)"
|
echo "directory name required (-d)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
working_directory="$base_directory/$directory_name"
|
candidate_base_directory="$base_directory/$directory_name"
|
||||||
|
|
||||||
|
# Resolve the real working directory by probing for an input/ directory.
|
||||||
|
# This makes the job orchestration resilient when containers create jobs under
|
||||||
|
# a different root (e.g. $HOME/LeafWeb) than where the manager scripts live.
|
||||||
|
working_directory=""
|
||||||
|
for candidate in \
|
||||||
|
"$candidate_base_directory" \
|
||||||
|
"$HOME/LeafWeb/$directory_name" \
|
||||||
|
"$HOME/$directory_name" \
|
||||||
|
"$PWD/$directory_name" \
|
||||||
|
"/srv/$directory_name"
|
||||||
|
do
|
||||||
|
if [ -d "$candidate/$input_directory_name" ] && [ -w "$candidate" ]; then
|
||||||
|
working_directory="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# If no candidates match, fall back to the historical behavior so errors remain explainable.
|
||||||
|
if [ -z "$working_directory" ]; then
|
||||||
|
working_directory="$candidate_base_directory"
|
||||||
|
fi
|
||||||
|
|
||||||
|
input_directory="$working_directory/$input_directory_name"
|
||||||
|
pid_path="$working_directory/$pid_filename"
|
||||||
|
|
||||||
|
# Preserve fail-loud behavior for launch and status operations.
|
||||||
|
# Cleanup/kill are handled idempotently further below.
|
||||||
|
if [ "$task" = "launch" ] || [ "$task" = "get_status" ] || [ "$task" = "get_status_cleaned_input" ]; then
|
||||||
if [ ! -d "$working_directory" ]; then
|
if [ ! -d "$working_directory" ]; then
|
||||||
echo "working directory $working_directory not found"
|
echo "working directory $working_directory not found"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
input_directory="$working_directory/$input_directory_name"
|
if [ ! -w "$working_directory" ]; then
|
||||||
|
echo "working directory $working_directory is not writable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
if [ ! -d "$input_directory" ]; then
|
if [ ! -d "$input_directory" ]; then
|
||||||
echo "input directory $input_directory not found"
|
echo "input directory $input_directory not found"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
pid_path="$working_directory/$pid_filename"
|
fi
|
||||||
|
|
||||||
## Process task
|
## Process task
|
||||||
if [ "$task" = "launch" ]; then
|
if [ "$task" = "launch" ]; then
|
||||||
# verify process isn't running yet
|
# verify process isn't running yet
|
||||||
pid=$(head -n 1 $pid_path 2>/dev/null)
|
pid="$(head -n 1 "$pid_path" 2>/dev/null)"
|
||||||
# if the pid exists, check the process status using ps
|
# if the pid exists, check the process status using ps
|
||||||
if ps -p $pid &>/dev/null; then
|
if [ -n "$pid" ] && ps -p "$pid" &>/dev/null; then
|
||||||
# if it is in ps, then it's still running
|
# if it is in ps, then it's still running
|
||||||
echo still running
|
echo still running
|
||||||
exit 1
|
exit 1
|
||||||
@@ -115,10 +147,10 @@ if [ "$task" = "launch" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# launch it, sending error output to file
|
# launch it, sending error output to file
|
||||||
nohup ${command} > $working_directory/$stderr_filename 2>&1 &
|
nohup ${command} > "$working_directory/$stderr_filename" 2>&1 &
|
||||||
|
|
||||||
# write the PID to a temp file to check for completion later
|
# write the PID to a temp file to check for completion later
|
||||||
echo $! > $pid_path
|
echo $! > "$pid_path"
|
||||||
echo started
|
echo started
|
||||||
elif [ "$task" = "get_status" ] || [ "$task" = "get_status_cleaned_input" ]; then
|
elif [ "$task" = "get_status" ] || [ "$task" = "get_status_cleaned_input" ]; then
|
||||||
# if the pid doesn't exist, then process hasn't started
|
# if the pid doesn't exist, then process hasn't started
|
||||||
@@ -127,9 +159,13 @@ elif [ "$task" = "get_status" ] || [ "$task" = "get_status_cleaned_input" ]; the
|
|||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
pid=$(head -n 1 $pid_path 2>/dev/null)
|
pid="$(head -n 1 "$pid_path" 2>/dev/null)"
|
||||||
|
if [ -z "$pid" ]; then
|
||||||
|
echo "not started"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
# if the pid exists, check the process status using ps
|
# if the pid exists, check the process status using ps
|
||||||
if ps -p $pid > /dev/null; then
|
if ps -p "$pid" > /dev/null; then
|
||||||
# if it is in ps, then it's still running
|
# if it is in ps, then it's still running
|
||||||
echo running
|
echo running
|
||||||
else
|
else
|
||||||
@@ -161,26 +197,39 @@ elif [ "$task" = "get_status" ] || [ "$task" = "get_status_cleaned_input" ]; the
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
elif [ "$task" = "cleanup" ]; then
|
elif [ "$task" = "cleanup" ]; then
|
||||||
pid=$(head -n 1 $pid_path 2>/dev/null)
|
# Idempotent cleanup: safe after partial failures.
|
||||||
|
if [ ! -d "$working_directory" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [ ! -f "$pid_path" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
pid="$(head -n 1 "$pid_path" 2>/dev/null)"
|
||||||
# if the pid exists, check the process status using ps
|
# if the pid exists, check the process status using ps
|
||||||
if ps -p $pid > /dev/null; then
|
if [ -n "$pid" ] && ps -p "$pid" > /dev/null; then
|
||||||
# if it is in ps, then it's still running
|
# if it is in ps, then it's still running
|
||||||
echo still running
|
echo still running
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
rm -rf "$working_directory"
|
rm -rf "$working_directory"
|
||||||
elif [ "$task" = "kill" ]; then
|
elif [ "$task" = "kill" ]; then
|
||||||
# if the pid doesn't exist, then process hasn't started
|
# Idempotent kill: safe after partial failures.
|
||||||
|
if [ ! -d "$working_directory" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
if [ ! -f "$pid_path" ]; then
|
if [ ! -f "$pid_path" ]; then
|
||||||
echo "not started"
|
exit 0
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
pid=$(head -n 1 $pid_path 2>/dev/null)
|
pid="$(head -n 1 "$pid_path" 2>/dev/null)"
|
||||||
|
if [ -z "$pid" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
# if the pid exists, check the process status using ps
|
# if the pid exists, check the process status using ps
|
||||||
if ps -p $pid > /dev/null; then
|
if ps -p "$pid" > /dev/null; then
|
||||||
# if it is in ps, then it's still running
|
# if it is in ps, then it's still running
|
||||||
kill $pid
|
kill "$pid"
|
||||||
fi
|
fi
|
||||||
echo killed
|
echo killed
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user