Hugo site with Woodpecker CI -> Surfer deploy pipeline
Builds the site with hugomods/hugo:dart-sass-node-git and syncs public/ to the Surfer app on the same Cloudron server (surfer put --all --delete). README covers full setup: Gitea OAuth app, Woodpecker env.sh forge config, agent options, and repo secrets.
This commit is contained in:
commit
346c859eb5
8 files changed
+480
No files matched your search
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# Hugo build output
|
||||||
|
/public/
|
||||||
|
/resources/_gen/
|
||||||
|
/.hugo_build.lock
|
||||||
|
|
||||||
|
# OS and editor cruft
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Build the Hugo site and push the result to Surfer (both apps on the same Cloudron).
|
||||||
|
#
|
||||||
|
# Docs:
|
||||||
|
# Workflow syntax: https://woodpecker-ci.org/docs/usage/workflow-syntax
|
||||||
|
# Surfer CLI: https://docs.cloudron.io/packages/surfer/#cli-tool
|
||||||
|
#
|
||||||
|
# Repo secrets expected in Woodpecker (Settings -> Secrets):
|
||||||
|
# surfer_server - Surfer app domain, e.g. surfer.cloudron.example (no scheme)
|
||||||
|
# surfer_token - access token created in the Surfer admin UI (/_admin)
|
||||||
|
|
||||||
|
when:
|
||||||
|
# Deploy on pushes to main only. Other branches never build or deploy.
|
||||||
|
- event: push
|
||||||
|
branch: main
|
||||||
|
# Also allow "Run pipeline" in the Woodpecker UI (redeploying without a push).
|
||||||
|
# If you use this, make sure the two secrets below are also allowed for the
|
||||||
|
# "manual" event, not just push.
|
||||||
|
- event: manual
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# 1) Build the site into ./public
|
||||||
|
#
|
||||||
|
# hugomods/hugo variants: https://docker.hugomods.com/
|
||||||
|
# `dart-sass-node-git` is the maintained "everything" image: extended Hugo +
|
||||||
|
# Node/yarn + Git + Dart Sass. Pin a version with, for example,
|
||||||
|
# hugomods/hugo:dart-sass-node-git-0.165.0 (and drop `pull: true`).
|
||||||
|
- name: build
|
||||||
|
image: hugomods/hugo:dart-sass-node-git
|
||||||
|
pull: true
|
||||||
|
commands:
|
||||||
|
- hugo --minify --gc
|
||||||
|
|
||||||
|
# 2) Upload ./public to Surfer
|
||||||
|
- name: deploy
|
||||||
|
image: node:24-alpine
|
||||||
|
environment:
|
||||||
|
# Where on the Surfer server the site lives.
|
||||||
|
# "/" - the site is served at the Surfer app's domain root
|
||||||
|
# "/mysite/" - served in a subdirectory (keep baseURL in hugo.toml in sync!)
|
||||||
|
SURFER_DEST: /
|
||||||
|
SURFER_SERVER:
|
||||||
|
from_secret: surfer_server
|
||||||
|
SURFER_TOKEN:
|
||||||
|
from_secret: surfer_token
|
||||||
|
commands:
|
||||||
|
- npm install --global cloudron-surfer
|
||||||
|
# public/* (not public) so the CONTENTS of public/ end up in $SURFER_DEST -
|
||||||
|
# `surfer put public /` would upload a literal "public" folder instead.
|
||||||
|
# --all also includes hidden files nested inside those dirs, and --delete removes remote files that are no longer in public/
|
||||||
|
# (a real sync, so renamed assets don't pile up on the server).
|
||||||
|
- surfer put --all --delete --token "$SURFER_TOKEN" --server "$SURFER_SERVER" public/* "$SURFER_DEST"
|
||||||
|
# Optional: top-level dot directories (e.g. .well-known/) are skipped by the
|
||||||
|
# shell glob above, so re-sync them explicitly without --delete:
|
||||||
|
# - if [ -d public/.well-known ]; then surfer put --token "$SURFER_TOKEN" --server "$SURFER_SERVER" public/.well-known "$SURFER_DEST"; fi
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
# auto-deploy — a Gitea → Woodpecker CI → Surfer pipeline on one Cloudron
|
||||||
|
|
||||||
|
A minimal but complete example of a fully self-hosted static-site publishing
|
||||||
|
pipeline, with every piece running on a single
|
||||||
|
[Cloudron](https://cloudron.io) server:
|
||||||
|
|
||||||
|
```text
|
||||||
|
git push webhook "surfer put"
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌───────────┐ clone ┌─────────────────────┐ upload ┌──────────┐
|
||||||
|
│ Gitea ├──────────►│ Woodpecker ├───────────►│ Surfer │
|
||||||
|
│ git host │◄──OAuth───┤ (server + agent) │ public/* │ static │
|
||||||
|
└───────────┘ └─────────────────────┘ │ files │
|
||||||
|
source build & CI └────┬─────┘
|
||||||
|
▼
|
||||||
|
https://surfer.cloudron.example
|
||||||
|
```
|
||||||
|
|
||||||
|
You edit content, push to `main` on Gitea, and the site reappears on Surfer a
|
||||||
|
minute later — no manual uploads, no credentials in the repo. This repo is a
|
||||||
|
working Hugo site; the pipeline in `.woodpecker.yml` is all the plumbing.
|
||||||
|
|
||||||
|
## What's in this repo
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| ---- | ------- |
|
||||||
|
| `.woodpecker.yml` | The CI pipeline: build Hugo, upload the output with `surfer put` |
|
||||||
|
| `hugo.toml` | Site config — **`baseURL` must match the Surfer deployment domain** |
|
||||||
|
| `content/_index.md`, `content/about.md` | Minimal content so the example deploys out of the box |
|
||||||
|
| `layouts/home.html`, `layouts/single.html` | Tiny standalone templates (no theme dependency) |
|
||||||
|
| `.gitignore` | Keeps build output (`public/`) out of git |
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
A Cloudron server with three apps installed from the App Store:
|
||||||
|
|
||||||
|
| Stage | App | Example location | Cloudron docs |
|
||||||
|
| ----- | --- | ---------------- | ------------- |
|
||||||
|
| Source | Gitea | `https://gitea.cloudron.example` | [docs](https://docs.cloudron.io/packages/gitea/) |
|
||||||
|
| CI | Woodpecker | `https://woodpecker.cloudron.example` | [docs](https://docs.cloudron.io/packages/woodpecker/) |
|
||||||
|
| Serving | Surfer | `https://surfer.cloudron.example` | [docs](https://docs.cloudron.io/packages/surfer/) |
|
||||||
|
|
||||||
|
Substitute your real domains everywhere below.
|
||||||
|
|
||||||
|
## 1. Create a Surfer access token
|
||||||
|
|
||||||
|
1. Open the Surfer admin UI: `https://surfer.cloudron.example/_admin/`.
|
||||||
|
2. Go to **Settings** and create an **Access Token** (it looks like
|
||||||
|
`api-7e6d90ff-...`). You'll add it to Woodpecker in the next sections.
|
||||||
|
3. Decide where the site lives on the Surfer app:
|
||||||
|
- **At the root** — `https://surfer.cloudron.example/` serves the site.
|
||||||
|
Keep `SURFER_DEST: /` in `.woodpecker.yml` and `baseURL = "https://surfer.cloudron.example/"` in `hugo.toml`.
|
||||||
|
- **In a subdirectory** — e.g. `.../mysite/`. Set `SURFER_DEST: /mysite/` **and**
|
||||||
|
`baseURL = "https://surfer.cloudron.example/mysite/"`. If these don't match,
|
||||||
|
CSS/JS/links will 404.
|
||||||
|
|
||||||
|
The simplest setup is one Surfer app per site, each deployed at `/`. Surfer is
|
||||||
|
cheap, so prefer that unless you specifically need several sites in one place.
|
||||||
|
|
||||||
|
## 2. Point Woodpecker at Gitea
|
||||||
|
|
||||||
|
### 2a. Register an OAuth application in Gitea
|
||||||
|
|
||||||
|
In Gitea go to **Settings → Applications → Manage OAuth2 Applications**
|
||||||
|
(`https://gitea.cloudron.example/user/settings/applications`) and create a new
|
||||||
|
application:
|
||||||
|
|
||||||
|
- **Name:** `Woodpecker CI`
|
||||||
|
- **Redirect URI:** `https://woodpecker.cloudron.example/authorize`
|
||||||
|
|
||||||
|
The redirect URI must match your Woodpecker domain *exactly* (scheme, host,
|
||||||
|
`/authorize` path). Copy the generated **Client ID** and **Client Secret**.
|
||||||
|
|
||||||
|
### 2b. Configure the Woodpecker app
|
||||||
|
|
||||||
|
Open the Woodpecker app's **File manager** in the Cloudron dashboard and edit
|
||||||
|
`/app/data/env.sh`, adding a Gitea block:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export WOODPECKER_GITEA=true
|
||||||
|
export WOODPECKER_GITEA_URL=https://gitea.cloudron.example
|
||||||
|
export WOODPECKER_GITEA_CLIENT=<client id from 2a>
|
||||||
|
export WOODPECKER_GITEA_SECRET=<client secret from 2a>
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart the Woodpecker app. Notes from the [Cloudron
|
||||||
|
docs](https://docs.cloudron.io/packages/woodpecker/):
|
||||||
|
|
||||||
|
- Only one auth provider may be active at a time — remove or set any other
|
||||||
|
`WOODPECKER_<forge>` variables to `false`.
|
||||||
|
- To keep strangers out, add `export WOODPECKER_OPEN=false` and add explicit
|
||||||
|
users via `WOODPECKER_ADMIN=<your-gitea-username,...>` if desired.
|
||||||
|
|
||||||
|
### 2c. Log in
|
||||||
|
|
||||||
|
Open `https://woodpecker.cloudron.example`, choose the Gitea login, and
|
||||||
|
authorize. The first user to sign in becomes a Woodpecker admin.
|
||||||
|
|
||||||
|
## 3. Start a Woodpecker agent
|
||||||
|
|
||||||
|
The Woodpecker app only provides the *server*; pipelines are run by *agents*
|
||||||
|
that connect to it. An agent needs Docker and **must run outside the Cloudron
|
||||||
|
apps themselves**:
|
||||||
|
|
||||||
|
> From the Cloudron docs: *Do not install the agent on the Cloudron server
|
||||||
|
> itself. This is dangerous because the agent has full access to docker and it
|
||||||
|
> can (accidentally) delete or corrupt your apps.*
|
||||||
|
|
||||||
|
Two ways to get a running agent, given the first agent's shared secret is in
|
||||||
|
the Woodpecker app at `/app/data/env.sh` (open the File manager, note the
|
||||||
|
`WOODPECKER_AGENT_SECRET` value):
|
||||||
|
|
||||||
|
**Option A — recommended: separate Docker host / VM**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name=woodpecker-agent --restart=always --detach \
|
||||||
|
-e WOODPECKER_SERVER="woodpecker.cloudron.example:9000" \
|
||||||
|
-e WOODPECKER_MAX_WORKFLOWS=4 \
|
||||||
|
-e WOODPECKER_GRPC_SECURE=true \
|
||||||
|
-e WOODPECKER_LOG_LEVEL=info \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-e WOODPECKER_BACKEND=docker \
|
||||||
|
-e WOODPECKER_AGENT_SECRET="value from /app/data/env.sh" \
|
||||||
|
woodpeckerci/woodpecker-agent:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Any cheap VM works, since the agent only needs outbound access to the server.
|
||||||
|
|
||||||
|
**Option B — same Cloudron server** (accepting the risk flagged above): run the
|
||||||
|
exact same command on the Cloudron host itself, via `ssh`. The agent talks to
|
||||||
|
the Woodpecker server over its public gRPC endpoint (`:9000`, TLS) and spawns
|
||||||
|
pipeline step containers (Hugo build, Surfer upload). For a one-person server
|
||||||
|
this is a common trade-off; keep the exposure as small as it can be:
|
||||||
|
|
||||||
|
- `WOODPECKER_OPEN=false` so only your Gitea users can ever log in.
|
||||||
|
- Enable only repositories you control, and never turn on *trusted* mode for
|
||||||
|
them: without trusted mode, pipeline steps run as ordinary throwaway
|
||||||
|
containers — no docker socket inside them, no host mounts, no privileged
|
||||||
|
access. The socket lives in the agent process only.
|
||||||
|
- Keep secrets unavailable to pull requests (Woodpecker's default) and never
|
||||||
|
store anything more powerful than the Surfer token in repo secrets.
|
||||||
|
- Point Cloudron backups off-server (e.g. S3): the scenario this warning
|
||||||
|
guards against is damage to the whole platform, which local backups on the
|
||||||
|
same disk may not survive.
|
||||||
|
- Rotate `WOODPECKER_AGENT_SECRET` if in doubt (it also lives in
|
||||||
|
`/app/data/env.sh`).
|
||||||
|
|
||||||
|
Verify the agent registered: `docker logs -f woodpecker-agent` should show it
|
||||||
|
polling without errors, and the Woodpecker UI's *Agents* page lists it.
|
||||||
|
|
||||||
|
## 4. Create the repo in Gitea and push
|
||||||
|
|
||||||
|
Create an empty repository in the Gitea web UI (e.g.
|
||||||
|
`<you>/auto-deploy`), then push this directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git init
|
||||||
|
git add .
|
||||||
|
git commit -m "Hugo site with Woodpecker → Surfer deploy pipeline"
|
||||||
|
git branch -M main
|
||||||
|
git remote add origin https://gitea.cloudron.example/<you>/auto-deploy.git
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Activate the repo in Woodpecker and add secrets
|
||||||
|
|
||||||
|
1. In Woodpecker, open **Repositories → New repository** and enable your repo
|
||||||
|
(you need admin rights on it in Gitea). Woodpecker installs a webhook on it.
|
||||||
|
2. In the repo's **Settings → Secrets**, add:
|
||||||
|
|
||||||
|
| Name | Value |
|
||||||
|
| ---- | ----- |
|
||||||
|
| `surfer_server` | `surfer.cloudron.example` — domain only, no `https://` |
|
||||||
|
| `surfer_token` | the access token from step 1 (`api-...`) |
|
||||||
|
|
||||||
|
When adding the secrets, make sure they are permitted for the events your
|
||||||
|
pipeline uses — at minimum `push`; add `manual` as well if you plan UI-triggered
|
||||||
|
redeploys. Woodpecker's default secrecy rules already exclude pull requests.
|
||||||
|
|
||||||
|
## 6. Deploy
|
||||||
|
|
||||||
|
Every push to `main` now triggers a build. If nothing runs (e.g. you want to
|
||||||
|
re-deploy without a commit), use **Pipelines → Run pipeline** (the `manual`
|
||||||
|
event). After the pipeline goes green, visit
|
||||||
|
`https://surfer.cloudron.example/` — the example site is there, complete with
|
||||||
|
a footer noting how it got built.
|
||||||
|
|
||||||
|
## How the pipeline works
|
||||||
|
|
||||||
|
`.woodpecker.yml` has two steps, triggered only by pushes to `main` and manual
|
||||||
|
runs:
|
||||||
|
|
||||||
|
1. **build** — `hugomods/hugo:dart-sass-node-git` runs `hugo --minify --gc`,
|
||||||
|
producing `public/`. That image variant contains extended Hugo plus Node,
|
||||||
|
Git and Dart Sass; other variants are listed at
|
||||||
|
[docker.hugomods.com](https://docker.hugomods.com/). To pin an exact Hugo
|
||||||
|
version, set `image: hugomods/hugo:dart-sass-node-git-0.165.0` and drop the
|
||||||
|
`pull: true` line (which only exists to keep a floating tag fresh).
|
||||||
|
2. **deploy** — a small `node:24-alpine` step installs the Surfer CLI and runs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
surfer put --all --delete --token ... --server ... public/* "$SURFER_DEST"
|
||||||
|
```
|
||||||
|
|
||||||
|
Exactly why each part matters:
|
||||||
|
|
||||||
|
| piece | effect |
|
||||||
|
| ----- | ------ |
|
||||||
|
| `public/*` (not `public`) | uploads the *contents* of `public/` into the destination; `surfer put public /` would create a literal `public/` folder |
|
||||||
|
| `--all` | include dotfiles in subdirectories |
|
||||||
|
| `--delete` | remove remote files that are no longer in `public/`, so renamed assets don't pile up; changed files upload incrementally (mtime/size), unchanged ones are skipped |
|
||||||
|
| destination | must be an absolute path like `/` or `/mysite/`, kept in sync with `baseURL` in `hugo.toml` |
|
||||||
|
|
||||||
|
Note that this makes Surfer's destination a **managed mirror of `public/`**:
|
||||||
|
anything you upload manually to that folder will be deleted on the next
|
||||||
|
deploy. The `--server` value may be the bare domain — the CLI adds
|
||||||
|
`https://` automatically.
|
||||||
|
|
||||||
|
## Top-level dotfiles (`.well-known/`, `.htaccess`, …)
|
||||||
|
|
||||||
|
The shell glob `public/*` never expands to top-level dotfiles, and `--delete`
|
||||||
|
would then treat them as stale. If you need one (e.g. `.well-known/` for
|
||||||
|
webmaster verification files), uncomment the optional line at the end of the
|
||||||
|
`deploy` step — it re-uploads the dotdir right after the main sync, guarding on
|
||||||
|
its existence so deploys without it keep working.
|
||||||
|
|
||||||
|
## Adapting it
|
||||||
|
|
||||||
|
- **Another generator?** Replace the build step and the output dir; the deploy
|
||||||
|
step stays identical:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Astro # Eleventy # Zola
|
||||||
|
commands: commands: image: ghcr.io/getzola/zola:v0.20.2
|
||||||
|
- npm ci - npm ci commands:
|
||||||
|
- npm run build - npx @11ty/eleventy - zola build
|
||||||
|
# deploy step source: # deploy step source: # deploy step source:
|
||||||
|
# dist/* # _site/* # public/*
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Multiple sites on one Surfer** in subfolders: give each repo its own
|
||||||
|
`SURFER_DEST` (e.g. `/blog/`, `/photos/`) and match `baseURL` in each. Keep
|
||||||
|
in mind the `--delete` sync only touches that destination folder.
|
||||||
|
- **Private repos work fine**: Woodpecker clones via Gitea OAuth; nothing here
|
||||||
|
assumes your Gitea repo is public.
|
||||||
|
- **Non-deploy jobs** (lint, tests) just become a normal step before `deploy`
|
||||||
|
— steps run in declaration order and a failure stops the pipeline.
|
||||||
|
|
||||||
|
## Security notes
|
||||||
|
|
||||||
|
- Never commit the Surfer token — it lives only in Woodpecker secrets. If it
|
||||||
|
ever leaks, revoke it in the Surfer admin UI and update the secret, then
|
||||||
|
re-run the pipeline manually.
|
||||||
|
- The `deploy` step only exposes the two secrets to `node:24-alpine`; other
|
||||||
|
steps never see them.
|
||||||
|
- For extra paranoia you can restrict both secrets to specific images in the
|
||||||
|
secret's settings, and consider Surfer's own access control (password-restricted
|
||||||
|
sites) for staging sites.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Symptom | Likely cause | Fix |
|
||||||
|
| ------- | ------------ | --- |
|
||||||
|
| Pipeline never appears after push | webhook missing or blocked | re-check repo activation; check Gitea → repo → Webhooks for delivery errors; on Cloudron, Gitea config lives in `/app/data/app.ini` |
|
||||||
|
| Pipeline stuck in *pending* | no agent connected | `docker logs woodpecker-agent`; verify `WOODPECKER_SERVER=<woodpecker-domain>:9000`, `WOODPECKER_GRPC_SECURE=true`, and the agent secret from the Woodpecker app's `/app/data/env.sh` |
|
||||||
|
| Login fails / repo list empty | OAuth setup | redirect URI must be exactly `https://<woodpecker-domain>/authorize`; only one `WOODPECKER_<forge>` provider active |
|
||||||
|
| Deploy step says "Run surfer config first" | secret missing / wrong name | secrets must be named `surfer_server` / `surfer_token` (see `.woodpecker.yml`) and permitted for this event |
|
||||||
|
| `Invalid token` at deploy | token rotated or revoked | create a fresh token in Surfer's admin UI, update the `surfer_token` secret, run pipeline manually |
|
||||||
|
| Site deploys but CSS/links 404 | `baseURL` and `SURFER_DEST` don't match | make them identical (including path) |
|
||||||
|
| Site under a literal `public/` path | forgot the glob | use `public/*`, not `public` |
|
||||||
|
| Old files still served after renaming assets | sync flags missing | keep `--delete --all` in the `surfer put` command |
|
||||||
|
| `surfer: command not found` | npm install failed in the deploy step | check the deploy step logs (registry access) |
|
||||||
|
|
||||||
|
## Local development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hugo server -D # preview at http://localhost:1313
|
||||||
|
hugo --minify --gc # same build as CI, output in public/
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also dry-run the workflow with [woodpecker-cli](https://woodpecker-ci.org/docs/usage/cli) (`woodpecker-cli exec .woodpecker.yml`).
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- Cloudron Surfer docs & CLI: https://docs.cloudron.io/packages/surfer/
|
||||||
|
- Cloudron Woodpecker docs (forge setup, agent): https://docs.cloudron.io/packages/woodpecker/
|
||||||
|
- Woodpecker workflow syntax: https://woodpecker-ci.org/docs/usage/workflow-syntax
|
||||||
|
- Woodpecker secrets: https://woodpecker-ci.org/docs/usage/secrets
|
||||||
|
- Woodpecker × Gitea: https://woodpecker-ci.org/docs/administration/configuration/forges/gitea
|
||||||
|
- Hugo Docker images: https://docker.hugomods.com/
|
||||||
|
- Hugo layout rules: https://gohugo.io/templates/lookup-order/
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
title: Home
|
||||||
|
---
|
||||||
|
|
||||||
|
# It is alive!
|
||||||
|
|
||||||
|
This static site is deployed automatically:
|
||||||
|
|
||||||
|
1. You push a commit to `main` on **Gitea** (same Cloudron server).
|
||||||
|
2. **Woodpecker CI** picks up the push, builds the site with Hugo.
|
||||||
|
3. The build output is uploaded to **Surfer**, which serves it.
|
||||||
|
|
||||||
|
That's it — no credentials in the repo, no manual uploads. Push something to
|
||||||
|
`main` right now and watch the pipeline in Woodpecker.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
title: About
|
||||||
|
---
|
||||||
|
|
||||||
|
This example shows a complete, self-hosted publishing pipeline on a single
|
||||||
|
Cloudron server:
|
||||||
|
|
||||||
|
| Stage | App | Example location |
|
||||||
|
| ----- | --- | ---------------- |
|
||||||
|
| Source | Gitea | `https://gitea.cloudron.example` |
|
||||||
|
| Build | Woodpecker CI | `https://woodpecker.cloudron.example` |
|
||||||
|
| Serving | Surfer | `https://surfer.cloudron.example` |
|
||||||
|
|
||||||
|
The pipeline is defined by a single checked-in file, [`.woodpecker.yml`](https://woodpecker-ci.org/docs/usage/workflow-syntax), and the only
|
||||||
|
secrets it needs are a Surfer domain and an access token. See the repository
|
||||||
|
README for full setup instructions.
|
||||||
|
|
||||||
|
Delete this page or rewrite everything — the templates in `layouts/` are
|
||||||
|
deliberately small so you can replace them with a real design.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# The final URL of the site, as served by Surfer. It must exactly match the
|
||||||
|
# deployment destination on the Surfer app, or CSS/JS/images will 404.
|
||||||
|
#
|
||||||
|
# Site at the Surfer app's root domain (SURFER_DEST = "/" in .woodpecker.yml):
|
||||||
|
baseURL = "https://surfer.cloudron.example/"
|
||||||
|
#
|
||||||
|
# Site in a subdirectory (SURFER_DEST = "/mysite/"):
|
||||||
|
# baseURL = "https://surfer.cloudron.example/mysite/"
|
||||||
|
|
||||||
|
locale = "en-us"
|
||||||
|
title = "My Cloudron Site"
|
||||||
|
|
||||||
|
# This minimal example uses no taxonomies; disabling them avoids empty
|
||||||
|
# /tags/ and /categories/ pages.
|
||||||
|
disableKinds = ["taxonomy", "term"]
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="{{ site.Language.Locale }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{{ .Title }} | {{ site.Title }}</title>
|
||||||
|
<style>
|
||||||
|
:root { color-scheme: light dark; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
max-width: 42rem; margin: 0 auto; padding: 0 1rem 3rem;
|
||||||
|
line-height: 1.6; font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
header, footer { padding: 1.25rem 0; }
|
||||||
|
header a { font-weight: 700; text-decoration: none; }
|
||||||
|
header nav { float: right; font-weight: 400; }
|
||||||
|
footer { border-top: 1px solid gray; font-size: .85rem; opacity: .75; }
|
||||||
|
img { max-width: 100%; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="{{ site.Home.RelPermalink }}">{{ site.Title }}</a>
|
||||||
|
<nav>
|
||||||
|
{{ with site.GetPage "/about" }}<a href="{{ .RelPermalink }}">{{ .Title }}</a>{{ end }}
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
{{ .Content }}
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<small>
|
||||||
|
Served by Surfer · built by Woodpecker CI · source on Gitea
|
||||||
|
— one Cloudron server.
|
||||||
|
</small>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="{{ site.Language.Locale }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{{ .Title }} | {{ site.Title }}</title>
|
||||||
|
<style>
|
||||||
|
:root { color-scheme: light dark; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
max-width: 42rem; margin: 0 auto; padding: 0 1rem 3rem;
|
||||||
|
line-height: 1.6; font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
header, footer { padding: 1.25rem 0; }
|
||||||
|
header a { font-weight: 700; text-decoration: none; }
|
||||||
|
header nav { float: right; font-weight: 400; }
|
||||||
|
footer { border-top: 1px solid gray; font-size: .85rem; opacity: .75; }
|
||||||
|
img { max-width: 100%; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="{{ site.Home.RelPermalink }}">{{ site.Title }}</a>
|
||||||
|
<nav>
|
||||||
|
{{ with site.GetPage "/about" }}<a href="{{ .RelPermalink }}">{{ .Title }}</a>{{ end }}
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
{{ .Content }}
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<small>
|
||||||
|
Served by Surfer · built by Woodpecker CI · source on Gitea
|
||||||
|
— one Cloudron server.
|
||||||
|
</small>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in new issue
Block a user