feat(bundled-binary): release-binaries.yml + install docs + binstall metadata
Adds the tag-triggered workflow that builds 4 musl-linux + macOS tarballs and attaches them to the tag's GitHub Release. Ships a matching install guide (docs/install/binary.md) with SHA256SUMS verify, systemd unit, upgrade flow, and hardware notes. Adds [package.metadata.binstall] so 'cargo binstall oxicloud' works automatically once the first release lands. Also re-enables incremental compilation in the dev profile — the 'modest single-crate savings' rationale from when the crate was small has been outgrown; full rebuild ~10 min is now the dev-loop bottleneck.
This commit is contained in:
@@ -0,0 +1,275 @@
|
|||||||
|
name: "Release Binaries (musl-linux + macOS)"
|
||||||
|
|
||||||
|
# Per-run title shown in the Actions tab — makes it obvious at a
|
||||||
|
# glance which tag is being packaged and whether a manual run is a
|
||||||
|
# dry-run (build tarballs into workflow artifacts, DON'T attach to
|
||||||
|
# any GitHub Release).
|
||||||
|
run-name: >-
|
||||||
|
Release Binaries
|
||||||
|
${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && '[DRY-RUN]' || '' }}
|
||||||
|
— ${{ github.event.inputs.version || github.ref_name }}
|
||||||
|
|
||||||
|
# TRIGGERS — deliberately narrow. This workflow builds 4 platform
|
||||||
|
# binaries (~15-25 min wall-clock, matrix of native runners) and
|
||||||
|
# attaches them to a GitHub Release. Running on every push to main
|
||||||
|
# would be gratuitous CI cost + noise — the point is to package
|
||||||
|
# releases, not to sanity-check the tip. The bundled-binary
|
||||||
|
# integration test in ci.yml already covers "does the embed still
|
||||||
|
# work" on every PR.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Existing tag to package (e.g. v0.9.0). Must exist on origin.'
|
||||||
|
required: true
|
||||||
|
dry_run:
|
||||||
|
description: 'Dry run — build + upload tarballs as workflow artifacts, skip attaching to a Release. Use to smoke-test workflow edits without publishing.'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
# Concurrency key includes the tag ref so different tags don't cancel
|
||||||
|
# each other; `cancel-in-progress: false` because tag builds are
|
||||||
|
# unique + immutable — a superseded release build has nothing to cancel.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ── Stage 1: Build the SPA once, share across all platforms ─────────
|
||||||
|
#
|
||||||
|
# The SvelteKit build is arch-independent so a single ubuntu runner
|
||||||
|
# produces static-dist/ for every downstream binary-build matrix
|
||||||
|
# entry — saves ~2 min × 4 = 8 min vs building it per platform.
|
||||||
|
frontend-build:
|
||||||
|
name: Build SPA (Vite → static-dist/)
|
||||||
|
# Publish gate — same fork-friendly pattern as docker-publish.yml.
|
||||||
|
# Canonical repo always builds; forks stay quiet unless the fork
|
||||||
|
# owner opts in via `vars.ENABLE_BINARY_RELEASE=true` under Settings
|
||||||
|
# → Secrets and variables → Actions → Variables.
|
||||||
|
if: |
|
||||||
|
github.repository == 'AtalayaLabs/OxiCloud' ||
|
||||||
|
vars.ENABLE_BINARY_RELEASE == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# Build the exact tag being packaged. `github.ref` is
|
||||||
|
# refs/tags/vX.Y.Z on push, refs/heads/... on dispatch (we
|
||||||
|
# override via `inputs.version` in that case).
|
||||||
|
ref: ${{ github.event.inputs.version || github.ref }}
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 26.3.0
|
||||||
|
cache: npm
|
||||||
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
|
||||||
|
- name: Build SPA
|
||||||
|
working-directory: frontend
|
||||||
|
run: npm ci && npm run build
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: static-dist
|
||||||
|
# Repo-root output (SvelteKit adapter-static's `pages:
|
||||||
|
# '../static-dist'`). Downstream jobs restore it to the same
|
||||||
|
# location so rust-embed's `#[folder = "static-dist/"]`
|
||||||
|
# resolves without any path juggling.
|
||||||
|
path: static-dist/
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
# ── Stage 2: Build one binary per target ────────────────────────────
|
||||||
|
#
|
||||||
|
# 4-way matrix — 2 musl-linux (native amd64 + arm64) + 2 macOS
|
||||||
|
# (Apple Silicon + last Intel runner tier). Windows is deferred.
|
||||||
|
#
|
||||||
|
# Linux builds run inside the `rust:1.96-alpine3.24` container the
|
||||||
|
# Dockerfile already uses — guarantees byte-for-byte parity with the
|
||||||
|
# published Docker image; zero new toolchain to maintain. macOS builds
|
||||||
|
# run natively (no cross-compile). See docs/plan/bundled-binary.md § 3
|
||||||
|
# for the target-matrix rationale.
|
||||||
|
binary-build:
|
||||||
|
name: Build ${{ matrix.triple }}
|
||||||
|
needs: frontend-build
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
timeout-minutes: 60
|
||||||
|
strategy:
|
||||||
|
# `fail-fast: false` — one platform's compile failure shouldn't
|
||||||
|
# cancel the other three. Partial releases are better than none.
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- triple: x86_64-unknown-linux-musl
|
||||||
|
runner: ubuntu-22.04
|
||||||
|
container: rust:1.96-alpine3.24
|
||||||
|
rustflags: "-C target-cpu=x86-64-v2"
|
||||||
|
- triple: aarch64-unknown-linux-musl
|
||||||
|
runner: ubuntu-22.04-arm
|
||||||
|
container: rust:1.96-alpine3.24
|
||||||
|
# ARMv8-A baseline — covers Pi 4/5, Graviton, every 64-bit
|
||||||
|
# ARM Linux server. `generic` is rustc's neutral baseline.
|
||||||
|
rustflags: "-C target-cpu=generic"
|
||||||
|
- triple: aarch64-apple-darwin
|
||||||
|
runner: macos-latest
|
||||||
|
container: ""
|
||||||
|
rustflags: "-C target-cpu=apple-m1"
|
||||||
|
- triple: x86_64-apple-darwin
|
||||||
|
runner: macos-13
|
||||||
|
container: ""
|
||||||
|
rustflags: "-C target-cpu=x86-64-v2"
|
||||||
|
container: ${{ matrix.container || null }}
|
||||||
|
steps:
|
||||||
|
# Alpine container image doesn't ship the deps our build needs
|
||||||
|
# (git for build.rs's GIT_HASH stamping, musl-dev for aws-lc-sys
|
||||||
|
# C parts, plus the toolchain scaffold from the Dockerfile
|
||||||
|
# builder stage). Install once at job start.
|
||||||
|
- name: Install Alpine build deps
|
||||||
|
if: matrix.container != ''
|
||||||
|
run: apk add --no-cache musl-dev pkgconfig gcc perl make bash git curl tar
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.version || github.ref }}
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
# Key by triple so the 4 targets don't share caches
|
||||||
|
# (different feature set + different target triple = different
|
||||||
|
# compiled artefacts).
|
||||||
|
key: ${{ matrix.triple }}
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: static-dist
|
||||||
|
path: static-dist/
|
||||||
|
|
||||||
|
# rustup targets are pre-installed for the runner's own host
|
||||||
|
# triple; other targets need explicit `rustup target add`. macOS
|
||||||
|
# runners already have both apple-* triples; only touch this in
|
||||||
|
# the container path where rustup's default target list is minimal.
|
||||||
|
- name: Add rustup target
|
||||||
|
if: matrix.container == '' && matrix.triple != ''
|
||||||
|
run: rustup target add ${{ matrix.triple }}
|
||||||
|
|
||||||
|
# `--features bundled-assets` bakes static-dist/ into the binary
|
||||||
|
# via rust-embed. `--bin oxicloud` — the single binary the merge
|
||||||
|
# (Deliverable 1b) consolidated everything into.
|
||||||
|
- name: Build binary
|
||||||
|
env:
|
||||||
|
# Per-triple CPU baseline — release binaries target the widest
|
||||||
|
# realistic install base for their arch. See
|
||||||
|
# docs/plan/bundled-binary.md § 3.
|
||||||
|
RUSTFLAGS: ${{ matrix.rustflags }}
|
||||||
|
# Git metadata injection — build.rs reads these env vars to
|
||||||
|
# stamp GIT_HASH / GIT_BRANCH into the binary. Without them
|
||||||
|
# `oxicloud --version` reports "unknown".
|
||||||
|
GITHUB_SHA: ${{ github.sha }}
|
||||||
|
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||||
|
run: |
|
||||||
|
cargo build --release --features bundled-assets --bin oxicloud --target ${{ matrix.triple }}
|
||||||
|
|
||||||
|
# Assemble the tarball layout documented in
|
||||||
|
# docs/plan/bundled-binary.md § 4: oxicloud + example.env +
|
||||||
|
# LICENSE + README-install.md, rooted under a per-version-per-
|
||||||
|
# triple directory so `tar xzf` lands cleanly.
|
||||||
|
- name: Package tarball
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# Version = tag stripped of leading `v` (workflow_dispatch)
|
||||||
|
# or ref_name stripped (push tag). Falls back to ref_name
|
||||||
|
# verbatim if neither strip matches.
|
||||||
|
RAW_REF="${{ github.event.inputs.version || github.ref_name }}"
|
||||||
|
VERSION="${RAW_REF#v}"
|
||||||
|
DIST="oxicloud-${VERSION}-${{ matrix.triple }}"
|
||||||
|
mkdir -p "dist/${DIST}"
|
||||||
|
cp "target/${{ matrix.triple }}/release/oxicloud" "dist/${DIST}/oxicloud"
|
||||||
|
cp example.env "dist/${DIST}/example.env"
|
||||||
|
cp LICENSE "dist/${DIST}/LICENSE"
|
||||||
|
# README-install.md may not exist yet in early releases —
|
||||||
|
# ship a stub that points at the docs site so users have
|
||||||
|
# something in the tarball. Deliverable 6 replaces it with
|
||||||
|
# a proper install guide.
|
||||||
|
if [ -f docs/install/binary.md ]; then
|
||||||
|
cp docs/install/binary.md "dist/${DIST}/README-install.md"
|
||||||
|
else
|
||||||
|
cat > "dist/${DIST}/README-install.md" <<'MD'
|
||||||
|
# OxiCloud — Installation
|
||||||
|
|
||||||
|
Full documentation: https://github.com/AtalayaLabs/OxiCloud/tree/main/docs
|
||||||
|
|
||||||
|
Quickstart:
|
||||||
|
1. Set DATABASE_URL to a PostgreSQL 13+ instance
|
||||||
|
(with pg_trgm + ltree extensions).
|
||||||
|
2. Copy example.env → .env, edit as needed.
|
||||||
|
3. Run ./oxicloud.
|
||||||
|
|
||||||
|
Optional: install ffmpeg for server-side video thumbnails
|
||||||
|
(or set OXICLOUD_ENABLE_VIDEO_THUMBNAILS=false to disable).
|
||||||
|
MD
|
||||||
|
fi
|
||||||
|
# Deterministic tar (owner/group/mtime pinned) so re-running
|
||||||
|
# the build produces byte-identical archives — helps with
|
||||||
|
# reproducible-build audits and cheap hash verification.
|
||||||
|
tar --owner=0 --group=0 -czf "dist/${DIST}.tar.gz" -C dist "${DIST}"
|
||||||
|
ls -la "dist/${DIST}.tar.gz"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: tarball-${{ matrix.triple }}
|
||||||
|
path: dist/*.tar.gz
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
# ── Stage 3: Attach all tarballs + SHA256SUMS to the Release ────────
|
||||||
|
#
|
||||||
|
# `dry_run: true` (workflow_dispatch only) skips this job — the
|
||||||
|
# binary tarballs stay as workflow artifacts (accessible from the
|
||||||
|
# run page for 1 day) but nothing lands on any Release.
|
||||||
|
release:
|
||||||
|
name: Attach tarballs to GitHub Release
|
||||||
|
needs: binary-build
|
||||||
|
if: |
|
||||||
|
needs.binary-build.result == 'success' &&
|
||||||
|
(github.event_name != 'workflow_dispatch' || github.event.inputs.dry_run != 'true')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: tarball-*
|
||||||
|
path: dist/
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Compute SHA256SUMS
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd dist
|
||||||
|
# Sort output for stable ordering across re-runs — the file
|
||||||
|
# doubles as a manifest an operator can `diff` between two
|
||||||
|
# release runs to prove they're identical.
|
||||||
|
sha256sum *.tar.gz | sort > SHA256SUMS
|
||||||
|
cat SHA256SUMS
|
||||||
|
|
||||||
|
# softprops/action-gh-release@v2 semantics:
|
||||||
|
# - If the Release for this tag EXISTS (created by release.yml
|
||||||
|
# which runs in parallel on the same tag push), attaches the
|
||||||
|
# files to it.
|
||||||
|
# - If it doesn't yet exist (race — release.yml still running),
|
||||||
|
# creates a bare Release which release.yml then fills in with
|
||||||
|
# notes when it finishes.
|
||||||
|
# Benign either way; see docs/plan/bundled-binary.md § 5
|
||||||
|
# "Parallel-fire behaviour on tag push".
|
||||||
|
- name: Attach to Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
dist/*.tar.gz
|
||||||
|
dist/SHA256SUMS
|
||||||
|
fail_on_unmatched_files: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
+32
-5
@@ -4,6 +4,24 @@ version = "0.8.7"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
default-run = "oxicloud"
|
default-run = "oxicloud"
|
||||||
|
|
||||||
|
# `cargo binstall oxicloud` — fetches the prebuilt release tarball for the
|
||||||
|
# host triple from GitHub Releases (attached by
|
||||||
|
# `.github/workflows/release-binaries.yml`) instead of compiling from
|
||||||
|
# source. Templates match the tarball naming
|
||||||
|
# `oxicloud-<version>-<triple>.tar.gz` produced by that workflow.
|
||||||
|
#
|
||||||
|
# `pkg-fmt = "tgz"` — otherwise binstall guesses from the URL extension;
|
||||||
|
# being explicit lets `cargo binstall oxicloud` succeed on Windows too
|
||||||
|
# (where the URL string parsing differs).
|
||||||
|
#
|
||||||
|
# Once the first tagged release lands on GitHub, this becomes a one-line
|
||||||
|
# install for anyone with the Rust toolchain who prefers not to build
|
||||||
|
# from source and doesn't want Docker either.
|
||||||
|
[package.metadata.binstall]
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/oxicloud-{ version }-{ target }.tar.gz"
|
||||||
|
pkg-fmt = "tgz"
|
||||||
|
bin-dir = "oxicloud-{ version }-{ target }/{ bin }{ binary-ext }"
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mimalloc = { version = "0.1.52", default-features = false }
|
mimalloc = { version = "0.1.52", default-features = false }
|
||||||
@@ -979,11 +997,20 @@ opt-level = 1
|
|||||||
debug = "line-tables-only"
|
debug = "line-tables-only"
|
||||||
split-debuginfo = "unpacked"
|
split-debuginfo = "unpacked"
|
||||||
# Incremental compilation caches per-function IR fingerprints so a
|
# Incremental compilation caches per-function IR fingerprints so a
|
||||||
# small edit only recompiles what changed. On a single-crate rebuild
|
# small edit only recompiles what changed. The `target/incremental/`
|
||||||
# (oxicloud is one crate) the savings are modest — worth < the ~7 GB
|
# cache costs ~7 GB per profile, but at the current codebase size a
|
||||||
# incremental/ cache costs on disk. Rust-analyzer uses `cargo check`,
|
# full rebuild is ~10 minutes and an incremental single-file edit is
|
||||||
# which has its own cache, so LSP responsiveness is unaffected.
|
# seconds — the disk is worth it and then some. Rust-analyzer's own
|
||||||
incremental = false
|
# `cargo check` cache is separate; LSP responsiveness is unaffected
|
||||||
|
# either way.
|
||||||
|
#
|
||||||
|
# History: this was `= false` early on when the crate was small and
|
||||||
|
# incremental's savings didn't cover the disk cost. Re-enabled
|
||||||
|
# 2026-08-29 as the full-rebuild time crossed the "feels annoying"
|
||||||
|
# threshold on typical dev-loop edits. If disk pressure ever spikes,
|
||||||
|
# `cargo clean -p oxicloud --profile dev` clears the incremental cache
|
||||||
|
# without wiping compiled deps.
|
||||||
|
incremental = true
|
||||||
|
|
||||||
[profile.bench]
|
[profile.bench]
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
|
|||||||
@@ -68,6 +68,16 @@ docker compose up -d
|
|||||||
|
|
||||||
Open `http://localhost:8086`.
|
Open `http://localhost:8086`.
|
||||||
|
|
||||||
|
### Prebuilt binary
|
||||||
|
|
||||||
|
Binary releases (Linux musl amd64/arm64, macOS Intel/Apple Silicon)
|
||||||
|
are attached to every tagged release on GitHub — the whole SPA + all
|
||||||
|
operator subcommands + migrations bake into a single self-contained
|
||||||
|
executable. See [`docs/install/binary.md`](docs/install/binary.md) for
|
||||||
|
the download / verify / systemd walkthrough.
|
||||||
|
|
||||||
|
`cargo binstall oxicloud` works too once a release is out.
|
||||||
|
|
||||||
### Run from source
|
### Run from source
|
||||||
|
|
||||||
Requires Rust 1.93+ and PostgreSQL.
|
Requires Rust 1.93+ and PostgreSQL.
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
# Installing OxiCloud from a Binary Release
|
||||||
|
|
||||||
|
OxiCloud ships prebuilt binaries for common Linux and macOS platforms
|
||||||
|
attached to every tagged release on GitHub. This page covers downloading,
|
||||||
|
verifying, and running one.
|
||||||
|
|
||||||
|
If you'd rather run OxiCloud as a container, see the Docker image at
|
||||||
|
`ghcr.io/atalayalabs/oxicloud`. If you're a Rust developer who just
|
||||||
|
wants the binary without hand-fetching a tarball, `cargo binstall
|
||||||
|
oxicloud` picks the right archive for your host automatically.
|
||||||
|
|
||||||
|
## Which tarball do I want?
|
||||||
|
|
||||||
|
Every release attaches four tarballs plus a `SHA256SUMS` manifest. Pick
|
||||||
|
by your host's architecture and OS:
|
||||||
|
|
||||||
|
| Host | Tarball |
|
||||||
|
|---|---|
|
||||||
|
| Linux x86-64 (Intel / AMD servers, most VPS, WSL) | `oxicloud-<version>-x86_64-unknown-linux-musl.tar.gz` |
|
||||||
|
| Linux ARM64 (Raspberry Pi 4/5, Ampere, Graviton, ARM servers) | `oxicloud-<version>-aarch64-unknown-linux-musl.tar.gz` |
|
||||||
|
| macOS Apple Silicon (M-series) | `oxicloud-<version>-aarch64-apple-darwin.tar.gz` |
|
||||||
|
| macOS Intel | `oxicloud-<version>-x86_64-apple-darwin.tar.gz` |
|
||||||
|
|
||||||
|
The Linux tarballs link against musl, so they run on ANY glibc version
|
||||||
|
— Alpine, Debian, Ubuntu, Fedora, Arch, Rocky, and every version in
|
||||||
|
between. You never need to worry about `GLIBC_x.yy not found`.
|
||||||
|
|
||||||
|
Windows and 32-bit ARM are not currently shipped.
|
||||||
|
|
||||||
|
## Hardware notes
|
||||||
|
|
||||||
|
| Model | Notes |
|
||||||
|
|---|---|
|
||||||
|
| Pi 5 (4 GB / 8 GB) | Good experience |
|
||||||
|
| Pi 4 (4 GB / 8 GB) | Solid |
|
||||||
|
| Pi 4 (2 GB) | Works with face indexing disabled; expect swap under load |
|
||||||
|
| Pi 3 (any variant) | Marginal — only for a very light single-user personal cloud |
|
||||||
|
| Pi 2 / Pi Zero / Pi 1 | Not supported (1 GB RAM is below the practical floor) |
|
||||||
|
| Any ARM64 server | Good — the aarch64 tarball is what you want |
|
||||||
|
| Any x86-64 server from 2010 or newer | Good — Nehalem / Bulldozer + newer, per the release CPU baseline |
|
||||||
|
|
||||||
|
## Verifying the download
|
||||||
|
|
||||||
|
Every release ships a `SHA256SUMS` manifest listing every tarball with
|
||||||
|
its hash. Verify your download before extracting:
|
||||||
|
|
||||||
|
```
|
||||||
|
sha256sum -c SHA256SUMS
|
||||||
|
```
|
||||||
|
|
||||||
|
Only files present in the current directory are checked, so this
|
||||||
|
succeeds when just the tarball you downloaded matches its entry.
|
||||||
|
|
||||||
|
## Extracting
|
||||||
|
|
||||||
|
The archive lands as a per-version-per-triple directory next to it:
|
||||||
|
|
||||||
|
```
|
||||||
|
tar xzf oxicloud-<version>-<triple>.tar.gz
|
||||||
|
cd oxicloud-<version>-<triple>/
|
||||||
|
ls
|
||||||
|
# oxicloud example.env LICENSE README-install.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The four files:
|
||||||
|
|
||||||
|
- `oxicloud` — the single self-contained binary. The server, all
|
||||||
|
operator subcommands (`oxicloud opaque setup`, `oxicloud migrate
|
||||||
|
nfc-filenames`, `oxicloud storage select`), and the SvelteKit web
|
||||||
|
frontend are all baked in.
|
||||||
|
- `example.env` — every OxiCloud environment variable documented with
|
||||||
|
defaults. Copy to `.env` and edit as needed.
|
||||||
|
- `LICENSE` — the project license.
|
||||||
|
- `README-install.md` — a shorter version of this page for offline
|
||||||
|
reference.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Only one moving part is required: a PostgreSQL 13+ instance with the
|
||||||
|
`pg_trgm` and `ltree` extensions available. Anything else you might
|
||||||
|
need is either baked into the binary or optional.
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- **PostgreSQL 13+** with `pg_trgm` and `ltree` extensions. Any distro
|
||||||
|
package works (Debian/Ubuntu's `postgresql`, Alpine's `postgresql`,
|
||||||
|
Homebrew's `postgresql@17`, etc.). Cloud databases like Neon,
|
||||||
|
Supabase, and RDS also work provided the two extensions are enabled.
|
||||||
|
|
||||||
|
### System libraries (usually pre-installed)
|
||||||
|
|
||||||
|
- **`ca-certificates`** — for outbound HTTPS (OIDC discovery, S3, magic
|
||||||
|
links). Pre-installed on essentially every distribution.
|
||||||
|
- **`tzdata`** — timezone database. Pre-installed on nearly every
|
||||||
|
distribution; alpine minimal images sometimes need it added.
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
- **`ffmpeg`** — only needed if you want the server to extract a
|
||||||
|
thumbnail frame from uploaded videos. When ffmpeg is missing the
|
||||||
|
server logs a warning at boot and videos get a placeholder icon —
|
||||||
|
everything else keeps working. If your client uploads video
|
||||||
|
previews itself (some desktop and mobile clients do), or if you
|
||||||
|
simply don't want thumbnails, set
|
||||||
|
`OXICLOUD_ENABLE_VIDEO_THUMBNAILS=false` in your `.env` to silence
|
||||||
|
the warning.
|
||||||
|
|
||||||
|
Distro install commands for the optional prerequisite:
|
||||||
|
|
||||||
|
| Distro | Command |
|
||||||
|
|---|---|
|
||||||
|
| Alpine | `apk add ffmpeg` |
|
||||||
|
| Debian / Ubuntu | `apt install ffmpeg` |
|
||||||
|
| Fedora / RHEL | `dnf install ffmpeg` (RPM Fusion for the full codec set) |
|
||||||
|
| Arch | `pacman -S ffmpeg` |
|
||||||
|
| macOS | `brew install ffmpeg` |
|
||||||
|
| Any Linux (portable) | grab a static build from https://github.com/BtbN/FFmpeg-Builds/releases and point `OXICLOUD_FFMPEG_PATH` at it |
|
||||||
|
|
||||||
|
## First run
|
||||||
|
|
||||||
|
The absolute minimum to boot the server is `DATABASE_URL`:
|
||||||
|
|
||||||
|
```
|
||||||
|
DATABASE_URL="postgres://oxicloud:secret@localhost:5432/oxicloud" \
|
||||||
|
./oxicloud
|
||||||
|
```
|
||||||
|
|
||||||
|
The binary applies its embedded database migrations on startup, then
|
||||||
|
listens on `127.0.0.1:8086` by default. Open your browser at
|
||||||
|
`http://localhost:8086/` and follow the setup flow to create the first
|
||||||
|
admin account.
|
||||||
|
|
||||||
|
For anything more than a smoke test, copy `example.env` to `.env`,
|
||||||
|
edit it, and run `./oxicloud --config .env` — that pins the config
|
||||||
|
source and makes stray shell environment variables not silently leak
|
||||||
|
in.
|
||||||
|
|
||||||
|
## Running as a systemd service (Linux)
|
||||||
|
|
||||||
|
Move the binary to a system location and create a systemd unit. The
|
||||||
|
example below runs as a dedicated `oxicloud` user, loads config from
|
||||||
|
`/etc/oxicloud/oxicloud.env`, and stores data under `/var/lib/oxicloud`.
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo useradd --system --home /var/lib/oxicloud --create-home --shell /usr/sbin/nologin oxicloud
|
||||||
|
sudo install -m 0755 oxicloud /usr/local/bin/oxicloud
|
||||||
|
sudo mkdir -p /etc/oxicloud
|
||||||
|
sudo cp example.env /etc/oxicloud/oxicloud.env
|
||||||
|
sudo chown -R oxicloud:oxicloud /etc/oxicloud
|
||||||
|
sudo chmod 0640 /etc/oxicloud/oxicloud.env
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `/etc/systemd/system/oxicloud.service`:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Unit]
|
||||||
|
Description=OxiCloud self-hosted cloud storage
|
||||||
|
After=network-online.target postgresql.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=oxicloud
|
||||||
|
Group=oxicloud
|
||||||
|
WorkingDirectory=/var/lib/oxicloud
|
||||||
|
ExecStart=/usr/local/bin/oxicloud --config /etc/oxicloud/oxicloud.env
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
# Sandbox — plenty of room to tighten further per your policy
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/var/lib/oxicloud
|
||||||
|
PrivateTmp=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable and start:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now oxicloud
|
||||||
|
sudo systemctl status oxicloud
|
||||||
|
journalctl -u oxicloud -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Terminate the reverse-proxy (nginx, Caddy, HAProxy, Traefik) in front
|
||||||
|
of it for TLS and public exposure — OxiCloud itself binds plaintext
|
||||||
|
HTTP on `127.0.0.1` by default.
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
Replace the binary and restart the service:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Download and verify the new tarball
|
||||||
|
sha256sum -c SHA256SUMS
|
||||||
|
tar xzf oxicloud-<new-version>-<triple>.tar.gz
|
||||||
|
cd oxicloud-<new-version>-<triple>/
|
||||||
|
|
||||||
|
sudo systemctl stop oxicloud
|
||||||
|
sudo install -m 0755 oxicloud /usr/local/bin/oxicloud
|
||||||
|
sudo systemctl start oxicloud
|
||||||
|
```
|
||||||
|
|
||||||
|
Database migrations apply automatically on startup. Rollbacks are not
|
||||||
|
supported by sqlx's migration model; if you need to roll back, stop
|
||||||
|
the server, roll back your Postgres data directory to a snapshot, and
|
||||||
|
install the previous binary.
|
||||||
|
|
||||||
|
## Installing via `cargo binstall`
|
||||||
|
|
||||||
|
If you already have the Rust toolchain and just want the binary
|
||||||
|
without hand-picking a tarball:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo binstall oxicloud
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo-binstall` reads the URL template baked into the release
|
||||||
|
metadata, downloads the tarball for your host triple, verifies its
|
||||||
|
signature (when present), and installs `oxicloud` into
|
||||||
|
`~/.cargo/bin`. This resolves to the same tarball you'd download by
|
||||||
|
hand.
|
||||||
|
|
||||||
|
## Where to go from here
|
||||||
|
|
||||||
|
- Environment reference — see [`docs/config/env.md`](../config/env.md)
|
||||||
|
for every `OXICLOUD_*` variable and its default.
|
||||||
|
- Authentication setup (OPAQUE, OIDC, magic links) — see
|
||||||
|
[`docs/config/authentication.md`](../config/authentication.md).
|
||||||
|
- Storage backends (local disk, S3, Azure Blob, encryption) — see
|
||||||
|
[`docs/config/storage.md`](../config/storage.md) if present, or the
|
||||||
|
entries under `OXICLOUD_STORAGE_*` in the environment reference.
|
||||||
|
- File a bug or a feature request — GitHub issues at
|
||||||
|
https://github.com/AtalayaLabs/OxiCloud.
|
||||||
Reference in New Issue
Block a user