Initial code from ChatGPT
This commit is contained in:
parent
40e3e8f7b8
commit
1b1aa7f65d
|
|
@ -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
|
||||
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
HERE="$(dirname "$(readlink -f "$0")")"
|
||||
exec "$HERE/usr/bin/avidaed_onefile"
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
[Desktop Entry]
|
||||
Name=Avida-ED
|
||||
Exec=AppRun
|
||||
Type=Application
|
||||
Icon=avidaed
|
||||
Categories=Education;Science;
|
||||
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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)."
|
||||
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
@ -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@!
|
||||
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("cargo:rerun-if-changed=webroot");
|
||||
}
|
||||
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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"
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue