GooseHost自托管指南

GooseHost自托管指南

Goose Lv3

GooseHost 自托管教程

作者:Minecraft_goose

教程很长,耐心看完~

如果喜欢GooseHost可以点个star~

本教程理论上手机即可操作完成,不同型号略有差异

在自托管过程中我会以GooseHost这个名字来方便表述,大家在建造的过程中可以换成自己的名字,自己的域名。

No.1 —— 准备cloudflare和SUPABASE账号

分别前往cloudflare和SUPABASE注册账号

No.2 —— 在SUPABASE中建立GooseHost的储存库

创建一个SUPABASE项目

注册后根据SUPABASE引导创建组织

image
image

设置强密码

image
image

进入主界面

image
image

点击左侧第三个图标进入SQL Editor

image
image

执行以下SQL

insert into storage.buckets (id, name, public) values ('sites', 'sites', true) on conflict (id) do nothing;
insert into storage.buckets (id, name, public) values ('md', 'md', true) on conflict (id) do nothing;
insert into storage.buckets (id, name, public) values ('projects', 'projects', true) on conflict (id) do nothing;
insert into storage.buckets (id, name, public) values ('admin', 'admin', false) on conflict (id) do nothing;

创建4个桶,前三个负责储存,第四个是放email-map.json的

继续执行

create table if not exists public.gh_site (
id uuid primary key default gen_random_uuid(),
name text not null unique,
type text not null check (type in ('html', 'md', 'project')),
owner_id uuid not null,
ip_address text,
visit_count bigint default 0,
macos_submit_id text,
created_at timestamptz default now(),
updated_at timestamptz default now()
);

create index if not exists idx_gh_site_owner_id on public.gh_site(owner_id);
create index if not exists idx_gh_site_name on public.gh_site(name);
image
image

点绿色按钮,以后都点绿色的

站点主表,名称gh_site,可改,但是后面的话就得小心了。

create table if not exists public.gh_announcement (
id uuid primary key default gen_random_uuid(),
content text not null,
is_active boolean default false,
created_at timestamptz default now()
);

create index if not exists idx_gh_announcement_active on public.gh_announcement(is_active);

gh_announcement--全局公告

create table if not exists public.system_status (
id int primary key default 1 check (id = 1),
maintenance_mode boolean default false,
maintenance_message text,
services jsonb default '{}'::jsonb,
updated_at timestamptz default now(),
updated_by text
);

系统状态表


get_sites_paginated

