WARNING: Abusing code repositories (like Github/GitLab) may risk account suspension!#
Basic Principle#
Pages are deployed through Git repositories, and Workers access your Github repository directory via an API Key to open the query API. Pages read this API to display files.
Example#
Create a Github Repository#
- Not teaching, it's simple and stylish. There are more tutorials online than there are hairs on my head.
- Create a
file
folder in the root directory; all subsequent file uploads and downloads will be in this folder. - Create an
index.html
in the root directory and fill in the code:
Change the Body's query API URL to yours:
const response = await fetch('https://file-up.afo.im/list');
You currently don't know what to change it to, keep reading.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File List</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin: 5px 0;
}
a {
text-decoration: none;
color: #007bff;
}
a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<h1>File List</h1>
<ul id="file-list"></ul>
<script>
async function fetchFileList() {
try {
const response = await fetch('https://file-up.afo.im/list');
if (!response.ok) {
throw new Error('Failed to fetch file list');
}
const fileList = await response.text();
const files = fileList.split('\n').filter(file => file.trim() !== '');
displayFiles(files);
} catch (error) {
console.error('Error fetching file list:', error);
document.getElementById('file-list').innerHTML = '<li>Error fetching file list</li>';
}
}
function displayFiles(files) {
const fileListElement = document.getElementById('file-list');
fileListElement.innerHTML = files.map(file =>
`<li><a href="/file/${encodeURIComponent(file)}">${file}</a></li>`
).join('');
}
fetchFileList();
</script>
</body>
</html>
Create Workers, Open API Query and Upload Endpoints#
- Not teaching, it's simple and stylish. See Create Workers, Connect R2 do not look at the part about connecting R2.
- Change the Workers code:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const url = new URL(request.url)
const path = url.pathname
if (path === '/') {
return addCORSHeaders(showLoginForm())
} else if (path === '/login') {
return addCORSHeaders(await handleLogin(request))
} else if (path === '/upload') {
return addCORSHeaders(await handleUpload(request))
} else if (path === '/check-config') {
return addCORSHeaders(await checkConfig())
} else if (path === '/list') {
return addCORSHeaders(await listFiles()) // Allow direct access to /list without authorization
} else {
return addCORSHeaders(new Response('Not Found', { status: 404 }))
}
}
function showLoginForm() {
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form action="/login" method="POST">
<input type="password" name="password" placeholder="Enter password" required>
<button type="submit">Login</button>
</form>
<p><a href="/check-config">Check server configuration</a></p>
</body>
</html>
`
return new Response(html, {
headers: { 'Content-Type': 'text/html' }
})
}
async function handleLogin(request) {
const formData = await request.formData()
const password = formData.get('password')
if (password === PASSWORD) {
return showUploadForm()
} else {
return new Response('Invalid password', { status: 401 })
}
}
function showUploadForm() {
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload</title>
</head>
<body>
<h1>File Upload</h1>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" required>
<button type="submit">Upload</button>
</form>
</body>
</html>
`
return new Response(html, {
headers: { 'Content-Type': 'text/html' }
})
}
async function handleUpload(request) {
try {
const formData = await request.formData()
const file = formData.get('file')
if (!file) {
return new Response('No file uploaded', { status: 400 })
}
const content = await file.arrayBuffer()
const encodedContent = btoa(String.fromCharCode.apply(null, new Uint8Array(content)))
const githubResponse = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/contents/file/${file.name}`, {
method: 'PUT',
headers: {
'Authorization': `token ${GITHUB_TOKEN}`,
'Content-Type': 'application/json',
'User-Agent': 'Cloudflare Worker'
},
body: JSON.stringify({
message: `Upload ${file.name}`,
content: encodedContent
})
})
if (githubResponse.ok) {
return new Response('File uploaded successfully', { status: 200 })
} else {
const errorData = await githubResponse.text()
console.error('GitHub API Error:', errorData)
return new Response(`Failed to upload file: ${errorData}`, { status: 500 })
}
} catch (error) {
console.error('Upload error:', error)
return new Response(`Error during upload: ${error.message}`, { status: 500 })
}
}
async function listFiles() {
try {
const githubResponse = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/contents/file`, {
headers: {
'Authorization': `token ${GITHUB_TOKEN}`,
'User-Agent': 'Cloudflare Worker'
}
})
if (!githubResponse.ok) {
const errorData = await githubResponse.text()
console.error('GitHub API Error:', errorData)
return new Response(`Failed to fetch file list: ${errorData}`, { status: 500 })
}
const files = await githubResponse.json()
// Convert file names to plain text, one per line
const fileNames = files.map(file => file.name).join('\n')
return new Response(fileNames, {
headers: { 'Content-Type': 'text/plain' }
})
} catch (error) {
console.error('Error fetching files:', error)
return new Response(`Error during fetching files: ${error.message}`, { status: 500 })
}
}
async function checkConfig() {
let configStatus = 'All configurations are set correctly.'
if (!PASSWORD) {
configStatus = 'ERROR: PASSWORD is not set.'
} else if (!GITHUB_TOKEN) {
configStatus = 'ERROR: GITHUB_TOKEN is not set.'
} else if (!GITHUB_REPO) {
configStatus = 'ERROR: GITHUB_REPO is not set.'
} else {
try {
const response = await fetch(`https://api.github.com/repos/${GITHUB_REPO}`, {
headers: {
'Authorization': `token ${GITHUB_TOKEN}`,
'User-Agent': 'Cloudflare Worker'
}
})
const responseText = await response.text()
if (!response.ok) {
try {
const data = JSON.parse(responseText)
configStatus = `ERROR: GitHub API returned an error: ${data.message}`
} catch (jsonError) {
configStatus = `ERROR: GitHub API returned a non-JSON response. Status: ${response.status}, Body: ${responseText.substring(0, 100)}...`
}
} else {
try {
JSON.parse(responseText)
configStatus += ' GitHub connection successful.'
} catch (jsonError) {
configStatus = `WARNING: GitHub API returned a non-JSON response, but the connection was successful. Status: ${response.status}, Body: ${responseText.substring(0, 100)}...`
}
}
} catch (error) {
configStatus = `ERROR: Failed to connect to GitHub API: ${error.message}`
}
}
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Configuration Check</title>
</head>
<body>
<h1>Configuration Check</h1>
<p>${configStatus}</p>
<a href="/">Back to Login</a>
</body>
</html>
`
return new Response(html, {
status: 200,
headers: { 'Content-Type': 'text/html' }
})
}
// Add CORS headers
function addCORSHeaders(response) {
const headers = new Headers(response.headers)
headers.set('Access-Control-Allow-Origin', '*')
headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS')
headers.set('Access-Control-Allow-Headers', 'Content-Type')
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers
})
}
-
Add variables for Workers
GITHUB_REPO
format:GithubUsername/GithubRepoName
GITHUB_TOKEN
format:Your Github API Key
. It needs to have repository modification permissions.
PASSWORD
is the password for accessing the upload endpoint, set by yourself (it is more recommended to use Git for uploads).
-
Access your custom domain's
/list
to see if the query API is functioning normally and if it can list the files in the file folder under the repository, such as:
- Fill in your URL in step three of Create Github Repository
const response = await fetch('https://file-up.afo.im/list');
Replace https://file-up.afo.im/list
with yours.
Create Pages, Connect Git Repository#
-
Not teaching, it's simple and stylish. See Teach you to build your first website! Get started without a server! Note to use Git repository deployment instead of local deployment.
-
Cloudflare Pages will automatically redeploy when there are commits, achieving automatic updates of new files.
Access#
- Written in a hurry, if there are questions, comment and ask.