mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-05-21 02:40:09 +00:00
Compare commits
3 Commits
6288665170
...
arbitrary-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
624601b1d4 | ||
|
|
a9c3daedb1 | ||
|
|
1039363de6 |
@@ -1,4 +1,11 @@
|
||||
# syntax=docker/dockerfile:latest
|
||||
FROM docker.io/library/golang:alpine AS aio-container-tools-builder
|
||||
|
||||
# hadolint ignore=DL3022
|
||||
COPY --from=aio-container-tools . /tmp/aio-container-tools/
|
||||
WORKDIR /tmp/aio-container-tools
|
||||
RUN go build -o /usr/local/bin/aio-pg-healthcheck ./cmd/aio-pg-healthcheck
|
||||
|
||||
FROM php:8.3.30-fpm-alpine3.23
|
||||
|
||||
ENV PHP_MEMORY_LIMIT=512M
|
||||
@@ -17,6 +24,7 @@ COPY --chmod=775 Containers/nextcloud/*.sh /
|
||||
COPY --chmod=774 Containers/nextcloud/upgrade.exclude /upgrade.exclude
|
||||
COPY Containers/nextcloud/config/*.php /
|
||||
COPY Containers/nextcloud/supervisord.conf /supervisord.conf
|
||||
COPY --from=aio-container-tools-builder /usr/local/bin/aio-pg-healthcheck /usr/local/bin/aio-pg-healthcheck
|
||||
|
||||
# AIO cloning start # Do not remove or change this line!
|
||||
COPY app /usr/src/nextcloud/apps/nextcloud-aio
|
||||
@@ -226,7 +234,6 @@ RUN set -ex; \
|
||||
openssl \
|
||||
gnupg \
|
||||
git \
|
||||
postgresql-client \
|
||||
tzdata \
|
||||
sudo \
|
||||
grep \
|
||||
|
||||
@@ -25,7 +25,7 @@ fi
|
||||
# Fix false database connection on old instances
|
||||
if [ -f "/var/www/html/config/config.php" ]; then
|
||||
sleep 2
|
||||
while ! sudo -E -u www-data psql -d "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" -c "select now()"; do
|
||||
while ! sudo -E -u www-data /usr/local/bin/aio-pg-healthcheck; do
|
||||
echo "Waiting for the database to start..."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
# syntax=docker/dockerfile:latest
|
||||
FROM docker.io/library/golang:alpine AS aio-container-tools-builder
|
||||
|
||||
# hadolint ignore=DL3022
|
||||
COPY --from=aio-container-tools . /tmp/aio-container-tools/
|
||||
WORKDIR /tmp/aio-container-tools
|
||||
RUN go build -o /usr/local/bin/aio-pg-init ./cmd/aio-pg-init \
|
||||
&& go build -o /usr/local/bin/aio-pg-healthcheck ./cmd/aio-pg-healthcheck
|
||||
|
||||
# From https://github.com/docker-library/postgres/blob/master/17/alpine3.23/Dockerfile
|
||||
FROM postgres:17.9-alpine
|
||||
|
||||
COPY --chmod=775 start.sh /start.sh
|
||||
COPY --from=aio-container-tools-builder /usr/local/bin/aio-pg-init /usr/local/bin/aio-pg-init
|
||||
COPY --from=aio-container-tools-builder /usr/local/bin/aio-pg-healthcheck /usr/local/bin/aio-pg-healthcheck
|
||||
COPY --chmod=775 healthcheck.sh /healthcheck.sh
|
||||
COPY --chmod=775 init-user-db.sh /docker-entrypoint-initdb.d/init-user-db.sh
|
||||
|
||||
|
||||
@@ -2,6 +2,4 @@
|
||||
|
||||
test -f "/mnt/data/backup-is-running" && exit 0
|
||||
|
||||
psql -d "postgresql://oc_$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:11000/$POSTGRES_DB" -c "select now()" && exit 0
|
||||
|
||||
psql -d "postgresql://oc_$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:5432/$POSTGRES_DB" -c "select now()" || exit 1
|
||||
POSTGRES_PORT=11000 /usr/local/bin/aio-pg-healthcheck debug || exec /usr/local/bin/aio-pg-healthcheck
|
||||
|
||||
@@ -3,12 +3,7 @@ set -ex
|
||||
|
||||
touch "$DUMP_DIR/initialization.failed"
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
CREATE USER "oc_$POSTGRES_USER" WITH PASSWORD '$POSTGRES_PASSWORD' CREATEDB;
|
||||
ALTER DATABASE "$POSTGRES_DB" OWNER TO "oc_$POSTGRES_USER";
|
||||
GRANT ALL PRIVILEGES ON DATABASE "$POSTGRES_DB" TO "oc_$POSTGRES_USER";
|
||||
GRANT ALL PRIVILEGES ON SCHEMA public TO "oc_$POSTGRES_USER";
|
||||
EOSQL
|
||||
POSTGRES_DB_OWNER="oc_$POSTGRES_USER" /usr/local/bin/aio-pg-init
|
||||
|
||||
rm "$DUMP_DIR/initialization.failed"
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
DATADIR="/var/lib/postgresql/data"
|
||||
export DUMP_DIR="/mnt/data"
|
||||
DUMP_FILE="$DUMP_DIR/database-dump.sql"
|
||||
# TODO: Do we need this? It's not used anywhere visible
|
||||
export PGPASSWORD="$POSTGRES_PASSWORD"
|
||||
|
||||
# Don't start database as long as backup is running
|
||||
@@ -85,7 +86,7 @@ if ( [ -f "$DATADIR/PG_VERSION" ] && [ "$PG_MAJOR" != "$(cat "$DATADIR/PG_VERSIO
|
||||
exec docker-entrypoint.sh postgres &
|
||||
|
||||
# Wait for creation
|
||||
while ! psql -d "postgresql://oc_$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:11000/$POSTGRES_DB" -c "select now()"; do
|
||||
while ! env POSTGRES_PORT=11000 POSTGRES_USER="oc_$POSTGRES_USER" /usr/local/bin/aio-pg-healthcheck; do
|
||||
echo "Waiting for the database to start."
|
||||
sleep 5
|
||||
done
|
||||
@@ -107,12 +108,7 @@ if ( [ -f "$DATADIR/PG_VERSION" ] && [ "$PG_MAJOR" != "$(cat "$DATADIR/PG_VERSIO
|
||||
exit 1
|
||||
elif [ "$DB_OWNER" != "oc_$POSTGRES_USER" ]; then
|
||||
DIFFERENT_DB_OWNER=1
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
CREATE USER "$DB_OWNER" WITH PASSWORD '$POSTGRES_PASSWORD' CREATEDB;
|
||||
ALTER DATABASE "$POSTGRES_DB" OWNER TO "$DB_OWNER";
|
||||
GRANT ALL PRIVILEGES ON DATABASE "$POSTGRES_DB" TO "$DB_OWNER";
|
||||
GRANT ALL PRIVILEGES ON SCHEMA public TO "$DB_OWNER";
|
||||
EOSQL
|
||||
POSTGRES_DB_OWNER="$DB_OWNER" /usr/local/bin/aio-pg-init
|
||||
fi
|
||||
|
||||
# Restore database
|
||||
|
||||
42
aio-container-tools/README.md
Normal file
42
aio-container-tools/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# aio-container-tools
|
||||
|
||||
Standalone tools for Nextcloud AIO containers, for tasks that shouldn't be executed in a shell environment
|
||||
(e.g. due to string handling issues).
|
||||
|
||||
Golang was chosen because it doesn't require additional runtimes in the containers, and has a pretty easy
|
||||
syntax that is comprehensible even for people without much experience with the language.
|
||||
|
||||
The tools should be built in the container image build process, so they are built for the correct target
|
||||
platform in multi-arch builds. See below for an example.
|
||||
|
||||
## Build process
|
||||
|
||||
To include the binary of `aio-pg-healhcheck` into your container image, include such a snippet into your Containerfile:
|
||||
|
||||
```dockerfile
|
||||
FROM docker.io/library/golang:alpine AS golang-builder
|
||||
|
||||
# hadolint ignore=DL3022
|
||||
COPY --from=aio-container-tools . /tmp/aio-container-tools/
|
||||
RUN cd /tmp/aio-container-tools \
|
||||
&& go build -o /usr/local/bin/aio-pg-healthcheck ./cmd/aio-pg-healthcheck
|
||||
|
||||
FROM your-base-image
|
||||
COPY --from=golang-builder /usr/local/bin/aio-pg-healthcheck /usr/local/bin/
|
||||
```
|
||||
|
||||
To build it you now have to pass the aio-container-tools directory as additional, named build-context like this:
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-context aio-container-tools=/path/to/all-in-one/aio-container-tools \
|
||||
.
|
||||
```
|
||||
|
||||
#### Remote git variant (without local clone of this repo)
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-context aio-container-tools="https://github.com/nextcloud-releases/all-in-one.git#main:aio-container-tools" \
|
||||
.
|
||||
```
|
||||
92
aio-container-tools/cmd/aio-pg-healthcheck/main.go
Normal file
92
aio-container-tools/cmd/aio-pg-healthcheck/main.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/nextcloud/aio-container-tools/internal/util"
|
||||
)
|
||||
|
||||
// tryConnect opens a TCP connection to the given database host:port and runs SELECT 1.
|
||||
// Returns nil on success, an error otherwise.
|
||||
func tryConnect(ctx context.Context, host string, port uint16, user, password, database string) error {
|
||||
util.Debugf("attempting connection: host=%s port=%d user=%s database=%s", host, port, user, database)
|
||||
|
||||
cfg, err := pgx.ParseConfig("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.Host = host
|
||||
cfg.Port = port
|
||||
cfg.User = user
|
||||
cfg.Password = password
|
||||
cfg.Database = database
|
||||
|
||||
conn, err := pgx.ConnectConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
util.Debugf("connection failed: %v", err)
|
||||
return err
|
||||
}
|
||||
defer conn.Close(ctx)
|
||||
|
||||
util.Debugf("connection established, running SELECT 1")
|
||||
var result string
|
||||
if err := conn.QueryRow(ctx, "SELECT 1").Scan(&result); err != nil {
|
||||
util.Debugf("SELECT 1 failed: %v", err)
|
||||
return err
|
||||
}
|
||||
util.Debugf("SELECT 1 returned %q", result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// envOrDefault returns the value of the named environment variable,
|
||||
// or the provided default if the variable is unset or empty.
|
||||
func envOrDefault(key, defaultVal string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
util.Debugf("env %s = %q", key, v)
|
||||
return v
|
||||
}
|
||||
util.Debugf("env %s not set, using default %q", key, defaultVal)
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func main() {
|
||||
debug := flag.Bool("debug", false, "enable debug output")
|
||||
flag.Parse()
|
||||
util.SetDebug(*debug)
|
||||
|
||||
util.Debugf("reading required environment variables")
|
||||
pgUser := util.RequireEnv("POSTGRES_USER")
|
||||
pgPassword := util.RequireEnv("POSTGRES_PASSWORD")
|
||||
pgDB := util.RequireEnv("POSTGRES_DB")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
pgHost := envOrDefault("POSTGRES_HOST", "127.0.0.1")
|
||||
|
||||
var pgPort uint16 = 5432
|
||||
if portStr := os.Getenv("POSTGRES_PORT"); portStr != "" {
|
||||
util.Debugf("env POSTGRES_PORT = %q", portStr)
|
||||
p, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "invalid POSTGRES_PORT %q: %v\n", portStr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pgPort = uint16(p)
|
||||
} else {
|
||||
util.Debugf("env POSTGRES_PORT not set, using default port %d", pgPort)
|
||||
}
|
||||
|
||||
util.Debugf("connecting to: host=%s port=%d user=%s", pgHost, pgPort, pgUser)
|
||||
if err := tryConnect(ctx, pgHost, pgPort, pgUser, pgPassword, pgDB); err == nil {
|
||||
util.Debugf("connection succeeded, exiting 0")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
util.Debugf("connection failed, exiting 1")
|
||||
os.Exit(1)
|
||||
}
|
||||
78
aio-container-tools/cmd/aio-pg-init/main.go
Normal file
78
aio-container-tools/cmd/aio-pg-init/main.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/nextcloud/aio-container-tools/internal/util"
|
||||
)
|
||||
|
||||
// quoteLiteral safely quotes a string as a PostgreSQL string literal.
|
||||
// Single quotes are escaped by doubling them. This is safe with
|
||||
// standard_conforming_strings=on (default since PostgreSQL 9.1).
|
||||
func quoteLiteral(s string) string {
|
||||
return "'" + strings.ReplaceAll(s, "'", "''") + "'"
|
||||
}
|
||||
|
||||
// main reimplements init-user-db.sh:
|
||||
// - Creates $POSTGRES_DB_OWNER (falling back to $POSTGRES_USER) with $POSTGRES_PASSWORD and CREATEDB
|
||||
// - Transfers ownership of $POSTGRES_DB to that user
|
||||
// - Grants all privileges on the database and public schema
|
||||
// - Connects using $POSTGRES_USER in all cases
|
||||
func main() {
|
||||
debug := flag.Bool("debug", false, "enable debug output")
|
||||
flag.Parse()
|
||||
util.SetDebug(*debug)
|
||||
|
||||
util.Debugf("reading required environment variables")
|
||||
pgUser := util.RequireEnv("POSTGRES_USER")
|
||||
pgPassword := util.RequireEnv("POSTGRES_PASSWORD")
|
||||
pgDB := util.RequireEnv("POSTGRES_DB")
|
||||
pgDBOwner := util.OptionalEnv("POSTGRES_DB_OWNER", pgUser)
|
||||
|
||||
util.Debugf("building connection config: host=/var/run/postgresql port=5432 user=%s database=%s", pgUser, pgDB)
|
||||
cfg, err := pgx.ParseConfig("")
|
||||
if err != nil {
|
||||
util.ErrorOut(fmt.Errorf("building connection config: %w", err))
|
||||
}
|
||||
cfg.Host = "/var/run/postgresql"
|
||||
cfg.Port = 5432
|
||||
cfg.User = pgUser
|
||||
cfg.Password = pgPassword
|
||||
cfg.Database = pgDB
|
||||
|
||||
ctx := context.Background()
|
||||
util.Debugf("connecting to postgres via unix socket")
|
||||
conn, err := pgx.ConnectConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
util.ErrorOut(fmt.Errorf("connecting to postgres: %w", err))
|
||||
}
|
||||
defer conn.Close(ctx)
|
||||
util.Debugf("connected successfully")
|
||||
|
||||
dbOwner := pgDBOwner
|
||||
util.Debugf("dbOwner = %q (from POSTGRES_DB_OWNER=%q, POSTGRES_USER=%q)", dbOwner, pgDBOwner, pgUser)
|
||||
// pgx.Identifier.Sanitize() double-quotes and escapes the identifier safely.
|
||||
dbOwnerIdent := pgx.Identifier{dbOwner}.Sanitize()
|
||||
dbIdent := pgx.Identifier{pgDB}.Sanitize()
|
||||
util.Debugf("quoted dbOwnerIdent = %s, dbIdent = %s", dbOwnerIdent, dbIdent)
|
||||
|
||||
statements := []string{
|
||||
fmt.Sprintf("CREATE USER %s WITH PASSWORD %s CREATEDB", dbOwnerIdent, quoteLiteral(pgPassword)),
|
||||
fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", dbIdent, dbOwnerIdent),
|
||||
fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s", dbIdent, dbOwnerIdent),
|
||||
fmt.Sprintf("GRANT ALL PRIVILEGES ON SCHEMA public TO %s", dbOwnerIdent),
|
||||
}
|
||||
|
||||
for i, stmt := range statements {
|
||||
util.Debugf("executing statement %d/%d: %s", i+1, len(statements), stmt)
|
||||
if _, err := conn.Exec(ctx, stmt); err != nil {
|
||||
util.ErrorOut(fmt.Errorf("executing statement: %w", err))
|
||||
}
|
||||
util.Debugf("statement %d/%d succeeded", i+1, len(statements))
|
||||
}
|
||||
util.Debugf("all statements executed successfully")
|
||||
}
|
||||
10
aio-container-tools/go.mod
Normal file
10
aio-container-tools/go.mod
Normal file
@@ -0,0 +1,10 @@
|
||||
module github.com/nextcloud/aio-container-tools
|
||||
|
||||
go 1.25.1
|
||||
|
||||
require (
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.8.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
)
|
||||
15
aio-container-tools/go.sum
Normal file
15
aio-container-tools/go.sum
Normal file
@@ -0,0 +1,15 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
|
||||
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
49
aio-container-tools/internal/util/util.go
Normal file
49
aio-container-tools/internal/util/util.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var debugEnabled bool
|
||||
|
||||
// SetDebug enables or disables debug output.
|
||||
func SetDebug(enabled bool) {
|
||||
debugEnabled = enabled
|
||||
}
|
||||
|
||||
// Debugf prints a formatted debug message to stdout when debug mode is enabled.
|
||||
func Debugf(format string, args ...any) {
|
||||
if debugEnabled {
|
||||
fmt.Printf("[debug] "+format+"\n", args...)
|
||||
}
|
||||
}
|
||||
|
||||
// RequireEnv returns the value of the named environment variable.
|
||||
// It writes an error to stderr and exits with code 1 if the variable is unset or empty.
|
||||
func RequireEnv(key string) string {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
fmt.Fprintf(os.Stderr, "required environment variable %q is not set\n", key)
|
||||
os.Exit(1)
|
||||
}
|
||||
Debugf("env %s = %q", key, v)
|
||||
return v
|
||||
}
|
||||
|
||||
// OptionalEnv returns the value of the named environment variable, or fallback if it is unset or empty.
|
||||
func OptionalEnv(key, fallback string) string {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
Debugf("env %s unset, using fallback %q", key, fallback)
|
||||
return fallback
|
||||
}
|
||||
Debugf("env %s = %q", key, v)
|
||||
return v
|
||||
}
|
||||
|
||||
// ErrorOut logs the error with a standard prefix and exits with code 1.
|
||||
func ErrorOut(err error) {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
Reference in New Issue
Block a user