create or replace function public.get_sites_paginated(
p_limit int,
p_offset int,
p_order_by text default 'updated_at',
p_order_dir text default 'DESC'
)
returns table(
id uuid,
name text,
owner_id uuid,
ip_address text,
created_at timestamptz,
updated_at timestamptz,
total_count bigint
) language plpgsql as $$
declare
order_sql text;
begin
order_sql := format('order by %I %s', p_order_by, p_order_dir);
return query execute format('
select
s.id,
s.name,
s.owner_id,
s.ip_address,
s.created_at,
s.updated_at,
count(*) over() as total_count
from public.gh_site s
%s
limit $1 offset $2
', order_sql) using p_limit, p_offset;
end;
$$;

get_users_paginated

create or replace function public.get_users_paginated(
p_limit int,
p_offset int
)
returns table(
owner_id uuid,
site_count bigint,
created_at timestamptz,
sites jsonb,
total_count bigint
) language plpgsql as $$
begin
return query
with agg as (
select
s.owner_id,
count(*) as site_count,
min(s.created_at) as created_at,
jsonb_agg(jsonb_build_object('name', s.name, 'type', s.type)) as sites
from public.gh_site s
group by s.owner_id
),
total as (select count(*) from agg)
select
agg.owner_id,
agg.site_count,
agg.created_at,
agg.sites,
(select count from total) as total_count
from agg
order by agg.created_at desc
limit p_limit offset p_offset;
end;
$$;

get_site_stats

create or replace function public.get_site_stats()
returns table(
total_sites bigint,
total_users bigint,
sites_today bigint
) language plpgsql as $$
begin
return query
select
count(*) as total_sites,
count(distinct owner_id) as total_users,
count(*) filter (where created_at::date = current_date) as sites_today
from public.gh_site;
end;
$$;

increment_visit

create or replace function public.increment_visit(p_name text)
returns void language plpgsql as $$
begin
update public.gh_site
set visit_count = visit_count + 1
where name = p_name;
end;
$$;

rls策略

alter table public.gh_site disable row level security;
alter table public.gh_announcement disable row level security;
alter table public.system_status disable row level security;

updated_at 自动更新触发器

create or replace function public.update_updated_at()
returns trigger language plpgsql as $$
begin
new.updated_at = now();
return new;
end;
$$;

create trigger trigger_gh_site_updated_at
before update on public.gh_site
for each row execute function public.update_updated_at();

No.3 现在前往cloudflare

[!NOTE]
必须拥有一个可以绑定在cloudflare上的域名,这不是为了好看,因为worker域名在国内打不开

因为我没有域名了,所以用worker域名做演示,在实际使用中,你需要把worker域名换成你自己的

现在你从GitHub上下载了GooseHost源码

image
image

好打开cloudflare

image
image

按下Ctrl+K搜索KV

点击创建

image
image

命名为RATE_LIMIT_KV

image
image

复制此iD

现在打开API文件夹

image
image

找到wrangler.toml

image
image

然后

image
image

删成这样

详解:

name = "这里填一会要创建的cloudflareworker名称"

API_URL = "https://worker域名"

ADMIN_USER_IDS = "管理员UID,用英文逗号分割"

id = "这里填从cloudflare复制过来的2edc698e7f884d6ea146cdc5ad0a74e0"

完事长这样

name = "goosehost-example"  
main = "worker.js"
compatibility_date = "2026-07-27"

[vars]
API_URL = "https://goosehost-example.goose-bai.workers.dev"
ADMIN_USER_IDS = ""

[[kv_namespaces]]
binding = "RATE_LIMIT_KV"
id = "2edc698e7f884d6ea146cdc5ad0a74e0"

[triggers]
crons = ["*/30 * * * *"]

现在创建一个名为goosehost-example的worker

image
image

点击创建

image
image

从helloworld开始

image
image

修改名称,部署

然后来到GitHub,新建一个仓库

image
image

将整个API文件夹上传到这个仓库

上传完毕后点击设置>Security and quality>Secrets and variables>Actions>Repository secrets>New repository secret 新建以下机密:

  • CLOUDFLARE_ACCOUNT_ID
  • CLOUDFLARE_API_TOKEN
  • CRON_SECRET
  • SUPABASE_ANON_KEY
  • SUPABASE_SERVICE_ROLE_KEY
  • SUPABASE_URL

CLOUDFLARE_ACCOUNT_ID 获取方法

看你的cloudflare网址

image
image

中间那一大串就是

CLOUDFLARE_API_TOKEN 获取方法

cloudflare dashboard 右上角小人头像,点击出现下拉菜单,点击配置文件,或者直接点这里

image
image

点击创建令牌>API 令牌模板>编辑 Cloudflare Workers

所有显示 Select... 的都填上,填啥随便,最好选所有,客户端 IP 地址筛选不用管

点击继续以显示摘要,创建成功,复制好填入GitHub机密

SUPABASE_URL获取方法

回到SUPABASE首页

image
image

.co 网址就是

SUPABASE_ANON_KEY和SUPABASE_SERVICE_ROLE_KEY获取方法

image
image

点击左侧边栏设置图标

Settings>CONFIGURATION>API keys>Legacy anon, service_role API keys

image
image

这两个就是

CRON_SECRET

瞎填就行


全部完成后点击actions

image
image

选择yourself

image
image

复制以下代码

name: Deploy GooseHost Worker

on:
push:
branches: [main, master]
workflow_dispatch:

concurrency:
group: deploy-goosehost-worker
cancel-in-progress: true

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"

- name: Install dependencies
run: npm install

- name: Build (esbuild 打包 index.js -> worker.js)
run: npm run build

- name: Push secrets to Cloudflare
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
echo -n "${{ secrets.SUPABASE_URL }}" | npx wrangler secret put SUPABASE_URL
echo -n "${{ secrets.SUPABASE_ANON_KEY }}" | npx wrangler secret put SUPABASE_ANON_KEY
echo -n "${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}" | npx wrangler secret put SUPABASE_SERVICE_ROLE_KEY
echo -n "${{ secrets.CRON_SECRET }}" | npx wrangler secret put CRON_SECRET

- name: Deploy to Cloudflare Workers
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: npx wrangler deploy

命名 deploy.yml ,保存后等待半分钟回到cloudflare

image
image

action已经部署完了

No.4 部署前端

改代码

1. auth/register.js

位置:第 63 行

原代码:

redirect_to: 'https://host.goose.gs.cn/login.html'

改为:

redirect_to: 'https://你的前端域名/login.html'

2. auth/signup.js

原代码:

redirect_to: 'https://host.goose.gs.cn/login.html'

改为:

redirect_to: 'https://你的前端域名/login.html'

3. auth/forgot-password.js

位置:第 3 行

原代码:

const RESET_PAGE = 'https://host.goose.gs.cn/reset-password.html';

改为:

const RESET_PAGE = 'https://你的前端域名/reset-password.html';

4. sites/serve.js

位置 1:render404Page 函数
<link rel="icon" type="image/x-icon" href="https://host.goose.gs.cn/icons/favicon.ico">

以及页脚和导航中的链接:

<a href="https://host.goose.gs.cn/">返回首页</a>

全部改为:https://你的前端域名/...

位置 2:Markdown 渲染 HTML

顶部导航、页脚水印、字体链接、CSS 引用等多处硬编码:

<link rel="icon" type="image/x-icon" href="https://host.goose.gs.cn/icons/favicon.ico">
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/...">
<a href="https://host.goose.gs.cn/" class="header-brand">
<a href="https://host.goose.gs.cn/docs/" class="header-link">
<a href="https://host.goose.gs.cn/" target="_blank">GooseHost</a>

改为:https://你的前端域名/...

5. utils/cors.js

打开就能看见

6. 所有front目录里的page.goose.gs.cn替换为刚才的worker域名

一键转换.bat


现在部署pages

创建应用程序
创建应用程序

上传文件夹
上传文件夹

拖入
拖入

quanq
quanq

此时你打开你的GooseHost链接,已经可以收到注册邮件了

image
image

但是,还没完

现在回到SUPABASE

image
image

点击

image
image

跟着我鼠标

image
image

换成

image
image

同时下面

image
image

🎉 恭喜你,你的GooseHost已经具备基本可运行状态!

设置管理员

找到这里

image
image

复制管理员uid

image
image

回到 wrangler.toml

image
image

保存,Action会重新部署

部署完成后你就可以打开管理员页面啦

  • 标题: GooseHost自托管指南
  • 作者: Goose
  • 创建于 : 2026-08-23 22:00:01
  • 更新于 : 2026-08-23 22:00:01
  • 链接: https://blog.goose.cc.cd/posts/goosehost-deploy-yourself/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论