Initial code from ChatGPT

This commit is contained in:
Wesley R. Elsberry 2025-10-23 15:22:05 -04:00
parent 40e3e8f7b8
commit 1b1aa7f65d
17 changed files with 386 additions and 0 deletions

80
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,80 @@
name: Build Avida-ED Onefile
on:
workflow_dispatch:
inputs:
ed_versions:
description: "Comma-separated versions (v3,v4)"
default: "v3,v4"
required: true
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-2022, macos-13]
ver: [v3, v4]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Fetch assets via docker compose (using URL fallback on mac/win if docker unavailable)
shell: bash
run: |
if command -v docker >/dev/null 2>&1; then
docker compose run --rm fetch-${{ matrix.ver }}
else
MODE=url URL=https://avida-ed.msu.edu/app4/ OUTDIR=./apps/${{ matrix.ver }} bash tools/fetch_assets.sh
fi
- name: Inject webroot
run: |
rm -rf server-ui/webroot
mkdir -p server-ui/webroot
rsync -a apps/${{ matrix.ver }}/Avida-ED-Eco/ server-ui/webroot/Avida-ED-Eco/
- name: Build binary
run: |
cd server-ui
cargo build --release
- name: Package (Linux AppImage)
if: startsWith(matrix.os, 'ubuntu')
run: |
sudo apt-get update && sudo apt-get install -y imagemagick || true
bash packaging/linux/make_appimage.sh ${{ matrix.ver }} server-ui/target/release/avidaed_onefile
mv Avida-ED-${{ matrix.ver }}-Linux-x86_64.AppImage Avida-ED-${{ matrix.ver }}-Linux-x86_64.AppImage
- name: Package (Windows EXE)
if: startsWith(matrix.os, 'windows')
shell: powershell
run: |
$WV2 = "$env:RUNNER_TEMP\WebView2Fixed" # pre-cached in your org, or downloaded here
# TODO: fetch/unzip WebView2 Fixed into $WV2 if not cached
powershell -ExecutionPolicy Bypass -File packaging/windows/make_windows_sfx.ps1 `
-Version ${{ matrix.ver }} `
-BinPath server-ui/target/release/avidaed_onefile.exe `
-WV2Fixed $WV2
Move-Item packaging\windows\Avida-ED-${{ matrix.ver }}-Windows.exe .
- name: Package (macOS .app)
if: startsWith(matrix.os, 'macos')
run: |
bash packaging/mac/make_macos_bundle.sh ${{ matrix.ver }} server-ui/target/release/avidaed_onefile
# Optionally make a DMG
# brew install create-dmg
# create-dmg "Avida-ED-${{ matrix.ver }}.app"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: Avida-ED-${{ matrix.ver }}-${{ matrix.os }}
path: |
*.AppImage
*.exe
Avida-ED-${{ matrix.ver }}.app
if-no-files-found: ignore

44
Makefile Normal file
View File

@ -0,0 +1,44 @@
VER ?= v4 # or v3
.PHONY: fetch-$(VER) inject-$(VER) build-linux build-mac build-win appimage winexe macapp all
fetch-v3:
docker compose run --rm fetch-v3
fetch-v4:
docker compose run --rm fetch-v4
inject-$(VER):
rm -rf server-ui/webroot
mkdir -p server-ui/webroot
rsync -a apps/$(VER)/Avida-ED-Eco/ server-ui/webroot/Avida-ED-Eco/
# Build binaries natively on each OS runner
build-linux: inject-$(VER)
cd server-ui && cargo build --release
build-mac: inject-$(VER)
cd server-ui && cargo build --release
build-win: inject-$(VER)
cd server-ui && cargo build --release
# Package
appimage: build-linux
bash packaging/linux/make_appimage.sh $(VER) server-ui/target/release/avidaed_onefile
winexe: build-win
# Example: pass location of WebView2 Fixed runtime and built exe
powershell -ExecutionPolicy Bypass -File packaging/windows/make_windows_sfx.ps1 \
-Version $(VER) \
-BinPath $(CURDIR)/server-ui/target/release/avidaed_onefile.exe \
-WV2Fixed "C:\SDKs\WebView2.FixedRuntime"
macapp: build-mac
bash packaging/mac/make_macos_bundle.sh $(VER) server-ui/target/release/avidaed_onefile
all:
@echo "Targets:"
@echo " make fetch-v3 | fetch-v4"
@echo " make build-linux | build-mac | build-win"
@echo " make appimage | winexe | macapp"

21
docker-compose.yml Normal file
View File

@ -0,0 +1,21 @@
version: "3.8"
services:
fetch-v3:
build: { context: ./tools, dockerfile: Dockerfile.fetch }
environment:
MODE: "docker" # or "url"
ED_VER: "v3"
OUTDIR: "/out"
volumes:
- ./apps/v3:/out
# If MODE=url, also pass URL as env
fetch-v4:
build: { context: ./tools, dockerfile: Dockerfile.fetch }
environment:
MODE: "docker"
ED_VER: "v4"
OUTDIR: "/out"
volumes:
- ./apps/v4:/out

4
packaging/linux/Apprun Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
HERE="$(dirname "$(readlink -f "$0")")"
exec "$HERE/usr/bin/avidaed_onefile"

View File

@ -0,0 +1,10 @@
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
build-essential curl git libgtk-3-dev libayatana-appindicator3-dev \
libwebkit2gtk-4.0-dev pkg-config cmake patchelf desktop-file-utils \
&& rm -rf /var/lib/apt/lists/*
# appimagetool
RUN curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o /usr/local/bin/appimagetool \
&& chmod +x /usr/local/bin/appimagetool
WORKDIR /work

View File

@ -0,0 +1,7 @@
[Desktop Entry]
Name=Avida-ED
Exec=AppRun
Type=Application
Icon=avidaed
Categories=Education;Science;

View File

@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
VER="${1:?version folder, e.g., v4}"
BINPATH="${2:-../..//server-ui/target/release/avidaed_onefile}"
APPDIR="AvidaED-${VER}.AppDir"
rm -rf "$APPDIR"; mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/share/icons/hicolor/256x256/apps"
cp "$BINPATH" "$APPDIR/usr/bin/avidaed_onefile"
cp "$(dirname "$0")/AppRun" "$APPDIR/AppRun"
chmod +x "$APPDIR/AppRun"
cp "$(dirname "$0")/desktop/avidaed.desktop" "$APPDIR/avidaed.desktop"
# placeholder icon; replace with your PNG
convert -size 256x256 xc:white "$APPDIR/usr/share/icons/hicolor/256x256/apps/avidaed.png" 2>/dev/null || true
appimagetool "$APPDIR" "Avida-ED-${VER}-Linux-x86_64.AppImage"
echo "Wrote Avida-ED-${VER}-Linux-x86_64.AppImage"

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>Avida-ED</string>
<key>CFBundleDisplayName</key><string>Avida-ED</string>
<key>CFBundleIdentifier</key><string>org.avidaed.{{VER}}</string>
<key>CFBundleVersion</key><string>1.0</string>
<key>CFBundleShortVersionString</key><string>1.0</string>
<key>CFBundleExecutable</key><string>Avida-ED</string>
<key>LSMinimumSystemVersion</key><string>10.13</string>
<key>NSHighResolutionCapable</key><true/>
</dict>
</plist>

View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
VER="${1:?v3|v4}"
BINPATH="${2:-../../server-ui/target/release/avidaed_onefile}"
APP="Avida-ED-${VER}.app"
rm -rf "$APP"
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
cp "$BINPATH" "$APP/Contents/MacOS/Avida-ED"
chmod +x "$APP/Contents/MacOS/Avida-ED"
# Info.plist
sed "s/{{VER}}/${VER}/g" "$(dirname "$0")/Info.plist.tmpl" > "$APP/Contents/Info.plist"
# Icon placeholder
sips -s format icns /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns --out "$APP/Contents/Resources/AppIcon.icns" >/dev/null 2>&1 || true
echo "Wrote $APP (unsigned)."

View File

@ -0,0 +1,32 @@
param(
[Parameter(Mandatory=$true)][string]$Version, # v3 or v4
[Parameter(Mandatory=$true)][string]$BinPath, # path to target\release\avidaed_onefile.exe
[Parameter(Mandatory=$true)][string]$WV2Fixed # path to WebView2 Fixed Runtime folder
)
$Work = Join-Path $PSScriptRoot "payload_$Version"
Remove-Item $Work -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $Work | Out-Null
Copy-Item $BinPath -Destination (Join-Path $Work "avidaed_onefile.exe")
Copy-Item $WV2Fixed -Destination (Join-Path $Work "WebView2Fixed") -Recurse
# launcher
$runbat = @"
@echo off
setlocal
set WEBVIEW2_BROWSER_EXECUTABLE_FOLDER=%~dp0WebView2Fixed
start "" "%~dp0avidaed_onefile.exe"
"@
$runbat | Out-File -Encoding ASCII (Join-Path $Work "run.bat")
# create 7z archive
$SevenZip = "C:\Program Files\7-Zip\7z.exe"
& $SevenZip a -t7z (Join-Path $PSScriptRoot "payload_$Version.7z") "$Work\*"
# concatenate SFX + config + archive -> one EXE
$Sfx = Join-Path $PSScriptRoot "7z.sfx"
$Cfg = Join-Path $PSScriptRoot "config.txt"
$Out = Join-Path $PSScriptRoot ("Avida-ED-$Version-Windows.exe")
Get-Content $Sfx -Encoding Byte, $Cfg -Encoding Byte, (Join-Path $PSScriptRoot "payload_$Version.7z") -Encoding Byte |
Set-Content $Out -Encoding Byte
Write-Host "Wrote $Out"

View File

@ -0,0 +1,9 @@
;!@Install@!UTF-8!
Title="Avida-ED"
BeginPrompt="Install and run Avida-ED?"
RunProgram="run.bat"
; extract to temp folder
ExtractTitle="Avida-ED"
GUIMode="2"
;!@InstallEnd@!

18
server-ui/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "avidaed_onefile"
version = "0.1.0"
edition = "2021"
[dependencies]
wry = "0.39" # WebView wrapper: WebView2/WKWebView/WebKitGTK
tiny-http = "0.12" # tiny static server
include_dir = "0.7" # embed webroot into binary
mime_guess = "2.0"
once_cell = "1.19"
anyhow = "1.0"
[profile.release]
lto = true
codegen-units = 1
strip = "symbols"

3
server-ui/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
println!("cargo:rerun-if-changed=webroot");
}

63
server-ui/src/main.rs Normal file
View File

@ -0,0 +1,63 @@
use include_dir::{include_dir, Dir};
use tiny_http::{Request, Response, Method};
use wry::{
application::event::{Event, StartCause, WindowEvent},
application::event_loop::{ControlFlow, EventLoop},
application::window::WindowBuilder,
webview::WebViewBuilder
};
use std::{net::TcpListener, thread, time::Duration};
use anyhow::Result;
static WEBROOT: Dir = include_dir!("$CARGO_MANIFEST_DIR/webroot");
static DEFAULT_PATH: &str = "/Avida-ED-Eco/index.html";
fn mime(p: &str)->&'static str{
if p.ends_with(".wasm"){return "application/wasm";}
if p.ends_with(".js"){return "application/javascript";}
if p.ends_with(".css"){return "text/css";}
if p.ends_with(".html") || p.ends_with(".htm"){return "text/html; charset=utf-8";}
mime_guess::from_path(p).first_raw().unwrap_or("application/octet-stream")
}
fn serve(mut req:Request){
if req.method()!=&Method::Get && req.method()!=&Method::Head{
let _=req.respond(Response::from_string("Method Not Allowed").with_status_code(405));return;
}
let mut path=req.url().to_string();
if let Some(i)=path.find('?'){path.truncate(i);}
if path=="/"{path=DEFAULT_PATH.to_string();}
let fpath=path.trim_start_matches('/');
if let Some(f)=WEBROOT.get_file(fpath){
let mut resp=Response::from_data(f.contents().to_vec());
let _=resp.add_header(tiny_http::Header::from_bytes(b"Content-Type", mime(&path)).unwrap());
let _=resp.add_header(tiny_http::Header::from_bytes(b"Cache-Control", b"no-store").unwrap());
let _=req.respond(resp);
}else{
let _=req.respond(Response::from_string("Not Found").with_status_code(404));
}
}
fn main()->Result<()>{
let listener=TcpListener::bind(("127.0.0.1",0))?; listener.set_nonblocking(true)?;
let port=listener.local_addr()?.port(); let srv=tiny_http::Server::from_tcp(listener)?;
std::thread::spawn(move||{
loop{
match srv.recv_timeout(Duration::from_millis(200)){
Ok(Some(r))=>serve(r), Ok(None)=>continue, Err(_)=>break
}
}
});
let url=format!("http://127.0.0.1:{port}{DEFAULT_PATH}");
let event_loop=EventLoop::new()?;
let window=WindowBuilder::new()
.with_title("Avida-ED")
.with_inner_size(wry::application::dpi::LogicalSize::new(1280.0,800.0))
.build(&event_loop)?;
let _wv=WebViewBuilder::new(&window)?.with_url(&url)?.build()?;
event_loop.run(move|e,_,cf|{
*cf=wry::application::event_loop::ControlFlow::Wait;
if let Event::WindowEvent{event:WindowEvent::CloseRequested,..}=e{*cf=wry::application::event_loop::ControlFlow::Exit;}
if let Event::NewEvents(StartCause::Init)=e{}
})?;
Ok(())
}

5
tools/Dockerfile.fetch Normal file
View File

@ -0,0 +1,5 @@
FROM alpine:3.20
RUN apk add --no-cache ca-certificates curl bash rsync
WORKDIR /work
COPY fetch_assets.sh /work/fetch_assets.sh
ENTRYPOINT ["/work/fetch_assets.sh"]

32
tools/fetch_assets.sh Normal file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
MODE="${MODE:-docker}" # docker|url
ED_VER="${ED_VER:-v4}" # v3|v4
OUTDIR="${OUTDIR:-/out}"
mkdir -p "$OUTDIR"
case "$MODE" in
docker)
# 1) Start your aed-docker container and copy its served webroot
# Expectation: container serves Avida-ED-Eco at /usr/share/nginx/html/Avida-ED-Eco
CID="$(docker create --name aedtmp_$ED_VER welsberr/aed-docker:$ED_VER)"
trap 'docker rm -f aedtmp_'"$ED_VER"' >/dev/null 2>&1 || true' EXIT
docker cp "aedtmp_$ED_VER:/usr/share/nginx/html/." "$OUTDIR/"
;;
url)
URL="${URL:-https://avida-ed.msu.edu/app4/}"
apk add --no-cache wget >/dev/null 2>&1 || true
wget --recursive --no-parent --page-requisites --adjust-extension \
--compression=auto --convert-links --timestamping \
--directory-prefix "$OUTDIR" \
"$URL"
# normalize into $OUTDIR/Avida-ED-Eco as needed
if [ ! -d "$OUTDIR/Avida-ED-Eco" ]; then
SUB="$(find "$OUTDIR" -type f -name index.html | head -n1)"
[ -n "$SUB" ] && rsync -a "$(dirname "$SUB")"/ "$OUTDIR/Avida-ED-Eco"/
fi
;;
*) echo "Unknown MODE=$MODE" >&2; exit 1;;
esac
echo "Assets fetched to $OUTDIR"

10
tools/inject_webroot.rs Normal file
View File

@ -0,0 +1,10 @@
use std::{env, fs, path::Path};
fn main() {
let args:Vec<String>=env::args().collect();
if args.len()!=3{eprintln!("usage: inject_webroot <src_dir> <dst_dir>"); std::process::exit(2);}
let (src,dst)=(&args[1],&args[2]);
if Path::new(dst).exists(){fs::remove_dir_all(dst).ok();}
fs::create_dir_all(dst).unwrap();
fs_extra::dir::copy(src,dst,&fs_extra::dir::CopyOptions{overwrite:true,copy_inside:true, ..Default::default()}).unwrap();
}