diff --git a/.dockerignore b/.dockerignore index 291f808..a784341 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,3 +8,4 @@ sdk/node_modules *.db *.env !deploy/*.env.example +website diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f7db812..1e5363b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,9 +10,17 @@ updates: schedule: { interval: weekly } groups: web: { patterns: ["*"], update-types: [minor, patch] } + - package-ecosystem: composer + directory: /website + schedule: { interval: weekly } + groups: + website: { patterns: ["*"], update-types: [minor, patch] } - package-ecosystem: github-actions directory: / schedule: { interval: monthly } - package-ecosystem: docker directory: / schedule: { interval: monthly } + - package-ecosystem: docker + directory: /website + schedule: { interval: monthly } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f8b25d..4c27434 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,23 @@ jobs: node-version: 24 - run: node --test sdk/index.test.ts + website: + runs-on: ubuntu-latest + defaults: + run: + working-directory: website + steps: + - uses: actions/checkout@v7 + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.4" + tools: composer + - run: composer validate --no-check-publish + - run: composer install --no-interaction --prefer-dist + - run: cp .env.example .env && php artisan key:generate + - run: php artisan test + - run: docker build -t aven-website:ci . + image: runs-on: ubuntu-latest steps: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8788242..93d4843 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,8 @@ go test ./... cd web && npm run typecheck && npm run i18n:check ``` +The project website (aven.turin.my) is a separate Laravel app in `website/`; run its tests with `cd website && composer install && php artisan test`. + Build all distributable binaries and the web UI with `make all`. Edit web sources under `web/`; the embedded UI assets are generated by the build and are not checked in. ## Translations diff --git a/README.ko.md b/README.ko.md index d678db2..71d4123 100644 --- a/README.ko.md +++ b/README.ko.md @@ -266,6 +266,7 @@ Railway는 그 권리자의 상표이며, 여기서는 호환성을 설명하는 - [HTTP API](docs/api-v1.md)와 [GraphQL API](docs/graphql.md) - [권한과 작업 공간](docs/permissions.md) - [Feature flags SDK](sdk/README.md) +- [웹사이트](website/README.md): [aven.turin.my](https://aven.turin.my)와 설치 스크립트 호스트의 소스 ## 자주 묻는 질문 diff --git a/README.md b/README.md index 23f2888..2eea707 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,7 @@ Railway is a trademark of its owner and is used here only to describe compatibil - [HTTP API](docs/api-v1.md) and [GraphQL API](docs/graphql.md) - [Permissions and workspaces](docs/permissions.md) - [Feature flags SDK](sdk/README.md) +- [Website](website/README.md): the source of [aven.turin.my](https://aven.turin.my) and the installer host ## FAQ diff --git a/website/.dockerignore b/website/.dockerignore new file mode 100644 index 0000000..0e17540 --- /dev/null +++ b/website/.dockerignore @@ -0,0 +1,15 @@ +.git +.env +.env.* +!.env.example +vendor +node_modules +storage/logs/* +storage/framework/cache/data/* +storage/framework/views/* +storage/framework/sessions/* +bootstrap/cache/*.php +database/*.sqlite +tests +.keel +.aven diff --git a/website/.editorconfig b/website/.editorconfig new file mode 100644 index 0000000..6df8428 --- /dev/null +++ b/website/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[{compose,docker-compose}.{yml,yaml}] +indent_size = 4 diff --git a/website/.env.example b/website/.env.example new file mode 100644 index 0000000..f79e50d --- /dev/null +++ b/website/.env.example @@ -0,0 +1,65 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost:8000 + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +# PHP_CLI_SERVER_WORKERS=4 + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=sqlite +# DB_HOST=127.0.0.1 +# DB_PORT=3306 +# DB_DATABASE=laravel +# DB_USERNAME=root +# DB_PASSWORD= + +SESSION_DRIVER=array +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=file +# CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=log +MAIL_SCHEME=null +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +VITE_APP_NAME="${APP_NAME}" diff --git a/website/.gitattributes b/website/.gitattributes new file mode 100644 index 0000000..fcb21d3 --- /dev/null +++ b/website/.gitattributes @@ -0,0 +1,11 @@ +* text=auto eol=lf + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +/.github export-ignore +CHANGELOG.md export-ignore +.styleci.yml export-ignore diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..0cb2b80 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,33 @@ +*.log +.DS_Store +.env +.env.backup +.env.production +.phpactor.json +.phpunit.result.cache +/.codex +/.cursor/ +/.idea +/.nova +/.phpunit.cache +/.vscode +/.zed +/auth.json +/node_modules +/public/build +/public/fonts-manifest.dev.json +/public/hot +/public/storage +/storage/*.key +/storage/pail +/vendor +_ide_helper.php +Homestead.json +Homestead.yaml +Thumbs.db + +# Agent notes from the Laravel scaffold (not for this project) +/AGENTS.md +/CLAUDE.md +/.keel +/.aven diff --git a/website/.npmrc b/website/.npmrc new file mode 100644 index 0000000..495a6af --- /dev/null +++ b/website/.npmrc @@ -0,0 +1,2 @@ +ignore-scripts=true +audit=true diff --git a/website/Dockerfile b/website/Dockerfile new file mode 100644 index 0000000..2fdb79f --- /dev/null +++ b/website/Dockerfile @@ -0,0 +1,24 @@ +FROM composer:2 AS vendor +WORKDIR /app +COPY composer.json composer.lock ./ +RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist --no-interaction +COPY . . +RUN composer dump-autoload --optimize --classmap-authoritative --no-dev + +FROM dunglas/frankenphp:1-php8.4-alpine +WORKDIR /app +ENV SERVER_NAME=":8080" \ + APP_ENV=production \ + APP_DEBUG=false \ + LOG_CHANNEL=stderr \ + SESSION_DRIVER=array \ + CACHE_STORE=file \ + DB_CONNECTION=sqlite +COPY --from=vendor /app /app +COPY docker/Caddyfile /etc/frankenphp/Caddyfile +RUN rm -f .env \ + && mkdir -p storage/framework/cache/data storage/framework/views storage/logs bootstrap/cache /data/caddy /config/caddy \ + && chown -R www-data:www-data storage bootstrap/cache /data /config +USER www-data +EXPOSE 8080 +CMD ["sh", "-c", "php artisan config:cache && php artisan route:cache && php artisan view:cache && exec frankenphp run --config /etc/frankenphp/Caddyfile"] diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..5ef87ab --- /dev/null +++ b/website/README.md @@ -0,0 +1,51 @@ +# Aven website + +The website for Aven, a self-hosted PaaS. It runs at [aven.turin.my](https://aven.turin.my) and also serves the installer at `install.aven.turin.my`. + +It is a small Laravel 13 app with no database, sessions or build step: Blade views, plain CSS and JS in `public/`, and self-hosted fonts. + +## Layout + +| Path | What it holds | +| --- | --- | +| `lang/en/site.php`, `lang/ko/site.php` | All page copy, English (`/`) and Korean (`/ko`) | +| `config/compare.php` | The Aven vs Railway / Coolify / Dokploy table, with a source link per cell | +| `config/site.php` | Install host, fallback release and the `aven up` transcript shown on the page | +| `resources/views/home.blade.php` | The landing page | +| `app/Http/Controllers/InstallController.php` | `install.aven.turin.my`: serves `install-server.sh` from Aven's `main` (cached for 5 minutes, last good copy kept as a fallback) and an HTML preview for link crawlers | +| `app/Support/DiscordEmbed.php`, `app/Support/StructuredData.php` | Discord link embeds and JSON-LD | +| `resources/views/llms*.blade.php`, `sitemap.blade.php` | `/llms.txt`, `/llms-full.txt`, `/sitemap.xml` | +| `public/` | CSS, JS, fonts, images and the demo videos | +| `Dockerfile`, `docker/Caddyfile` | FrankenPHP image listening on `:8080` | + +## Run it locally + +With PHP 8.3+ and Composer: + +```sh +composer install +cp .env.example .env +php artisan key:generate +php artisan serve +``` + +Without PHP on the host, use Docker: + +```sh +docker build -t aven-site . +docker run --rm -p 8080:8080 -e APP_KEY=base64:$(openssl rand -base64 32) aven-site +``` + +Run these from `website/`. Then open http://localhost:8080 (or http://localhost:8080/ko). + +## Deploy + +The production site runs on Aven itself and is deployed with `aven up` from this directory. It needs one variable, `APP_KEY`; everything else has defaults in the `Dockerfile`. + +## Contributing + +Corrections are welcome, especially to the comparison table: every cell links to its source, so please include a link when you change one. Use the same issues and pull requests as the rest of the repository; see [CONTRIBUTING.md](../CONTRIBUTING.md). + +## License + +[AGPL-3.0-only](../LICENSE), like the rest of Aven. Fonts and other bundled third-party files are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). diff --git a/website/THIRD_PARTY_NOTICES.md b/website/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..2a8df0a --- /dev/null +++ b/website/THIRD_PARTY_NOTICES.md @@ -0,0 +1,10 @@ +# Third-party notices + +This repository bundles the following third-party files. They keep their own licenses. + +| Files | Project | License | +| --- | --- | --- | +| `public/fonts/pretendard/` | [Pretendard](https://github.com/orioncactus/pretendard) by Kil Hyung-jin | [SIL Open Font License 1.1](https://github.com/orioncactus/pretendard/blob/main/LICENSE) | +| `public/fonts/jetbrains/` | [JetBrains Mono](https://github.com/JetBrains/JetBrainsMono) by JetBrains | [SIL Open Font License 1.1](https://github.com/JetBrains/JetBrainsMono/blob/master/OFL.txt) | + +PHP dependencies are installed by Composer and are listed with their licenses in `composer.lock`. diff --git a/website/app/Http/Controllers/Controller.php b/website/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..8677cd5 --- /dev/null +++ b/website/app/Http/Controllers/Controller.php @@ -0,0 +1,8 @@ +userAgent())) { + return $this->preview(); + } + + $script = Cache::get('install.fresh') ?? $this->fetch(); + + if ($script === null) { + return response("install-server.sh is unavailable right now. Get it from ".self::SOURCE."\n", 503) + ->header('Content-Type', 'text/plain; charset=utf-8') + ->header('Retry-After', '60'); + } + + return response($script) + ->header('Content-Type', 'text/plain; charset=utf-8') + ->header('Cache-Control', 'public, max-age=300') + ->header('X-Source', self::SOURCE) + ->header('Vary', 'User-Agent'); + } + + private function preview(): Response + { + $install = 'curl -fsSL https://'.config('site.install_host').' | sudo sh'; + + return response()->view('install-preview', [ + 'install' => $install, + 'discord' => DiscordEmbed::json(DiscordEmbed::install(SiteController::REPO, $install, self::SOURCE)), + ])->header('Vary', 'User-Agent'); + } + + /** Fetch the script; fall back to the last good copy when GitHub fails. */ + private function fetch(): ?string + { + try { + $res = Http::timeout(5)->get(self::SOURCE); + $body = $res->successful() ? $res->body() : null; + } catch (\Throwable) { + $body = null; + } + + if ($body !== null && str_starts_with($body, '#!')) { + Cache::put('install.fresh', $body, now()->addMinutes(5)); + Cache::forever('install.last', $body); + + return $body; + } + + return Cache::get('install.last'); + } +} diff --git a/website/app/Http/Controllers/SiteController.php b/website/app/Http/Controllers/SiteController.php new file mode 100644 index 0000000..98d1a26 --- /dev/null +++ b/website/app/Http/Controllers/SiteController.php @@ -0,0 +1,76 @@ +latestRelease(); + $install = 'curl -fsSL https://'.config('site.install_host').' | sudo sh'; + + return view('home', [ + 'locale' => $locale, + 'repo' => self::REPO, + 'release' => $release, + 'install' => $install, + 'discord' => DiscordEmbed::json(DiscordEmbed::home($locale, self::REPO, $release, $install)), + 'jsonld' => StructuredData::home($locale, self::REPO, $release, $install), + ]); + } + + /** /llms.txt and /llms-full.txt: the site as Markdown for AI crawlers (llmstxt.org). */ + public function llms(bool $full = false): Response + { + App::setLocale('en'); + + return response() + ->view($full ? 'llms-full' : 'llms', [ + 'repo' => self::REPO, + 'release' => $this->latestRelease(), + 'install' => 'curl -fsSL https://'.config('site.install_host').' | sudo sh', + 'compare' => config('compare'), + 'ko' => fn (string $key) => trans($key, [], 'ko'), + ]) + ->header('Content-Type', 'text/markdown; charset=utf-8'); + } + + public function sitemap(): Response + { + $pages = [route('home'), route('home.ko')]; + // Content changes with each deploy, so the page template's mtime is a fair lastmod. + $lastmod = date('Y-m-d', filemtime(resource_path('views/home.blade.php'))); + + return response() + ->view('sitemap', compact('pages', 'lastmod')) + ->header('Content-Type', 'application/xml; charset=utf-8'); + } + + private function latestRelease(): string + { + return Cache::remember('aven.release', now()->addHours(6), function () { + try { + $tag = Http::timeout(3) + ->acceptJson() + ->get('https://api.github.com/repos/turin-dev/aven/releases/latest') + ->json('tag_name'); + } catch (\Throwable) { + $tag = null; + } + + return is_string($tag) && $tag !== '' ? $tag : config('site.fallback_release'); + }); + } +} diff --git a/website/app/Models/User.php b/website/app/Models/User.php new file mode 100644 index 0000000..f6ba1d2 --- /dev/null +++ b/website/app/Models/User.php @@ -0,0 +1,32 @@ + */ + use HasFactory, Notifiable; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; + } +} diff --git a/website/app/Providers/AppServiceProvider.php b/website/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..452e6b6 --- /dev/null +++ b/website/app/Providers/AppServiceProvider.php @@ -0,0 +1,24 @@ + 9, + 'components' => [self::text("## Aven\n**".__('site.hero.title')."**\n".__('site.hero.lead'))], + 'accessory' => ['type' => 11, 'media' => ['url' => asset('img/icon-512.png')]], + ], + ['type' => 12, 'items' => [[ + 'media' => ['url' => asset('img/demo-'.$locale.'.mp4')], + 'description' => __('site.shots.items')[0]['alt'], + ]]], + self::text("```sh\n{$install}\n```"), + ['type' => 14, 'divider' => true, 'spacing' => 1], + self::buttons([ + [$ko ? '웹사이트' : 'Website', $ko ? route('home.ko') : route('home')], + ['GitHub', $repo], + [$ko ? '셀프호스팅 가이드' : 'Self-hosting guide', $docs], + [$release, $repo.'/releases/latest'], + ]), + ]); + } + + public static function install(string $repo, string $install, string $source): array + { + return self::container([ + self::text("## Install Aven\n```sh\n{$install}\n```\nRun it on a Linux server (amd64 or arm64) with Docker, ports 80 and 443 free. The script asks for the dashboard URL, the base domain for services, an email for Let's Encrypt and the admin password, checks the release checksum, then installs `aven-server`, the CLI and Railpack."), + ['type' => 14, 'divider' => true, 'spacing' => 1], + self::buttons([ + ['Read the script', $source], + ['Self-hosting guide', $repo.'/blob/main/docs/self-hosting.md'], + ['aven.turin.my', config('app.url')], + ]), + ]); + } + + /** JSON for an inline + + + + + + + + + + + + + +
+ {{-- Hero: the first screen is a canvas; the pitch is the main service card --}} + @php + $sats = [ + ['id' => 'storefront', 'ico' => 'S', 'sub' => 'shop.example.com', 'x' => 13, 'y' => 20, 'm' => [16, 9]], + ['id' => 'api', 'ico' => 'A', 'sub' => 'api.example.com', 'x' => 87, 'y' => 18, 'm' => [84, 9]], + ['id' => 'worker', 'ico' => 'W', 'sub' => 'cron */5 * * * *', 'x' => 10, 'y' => 74, 'm' => [14, 91]], + ['id' => 'postgres', 'ico' => 'PG', 'sub' => 'pg-data 4.2 GB', 'x' => 89, 'y' => 66, 'm' => [50, 92]], + ['id' => 'redis', 'ico' => 'RD', 'sub' => 'cache', 'x' => 76, 'y' => 92, 'm' => [86, 91]], + ]; + @endphp +
+ + @foreach ($sats as $i => $n) + + @endforeach + +
+
+ + aven + {{ __('site.canvas.online') }} + {{ $release }} +
+

{{ __('site.hero.title') }}

+

{{ __('site.hero.lead') }}

+
+ + {{ $install }} + +
+ +
+ +
+ + {{-- Features --}} +
+ @foreach (__('site.features') as $f) +
+
+

{{ $f['title'] }}

+

{{ $f['text'] }}

+
    + @foreach ($f['points'] as $point) +
  • {{ $point }}
  • + @endforeach +
+
+ +
+ @endforeach + +
+

{{ __('site.also.title') }}

+
    + @foreach (__('site.also.items') as $item) +
  • {{ $item }}
  • + @endforeach +
+
+
+ + {{-- Comparison --}} +
+

{{ __('site.compare.title') }}

+

{{ __('site.compare.lead') }}

+
+ + + + + @foreach ($compare['products'] as $p) + + @endforeach + + + + @foreach ($compare['rows'] as $r => $row) + + + @foreach ($row['cells'] as $c => $cell) + + @endforeach + + @endforeach + +
Feature{{ $p }}
{{ $row['label'][$ko ? 1 : 0] }} + {{ $marks[$cell[0]] }}{{ __('site.compare.legend.'.$cell[0]) }} + {{ $cell[$col] }} +
+
+

+ @foreach (__('site.compare.legend') as $k => $label) + {{ $marks[$k] }}{{ $label }} + @endforeach +

+

{!! __('site.compare.note', ['date' => $compare['checked'], 'readme' => e($compare['readme'])]) !!}

+
+ + {{-- Screenshots --}} +
+

{{ __('site.shots.title') }}

+
+ @foreach (__('site.shots.items') as $i => $shot) + + @endforeach +
+
+ @foreach (__('site.shots.items') as $i => $shot) + + @endforeach +
+
+ + {{-- Install --}} +
+

{{ __('site.install.title') }}

+

{{ __('site.install.lead') }}

+
    + @foreach (__('site.install.steps') as $step) +
  1. +

    {{ $step['name'] }}

    +

    {{ $step['text'] }}

    +
  2. + @endforeach +
+
+ + {{ $install }} + +
+

{{ __('site.install.transcript') }}

+
@foreach (config('site.transcript') as [$kind, $line])@if ($kind === 'cmd')~/aven-site $ @endif{{ $line }}
+@endforeach
+

{{ __('site.install.guide') }}

+
+ + {{-- FAQ --}} +
+

{{ __('site.faq.title') }}

+
+ @foreach (__('site.faq.items') as $item) +
+ {{ $item['q'] }} +

{{ $item['a'] }}

+
+ @endforeach +
+
+ +
+
+ +

{{ __('site.cta.title') }}

+

{{ __('site.cta.text') }}

+
+ + {{ $install }} + +
+
+
+
+ + + + + + diff --git a/website/resources/views/install-preview.blade.php b/website/resources/views/install-preview.blade.php new file mode 100644 index 0000000..50d2cdd --- /dev/null +++ b/website/resources/views/install-preview.blade.php @@ -0,0 +1,22 @@ + + + + + Install Aven + + + + + + + + + + + + + + +
{{ $install }}
+ + diff --git a/website/resources/views/llms-full.blade.php b/website/resources/views/llms-full.blade.php new file mode 100644 index 0000000..bbbc8a9 --- /dev/null +++ b/website/resources/views/llms-full.blade.php @@ -0,0 +1,75 @@ +# Aven + +> {!! __('site.description') !!} + +- Website: {!! route('home') !!} (Korean: {!! route('home.ko') !!}) +- Source: {!! $repo !!} (AGPL-3.0) +- Latest release: {!! $release !!} +- Install: `{!! $install !!}` + +## What Aven is + +{!! __('site.hero.lead') !!} + +Aven is a self-hosted platform for apps, databases and background jobs. The dashboard has a canvas per environment, like Railway. Underneath it is one Go binary, `aven-server`, running next to Docker with its state in SQLite. More servers join with `aven-agent` over an mTLS mesh (Tailscale or WireGuard addresses). There is no Kubernetes. Aven is free, has no usage fees and no paid feature tier; you pay only for your own servers. + +Aven is not a fork, clone or redistribution of Railway and contains no Railway code. It reads `railway.json` / `railway.toml` and answers Railway-shaped GraphQL queries so projects can move over. + +## Features + +@foreach (__('site.features') as $f) +### {!! $f['title'] !!} + +{!! $f['text'] !!} + +@foreach ($f['points'] as $point) +- {!! $point !!} +@endforeach + +@endforeach +### Also included + +@foreach (__('site.also.items') as $item) +- {!! $item !!} +@endforeach + +## Comparison with Railway, Coolify and Dokploy + +Condensed from the comparison table in the Aven README. Competitor cells come from their official pages, checked on {!! $compare['checked'] !!}. "Not documented" means those pages did not describe the feature, not that it is missing. Full table with footnotes: {!! $compare['readme'] !!} + +| | {!! implode(' | ', $compare['products']) !!} | +| --- | {!! implode(' | ', array_fill(0, count($compare['products']), '---')) !!} | +@foreach ($compare['rows'] as $row) +| {!! $row['label'][0] !!} | {!! implode(' | ', array_map(fn ($c) => __('site.compare.legend.'.$c[0]).'. '.$c[1].' ([source]('.$c[3].'))', $row['cells'])) !!} | +@endforeach + +## Install + +{!! __('site.install.lead') !!} + +```sh +{!! $install !!} +``` + +@foreach (__('site.install.steps') as $i => $step) +{{ $i + 1 }}. **{!! $step['name'] !!}.** {!! $step['text'] !!} +@endforeach + +Self-hosting guide: {!! $repo !!}/blob/main/docs/self-hosting.md + +## Questions + +@foreach (__('site.faq.items') as $item) +### {!! $item['q'] !!} + +{!! $item['a'] !!} + +@endforeach +## 한국어 요약 + +{!! $ko('site.description') !!} + +@foreach ($ko('site.faq.items') as $item) +**{!! $item['q'] !!}** {!! $item['a'] !!} + +@endforeach diff --git a/website/resources/views/llms.blade.php b/website/resources/views/llms.blade.php new file mode 100644 index 0000000..b0bbb61 --- /dev/null +++ b/website/resources/views/llms.blade.php @@ -0,0 +1,27 @@ +# Aven + +> {!! __('site.description') !!} Licensed AGPL-3.0, free, no paid tier. Latest release: {!! $release !!}. + +Aven is a self-hosted PaaS (platform as a service) in the style of Railway: a dashboard with a canvas per environment, GitHub push-to-deploy, managed databases, domains with automatic TLS and multi-server support. It is one Go binary (`aven-server`) next to Docker with state in SQLite; more servers join with `aven-agent`. It is not a Railway fork and contains no Railway code. + +Install on a Linux server (amd64 or arm64) with Docker and ports 80/443 free: + +```sh +{!! $install !!} +``` + +## Docs + +- [Website (English)]({!! route('home') !!}): overview, features, comparison, install +- [Website (Korean)]({!! route('home.ko') !!}): 한국어 소개 페이지 +- [Full site as Markdown]({!! route('llms.full') !!}): everything on the website, including the comparison table with sources +- [Self-hosting guide]({!! $repo !!}/blob/main/docs/self-hosting.md): install, DNS, configuration, backups, upgrades, adding servers +- [CLI reference]({!! $repo !!}/blob/main/docs/cli.md): the `aven` command +- [HTTP API]({!! $repo !!}/blob/main/docs/api-v1.md) and [GraphQL API]({!! $repo !!}/blob/main/docs/graphql.md) +- [README]({!! $repo !!}#readme): highlights, architecture, comparison with Railway, Coolify and Dokploy, FAQ + +## Optional + +- [Source code]({!! $repo !!}): GitHub, AGPL-3.0 +- [Releases]({!! $repo !!}/releases) +- [Security policy]({!! $repo !!}/blob/main/SECURITY.md) diff --git a/website/resources/views/sitemap.blade.php b/website/resources/views/sitemap.blade.php new file mode 100644 index 0000000..23b785f --- /dev/null +++ b/website/resources/views/sitemap.blade.php @@ -0,0 +1,13 @@ +{!! '<'.'?xml version="1.0" encoding="UTF-8"?'.'>' !!} +{!! '<'.'?xml-stylesheet type="text/xsl" href="/sitemap.xsl"?'.'>' !!} + +@foreach ($pages as $page) + + {{ $page }} + {{ $lastmod }} + + + + +@endforeach + diff --git a/website/routes/console.php b/website/routes/console.php new file mode 100644 index 0000000..3c9adf1 --- /dev/null +++ b/website/routes/console.php @@ -0,0 +1,8 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote'); diff --git a/website/routes/web.php b/website/routes/web.php new file mode 100644 index 0000000..a9119d2 --- /dev/null +++ b/website/routes/web.php @@ -0,0 +1,31 @@ +group(function () { + // install.aven.turin.my: the installer script itself, for `curl … | sudo sh`. + Route::domain(config('site.install_host'))->group(function () { + Route::get('/', InstallController::class)->name('install'); + Route::get('/install-server.sh', InstallController::class); + }); + + Route::get('/', [SiteController::class, 'home'])->name('home'); + Route::get('/ko', fn (SiteController $site) => $site->home('ko'))->name('home.ko'); + Route::get('/sitemap.xml', [SiteController::class, 'sitemap'])->name('sitemap'); + Route::get('/llms.txt', fn (SiteController $site) => $site->llms())->name('llms'); + Route::get('/llms-full.txt', fn (SiteController $site) => $site->llms(true))->name('llms.full'); +}); diff --git a/website/storage/app/.gitignore b/website/storage/app/.gitignore new file mode 100644 index 0000000..fedb287 --- /dev/null +++ b/website/storage/app/.gitignore @@ -0,0 +1,4 @@ +* +!private/ +!public/ +!.gitignore diff --git a/website/storage/app/private/.gitignore b/website/storage/app/private/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/website/storage/app/private/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/website/storage/app/public/.gitignore b/website/storage/app/public/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/website/storage/app/public/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/website/storage/framework/.gitignore b/website/storage/framework/.gitignore new file mode 100644 index 0000000..7a0b408 --- /dev/null +++ b/website/storage/framework/.gitignore @@ -0,0 +1,10 @@ +compiled.php +config.php +down +events.scanned.php +lsp-*.php +maintenance.php +routes.php +routes.scanned.php +schedule-* +services.json diff --git a/website/storage/framework/cache/.gitignore b/website/storage/framework/cache/.gitignore new file mode 100644 index 0000000..d7852b8 --- /dev/null +++ b/website/storage/framework/cache/.gitignore @@ -0,0 +1,4 @@ +* +!data/ +!locks/ +!.gitignore diff --git a/website/storage/framework/cache/data/.gitignore b/website/storage/framework/cache/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/website/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/website/storage/framework/cache/locks/.gitignore b/website/storage/framework/cache/locks/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/website/storage/framework/cache/locks/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/website/storage/framework/sessions/.gitignore b/website/storage/framework/sessions/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/website/storage/framework/sessions/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/website/storage/framework/testing/.gitignore b/website/storage/framework/testing/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/website/storage/framework/testing/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/website/storage/framework/views/.gitignore b/website/storage/framework/views/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/website/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/website/storage/logs/.gitignore b/website/storage/logs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/website/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/website/tests/Feature/ExampleTest.php b/website/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..8364a84 --- /dev/null +++ b/website/tests/Feature/ExampleTest.php @@ -0,0 +1,19 @@ +get('/'); + + $response->assertStatus(200); + } +} diff --git a/website/tests/TestCase.php b/website/tests/TestCase.php new file mode 100644 index 0000000..fe1ffc2 --- /dev/null +++ b/website/tests/TestCase.php @@ -0,0 +1,10 @@ +assertTrue(true); + } +} diff --git a/website/vite.config.js b/website/vite.config.js new file mode 100644 index 0000000..1fd66d5 --- /dev/null +++ b/website/vite.config.js @@ -0,0 +1,24 @@ +import { defineConfig } from 'vite'; +import laravel from 'laravel-vite-plugin'; +import { bunny } from 'laravel-vite-plugin/fonts'; +import tailwindcss from '@tailwindcss/vite'; + +export default defineConfig({ + plugins: [ + laravel({ + input: ['resources/css/app.css', 'resources/js/app.js'], + refresh: true, + fonts: [ + bunny('Instrument Sans', { + weights: [400, 500, 600], + }), + ], + }), + tailwindcss(), + ], + server: { + watch: { + ignored: ['**/storage/framework/views/**'], + }, + }, +});