Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.git/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep Git metadata available to the Docker build

Removing .git from the build context makes both Dockerfile and Dockerfile.railway fail at RUN bash build.sh release docker: build.sh runs under set -e and line 5 unconditionally executes gitCommit=$(git log ... -1), which exits 128 when the copied source has no .git directory. Preserve the metadata or make the build script tolerate its absence.

Useful? React with 👍 / 👎.

.github/
.idea/
.DS_Store
build/
dist/
*.db
*.bin
*.test
*.out
/tmp/
/log/
/lang/
/daemon/
/public/dist/*
!/public/dist/README.md
16 changes: 16 additions & 0 deletions .railwayignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.git/
.github/
.idea/
.DS_Store
build/
dist/
*.db
*.bin
*.test
*.out
/tmp/
/log/
/lang/
/daemon/
/public/dist/*
!/public/dist/README.md
36 changes: 36 additions & 0 deletions Dockerfile.railway
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
FROM alpine:edge AS builder
LABEL stage=go-builder
WORKDIR /app/
RUN apk add --no-cache bash curl jq gcc git go musl-dev
COPY go.mod go.sum ./
RUN go mod download
COPY ./ ./
RUN bash build.sh release docker

FROM alpine:edge
LABEL MAINTAINER="OpenList"
ARG INSTALL_FFMPEG=false
ARG INSTALL_ARIA2=false
ARG USER=openlist
ARG UID=1001
ARG GID=1001

WORKDIR /opt/openlist/

RUN addgroup -g ${GID} ${USER} && \
adduser -D -u ${UID} -G ${USER} ${USER} && \
mkdir -p /opt/openlist/data && \
Comment on lines +20 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Grant the runtime user ownership of the data directory

The directory is created by root with the default 0755 ownership and is never chowned before the image switches to USER openlist. On startup, entrypoint.sh explicitly checks that ./data is writable and exits with status 1 when it is not, so the Railway image cannot start without an externally mounted volume that happens to supply compatible ownership. Create or chown the directory as ${UID}:${GID}.

Useful? React with 👍 / 👎.

apk add --no-cache ca-certificates tzdata && \
if [ "${INSTALL_FFMPEG}" = "true" ]; then apk add --no-cache ffmpeg; fi && \
if [ "${INSTALL_ARIA2}" = "true" ]; then apk add --no-cache aria2; fi

COPY --from=builder --chmod=755 --chown=${UID}:${GID} /app/bin/openlist ./
COPY --chmod=755 --chown=${UID}:${GID} entrypoint.sh /entrypoint.sh

USER ${USER}
RUN /entrypoint.sh version

ENV UMASK=022 RUN_ARIA2=${INSTALL_ARIA2}
VOLUME /opt/openlist/data/
EXPOSE 5244 5245
CMD [ "/entrypoint.sh" ]
169 changes: 169 additions & 0 deletions RAILWAY_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Railway Deployment Guide

Deploy OpenList on Railway with persistent storage and automatic HTTPS.

## Prerequisites

- A [Railway](https://railway.app) account
- This repository connected to Railway (or a new project from this repo)

## Quick Start

### 1. Create a New Project

1. Go to [Railway Dashboard](https://railway.app/dashboard)
2. Click **"New Project"**
3. Select **"Deploy from GitHub repo"** (or upload this repo)
4. Choose this repository

### 2. Railway will Auto-Detect

Railway will automatically detect the `railway.json` or `railway.toml` in the repo root and use `Dockerfile.railway` for building.

### 3. Add a Volume (Persistent Storage)

OpenList stores data (database, uploads, configs) in `/opt/openlist/data`. You need a persistent volume:

1. In your Railway project, click **"New"** → **"Volume"**
2. Name it `openlist-data`
3. Mount it to `/opt/openlist/data` in your service

### 4. Configure Environment Variables

Go to your service → **Variables** tab and add:

| Variable | Value | Description |
|----------|-------|-------------|
| `UMASK` | `022` | File permissions mask |
| `TZ` | `Asia/Shanghai` | Timezone (optional) |
| `RUN_ARIA2` | `false` | Enable aria2 (optional, increases build time) |
| `INSTALL_FFMPEG` | `false` | Install ffmpeg (optional, increases build time) |
| `INSTALL_ARIA2` | `false` | Install aria2 (optional, increases build time) |
| `GITHUB_TOKEN` | `<your_token>` | GitHub token to avoid API rate limits when downloading the frontend (optional) |

> **Note:** Railway's dynamic `PORT` is automatically mapped to `HTTP_PORT` by the entrypoint script. You do **not** need to manually set `HTTP_PORT=$PORT`.

### 5. Deploy

1. Railway will automatically build and deploy
2. Wait for the build to complete (first build takes ~3-5 minutes)
3. Check the **Deployments** tab for build logs

### 6. Get Admin Credentials

On first startup, OpenList generates a random admin password. Find it in the deployment logs:

1. Go to **Deployments** → click on the latest deployment
2. View **Logs**
3. Look for: `Successfully created the admin user and the initial password is: XXXXXXXX`

Alternatively, you can set a fixed admin password by running:
```
openlist admin set YOUR_PASSWORD
```

### 7. Access OpenList

Once deployed, Railway will provide a public URL like:
```
https://your-project.up.railway.app
```

Open this URL in your browser and log in with the admin credentials.

## Configuration

### Database

By default, OpenList uses SQLite and stores the database in the volume at `/opt/openlist/data/data.db`.

For better performance, you can use Railway's PostgreSQL:

1. Add a **PostgreSQL** database to your Railway project
2. Set these environment variables:
- `DB_TYPE=postgresql`
- `DB_HOST=${{Postgres.HOSTNAME}}`
- `DB_PORT=${{Postgres.PORT}}`
- `DB_USER=${{Postgres.USERNAME}}`
- `DB_PASS=${{Postgres.PASSWORD}}`
- `DB_NAME=${{Postgres.DATABASE}}`

### Custom Domain

1. Go to **Settings** → **Domains**
2. Add your custom domain
3. Railway will automatically provision an SSL certificate

### Build Options

Edit `railway.json` or `railway.toml` to customize:

- `restartPolicyType`: `"on-failure"` or `"always"`
- `healthcheckPath`: Health check endpoint
- `INSTALL_FFMPEG`: Set to `true` to enable ffmpeg
- `INSTALL_ARIA2`: Set to `true` to enable aria2

## Troubleshooting

### Build Fails

- Check build logs for missing dependencies
- Ensure `go.mod` and `go.sum` are present
- Try rebuilding with `INSTALL_FFMPEG=false` and `INSTALL_ARIA2=false`
- If the build fails while downloading the frontend, add a `GITHUB_TOKEN` variable with a GitHub personal access token to avoid API rate limits
- As a fallback, you can change `bash build.sh release docker` to `bash build.sh dev docker` in `Dockerfile.railway` to skip frontend version pinning

### Port Not Accessible

- The entrypoint automatically maps Railway's `PORT` to `HTTP_PORT`
- Check that the service is listening on `0.0.0.0` (default)
- Ensure no other process is using the assigned port

### Data Lost After Redeploy

- Ensure the volume is mounted to `/opt/openlist/data`
- Check volume status in Railway dashboard

### Cannot Login

- Check deployment logs for admin password
- Ensure database file exists in volume

## Manual Railway CLI Deployment

If you prefer using the Railway CLI:

```bash
# Install Railway CLI
npm i -g @railway/cli

# Login
railway login

# Initialize project
railway init

# Link to existing project
railway link

# Deploy
railway up
```

## Architecture

```
Railway Project
├── Service: OpenList
│ ├── Build: Dockerfile.railway
│ ├── Port: $PORT (mapped to HTTP_PORT)
│ └── Volume: /opt/openlist/data
└── (Optional) PostgreSQL Database
```

## Files Added

- `railway.json` - Railway project configuration
- `railway.toml` - Alternative Railway configuration
- `Dockerfile.railway` - Self-contained Dockerfile for Railway
- `.railwayignore` - Files excluded from Railway builds
15 changes: 13 additions & 2 deletions drivers/189/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,22 @@ func random() string {
func RsaEncode(origData []byte, j_rsakey string, hex bool) string {
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
block, _ := pem.Decode(publicKey)
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
pub := pubInterface.(*rsa.PublicKey)
if block == nil {
return ""
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
log.Errorf("err: %s", err.Error())
return ""
}
pub, ok := pubInterface.(*rsa.PublicKey)
if !ok {
return ""
}
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
if err != nil {
log.Errorf("err: %s", err.Error())
return ""
}
res := base64.StdEncoding.EncodeToString(b)
if hex {
Expand Down
17 changes: 15 additions & 2 deletions drivers/189pc/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,21 @@ func signatureOfHmac(sessionSecret, sessionKey, operate, fullUrl, dateOfGmt, par
// RAS 加密用户名密码
func RsaEncrypt(publicKey, origData string) string {
block, _ := pem.Decode([]byte(publicKey))
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
data, _ := rsa.EncryptPKCS1v15(rand.Reader, pubInterface.(*rsa.PublicKey), []byte(origData))
if block == nil {
return ""
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return ""
}
pub, ok := pubInterface.(*rsa.PublicKey)
if !ok {
return ""
}
data, err := rsa.EncryptPKCS1v15(rand.Reader, pub, []byte(origData))
if err != nil {
return ""
}
return strings.ToUpper(hex.EncodeToString(data))
}

Expand Down
16 changes: 12 additions & 4 deletions drivers/cloudreve_v4/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,9 @@ func (d *CloudreveV4) refreshToken() error {
if d.canLogin() {
return d.login()
}
d.GetStorage().SetStatus(fmt.Sprintf("Invalid RefreshToken: %s", err.Error()))
if storage := d.GetStorage(); storage != nil {
storage.SetStatus(fmt.Sprintf("Invalid RefreshToken: %s", err.Error()))
}
op.MustSaveDriverStorage(d)
return fmt.Errorf("invalid refresh token: %w", err)
}
Expand All @@ -254,7 +256,9 @@ func (d *CloudreveV4) refreshToken() error {
// try to login again
return d.login()
}
d.GetStorage().SetStatus("This session is no longer valid")
if storage := d.GetStorage(); storage != nil {
storage.SetStatus("This session is no longer valid")
}
op.MustSaveDriverStorage(d)
return ErrorIssueToken
}
Expand Down Expand Up @@ -322,7 +326,9 @@ func (d *CloudreveV4) isTokenExpired() bool {
var jwt AccessJWT
err = d.parseJWT(d.AccessToken, &jwt)
if err != nil {
d.GetStorage().SetStatus(fmt.Sprintf("Invalid AccessToken: %s", err.Error()))
if storage := d.GetStorage(); storage != nil {
storage.SetStatus(fmt.Sprintf("Invalid AccessToken: %s", err.Error()))
}
op.MustSaveDriverStorage(d)
return false
}
Expand All @@ -345,7 +351,9 @@ func (d *CloudreveV4) isTokenExpired() bool {
// try to login again
return true
}
d.GetStorage().SetStatus("This session is no longer valid")
if storage := d.GetStorage(); storage != nil {
storage.SetStatus("This session is no longer valid")
}
op.MustSaveDriverStorage(d)
return false
}
Expand Down
8 changes: 7 additions & 1 deletion drivers/google_drive/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,13 @@ func (d *GoogleDrive) refreshToken() error {

// load private key from string
privateKeyPem, _ := pem.Decode([]byte(jsonData.PrivateKey))
privateKey, _ := x509.ParsePKCS8PrivateKey(privateKeyPem.Bytes)
if privateKeyPem == nil {
return fmt.Errorf("failed to decode PEM block containing private key")
}
privateKey, err := x509.ParsePKCS8PrivateKey(privateKeyPem.Bytes)
if err != nil {
return err
}

jwtToken := jwt.NewWithClaims(jwt.SigningMethodRS256,
jwt.MapClaims{
Expand Down
15 changes: 12 additions & 3 deletions drivers/halalcloud/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ func (d *HalalCloud) Init(ctx context.Context) error {
if d.Addition.RefreshToken == "" || !d.IsLogin() {
as, err := d.NewAuthServiceWithOauth()
if err != nil {
d.GetStorage().SetStatus(fmt.Sprintf("%+v", err.Error()))
if storage := d.GetStorage(); storage != nil {
storage.SetStatus(fmt.Sprintf("%+v", err.Error()))
}
return err
}
d.HalalCommon.AuthService = as
Expand Down Expand Up @@ -215,7 +217,11 @@ func (d *HalalCloud) getLink(ctx context.Context, file model.Obj, args model.Lin
ctx1, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()

result, err := client.ParseFileSlice(ctx1, (*pubUserFile.File)(file.(*Files)))
files, ok := file.(*Files)
if !ok {
return nil, fmt.Errorf("unsupported file type: %T", file)
}
result, err := client.ParseFileSlice(ctx1, (*pubUserFile.File)(files))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -370,7 +376,10 @@ func (d *HalalCloud) put(ctx context.Context, dstDir model.Obj, fileStream model
if err != nil {
return nil, err
}
u, _ := url.Parse(result.Endpoint)
u, err := url.Parse(result.Endpoint)
if err != nil {
return nil, fmt.Errorf("failed to parse endpoint: %w", err)
}
u.Host = "s3." + u.Host
result.Endpoint = u.String()
s, err := session.NewSession(&aws.Config{
Expand Down
Loading