docker中运行飞牛影视
飞牛影视是飞牛os的一部分,没有单独提供app。虽然飞牛os有x86和arm版本,能很方便的安装使用,但单独的飞牛影视也是很吸引人的。本文使用了leilee 所提供的Dockerfile,制作docker并应用。
下载飞牛系统iso
保存为fnos.iso备用。
建立Dockerfile文件
内容为:
FROM debian:12.12 AS builder
COPY ./fnos.iso ./fnos.iso
RUN cat > fakebroker.go <<'EOF'
package main
import (
"bytes"
"encoding/binary"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"path"
"strings"
)
const (
appCenterAddr = "/run/com.trim.app.center.sock"
brokerAddr = "/run/trim_app_cgi/rpcbroker"
defaultToken = "reserved"
magicNumber = "CPRT" // magic number for rpc protocol
headerSize = 80
payloadLenPos = 18
payloadLenSize = 2
)
type (
Service struct {
Id string `json:"id"`
Name string `json:"name"`
IP string `json:"ip"`
Uds string `json:"uds"`
Type int `json:"type"`
Token string `json:"token"`
}
Request struct {
Header []byte `json:"-"`
Data struct {
Uid uint32 `json:"uid"`
Pid uint32 `json:"pid"`
Req string `json:"req"`
ReqId string `json:"reqid"`
AppName string `json:"appName"`
UserName string `json:"user"`
Services []string `json:"services,omitempty"`
} `json:"data"`
}
BaseResp struct {
Data any `json:"data,omitempty"`
ReqId string `json:"reqid"`
Result string `json:"result"`
Rev string `json:"rev"`
Req string `json:"req,omitempty"`
}
Response struct {
Data BaseResp `json:"data"`
}
AppAuthorizedDir struct {
Type int `json:"storageType"`
Path string `json:"path"`
UserName string `json:"uname"`
}
AuthPath struct {
Editable bool `json:"isEditable"`
Perm int `json:"perm"`
Status int `json:"status"`
Path string `json:"path"`
}
)
var (
logPath string
folders string
authPathResp []byte
appAuthorizedDirs []AppAuthorizedDir
marshaledUserId = json.RawMessage(`{"uid": 1000}`)
marshaledVolsInfo = json.RawMessage(`{"vols":[{"index":1,"state":0,"sysname":"dm-0","uuid":"trim_00000000_1111_2222_3333_444444444444-0","size":107374182400,"used":0,"voltype":61267}],"count":1}`)
services = []Service{
{Id: "com.trim.main", Name: "TRIM Service", Uds: brokerAddr, Token: defaultToken, Type: 1},
{Id: "com.trim.sysinfo", Name: "System Info Provider Service", Uds: brokerAddr, Token: defaultToken},
{Id: "com.trim.filestor", Name: "File Storage Service", Uds: brokerAddr, Token: defaultToken},
{Id: "com.trim.usersrv", Name: "User Service", Uds: brokerAddr, Token: defaultToken},
}
)
func init() {
flag.StringVar(&logPath, "p", "/var/log/rpcbroker.log", "log file path")
flag.StringVar(&folders, "f", "/vol1/1000/media:", "media folders")
flag.Parse()
splited := strings.Split(folders, ":")
appAuthorizedDirs = make([]AppAuthorizedDir, 0, len(splited))
authPaths := make([]AuthPath, 0, len(splited))
for _, v := range splited {
if strings.TrimSpace(v) == "" {
continue
}
appAuthorizedDirs = append(appAuthorizedDirs, AppAuthorizedDir{Path: v, Type: 3, UserName: "admin"})
authPaths = append(authPaths, AuthPath{Path: v, Perm: 6, Editable: true})
}
authPathResp, _ = json.Marshal(map[string]any{"code": 0, "msg": "", "data": map[string][]AuthPath{"list": authPaths}})
}
func NewResp(req *Request, data any) BaseResp {
return BaseResp{ReqId: req.Data.ReqId, Req: req.Data.Req, Data: data, Result: "succ", Rev: "0.1"}
}
func NewErrorResp(req *Request) BaseResp {
return BaseResp{ReqId: req.Data.ReqId, Req: req.Data.Req, Result: "fail", Rev: "0.1"}
}
func readHeader(conn net.Conn) ([]byte, error) {
header := make([]byte, headerSize)
_, err := io.ReadFull(conn, header)
if err != nil {
return nil, err
}
if !bytes.Equal(header[:4], []byte(magicNumber)) {
return nil, fmt.Errorf("invalid magic number: %x", header[:4])
}
return header, nil
}
func parseRequest(conn net.Conn) (*Request, error) {
header, err := readHeader(conn)
if err != nil {
log.Printf("read header error from %s: %v\n", conn.RemoteAddr(), err)
return nil, err
}
plLen := getPayloadLength(header)
payload, err := readPayload(conn, plLen)
if err != nil {
log.Println("header:", string(header))
log.Printf("read payload error from %s: %v\n", conn.RemoteAddr(), err)
log.Println("payload:", string(payload))
return nil, err
}
var req Request
if err := json.Unmarshal(payload, &req); err != nil {
log.Println(string(payload))
return nil, fmt.Errorf("unmarshal payload failed: %w", err)
}
log.Println("request:", string(payload))
req.Header = header
return &req, nil
}
func getPayloadLength(header []byte) uint16 {
return binary.LittleEndian.Uint16(header[payloadLenPos : payloadLenPos+payloadLenSize])
}
func readPayload(conn net.Conn, length uint16) ([]byte, error) {
if length == 0 {
return nil, fmt.Errorf("payload len is zero")
}
payload := make([]byte, length)
_, err := io.ReadFull(conn, payload)
return payload, err
}
func writeResponse(conn net.Conn, header []byte, payload []byte) error {
length := uint16(len(payload))
binary.LittleEndian.PutUint16(header[payloadLenPos:payloadLenPos+payloadLenSize], length)
_, err := conn.Write(append(header, payload...))
return err
}
func processRequest(req *Request) BaseResp {
switch req.Data.Req {
case "com.trim.rpcbroker.apply":
return NewResp(req, services)
case "com.trim.usersrv.getUserId", "com.trim.sysinfo.getUserId":
return NewResp(req, marshaledUserId)
case "com.trim.filestor.getAppAuthorizedDir":
return NewResp(req, appAuthorizedDirs)
case "com.trim.sysinfo.getAllVolsInfo":
return NewResp(req, marshaledVolsInfo)
default:
log.Println("unknown req:", req.Data.Req)
return NewErrorResp(req)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
addr := conn.RemoteAddr()
log.Printf("client %s connected\n", addr)
for {
req, err := parseRequest(conn)
if err != nil {
log.Printf("read request error from %s: %v\n", addr, err)
return
}
resp := processRequest(req)
data, _ := json.Marshal(Response{Data: resp})
log.Println("response:", string(data))
if err := writeResponse(conn, req.Header, data); err != nil {
log.Printf("write resp error: %v\n", err)
return
}
}
}
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("cannot open log file: %s: %v", logPath, err)
}
defer logFile.Close()
log.SetOutput(logFile)
// start auth http server
os.RemoveAll(appCenterAddr)
al, err := net.Listen("unix", appCenterAddr)
if err != nil {
log.Fatalf("cannot listen http unix: %v", err)
}
defer al.Close()
http.HandleFunc("/rpc/v1/sysconfig/app/auth-path", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write(authPathResp)
})
go func() {
log.Println("app center serve:", http.Serve(al, nil))
}()
// start rpc broker
os.Remove(brokerAddr)
os.MkdirAll(path.Dir(brokerAddr), 0755)
listener, err := net.Listen("unix", brokerAddr)
if err != nil {
log.Fatalf("listen rpc broker %s failed: %v", brokerAddr, err)
}
defer listener.Close()
log.Printf("rpc broker listening on %s\n", brokerAddr)
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("accept error: %v\n", err)
continue
}
go handleConnection(conn)
}
}
EOF
RUN cat > init.sql <<"EOF"
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE `item` (`guid` text,`trim_id` text,`imdb_id` text,`tmdb_id` integer,`pinyin` text,`type` text,`lan` text,`title` text,`sort_title` text,`sort_num` integer DEFAULT 2147483647,`original_title` text,`overview` text,`adult` integer NOT NULL DEFAULT 0,`runtime` integer,`release_date` text,`parent_guid` text,`alternative_titles` text,`backdrops` text,`backdrop_height` integer DEFAULT 0,`backdrop_width` integer DEFAULT 0,`backdrop_height_width_try` integer DEFAULT 0,`logos` text,`posters` text,`poster_height` integer DEFAULT 0,`poster_width` integer DEFAULT 0,`poster_height_width_try` integer DEFAULT 0,`production_countries` text,`external_ids` text,`origin_country` text,`content_ratings` text,`first_air_date` text,`last_air_date` text,`air_date` text,`vote_average` real,`vote_count` integer,`number_of_seasons` integer,`number_of_episodes` integer,`status` text DEFAULT "1",`season_number` integer,`episode_number` integer,`still_path` text,`keywords` text,`path` text,`fetch_status` integer,`logic_type` integer DEFAULT 0,`dir` text,`filename` text,`episode_imdb_id` text,`create_time` integer,`update_time` integer,PRIMARY KEY (`guid`));
CREATE TABLE `item_ancestor` (`item_guid` text NOT NULL,`ancestor_guid` text NOT NULL,`create_time` integer,`update_time` integer);
CREATE TABLE `item_media` (`guid` text,`item_guid` text NOT NULL,`dir` text DEFAULT "",`path` text NOT NULL,`size` integer,`can_play` integer DEFAULT 1,`type` integer NOT NULL,`mod_time` integer,`file_hash` text,`create_time` integer,`update_time` integer,`file_birth_time` integer,`recognition_status` integer DEFAULT 1,`progress_thumb_hash_dir` text,`progress_thumb_errno` integer,`cloud_storage_type` integer,`mount_path` text,`fid` text,`pick_code` text,`content_hash` text,PRIMARY KEY (`guid`));
CREATE TABLE `item_person` (`item_guid` text NOT NULL,`person_guid` text NOT NULL,`role` text,`job` text,`order` integer,`department` text);
CREATE TABLE `item_tag` (`item_guid` text NOT NULL,`tag` text NOT NULL,`type` text);
CREATE TABLE `item_user` (`user_guid` text NOT NULL,`item_guid` text NOT NULL,`is_admin` integer NOT NULL,`create_time` integer,`update_time` integer);
CREATE TABLE `item_user_favorite` (`user_guid` text NOT NULL,`item_guid` text NOT NULL,`item_type` text,`create_time` integer,`update_time` integer);
CREATE TABLE `item_user_play` (`item_guid` text NOT NULL,`user_guid` text NOT NULL,`ts` integer DEFAULT 0,`watched` integer DEFAULT 0,`media_guid` text,`video_guid` text,`audio_guid` text,`subtitle_guid` text,`direct_link_audio_index` integer DEFAULT -1,`resolution` text,`bitrate` integer,`type` text,`visible` integer DEFAULT 1,`create_time` integer,`update_time` integer);
CREATE TABLE `media_delete` (`ancestor_guid` text NOT NULL,`media_path` text NOT NULL,`dir` text DEFAULT "",`is_dir` numeric DEFAULT false,`create_time` integer,`update_time` integer);
CREATE TABLE `media_property` (`media_guid` text NOT NULL,`category` text,`content` text,`update_time` integer,`create_time` integer);
CREATE TABLE `media_server` (`guid` text NOT NULL,`name` text,`lan` text,`meta_dir` text,`file_monitor` integer,`region` text,`direct_link_enable` integer DEFAULT 1,`direct_link_allowed_level` integer DEFAULT 0,`direct_link_allowed_drives` text,`create_time` integer,`update_time` integer,PRIMARY KEY (`guid`));
INSERT INTO media_server VALUES('24acb7d71a05482e8f4b6b4097d39373','MediaHub','zh-CN','/vol1/@appmeta/trim.media',1,'CN',1,0,'',1764039737866,1764039737866);
CREATE TABLE `media_stream` (`guid` text NOT NULL,`title` text,`media_guid` text NOT NULL,`codec_name` text,`codec_type` text,`color_range` text,`profile` text,`index` integer,`width` integer,`height` integer,`coded_width` integer,`coded_height` integer,`display_aspect_ratio` text,`pix_fmt` text,`level` text,`color_space` text,`color_transfer` text,`color_primaries` text,`dv_profile` integer NOT NULL DEFAULT 0,`refs` integer,`rotation` real,`r_frame_rate` text,`avg_frame_rate` text,`time_base` text,`start_pts` integer,`start_time` text,`duration_pts` integer,`duration` integer,`is_default` integer,`forced` integer,`bps` integer,`language` text,`channels` integer,`sample_rate` text,`bits_per_raw_sample` text,`is_external` integer,`channel_layout` text,`create_time` integer,`update_time` integer,`resolution_type` text,`audio_type` text,`color_range_type` text,`bit_depth` integer,`progressive` integer,`origin_filename` text,`filepath` text,`source_id` text,`source` text,`trim_id` text,`release` text,`uploader` text,`status` integer NOT NULL DEFAULT 1,`ext1` integer DEFAULT 0,`container_format` text,`is_bluray` numeric DEFAULT false,`key_frame_interval` integer,PRIMARY KEY (`guid`));
CREATE TABLE `mediadb_config` (`item_guid` text NOT NULL,`subtitle_lan` text,`auto_scrap_subtitle` integer,`category` text,`create_time` integer,`update_time` integer,`include_adult` numeric DEFAULT false,`view_type` integer DEFAULT 0,`auto_progress_thumb` integer DEFAULT 0,`skip_filesize` integer DEFAULT 0);
CREATE TABLE `permission` (`permission` text NOT NULL,`create_time` integer,`update_time` integer);
INSERT INTO permission VALUES('mdb_manager',1764039562954,1764039562954);
INSERT INTO permission VALUES('user_manager',1764039562954,1764039562954);
INSERT INTO permission VALUES('metadata_manager',1764039562954,1764039562954);
INSERT INTO permission VALUES('server_manager',1764039562954,1764039562954);
INSERT INTO permission VALUES('task_manager',1764039562954,1764039562954);
CREATE TABLE `person` (`guid` text NOT NULL,`trim_id` text,`imdb_id` text,`tmdb_id` integer,`lan` text NOT NULL,`pinyin` text,`name` text,`original_name` text,`also_know_as` text,`biography` text,`know_for_department` text,`images` text,`profile_path` text,`gender` integer,`create_time` integer,`update_time` integer,PRIMARY KEY (`guid`));
CREATE TABLE `schedule` (`guid` text NOT NULL,`name` text,`type` text,`interval` integer,`status` integer NOT NULL DEFAULT 1,`create_time` integer,`update_time` integer,PRIMARY KEY (`guid`));
INSERT INTO schedule VALUES('schedule-item-scrap','scrap-item','TaskItemScrap',86400,1,1764039562954,1764039562954);
INSERT INTO schedule VALUES('schedule-subtitle-extra','extra-subtitle','TaskSubtitleExtra',86400,1,1764039562954,1764039562954);
CREATE TABLE `sys_metadata` (`key` text NOT NULL,`value` text,`private` integer,`create_time` integer,`update_time` integer,PRIMARY KEY (`key`));
INSERT INTO sys_metadata VALUES('db_version','9',0,1764039563,1764039563);
INSERT INTO sys_metadata VALUES('global_task_switch','1',0,1764039563,1764039563);
INSERT INTO sys_metadata VALUES('defaultmetadir','/vol1/@appmeta/trim.media',0,1764039564,1764039564);
INSERT INTO sys_metadata VALUES('sys_secret','568ba6e4e64f38ed08e249538849dea95b7d542c36361eba05bacc1ce696f632c',1,1764039737,1764039737);
INSERT INTO sys_metadata VALUES('mediasrv_cache_dir','/vol1',0,1764039737,1764039737);
CREATE TABLE `tag` (`guid` text NOT NULL,`tag` text NOT NULL,`type` text,`trim_id` text,PRIMARY KEY (`guid`));
CREATE TABLE `user` (`guid` text,`username` text,`passwd` text,`lan` text,`last_login_time` integer,`is_admin` integer,`media_permission` integer,`status` integer,`create_time` integer,`update_time` integer,PRIMARY KEY (`guid`));
INSERT INTO user VALUES('default-user-template','user-template',NULL,NULL,NULL,0,1,0,1764039562954,1764039562954);
INSERT INTO user VALUES('62ea6f528af04dd3977bd364c4148c01','admin','dJp5/s0mfAaCA1d1p3YTxQBVDJJmQzJb3K/dNghHRLM=','zh-CN',1764039737,1,2,1,1764039737913,1764039737913);
CREATE TABLE `user_permission` (`user_guid` text NOT NULL,`permission` text NOT NULL,`status` integer NOT NULL DEFAULT 1,`create_time` integer,`update_time` integer);
CREATE TABLE `user_source` (`user_guid` text NOT NULL,`source_id` text NOT NULL,`source` text NOT NULL,`source_name` text,`create_time` integer,`update_time` integer);
INSERT INTO user_source VALUES('62ea6f528af04dd3977bd364c4148c01','1000','Trim-NAS','admin',1764039637294,1764039637294);
CREATE TABLE `download_task` (`guid` text NOT NULL,`media_guid` text NOT NULL,`user_guid` text NOT NULL,`resolution` text NOT NULL,`media_file` text NOT NULL,`output_file` text NOT NULL,`status` integer NOT NULL DEFAULT 0,`direct_download` integer NOT NULL DEFAULT 0,`create_time` integer,`update_time` integer,PRIMARY KEY (`guid`));
CREATE TABLE `item_play_config` (`item_guid` text,`user_guid` text,`skip_opening` integer,`skip_ending` integer,PRIMARY KEY (`item_guid`,`user_guid`));
CREATE INDEX `idx_dir` ON `item`(`dir`);
CREATE INDEX `idx_path` ON `item`(`path`);
CREATE INDEX `idx_parent_guid` ON `item`(`parent_guid`);
CREATE INDEX `idx_tmdb_id` ON `item`(`tmdb_id`);
CREATE INDEX `idx_imdb_id` ON `item`(`imdb_id`);
CREATE INDEX `idx_trim_id` ON `item`(`trim_id`);
CREATE INDEX `idx_ancestor_guid` ON `item_ancestor`(`ancestor_guid`);
CREATE INDEX `idx_itemancestor_item_guid` ON `item_ancestor`(`item_guid`);
CREATE INDEX `idx_file_hash` ON `item_media`(`file_hash`);
CREATE INDEX `idx_itemmedia_path` ON `item_media`(`path`);
CREATE INDEX `idx_itemmedia_dir` ON `item_media`(`dir`);
CREATE INDEX `idx_itemmedia_item_guid` ON `item_media`(`item_guid`);
CREATE INDEX `idx_person_guid` ON `item_person`(`person_guid`);
CREATE INDEX `idx_itemperson_item_guid` ON `item_person`(`item_guid`);
CREATE INDEX `idx_itemtag_tag` ON `item_tag`(`tag`);
CREATE INDEX `idx_item_tag_guid` ON `item_tag`(`item_guid`);
CREATE UNIQUE INDEX `uk_user_item` ON `item_user`(`user_guid`,`item_guid`);
CREATE INDEX `idx_itemuserfavorite_item_guid` ON `item_user_favorite`(`item_guid`);
CREATE INDEX `idx_itemuserfavorite_user_guid` ON `item_user_favorite`(`user_guid`);
CREATE INDEX `idx_itemuserplay_user_guid` ON `item_user_play`(`user_guid`);
CREATE UNIQUE INDEX `uk_item_user` ON `item_user_play`(`item_guid`,`user_guid`);
CREATE INDEX `idx_itemuserplay_item_guid` ON `item_user_play`(`item_guid`);
CREATE INDEX `idx_mediadelete_dir` ON `media_delete`(`dir`);
CREATE UNIQUE INDEX `uk_ancestor_path` ON `media_delete`(`ancestor_guid`,`media_path`);
CREATE INDEX `idx_mediaproperty_media_guid` ON `media_property`(`media_guid`);
CREATE INDEX `idx_filepath` ON `media_stream`(`filepath`);
CREATE INDEX `idx_mediastream_media_guid` ON `media_stream`(`media_guid`);
CREATE INDEX `idx_mediadbconfig_item_guid` ON `mediadb_config`(`item_guid`);
CREATE INDEX `idx_permission` ON `permission`(`permission`);
CREATE INDEX `idx_person_tmdb_id` ON `person`(`tmdb_id`);
CREATE INDEX `idx_person_imdb_id` ON `person`(`imdb_id`);
CREATE INDEX `idx_person_trim_id` ON `person`(`trim_id`);
CREATE INDEX `idx_tag_tag` ON `tag`(`tag`);
CREATE UNIQUE INDEX `uk_username` ON `user`(`username`);
CREATE UNIQUE INDEX `uk_user_guid_permission` ON `user_permission`(`user_guid`,`permission`);
CREATE UNIQUE INDEX `uk_user_guid_source` ON `user_source`(`user_guid`,`source_id`,`source`);
CREATE INDEX `idx_user_guid` ON `download_task`(`user_guid`);
CREATE INDEX `idx_task_guid` ON `download_task`(`media_guid`);
CREATE UNIQUE INDEX idx_item_guid_path ON item_media(item_guid, path);
COMMIT;
EOF
RUN cat > entrypoint.sh <<'EOF'
#!/bin/bash
set -euxm
shutdown() {
echo "Shutting down..." >&2
kill -9 0 2>/dev/null || true
exit 0
}
trap shutdown SIGINT SIGTERM
# start mediasrv
/usr/trim/bin/mediasrv -o /var/log/mediasrv.log -a /var/run/mediasrv.socket &
pid1=$!
# start rpcbroker
/usr/trim/bin/rpcbroker -f ${MEDIA_DIRS} &
pid2=$!
# init database
if [ ! -f /vol1/mediadata/database/trimmedia.db ]; then
# make sure folder exists
mkdir -p /vol1/mediadata/database
sqlite3 /vol1/mediadata/database/trimmedia.db < /usr/trim/init.sql
fi
cd /usr/local/apps/@appcenter/trim.media
/usr/local/apps/@appcenter/trim.media/trim-media --port=8005 \
--static=/usr/local/apps/@appcenter/trim.media \
--trim-appname=trim.media \
--trim-username=trim-media \
--root=/vol1/mediadata \
--meta=/vol1/@appmeta/trim.media \
--log-dir=/var/log --log-level=${LOG_LEVEL} &
pid3=$!
tail -vF /var/log/trim-media.log &
wait -n $pid1 $pid2 $pid3
exit_code=$?
echo "One of the apps exited with code $" >&2
kill -9 0
exit $exit_code
EOF
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources && \
apt update && apt install -y p7zip-full curl && 7z x fnos.iso -ofniso && \
tar -C /fniso -xvf fniso/trimfs.tgz usr/trim/bin/mediasrv usr/trim/lib/libnebula.so \
usr/trim/lib/libppjson.so usr/trim/lib/mediasrv && mkdir fniso/usr/trim/etc && \
mv entrypoint.sh init.sql fniso/usr/trim/ && GOPKG=go1.24.10.linux-amd64 && \
curl -O https://dl.google.com/go/${GOPKG}.tar.gz && tar -C /opt -xvf ${GOPKG}.tar.gz && \
/opt/go/bin/go build -o /fniso/usr/trim/bin/rpcbroker fakebroker.go
# ====== end of builder ======
# ====== final stage ======
FROM debian:12.12
ENV LD_LIBRARY_PATH=/usr/trim/lib/mediasrv LOG_LEVEL=info MEDIA_DIRS=/vol1/1000/media
COPY --from=builder /fniso/usr/trim /usr/trim
ADD ./trim.media.tar.gz /usr/local/apps/@appcenter/
WORKDIR /usr/trim
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources && \
apt update && apt install -y sqlite3 openssl ca-certificates libass9 libbluray2 libmp3lame0 \
libopenmpt0 libopus0 libtcmalloc-minimal4 libtheora0 libvorbisenc2 libvpx7 libwebp7 \
libwebpmux3 libx264-164 libx265-199 libzvbi0 && apt clean && rm -rf /var/lib/apt/lists/*
EXPOSE 8005
ENTRYPOINT ["/bin/bash"]
CMD ["/usr/trim/entrypoint.sh"]
从飞牛系统中拷贝影视应用文件
进入目录/usr/local/apps/@appcenter,执行tar czvf trim.media.tar.gz trim.media
制作docker镜像
把前面三个文件放在同一目录下,执行docker build -t fnmedia .,本机需安装好docker。
输出:
root@qs-dell:/home/qs/Downloads/fnmedia#docker build -t fnmedia .
[+] Building 133.6s (15/15) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 22.75kB 0.0s
=> [internal] load metadata for docker.io/library/debian:12.12 1.5s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [builder 1/6] FROM docker.io/library/debian:12.12@sha256:c66c6 0.0s
=> => resolve docker.io/library/debian:12.12@sha256:c66c66fac809bfb56a80 0.0s
=> [internal] load build context 12.5s
=> => transferring context: 2.74GB 12.5s
=> CACHED [builder 2/6] COPY ./fnos.iso ./fnos.iso 0.0s
=> CACHED [builder 3/6] RUN cat > fakebroker.go <<'EOF' 0.0s
=> CACHED [builder 4/6] RUN cat > init.sql <<"EOF" 0.0s
=> CACHED [builder 5/6] RUN cat > entrypoint.sh <<'EOF' 0.0s
=> [builder 6/6] RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /e 80.2s
=> [stage-1 2/5] COPY --from=builder /fniso/usr/trim /usr/trim 1.6s
=> [stage-1 3/5] ADD ./trim.media.tar.gz /usr/local/apps/@appcenter/ 1.0s
=> [stage-1 4/5] WORKDIR /usr/trim 0.1s
=> [stage-1 5/5] RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /e 18.7s
=> exporting to image 17.6s
=> => exporting layers 15.3s
=> => exporting manifest sha256:f3880c4163366882fa955d4bd61acf88f274b9fe 0.0s
=> => exporting config sha256:c6ad8bf5e0d145159aea3921a8da581bec29c14096 0.0s
=> => exporting attestation manifest sha256:b330c3a5253603db2dbbafd41656 0.0s
=> => exporting manifest list sha256:77d32117439ed242f688618e37d4c2d29c4 0.0s
=> => naming to docker.io/library/fnmedia:latest 0.0s
=> => unpacking to docker.io/library/fnmedia:latest 2.2s
root@qs-dell:/home/qs/Downloads/fnmedia# docker images
i Info → U In Use
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
fnmedia:latest 77d32117439e 755MB 199MB
这一步是在要单独使用飞牛影视的机子上制作的。
使用
docker run -d -v /dev/dri/by-path:/dev/dri/by-path --device /dev/dri:/dev/dri -v /home/qs/Public:/vol1/1000/media -p 8005:8005 --name fnmedia fnmedia:latest
或docker-compose.yml
services:
fnmedia:
image: 'fnmedia:latest'
container_name: fnmedia
ports:
- '8005:8005'
volumes:
- '/home/qs/Public:/vol1/1000/media'
- '/dev/dri/by-path:/dev/dri/by-path'
devices:
- '/dev/dri:/dev/dri'
由ip:8005进入,初始帐号是admin,密码123456




镜像下载
这是amd64版本,从fnos1.1.26提取制做。
fnmedia.tar下载
docker save -o fnmedia.tar fnmedia:latest
导入镜像:
docker load -i fnmedia.tar
此镜像可以用docker pull ghcr.io/qs100371/fnmedia:latest 下载(只支持amd64)。
参考:
飞牛影视独立化Docker镜像 需使用梯子。