docker中运行飞牛影视

• 45 分钟阅读 • nas · 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镜像 需使用梯子。

文章标签: nas, docker

上一篇 : TrendRadar:一个智能新闻热点助手
下一篇 : 卓奕引擎: linux系统上运行的安卓子系统
留言
阅读进度 0%