11 Commits

Author SHA1 Message Date
poprhythm 446b4e4e36 Add support for no-cache option in Docker build scripts. Update build-docker.ps1 and build-docker.sh to include --no-cache flag when PISCAL_BUILD_NO_CACHE environment variable is set, improving build flexibility. 2026-04-04 21:37:02 -04:00
poprhythm e6e95ad31c Merge branch 'master' into v1-config 2026-03-20 13:49:27 -04:00
poprhythm a36d84048d Add wget to Dockerfile dependencies for enhanced functionality 2026-03-20 13:48:28 -04:00
poprhythm c700c33569 Implement support for custom passwords in Docker builds by introducing .piscal-build.env file. Update build scripts to load environment variables from this file, enhancing security and flexibility. Revise README documentation to reflect new password handling options. 2026-03-20 13:22:27 -04:00
poprhythm 2dee620e36 Update Dockerfile and scripts for v1 layout compatibility, enhancing directory structure and credential handling. Adjust paths for executable and storage, and improve README documentation to reflect changes. 2026-03-19 21:56:46 -04:00
poprhythm fa2029b1cb Enhance Dockerfile to create a writable workspace under /srv/LeafWeb and update piscal_manager.sh to check for write permissions on working directories. 2026-03-19 21:44:23 -04:00
poprhythm 9be068962c Refactor piscal_launcher.sh and piscal_manager.sh for improved quoting and error handling. Ensure proper handling of variables and directory paths to enhance script robustness. 2026-03-18 22:13:50 -04:00
poprhythm 44fd41f0e3 Enhance configuration handling in piscal_launcher.sh and improve working directory resolution in piscal_manager.sh. Added fallback mechanisms for configuration files and refined error handling for directory checks. 2026-03-18 22:08:23 -04:00
poprhythm 68525ad820 rework credential strategy and cleanup 2026-03-16 23:44:27 -04:00
poprhythm 216cd3ff1a add versioning 2026-03-16 22:54:12 -04:00
poprhythm 1875386157 Refactor Dockerfile to use multi-stage builds for PISCAL, separating build and runtime stages. 2026-01-14 12:10:23 -05:00
14 changed files with 934 additions and 70 deletions
+11
View File
@@ -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:*)"
]
}
}
+65
View File
@@ -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/
+22
View File
@@ -32,3 +32,25 @@ 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
.piscal-build.env
*.secrets
*.key
*.pem
*.crt
*.pfx
+4
View File
@@ -0,0 +1,4 @@
# Copy to .piscal-build.env and set your production password.
# .piscal-build.env is gitignored - never commit it.
#
# PISCAL_SSH_PASSWORD=your_secure_password
+82 -35
View File
@@ -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 (v1 VM layout)
ARG SSH_USERNAME=piscaladmin
ARG SSH_PASSWORD=piscaladmin
ARG SSH_GROUP=piscaladmin
ARG STORAGE_PATH=/home/piscaladmin/LeafWeb_storage
ARG PISCAL_EXECUTABLE=/home/piscaladmin/piscal_executable/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,27 +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 # v1 VM layout: LeafWeb contains scripts + project directories
# RUN set -xe \ # Create LeafWeb (scripts and job dirs), piscal_executable, and storage
# && groupadd l2g \ RUN set -xe \
# && useradd -g l2g -G sudo -m -s /bin/bash l2g \ && mkdir -p /home/${SSH_USERNAME}/LeafWeb \
# && echo 'l2g:pwdpwd' | chpasswd && mkdir -p /home/${SSH_USERNAME}/piscal_executable \
# RUN set -xe \ && chown -R ${SSH_USERNAME}:${SSH_GROUP} /home/${SSH_USERNAME}/LeafWeb \
# && chown -R l2g:l2g /home/l2g && chown -R ${SSH_USERNAME}:${SSH_GROUP} /home/${SSH_USERNAME}/piscal_executable
# RUN set -xe \
# && apt-get install nano
#####
ADD piscal-manager /srv # Create storage directory structure with proper ownership
ADD leafres/testrun/piscal /srv RUN set -xe \
#RUN chmod R +x /srv/*.sh && mkdir -p ${STORAGE_PATH}/input \
WORKDIR /srv && mkdir -p ${STORAGE_PATH}/output \
&& chown -R ${SSH_USERNAME}:${SSH_GROUP} ${STORAGE_PATH}
# Copy compiled executable and scripts to staging dir (COPY can't use ARG in dest)
COPY --from=builder /build/leafres/testrun/piscal /tmp/piscal
COPY piscal-manager/piscal_launcher.sh piscal-manager/piscal_manager.sh piscal-manager/subdir_year.sh piscal-manager/README.txt /tmp/leafweb/
# Install to user's home (supports custom SSH_USERNAME)
RUN set -xe \
&& cp /tmp/piscal /home/${SSH_USERNAME}/piscal_executable/piscal \
&& chown ${SSH_USERNAME}:${SSH_GROUP} /home/${SSH_USERNAME}/piscal_executable/piscal \
&& cp /tmp/leafweb/* /home/${SSH_USERNAME}/LeafWeb/ \
&& echo "piscal_executable=\"${PISCAL_EXECUTABLE}\"" > /home/${SSH_USERNAME}/LeafWeb/piscal_launcher.cfg \
&& echo "storage_directory=\"${STORAGE_PATH}\"" >> /home/${SSH_USERNAME}/LeafWeb/piscal_launcher.cfg \
&& chown ${SSH_USERNAME}:${SSH_GROUP} /home/${SSH_USERNAME}/LeafWeb/* \
&& find /home/${SSH_USERNAME}/LeafWeb -name "*.sh" -type f -exec sed -i 's/\r$//' {} \; \
&& chmod +x /home/${SSH_USERNAME}/LeafWeb/*.sh || true \
&& rm -rf /tmp/piscal /tmp/leafweb
WORKDIR /home/${SSH_USERNAME}/LeafWeb
EXPOSE 22 EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"] CMD ["/usr/sbin/sshd", "-D"]
+316
View File
@@ -0,0 +1,316 @@
# 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` | `/home/piscaladmin/piscal_executable/piscal` | Path to PISCAL executable |
### v1 Directory Layout (VM-compatible)
The image uses the v1 layout expected by the LeafWeb client:
```
/home/piscaladmin/
├── LeafWeb/ # Scripts + project directories
│ ├── piscal_launcher.cfg
│ ├── piscal_launcher.sh
│ ├── piscal_manager.sh
│ ├── subdir_year.sh
│ ├── README.txt
│ └── <project_name>/ # Job dirs (e.g. 280_CinnamomumbodinieriLévl)
├── LeafWeb_storage/
│ ├── input/
│ └── output/
└── piscal_executable/
└── piscal
```
### 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
### Baking In a Production Password
To use a custom password without passing it on the command line:
**Option 1: `.piscal-build.env` (recommended)**
```bash
cp .piscal-build.env.example .piscal-build.env
# Edit .piscal-build.env and set: PISCAL_SSH_PASSWORD=your_secure_password
./build-docker.sh # or .\build-docker.ps1
```
The build scripts automatically load `.piscal-build.env` (gitignored) and pass the password to the Docker build.
**Option 2: Environment variable**
```bash
export PISCAL_SSH_PASSWORD="your_secure_password"
./build-docker.sh
```
**Option 3: Direct build-arg**
```bash
docker build --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`
+59
View File
@@ -0,0 +1,59 @@
# v1 VM Deployment (LeafWeb Layout)
This branch configures PISCAL for deployment on a VM matching the layout used by `piscal.eastus.cloudapp.azure.com`, so the LeafWeb client can access jobs and storage at the expected paths.
## Directory Layout
```
/home/piscaladmin/
├── LeafWeb/ # Scripts + project directories
│ ├── piscal_launcher.cfg # Config (piscal_executable, storage paths)
│ ├── piscal_launcher.sh
│ ├── piscal_manager.sh
│ ├── subdir_year.sh
│ ├── README.txt
│ ├── 280_CinnamomumbodinieriLévl/ # Project dirs (input/, output/, run/, etc.)
│ ├── 311_AnnaAsaday3high/
│ └── ...
├── LeafWeb_storage/ # Shared storage for client
│ ├── input/
│ └── output/
├── piscal/ # Source (optional)
└── piscal_executable/ # Compiled binary
└── piscal
```
## Deployment
**Docker (recommended):** The Docker image uses this layout by default. See [README-Docker.md](README-Docker.md).
**Manual VM deployment:** Copy scripts to LeafWeb and the compiled `piscal` binary to `piscal_executable/`:
```bash
ssh piscaladmin@host 'mkdir -p ~/LeafWeb ~/piscal_executable ~/LeafWeb_storage/input ~/LeafWeb_storage/output'
scp piscal-manager/*.cfg piscal-manager/*.sh piscal-manager/README.txt piscaladmin@host:~/LeafWeb/
scp leafres/testrun/piscal piscaladmin@host:~/piscal_executable/
ssh piscaladmin@host 'chmod +x ~/LeafWeb/*.sh'
```
## Configuration
`piscal_launcher.cfg` uses VM paths:
- `piscal_executable="/home/piscaladmin/piscal_executable/piscal"`
- `storage_directory="/home/piscaladmin/LeafWeb_storage"`
Adjust if your VM uses different locations.
## Running Jobs
From `~/LeafWeb`:
```bash
cd ~/LeafWeb
./piscal_manager.sh -d 280_CinnamomumbodinieriLévl -s -p C3_photosynthesis_leafweb
./piscal_manager.sh -d 280_CinnamomumbodinieriLévl # status
./piscal_manager.sh -d 280_CinnamomumbodinieriLévl -c # cleanup
```
The `-d` argument is the project directory name (a subdir of LeafWeb).
+162
View File
@@ -0,0 +1,162 @@
# 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
```
For production builds with a custom password, copy `.piscal-build.env.example` to `.piscal-build.env` and set `PISCAL_SSH_PASSWORD`. See [README-Docker.md](README-Docker.md).
### 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`.
+3
View File
@@ -0,0 +1,3 @@
# TODO
- [ ] **LeafWeb_storage sunset** Migrate away from the LeafWeb_storage directory (input/output files) to SQL-backed storage. Data can be reconstituted from SQL as needed.
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env pwsh
# Build script for PISCAL Docker image with automatic versioning
$ErrorActionPreference = "Stop"
# Load password from .piscal-build.env if it exists (gitignored)
if (Test-Path .piscal-build.env) {
Get-Content .piscal-build.env | ForEach-Object {
if ($_ -match '^\s*([^#][^=]+)=(.*)$') {
[Environment]::SetEnvironmentVariable($matches[1].Trim(), $matches[2].Trim(), 'Process')
}
}
}
# 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 args: use PISCAL_SSH_PASSWORD if set (from env or .piscal-build.env)
$BuildArgs = @("-t", "piscal:$Version", "-t", "piscal:latest", "-t", "piscal:dev")
if ($env:PISCAL_SSH_PASSWORD) {
$BuildArgs += "--build-arg", "SSH_PASSWORD=$($env:PISCAL_SSH_PASSWORD)"
}
if ($env:PISCAL_BUILD_NO_CACHE) {
$BuildArgs += "--no-cache"
}
docker build @BuildArgs .
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 ""
if ($env:PISCAL_SSH_PASSWORD) {
Write-Host "Credentials (from PISCAL_SSH_PASSWORD / .piscal-build.env):" -ForegroundColor Cyan
Write-Host " Username: piscaladmin" -ForegroundColor Cyan
Write-Host " Password: (custom)" -ForegroundColor Cyan
} else {
Write-Host "Default credentials (v1 layout):" -ForegroundColor Cyan
Write-Host " Username: piscaladmin" -ForegroundColor Cyan
Write-Host " Password: piscaladmin" -ForegroundColor Cyan
}
Write-Host " LeafWeb: /home/piscaladmin/LeafWeb (scripts + project dirs)" -ForegroundColor Cyan
Write-Host " Storage: /home/piscaladmin/LeafWeb_storage" -ForegroundColor Cyan
Write-Host " Executable: /home/piscaladmin/piscal_executable/piscal" -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" -ForegroundColor Yellow
} else {
Write-Host "Build failed!" -ForegroundColor Red
exit $LASTEXITCODE
}
+49 -2
View File
@@ -1,4 +1,51 @@
#!/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
# Load password from .piscal-build.env if it exists (gitignored)
if [ -f .piscal-build.env ]; then
set -a
source .piscal-build.env
set +a
fi
# 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 args: use PISCAL_SSH_PASSWORD if set (from env or .piscal-build.env)
BUILD_ARGS=(-t "piscal:${VERSION}" -t piscal:latest -t piscal:dev)
if [ -n "${PISCAL_SSH_PASSWORD:-}" ]; then
BUILD_ARGS+=(--build-arg "SSH_PASSWORD=${PISCAL_SSH_PASSWORD}")
fi
[ -n "${PISCAL_BUILD_NO_CACHE:-}" ] && BUILD_ARGS+=(--no-cache)
docker build "${BUILD_ARGS[@]}" .
echo ""
echo "Successfully built:"
echo " - piscal:${VERSION}"
echo " - piscal:latest"
echo " - piscal:dev"
echo ""
if [ -n "${PISCAL_SSH_PASSWORD:-}" ]; then
echo "Credentials (from PISCAL_SSH_PASSWORD / .piscal-build.env):"
echo " Username: piscaladmin"
echo " Password: (custom)"
else
echo "Default credentials (v1 layout):"
echo " Username: piscaladmin"
echo " Password: piscaladmin"
fi
echo " LeafWeb: /home/piscaladmin/LeafWeb (scripts + project dirs)"
echo " Storage: /home/piscaladmin/LeafWeb_storage"
echo " Executable: /home/piscaladmin/piscal_executable/piscal"
echo ""
echo "To run: docker run -d -p 2222:22 --name piscal-server piscal:latest"
echo "To SSH: ssh -p 2222 piscaladmin@localhost"
+8
View File
@@ -1,2 +1,10 @@
# v1 VM-compatible configuration
# Deploy this file to /home/piscaladmin/LeafWeb/ alongside piscal_launcher.sh,
# piscal_manager.sh, and subdir_year.sh. Project directories (e.g. 280_*,
# 311_*) live as subdirs of LeafWeb.
#
# For Docker builds, this file is NOT used; the Dockerfile generates
# Docker builds generate this file in LeafWeb at build time.
piscal_executable="/home/piscaladmin/piscal_executable/piscal" piscal_executable="/home/piscaladmin/piscal_executable/piscal"
storage_directory="/home/piscaladmin/LeafWeb_storage" storage_directory="/home/piscaladmin/LeafWeb_storage"
+19 -7
View File
@@ -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
+66 -17
View File
@@ -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