diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..dd9ac55 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,26 @@ +# Ignore build artifacts +target/ +build_deb_temp/ +outputs/ + +# Ignore .NET build artifacts +frontend/FilePiWeb/bin/ +frontend/FilePiWeb/obj/ +temp-publish/ + +# Ignore logs +logs/ +*.log + +# Ignore git +.git/ +.gitignore + +# Ignore IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# Ignore macOS files +.DS_Store diff --git a/.github/workflows/on-push-build-all.yml b/.github/workflows/on-push-build-all.yml new file mode 100644 index 0000000..45d9547 --- /dev/null +++ b/.github/workflows/on-push-build-all.yml @@ -0,0 +1,243 @@ +name: Build Rust Server with Blazor Frontend + +on: + push: + branches: + - main_rs + - try/* + pull_request: + branches: + - main_rs + workflow_dispatch: + +jobs: + setup: + name: Setup Build Variables + runs-on: ubuntu-latest + outputs: + build-name: ${{ steps.set-artifact-props.outputs.build-name }} + version: ${{ steps.set-artifact-props.outputs.version }} + deb-version: ${{ steps.set-artifact-props.outputs.deb-version }} + + steps: + - name: Set artifact name and development version + id: set-artifact-props + run: | + # Get branch name + if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then + BRANCH_NAME="${GITHUB_HEAD_REF}" + else + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + fi + + # Clean branch name for artifact naming + CLEAN_NAME="$(echo "$BRANCH_NAME" | sed -E 's/^(b|feat\/|try\/)//g' | sed -e 's/ /_/g' | sed -e 's/\//_/g')" + echo "Clean branch name is: $CLEAN_NAME" + + RUN_NUMBER=${{ github.run_number }} + VERSION="${CLEAN_NAME}_${RUN_NUMBER}" + + # Create a Debian-compatible version (must start with a digit) + DEB_VERSION="1.0.0+${CLEAN_NAME}.${RUN_NUMBER}" + + echo "build-name=$VERSION" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "deb-version=$DEB_VERSION" >> $GITHUB_OUTPUT + + echo "Using artifact name: filepi-${VERSION}" + echo "Using version: $VERSION" + echo "Using Debian version: $DEB_VERSION" + + build-blazor: + name: Build Blazor Frontend + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.x' + + - name: Install LibMan CLI + run: dotnet tool install -g Microsoft.Web.LibraryManager.Cli + + - name: Build Blazor WebAssembly Frontend + env: + SYNCFUSION_LICENSE_KEY: ${{ secrets.SYNCFUSION_LICENSE_KEY }} + run: | + echo "Building Blazor WebAssembly frontend..." + ./build.sh --type blazor + + - name: Upload webdeploy artifact + uses: actions/upload-artifact@v4 + with: + name: webdeploy + path: webdeploy/ + retention-days: 1 + + build-rust: + name: Build Rust (${{ matrix.arch }}) + needs: setup + runs-on: ubuntu-latest + strategy: + matrix: + arch: [amd64, arm64] + include: + - arch: amd64 + rust_target: x86_64-unknown-linux-gnu + - arch: arm64 + rust_target: aarch64-unknown-linux-gnu + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + target: ${{ matrix.rust_target }} + + - name: Install cross-compilation dependencies + if: matrix.arch == 'arm64' + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu + + - name: Build Rust binary + env: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + run: | + echo "Building Rust backend for ${{ matrix.rust_target }}..." + cargo build --release --target ${{ matrix.rust_target }} + + # Copy binary to root with standard name + cp target/${{ matrix.rust_target }}/release/filepi ./filepi + + echo "Rust binary built successfully" + ls -la filepi + + - name: Upload Rust binary artifact + uses: actions/upload-artifact@v4 + with: + name: filepi-binary-${{ matrix.arch }} + path: filepi + retention-days: 1 + + package: + name: Create Packages (${{ matrix.arch }}) + needs: [setup, build-blazor, build-rust] + runs-on: ubuntu-latest + strategy: + matrix: + arch: [amd64, arm64] + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Download webdeploy + uses: actions/download-artifact@v6 + with: + name: webdeploy + path: webdeploy + + - name: Download Rust binary + uses: actions/download-artifact@v6 + with: + name: filepi-binary-${{ matrix.arch }} + path: . + + - name: Make binary executable + run: chmod +x filepi + + - name: Verify artifacts + run: | + echo "Verifying filepi binary..." + ls -la filepi + + echo "Verifying webdeploy directory..." + if [ ! -d "webdeploy" ] || [ ! -f "webdeploy/index.html" ]; then + echo "ERROR: webdeploy directory is missing or incomplete" + exit 1 + fi + + echo "✅ All artifacts verified" + find webdeploy -type f | head -10 + + - name: Create binary tarball + run: | + BINARY_NAME="filepi-${{ needs.setup.outputs.build-name }}-linux-${{ matrix.arch }}" + + # Create a temporary directory structure + mkdir -p ${BINARY_NAME} + cp filepi ${BINARY_NAME}/ + cp -r webdeploy ${BINARY_NAME}/ + + # Create tarball + tar -czvf ${BINARY_NAME}.tar.gz ${BINARY_NAME} + + echo "Tarball created: ${BINARY_NAME}.tar.gz" + tar -tzvf ${BINARY_NAME}.tar.gz | head -10 + + - name: Build Debian package + run: | + echo "Building Debian package..." + chmod +x build-deb.sh + ./build-deb.sh "${{ needs.setup.outputs.deb-version }}" "${{ matrix.arch }}" + + # Rename the deb file to include architecture + mv outputs/filepi_*.deb filepi_${{ needs.setup.outputs.build-name }}_${{ matrix.arch }}.deb + + echo "Debian package created:" + ls -la filepi_${{ needs.setup.outputs.build-name }}_${{ matrix.arch }}.deb + + - name: Upload binary tarball + uses: actions/upload-artifact@v4 + with: + name: filepi-${{ needs.setup.outputs.build-name }}-linux-${{ matrix.arch }} + path: filepi-${{ needs.setup.outputs.build-name }}-linux-${{ matrix.arch }}.tar.gz + + - name: Upload Debian package + uses: actions/upload-artifact@v4 + with: + name: filepi_debian_${{ needs.setup.outputs.build-name }}_${{ matrix.arch }} + path: filepi_${{ needs.setup.outputs.build-name }}_${{ matrix.arch }}.deb + + summary: + needs: package + runs-on: ubuntu-latest + steps: + - name: Download all artifacts + uses: actions/download-artifact@v6 + with: + path: all-artifacts + pattern: 'filepi*' + merge-multiple: true + + - name: List artifacts + run: | + echo "## 🎉 Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The following artifacts have been created:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 📦 Rust Binaries (with Blazor WebAssembly UI):" >> $GITHUB_STEP_SUMMARY + for file in all-artifacts/*.tar.gz; do + if [ -f "$file" ]; then + filename=$(basename "$file") + size=$(ls -lh "$file" | awk '{print $5}') + echo "- $filename ($size)" >> $GITHUB_STEP_SUMMARY + fi + done + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 📦 Debian Packages:" >> $GITHUB_STEP_SUMMARY + for file in all-artifacts/*.deb; do + if [ -f "$file" ]; then + filename=$(basename "$file") + size=$(ls -lh "$file" | awk '{print $5}') + echo "- $filename ($size)" >> $GITHUB_STEP_SUMMARY + fi + done \ No newline at end of file diff --git a/.github/workflows/on-tag-push-create-release.yml b/.github/workflows/on-tag-push-create-release.yml new file mode 100644 index 0000000..33f509c --- /dev/null +++ b/.github/workflows/on-tag-push-create-release.yml @@ -0,0 +1,344 @@ +name: Create FilePi Rust Server Release from Tag + +on: + push: + tags: + - "v*.*.*" + - "v*.*.*-*" # For pre-releases like v1.0.1-rc1 + +permissions: + contents: write + actions: read + +jobs: + common-vars: + name: Setup Release Variables + runs-on: ubuntu-latest + outputs: + tag-name: ${{ github.ref_name }} + release-name: ${{ steps.set-release-name.outputs.release-name }} + is-prerelease: ${{ steps.check-prerelease.outputs.is-prerelease }} + latest-run-id: ${{ steps.get-latest-build.outputs.run-id }} + + steps: + - name: Echo context data + run: | + echo "github.ref - ${{github.ref}}" + echo "github.ref_name - ${{github.ref_name}}" + echo "github.event_name - ${{github.event_name}}" + + - name: Set release name + id: set-release-name + run: | + TAG_NAME="${{ github.ref_name }}" + RELEASE_NAME="FilePi Server ${TAG_NAME}" + echo "release-name=${RELEASE_NAME}" >> "$GITHUB_OUTPUT" + echo "Release name: ${RELEASE_NAME}" + + - name: Check if prerelease + id: check-prerelease + run: | + TAG_NAME="${{ github.ref_name }}" + if [[ "$TAG_NAME" == *"-"* ]]; then + echo "is-prerelease=true" >> "$GITHUB_OUTPUT" + echo "This is a prerelease: $TAG_NAME" + else + echo "is-prerelease=false" >> "$GITHUB_OUTPUT" + echo "This is a stable release: $TAG_NAME" + fi + + - name: Get latest successful build from main_rs + id: get-latest-build + uses: actions/github-script@v7 + with: + script: | + const runs = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'on-push-build-all.yml', + branch: 'main_rs', + status: 'completed', + conclusion: 'success', + per_page: 1 + }); + + if (runs.data.workflow_runs.length === 0) { + core.setFailed('No successful builds found on main_rs branch'); + return; + } + + const latestRun = runs.data.workflow_runs[0]; + console.log(`Latest successful run: ${latestRun.id} (${latestRun.head_sha})`); + core.setOutput('run-id', latestRun.id); + core.setOutput('commit-sha', latestRun.head_sha); + + download-artifacts: + name: Download Latest Build Artifacts + needs: common-vars + runs-on: ubuntu-latest + env: + ARTIFACTS_DIR: ./artifacts + RELEASE_DIR: ./release + + steps: + - name: Create artifact directories + run: | + mkdir -p ${{ env.ARTIFACTS_DIR }} ${{ env.RELEASE_DIR }} + + - name: Download artifacts from latest main_rs build + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = require('path'); + + // Get artifacts from the latest successful run + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{ needs.common-vars.outputs.latest-run-id }} + }); + + console.log(`Found ${artifacts.data.artifacts.length} artifacts`); + + // Filter for filepi artifacts (both binaries and debian packages) + const filepiArtifacts = artifacts.data.artifacts.filter(artifact => + artifact.name.includes('filepi') + ); + + if (filepiArtifacts.length === 0) { + core.setFailed('No FilePi artifacts found in the latest build'); + return; + } + + for (const artifact of filepiArtifacts) { + console.log(`Downloading artifact: ${artifact.name}`); + + const download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: artifact.id, + archive_format: 'zip' + }); + + const artifactPath = path.join('${{ env.ARTIFACTS_DIR }}', `${artifact.name}.zip`); + fs.writeFileSync(artifactPath, Buffer.from(download.data)); + console.log(`Saved to: ${artifactPath}`); + } + + - name: Extract and prepare release assets + run: | + TAG_NAME="${{ needs.common-vars.outputs.tag-name }}" + + echo "Extracting artifacts..." + cd ${{ env.ARTIFACTS_DIR }} + + # Extract all downloaded zip files + for zip_file in *.zip; do + if [ -f "$zip_file" ]; then + echo "Extracting $zip_file" + unzip -q "$zip_file" + rm "$zip_file" + fi + done + + cd "${{ github.workspace }}" + + echo "Contents of artifacts directory:" + find ${{ env.ARTIFACTS_DIR }} -type f -exec ls -la {} \; + + # Rename artifacts to include tag version instead of build number + echo "Preparing release assets..." + + # Process binary tarballs + for tarball in ${{ env.ARTIFACTS_DIR }}/*.tar.gz; do + if [ -f "$tarball" ]; then + filename=$(basename "$tarball") + # Replace the build name with tag name in filename + new_filename=$(echo "$filename" | sed "s/main_rs_[0-9]\+/${TAG_NAME}/g") + cp "$tarball" "${{ env.RELEASE_DIR }}/${new_filename}" + echo "Prepared: ${new_filename}" + fi + done + + # Process Debian packages + for deb in ${{ env.ARTIFACTS_DIR }}/*.deb; do + if [ -f "$deb" ]; then + filename=$(basename "$deb") + # Replace the build name with tag name in filename + new_filename=$(echo "$filename" | sed "s/main_rs_[0-9]\+/${TAG_NAME}/g") + cp "$deb" "${{ env.RELEASE_DIR }}/${new_filename}" + echo "Prepared: ${new_filename}" + fi + done + + echo "Release assets prepared:" + ls -la ${{ env.RELEASE_DIR }}/ + + - name: Upload prepared release assets + uses: actions/upload-artifact@v4 + with: + name: release-assets-${{ needs.common-vars.outputs.tag-name }} + path: ${{ env.RELEASE_DIR }} + + create-release: + name: Create GitHub Release + needs: + - common-vars + - download-artifacts + runs-on: ubuntu-latest + env: + RELEASE_DIR: ./release + + steps: + - name: Download prepared release assets + uses: actions/download-artifact@v6 + with: + name: release-assets-${{ needs.common-vars.outputs.tag-name }} + path: ${{ env.RELEASE_DIR }} + + - name: Create Release Notes + run: | + TAG_NAME="${{ needs.common-vars.outputs.tag-name }}" + cat > ${{ env.RELEASE_DIR }}/RELEASE_NOTES.md << EOF + # 🚀 FilePi Server ${TAG_NAME} + + FilePi is a lightweight network file browser with a modern web interface for Raspberry Pi and resource-constrained devices. + Browse, stream, and manage files from any web browser or the [Pi View mobile app](https://github.com/renjuashokan/pi_view). + + ## ✨ What's New in This Release + + - 🌐 **Modern Web Interface**: Blazor WebAssembly frontend with responsive design + - 📱 **Mobile-Friendly**: Works seamlessly on desktop, tablet, and mobile devices + - 🎬 **Enhanced Video Gallery**: Grid and list views with thumbnail generation + - 📁 **Improved File Management**: Upload, download, create folders, search files + - 🎨 **Beautiful UI**: Bootstrap 5 + Font Awesome icons for a polished experience + - ⚡ **High Performance**: Rust-powered backend optimized for resource-constrained devices + + ## 🚀 Quick Start + + **Prerequisites:** FFmpeg is required (\`sudo apt install ffmpeg\`) + + ### 📦 Debian Package (Recommended) + 1. Download: \`filepi_${TAG_NAME}_.deb\` + 2. Install: \`sudo dpkg -i filepi_${TAG_NAME}_.deb\` + 3. Access: \`http://[device-ip]:8080\` + + The service starts automatically and includes both the Rust backend and Blazor frontend. + + ### 📁 Binary Installation + 1. Download: \`filepi-${TAG_NAME}-linux-.tar.gz\` + 2. Extract: \`tar -xzf filepi-*.tar.gz\` + 3. Run: \`cd filepi-*/ && ./filepi\` + 4. Configure: \`export FILE_PI_ROOT_DIR=/path/to/your/files\` + + ## 🌐 Web Interface Features + + - **📂 File Browser**: Navigate directories with breadcrumb navigation + - **🔍 Search**: Find files quickly with powerful search + - **📤 Upload**: Drag & drop or select multiple files + - **🎬 Video Player**: Stream videos with thumbnail previews + - **📱 Responsive**: Works on any device size + - **🎨 Modern UI**: Clean, intuitive interface + + ## 📚 Documentation + + 📖 **[Installation Guide](docs/DEBIAN-INSTALL.md)** - Detailed setup and configuration + + 📚 **[Full Documentation](README.md)** - Features, API, and development guide + + ## 🔗 Access Your Files + + - **🌐 Web Browser**: \`http://[device-ip]:8080\` + - **📱 Mobile App**: [Pi View](https://github.com/renjuashokan/pi_view) + - **📺 Android TV**: [PiTV Explorer](https://github.com/renjuashokan/PiTVExplorer) + + ## 🏗️ Architecture + + - **Backend**: Rust server with high-performance async runtime (Tokio + Axum) + - **Frontend**: Blazor WebAssembly (C#) + - **UI Framework**: Bootstrap 5 + Font Awesome + - **Deployment**: Single binary + static files + + ## 📦 Available Packages + + - **amd64**: For 64-bit x86 systems (Intel/AMD) + - **arm64**: For 64-bit ARM systems (Raspberry Pi 4/5 with 64-bit OS) + + --- + + 💡 **New to FilePi?** Check out the [installation guide](docs/DEBIAN-INSTALL.md) for step-by-step setup instructions. + + 🐛 **Found an issue?** Please report it on [GitHub Issues](https://github.com/renjuashokan/FilePi/issues). + + EOF + + - name: Verify release assets exist + run: | + echo "Verifying release assets..." + + # Check for tarballs + TARBALL_COUNT=$(ls -1 ${{ env.RELEASE_DIR }}/filepi-*.tar.gz 2>/dev/null | wc -l) + if [ "$TARBALL_COUNT" -eq 0 ]; then + echo "ERROR: No tarball files found matching pattern: filepi-*.tar.gz" + exit 1 + fi + echo "✅ Found $TARBALL_COUNT tarball(s)" + + # Check for Debian packages + DEB_COUNT=$(ls -1 ${{ env.RELEASE_DIR }}/filepi_*.deb 2>/dev/null | wc -l) + if [ "$DEB_COUNT" -eq 0 ]; then + echo "ERROR: No Debian package files found matching pattern: filepi_*.deb" + exit 1 + fi + echo "✅ Found $DEB_COUNT Debian package(s)" + + echo "All expected release assets are present" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: ${{ needs.common-vars.outputs.release-name }} + tag_name: ${{ needs.common-vars.outputs.tag-name }} + body_path: ${{ env.RELEASE_DIR }}/RELEASE_NOTES.md + draft: false + prerelease: ${{ needs.common-vars.outputs.is-prerelease }} + files: | + ${{ env.RELEASE_DIR }}/filepi-*.tar.gz + ${{ env.RELEASE_DIR }}/filepi_*.deb + fail_on_unmatched_files: true + + - name: Clean up temporary artifacts + uses: geekyeggo/delete-artifact@v5 + with: + name: | + release-assets-${{ needs.common-vars.outputs.tag-name }} + + - name: Release Summary + run: | + echo "## 🎉 FilePi Server Release Created Successfully!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**🏷️ Tag:** ${{ needs.common-vars.outputs.tag-name }}" >> $GITHUB_STEP_SUMMARY + echo "**📦 Release:** ${{ needs.common-vars.outputs.release-name }}" >> $GITHUB_STEP_SUMMARY + echo "**🔖 Type:** ${{ needs.common-vars.outputs.is-prerelease == 'true' && 'Pre-release' || 'Stable Release' }}" >> $GITHUB_STEP_SUMMARY + echo "**🔨 Source Build:** Run #${{ needs.common-vars.outputs.latest-run-id }} from main_rs" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 📦 Release Assets:" >> $GITHUB_STEP_SUMMARY + for file in ${{ env.RELEASE_DIR }}/*; do + if [ -f "$file" ] && [[ "$file" != *"RELEASE_NOTES.md" ]]; then + filename=$(basename "$file") + size=$(ls -lh "$file" | awk '{print $5}') + if [[ "$filename" == *.tar.gz ]]; then + echo "- 🗃️ $filename ($size) - Binary with Web UI" >> $GITHUB_STEP_SUMMARY + elif [[ "$filename" == *.deb ]]; then + echo "- 📦 $filename ($size) - Debian Package" >> $GITHUB_STEP_SUMMARY + fi + fi + done + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 🌟 Key Features:" >> $GITHUB_STEP_SUMMARY + echo "- 🌐 Modern Blazor WebAssembly frontend" >> $GITHUB_STEP_SUMMARY + echo "- 📱 Responsive design for all devices" >> $GITHUB_STEP_SUMMARY + echo "- 🎬 Video streaming with thumbnails" >> $GITHUB_STEP_SUMMARY + echo "- 📁 Complete file management" >> $GITHUB_STEP_SUMMARY + echo "- ⚡ Optimized for Raspberry Pi" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.gitignore b/.gitignore index 379161f..238c43e 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,8 @@ target webdeploy/ temp-publish/ logs/ -working-cs \ No newline at end of file +working-cs +outputs/ +build_deb_temp/ +filepi +filepi.exe \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b85477c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# Use official Debian stable (e.g., bookworm) +FROM debian:bookworm-slim + +# Install only Debian packaging tools (no Rust or .NET build tools) +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + dpkg-dev \ + fakeroot \ + lintian \ + file && \ + rm -rf /var/lib/apt/lists/* + +# Create a non-root user matching the host UID (1000) +RUN useradd -m -u 1000 builder + +USER builder +WORKDIR /build + +# Default command to run the build script +CMD ["./build-deb.sh"] \ No newline at end of file diff --git a/README.md b/README.md index 4537ee1..de8027e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,26 @@ Before building and running FilePi, ensure you have the following installed: * **Rust**: [Install Rust](https://www.rust-lang.org/tools/install) (latest stable version) * **.NET SDK**: [Install .NET SDK](https://dotnet.microsoft.com/download) (version 10.0) +* **.Net Libman** + + * Install .Net Libman + ```bash + dotnet tool install --global dotnet-libman + ``` + +* **FFmpeg** + + #### On Raspberry Pi / Debian / Ubuntu: + ```bash + sudo apt update + sudo apt install ffmpeg + ``` + + #### On macOS: + ```bash + brew install ffmpeg + ``` + ## Building To build the project, simply run the build script: diff --git a/build-deb.sh b/build-deb.sh new file mode 100755 index 0000000..0c7222d --- /dev/null +++ b/build-deb.sh @@ -0,0 +1,158 @@ +#!/bin/bash +set -e + +# Check if version argument is provided +if [ "$1" != "" ]; then + PKG_VERSION="$1" +else + PKG_VERSION="1.0.0" +fi + +if [ "$2" != "" ]; then + PKG_ARCH="$2" +else + PKG_ARCH=$(dpkg --print-architecture 2>/dev/null || echo "amd64") +fi + +# Replace underscores with hyphens in version +PKG_VERSION=$(echo "$PKG_VERSION" | sed 's/_/-/g') + +# Create outputs directory if it doesn't exist +OUTPUT_DIR="outputs" +mkdir -p "${OUTPUT_DIR}" + +echo "Building FilePi Debian package..." +echo "Version: $PKG_VERSION" +echo "Architecture: $PKG_ARCH" + +# Check if filepi binary exists +if [ ! -f "filepi" ]; then + echo "Error: filepi binary not found in current directory" + echo "Please build the filepi binary first." + exit 1 +fi + +# Check if webdeploy directory exists +if [ ! -d "webdeploy" ]; then + echo "Error: webdeploy directory not found" + echo "Please build the Blazor frontend first." + exit 1 +fi + +# Set package details +PKG_NAME="filepi" +PKG_DIR="${PKG_NAME}_${PKG_VERSION}_${PKG_ARCH}" +STAGING_DIR="build_deb_temp/${PKG_DIR}" +CONTROL_FILE="${STAGING_DIR}/DEBIAN/control" +CONTROL_TMP="${STAGING_DIR}/DEBIAN/control.tmp" + +# Clean up previous staging +rm -rf "build_deb_temp" +mkdir -p "${STAGING_DIR}" + +echo "Creating package structure in ${STAGING_DIR}..." + +# Create directory structure +mkdir -p "${STAGING_DIR}/DEBIAN" +mkdir -p "${STAGING_DIR}/opt/filepi" +mkdir -p "${STAGING_DIR}/lib/systemd/system" +mkdir -p "${STAGING_DIR}/var/lib/filepi/media" + +# Copy Debian control files +if [ -d "debian" ]; then + cp -r debian/* "${STAGING_DIR}/DEBIAN/" + # Remove filepi.service from DEBIAN if it was copied there (it belongs in systemd) + rm -f "${STAGING_DIR}/DEBIAN/filepi.service" + + # Extract Maintainer from the source stanza before removing it + MAINTAINER=$(grep "^Maintainer:" "${STAGING_DIR}/DEBIAN/control" | head -1) + + # Extract only the Package stanza from control file (remove Source stanza) + sed -n '/^Package:/,$p' "$CONTROL_FILE" > "$CONTROL_TMP" + mv "$CONTROL_TMP" "$CONTROL_FILE" + + # Add Maintainer field after Package field if it's missing + if ! grep -q "^Maintainer:" "$CONTROL_FILE"; then + sed -i.bak "1a\\ +$MAINTAINER" "$CONTROL_FILE" 2>/dev/null || sed -i "" "1a\\ +$MAINTAINER +" "$CONTROL_FILE" + rm -f "${CONTROL_FILE}.bak" + fi + + # Remove debhelper variable placeholders + sed 's/${shlibs:Depends}, //g' "$CONTROL_FILE" > "$CONTROL_TMP" + mv "$CONTROL_TMP" "$CONTROL_FILE" + + sed 's/${misc:Depends}, //g' "$CONTROL_FILE" > "$CONTROL_TMP" + mv "$CONTROL_TMP" "$CONTROL_FILE" + + sed 's/${shlibs:Depends}//g' "$CONTROL_FILE" > "$CONTROL_TMP" + mv "$CONTROL_TMP" "$CONTROL_FILE" + + sed 's/${misc:Depends}//g' "$CONTROL_FILE" > "$CONTROL_TMP" + mv "$CONTROL_TMP" "$CONTROL_FILE" + + # Clean up any trailing commas or spaces in Depends + sed 's/Depends: , /Depends: /g' "$CONTROL_FILE" > "$CONTROL_TMP" + mv "$CONTROL_TMP" "$CONTROL_FILE" +else + echo "Error: debian directory not found!" + exit 1 +fi + +# Process control file to replace variables +sed "s/Architecture: .*/Architecture: ${PKG_ARCH}/" "$CONTROL_FILE" > "$CONTROL_TMP" +mv "$CONTROL_TMP" "$CONTROL_FILE" + +# Add Version field after Package field (since we removed it from the template) +sed -i.bak "2i\\ +Version: ${PKG_VERSION}" "$CONTROL_FILE" 2>/dev/null || sed -i "" "2i\\ +Version: ${PKG_VERSION} +" "$CONTROL_FILE" +rm -f "${CONTROL_FILE}.bak" + +# Ensure there is a newline at the end of the file +echo "" >> "$CONTROL_FILE" + +# Copy binary +echo "Copying binary..." +cp filepi "${STAGING_DIR}/opt/filepi/" +chmod 755 "${STAGING_DIR}/opt/filepi/filepi" + +# Copy service file +echo "Copying service file..." +if [ -f "debian/filepi.service" ]; then + cp debian/filepi.service "${STAGING_DIR}/lib/systemd/system/" +else + echo "Warning: debian/filepi.service not found" +fi + +# Copy webdeploy directory +echo "Copying webdeploy files..." +cp -r webdeploy "${STAGING_DIR}/opt/filepi/" + +# Set permissions for scripts +chmod 755 "${STAGING_DIR}/DEBIAN/postinst" +chmod 755 "${STAGING_DIR}/DEBIAN/postrm" 2>/dev/null || true +chmod 755 "${STAGING_DIR}/DEBIAN/preinst" 2>/dev/null || true +chmod 755 "${STAGING_DIR}/DEBIAN/prerm" 2>/dev/null || true + +# Calculate installed size +INSTALLED_SIZE=$(du -sk "${STAGING_DIR}" | cut -f1) +echo "Installed-Size: ${INSTALLED_SIZE}" >> "$CONTROL_FILE" + +# Build the package +echo "Building .deb package..." +if command -v dpkg-deb >/dev/null 2>&1; then + dpkg-deb --root-owner-group --build "${STAGING_DIR}" + # Move the .deb file to the outputs directory + mv "build_deb_temp/${PKG_DIR}.deb" "${OUTPUT_DIR}/" + echo "Package built successfully: ${OUTPUT_DIR}/${PKG_DIR}.deb" +else + echo "Warning: dpkg-deb not found, skipping package build." + echo "Package structure created in ${STAGING_DIR}" +fi + +# Clean up +# rm -rf "build_deb_temp" \ No newline at end of file diff --git a/build-in-docker.sh b/build-in-docker.sh new file mode 100755 index 0000000..87082a9 --- /dev/null +++ b/build-in-docker.sh @@ -0,0 +1,87 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_step() { + echo -e "${BLUE}📦 $1${NC}" +} + +print_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +print_error() { + echo -e "${RED}❌ $1${NC}" +} + +# Parse arguments +PKG_VERSION="1.0.0" +PKG_ARCH="amd64" + +while [[ $# -gt 0 ]]; do + case $1 in + --version) + PKG_VERSION="$2" + shift 2 + ;; + --arch) + PKG_ARCH="$2" + shift 2 + ;; + -h|--help) + echo "Usage: $0 [options]" + echo "Options:" + echo " --version VERSION Package version (default: 1.0.0)" + echo " --arch ARCH Package architecture (amd64 or arm64, default: amd64)" + echo " -h, --help Show this help" + exit 0 + ;; + *) + print_error "Unknown option: $1" + exit 1 + ;; + esac +done + +print_step "Building Debian package in Docker..." +echo "Version: $PKG_VERSION" +echo "Architecture: $PKG_ARCH" +echo "" + +# Check prerequisites +if [ ! -f "filepi" ]; then + print_error "filepi binary not found. Please build it first with:" + echo " ./build.sh --type rust --mode release" + exit 1 +fi + +if [ ! -d "webdeploy" ] || [ ! -f "webdeploy/index.html" ]; then + print_error "webdeploy directory not found. Please build it first with:" + echo " ./build.sh --type blazor" + exit 1 +fi + +# Build Docker image +print_step "Building Docker image..." +docker build -t filepi-deb-builder . + +# Run the build in Docker +print_step "Running Debian package build in container..." +docker run --rm \ + -v "$(pwd):/build" \ + -u "$(id -u):$(id -g)" \ + filepi-deb-builder \ + ./build-deb.sh "$PKG_VERSION" "$PKG_ARCH" + +print_success "Debian package build completed!" +echo "" +echo "📁 Output: outputs/filepi_${PKG_VERSION}_${PKG_ARCH}.deb" +echo "" +echo "To verify the package:" +echo " docker run --rm -v \"\$(pwd)/outputs:/outputs\" debian:bookworm-slim dpkg -c /outputs/filepi_${PKG_VERSION}_${PKG_ARCH}.deb" diff --git a/build.ps1 b/build.ps1 index b4ee064..630e1ac 100644 --- a/build.ps1 +++ b/build.ps1 @@ -74,7 +74,7 @@ function Build-Blazor { dotnet restore $WebProject dotnet build $WebProject -c Release dotnet publish $WebProject -c Release -o $TempPublishDir - + Pop-Location # Copy only the wwwroot contents to webdeploy diff --git a/build.sh b/build.sh index c9bd5ea..ca475e1 100755 --- a/build.sh +++ b/build.sh @@ -95,18 +95,18 @@ build_blazor() { print_step "Building Blazor WebAssembly frontend..." TEMP_PUBLISH_DIR="$SCRIPT_DIR/temp-publish" WEB_PROJECT="FilePiWeb.csproj" - + if [ ! -d "$FILEPI_WEB_DIR" ]; then print_error "FilePiWeb directory not found. Please create the Blazor project first." return 1 fi - + # Clean previous build rm -rf $WEBDEPLOY_DIR $TEMP_PUBLISH_DIR - + # Build Blazor WebAssembly cd $FILEPI_WEB_DIR - + # Restore LibMan packages if libman.json exists if [ -f "libman.json" ]; then print_step "Restoring client-side libraries..." @@ -116,18 +116,18 @@ build_blazor() { print_warning "libman not found, skipping client library restore" fi fi - + # Build and publish Blazor dotnet restore $WEB_PROJECT dotnet build $WEB_PROJECT -c Release dotnet publish $WEB_PROJECT -c Release -o $TEMP_PUBLISH_DIR cd .. - + # Copy only the wwwroot contents to webdeploy mkdir -p $WEBDEPLOY_DIR cp -r $TEMP_PUBLISH_DIR/wwwroot/* $WEBDEPLOY_DIR/ rm -rf $TEMP_PUBLISH_DIR - + print_success "Blazor WebAssembly build completed" echo "Output: $WEBDEPLOY_DIR" } @@ -136,7 +136,7 @@ build_blazor() { # Function to build Rust application build_rust() { print_step "Building Rust application..." - + # Clean previous build if [ "$CLEAN_BUILD" = "true" ]; then if [ "$BUILD_MODE" = "release" ]; then @@ -145,47 +145,47 @@ build_rust() { cargo clean fi fi - + # Build Rust application if [ "$BUILD_MODE" = "release" ]; then print_step "Building in release mode (optimized)..." cargo build --release - + # Copy binary to root for easier access - cp target/release/filepi-rust ./filepi || cp target/release/filepi-rust.exe ./filepi.exe 2>/dev/null || true - + cp target/release/filepi ./filepi || cp target/release/filepi ./filepi 2>/dev/null || true + print_success "Rust application build completed (release)" - echo "Output: ./target/release/filepi-rust or ./filepi" + echo "Output: ./target/release/filepi or ./filepi" else print_step "Building in debug mode..." cargo build - + # Copy binary to root for easier access - cp target/debug/filepi-rust ./filepi || cp target/debug/filepi-rust.exe ./filepi.exe 2>/dev/null || true - + cp target/debug/filepi ./filepi || cp target/debug/filepi ./filepi 2>/dev/null || true + print_success "Rust application build completed (debug)" - echo "Output: ./target/debug/filepi-rust or ./filepi" + echo "Output: ./target/debug/filepi or ./filepi" fi } # Function to create Debian package build_deb() { print_step "Creating Debian package..." - + # Check prerequisites - if [ ! -f "filepi" ] && [ ! -f "target/release/filepi-rust" ]; then + if [ ! -f "filepi" ] && [ ! -f "target/release/filepi" ]; then print_error "filepi binary not found. Run with --type rust first." return 1 fi - + if [ ! -d "webdeploy" ] || [ ! -f "webdeploy/index.html" ]; then print_error "webdeploy directory not found or incomplete. Run with --type blazor first." return 1 fi - + # Run the Debian package build ./build-deb.sh "$PKG_VERSION" "$PKG_ARCH" - + print_success "Debian package build completed" echo "Output: outputs/filepi_${PKG_VERSION}_${PKG_ARCH}.deb" } @@ -198,9 +198,9 @@ case $BUILD_TYPE in "rust") build_rust ;; - # "deb") - # build_deb - # ;; + "deb") + build_deb + ;; "all") build_blazor build_rust @@ -219,11 +219,11 @@ echo "📁 Generated files:" if [ -f "filepi" ]; then echo " - Rust executable: ./filepi" fi -if [ -f "target/release/filepi-rust" ]; then - echo " - Rust executable: ./target/release/filepi-rust" +if [ -f "target/release/filepi" ]; then + echo " - Rust executable: ./target/release/filepi" fi -if [ -f "target/debug/filepi-rust" ]; then - echo " - Rust executable: ./target/debug/filepi-rust" +if [ -f "target/debug/filepi" ]; then + echo " - Rust executable: ./target/debug/filepi" fi if [ -d "webdeploy" ]; then echo " - Blazor UI: ./webdeploy/" diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..39eb43a --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +filepi (1.0.0) unstable; urgency=medium + + * Initial release + + -- Renju Ashokan Sun, 09 Mar 2025 12:00:00 +0000 \ No newline at end of file diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..949854b --- /dev/null +++ b/debian/control @@ -0,0 +1,21 @@ +Source: filepi +Section: net +Priority: optional +Maintainer: Renju Ashokan +Build-Depends: debhelper-compat (= 12) +Standards-Version: 4.5.0 +Homepage: https://github.com/renjuashokan/filepi + +Package: filepi +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, ffmpeg +Description: Lightweight network file browser + FilePi is a lightweight network file browser designed + primarily for Raspberry Pi and other resource-constrained + devices. It allows you to browse, stream, and manage files + on your device from any web browser or through the + dedicated Pi View mobile app. + . + Features include file browsing with sorting and pagination, + video streaming with thumbnails, file search functionality, + and file upload capabilities. \ No newline at end of file diff --git a/debian/filepi.service b/debian/filepi.service new file mode 100644 index 0000000..3fcde39 --- /dev/null +++ b/debian/filepi.service @@ -0,0 +1,16 @@ +[Unit] +Description=FilePi Server +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/filepi +ExecStart=/opt/filepi/filepi +Restart=always +RestartSec=10 +Environment=FILE_PI_PORT=8080 +Environment=FILE_PI_ROOT_DIR=/var/lib/filepi/media + +[Install] +WantedBy=multi-user.target diff --git a/debian/postinst b/debian/postinst new file mode 100755 index 0000000..8b1e079 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,34 @@ +#!/bin/sh +set -e + +case "$1" in + configure) + # Set permissions + chmod 755 /opt/filepi/filepi + + # Set ownership and permissions for webdeploy + chown -R root:root /opt/filepi/webdeploy + find /opt/filepi/webdeploy -type f -exec chmod 644 {} \; + find /opt/filepi/webdeploy -type d -exec chmod 755 {} \; + + # Create the cache directory + mkdir -p /var/lib/filepi/media/.cache + + # Reload systemd to recognize the new service + systemctl daemon-reload + + # Enable and start the service + systemctl enable filepi.service + systemctl start filepi.service || true + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/debian/postrm b/debian/postrm new file mode 100755 index 0000000..5b2c166 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,27 @@ +#!/bin/sh +set -e + +case "$1" in + purge) + # Stop and disable the service before removing data + systemctl stop filepi.service || true + systemctl disable filepi.service || true + systemctl daemon-reload + # Remove the data directory + rm -rf /var/lib/filepi + ;; + + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + # Stop the service if it's running + systemctl stop filepi.service || true + systemctl disable filepi.service || true + systemctl daemon-reload + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/debian/rules b/debian/rules new file mode 100644 index 0000000..0084154 --- /dev/null +++ b/debian/rules @@ -0,0 +1,17 @@ +#!/usr/bin/make -f +%: + dh $@ + +override_dh_auto_build: + # The binary and webdeploy files will be provided externally, so no build step here + +override_dh_auto_install: + mkdir -p debian/filepi/opt/filepi + mkdir -p debian/filepi/lib/systemd/system + mkdir -p debian/filepi/var/lib/filepi/media + cp filepi debian/filepi/opt/filepi/ + cp debian/filepi.service debian/filepi/lib/systemd/system/ + # Copy the webdeploy directory if it exists + if [ -d "webdeploy" ]; then \ + cp -r webdeploy debian/filepi/opt/filepi/; \ + fi \ No newline at end of file diff --git a/docs/DEBIAN-INSTALL.md b/docs/DEBIAN-INSTALL.md new file mode 100644 index 0000000..3fe358a --- /dev/null +++ b/docs/DEBIAN-INSTALL.md @@ -0,0 +1,160 @@ +# Installing FilePi using Debian package + +This document provides instructions for installing FilePi using the Debian package (.deb). + +## Prerequisites + +- Debian-based Linux distribution (Debian, Ubuntu, Raspberry Pi OS, etc.) +- FFmpeg installed (`sudo apt install ffmpeg`) + +## Installation + +1. Download the appropriate .deb package for your architecture: + - `filepi_*_amd64.deb` for 64-bit x86 systems + - `filepi_*_arm64.deb` for 64-bit ARM systems (Raspberry Pi 4 with 64-bit OS) + - `filepi_*_armhf.deb` for 32-bit ARM systems (older Raspberry Pi models) + +2. Install the package: + ```bash + sudo dpkg -i filepi_*.deb + ``` + +3. If you encounter any dependency issues, run: + ```bash + sudo apt-get install -f + ``` + +## Post-Installation + +After installation, the FilePi service will be automatically enabled and started. + +- **Web Interface**: Access at `http://:8080` +- **Default Media Directory**: `/var/lib/filepi/media` +- **Default Port**: `8080` +- **Service Name**: `filepi.service` + +To verify the service is running: +```bash +sudo systemctl status filepi.service +``` + +To find your server's IP address: +```bash +hostname -I +``` + +## Configuration + +### Changing the media directory + +1. Edit the systemd service file: + ```bash + sudo systemctl edit filepi.service + ``` + +2. Add the following lines: + ```ini + [Service] + Environment=FILE_PI_ROOT_DIR=/your/preferred/path + ``` + +3. Restart the service: + ```bash + sudo systemctl restart filepi.service + ``` + +### Changing the server port + +1. Edit the systemd service file: + ```bash + sudo systemctl edit filepi.service + ``` + +2. Add the following lines: + ```ini + [Service] + Environment=FILE_PI_PORT=8012 + ``` + +3. Restart the service: + ```bash + sudo systemctl restart filepi.service + ``` + +### Changing log level + +1. Edit the systemd service file: + ```bash + sudo systemctl edit filepi.service + ``` + +2. Add the following lines: + ```ini + [Service] + Environment=FILE_PI_LOGLEVEL=DEBUG + ``` + +3. Restart the service: + ```bash + sudo systemctl restart filepi.service + ``` + +### Multiple configuration options + +You can combine multiple environment variables in a single override: + +```bash +sudo systemctl edit filepi.service +``` + +```ini +[Service] +Environment=FILE_PI_ROOT_DIR=/your/preferred/path +Environment=FILE_PI_PORT=8012 +Environment=FILE_PI_LOGLEVEL=INFO +``` + +## Service Management + +- Check service status: + ```bash + sudo systemctl status filepi.service + ``` + +- Stop the service: + ```bash + sudo systemctl stop filepi.service + ``` + +- Start the service: + ```bash + sudo systemctl start filepi.service + ``` + +- Disable automatic startup: + ```bash + sudo systemctl disable filepi.service + ``` + +- View logs: + ```bash + sudo journalctl -u filepi.service + ``` + +## Uninstallation + +To remove FilePi while preserving user data: + +```bash +sudo systemctl stop filepi.service +sudo apt remove filepi +``` + +To completely remove FilePi including all data and configuration: + +```bash +sudo systemctl stop filepi.service +sudo apt remove --purge filepi +``` + +**Note:** Using `--purge` will permanently delete all files in `/var/lib/filepi/media/` including your media files. Use with caution! \ No newline at end of file diff --git a/frontend/FilePiWeb/Layout/EmptyLayout.razor b/frontend/FilePiWeb/Layout/EmptyLayout.razor index 2666712..b90c6b6 100644 --- a/frontend/FilePiWeb/Layout/EmptyLayout.razor +++ b/frontend/FilePiWeb/Layout/EmptyLayout.razor @@ -13,7 +13,7 @@ padding: 0; overflow: hidden; } - + body { margin: 0; padding: 0;